From ebca8ba1becd6943e06b2e7537b40e02cb8d3752 Mon Sep 17 00:00:00 2001 From: ratulhasan Date: Thu, 21 Oct 2021 20:43:40 +0600 Subject: [PATCH 001/386] Fix Uncaught TypeError: Unsupported operand types: string * float --- .../woocommerce/includes/admin/class-wc-admin-post-types.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php index 2eaef024a14..511c07faea7 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php @@ -928,7 +928,7 @@ class WC_Admin_Post_Types { return false; } - $old_price = $product->{"get_{$price_type}_price"}(); + $old_price = doubleval( $product->{"get_{$price_type}_price"}() ); $price_changed = false; $change_price = absint( $request_data[ "change_{$price_type}_price" ] ); From d9016b5dc0bbb508258e090caa88b8af091adfe5 Mon Sep 17 00:00:00 2001 From: ratulhasan Date: Fri, 22 Oct 2021 10:56:11 +0600 Subject: [PATCH 002/386] Fix Uncaught TypeError: Unsupported operand types: string * float --- .../woocommerce/includes/admin/class-wc-admin-post-types.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php index 511c07faea7..5b16daafb19 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php @@ -928,7 +928,7 @@ class WC_Admin_Post_Types { return false; } - $old_price = doubleval( $product->{"get_{$price_type}_price"}() ); + $old_price = ( $product->{"get_{$price_type}_price"}() === '' ) ? 0 : $product->{"get_{$price_type}_price"}(); $price_changed = false; $change_price = absint( $request_data[ "change_{$price_type}_price" ] ); From a22c347902bad9398670014dbcd7e903445e7fc3 Mon Sep 17 00:00:00 2001 From: ratulhasan Date: Fri, 22 Oct 2021 11:01:39 +0600 Subject: [PATCH 003/386] empty check --- .../woocommerce/includes/admin/class-wc-admin-post-types.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php index 5b16daafb19..4749aaa6023 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php @@ -928,7 +928,7 @@ class WC_Admin_Post_Types { return false; } - $old_price = ( $product->{"get_{$price_type}_price"}() === '' ) ? 0 : $product->{"get_{$price_type}_price"}(); + $old_price = empty( $product->{"get_{$price_type}_price"}() ) ? 0 : $product->{"get_{$price_type}_price"}(); $price_changed = false; $change_price = absint( $request_data[ "change_{$price_type}_price" ] ); From cfff7eb7aa0340430f7289a02be2d96e0b643658 Mon Sep 17 00:00:00 2001 From: "Sicelo A. Mhlongo" Date: Sat, 13 Nov 2021 18:38:30 +0200 Subject: [PATCH 004/386] Rename Swaziland to Eswatini The country of Swaziland was officially renamed to Eswatini in April 2018. Let us reflect this change. --- plugins/woocommerce/i18n/countries.php | 2 +- plugins/woocommerce/includes/class-wc-geo-ip.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/i18n/countries.php b/plugins/woocommerce/i18n/countries.php index 73eb9ea040f..f38ed899f4d 100644 --- a/plugins/woocommerce/i18n/countries.php +++ b/plugins/woocommerce/i18n/countries.php @@ -225,7 +225,7 @@ return array( 'SD' => __( 'Sudan', 'woocommerce' ), 'SR' => __( 'Suriname', 'woocommerce' ), 'SJ' => __( 'Svalbard and Jan Mayen', 'woocommerce' ), - 'SZ' => __( 'Swaziland', 'woocommerce' ), + 'SZ' => __( 'Eswatini', 'woocommerce' ), 'SE' => __( 'Sweden', 'woocommerce' ), 'CH' => __( 'Switzerland', 'woocommerce' ), 'SY' => __( 'Syria', 'woocommerce' ), diff --git a/plugins/woocommerce/includes/class-wc-geo-ip.php b/plugins/woocommerce/includes/class-wc-geo-ip.php index c79be25b985..d856be2848a 100644 --- a/plugins/woocommerce/includes/class-wc-geo-ip.php +++ b/plugins/woocommerce/includes/class-wc-geo-ip.php @@ -843,7 +843,7 @@ class WC_Geo_IP { 'Sao Tome and Principe', 'El Salvador', 'Syrian Arab Republic', - 'Swaziland', + 'Eswatini', 'Turks and Caicos Islands', 'Chad', 'French Southern Territories', From e35c7ac6dcbb8d79beb3c12bcbb07f8312db451e Mon Sep 17 00:00:00 2001 From: Josh Betz Date: Fri, 4 Mar 2022 15:06:43 -0600 Subject: [PATCH 005/386] Filter out product variation line_item meta There are cases where we want to display line item meta, similar to the checkout flow on the web. The web filters out variation meta because it's redundant. The product name already includes the relevant meta. ref: https://github.com/woocommerce/woocommerce/blob/8bc310008c4786f3c7a8ecfc99b470accc77c183/plugins/woocommerce/includes/class-wc-order-item.php#L282-L285 --- .../class-wc-rest-orders-v2-controller.php | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php index 64cc4aa9df6..b80be00ef60 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php @@ -210,6 +210,27 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller { // Expand meta_data to include user-friendly values. $formatted_meta_data = $item->get_formatted_meta_data( null, true ); + + // Filter out product variations + // TODO: Add a query arg to activate this. Default should be to keep variation meta for back-compat + if ( $product ) { + $order_item_name = $data['name']; + $data['meta_data'] = array_filter( $data['meta_data'], function( $meta ) use ( $product, $order_item_name ) { + $meta->key = rawurldecode( (string) $meta->key ); + $meta->value = rawurldecode( (string) $meta->value ); + $attribute_key = str_replace( 'attribute_', '', $meta->key ); + $display_key = wc_attribute_label( $attribute_key, $product ); + $display_value = wp_kses_post( $meta->value ); + + // Skip items with values already in the product details area of the product name. + if ( $product && $product->is_type( 'variation' ) && wc_is_attribute_in_product_name( $display_value, $order_item_name ) ) { + return false; + } + + return true; + } ); + } + $data['meta_data'] = array_map( array( $this, 'merge_meta_item_with_formatted_meta_display_attributes' ), $data['meta_data'], From 0114d3b5d64879464db519e410ec842dd8c354e7 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Tue, 8 Mar 2022 16:54:45 +0100 Subject: [PATCH 006/386] Infrastructure for the sync process - Update settings UI - Start sync via scheduled actions when sync is enabled - Auto-switch authoritative table on sync finished if so configured - Disable auto-switch if sync is disabled - Show initial and current count of orders pending sync in settings UI --- .../admin/class-wc-admin-settings.php | 9 + .../Orders/CustomOrdersTableController.php | 217 ++++++++++++++++-- .../DataStores/Orders/DataSynchronizer.php | 127 +++++++++- 3 files changed, 331 insertions(+), 22 deletions(-) diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-settings.php b/plugins/woocommerce/includes/admin/class-wc-admin-settings.php index 69506066749..48bbe0e6e2a 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-settings.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-settings.php @@ -7,6 +7,7 @@ */ use Automattic\Jetpack\Constants; +use Automattic\WooCommerce\Utilities\ArrayUtil; if ( ! defined( 'ABSPATH' ) ) { exit; @@ -273,6 +274,12 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) : } break; + case 'info': + echo ''; + echo wp_kses_post( wpautop( wptexturize( $value['text'] ) ) ); + echo ''; + break; + // Section Ends. case 'sectionend': if ( ! empty( $value['id'] ) ) { @@ -417,6 +424,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) : // Radio inputs. case 'radio': $option_value = $value['value']; + $disabled_values = ArrayUtil::get_value_or_default($value, 'disabled', array()); ?> @@ -435,6 +443,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) : name="" value="" type="radio" + style="" class="" diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php b/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php index aaaa777505e..338a6e17608 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php @@ -92,6 +92,38 @@ class CustomOrdersTableController { 999, 2 ); + + add_filter( + 'updated_option', + function( $option, $old_value, $value ) { + $this->process_updated_option( $option, $old_value, $value ); + }, + 999, + 3 + ); + + add_filter( + 'pre_update_option', + function( $value, $option, $old_value ) { + return $this->process_pre_update_option( $option, $old_value, $value ); + }, + 999, + 3 + ); + + add_filter( + DataSynchronizer::PENDING_SYNCHRONIZATION_FINISHED_ACTION, + function() { + $this->process_sync_finished(); + } + ); + + add_action( + 'woocommerce_update_options_advanced_custom_data_stores', + function() { + $this->process_options_updated(); + } + ); } /** @@ -213,6 +245,7 @@ class CustomOrdersTableController { } $this->data_synchronizer->create_database_tables(); + update_option( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'no' ); } /** @@ -258,40 +291,182 @@ class CustomOrdersTableController { return $settings; } - $title_item = array( - 'title' => __( 'Custom orders tables', 'woocommerce' ), - 'type' => 'title', - 'desc' => sprintf( - /* translators: %1$s = tag, %2$s = tag. */ - __( '%1$sWARNING:%2$s This feature is currently under development and may cause database instability. For contributors only.', 'woocommerce' ), - '', - '' - ), - ); - if ( $this->data_synchronizer->check_orders_table_exists() ) { - $settings[] = $title_item; + $settings[] = array( + 'title' => __( 'Custom orders tables', 'woocommerce' ), + 'type' => 'title', + 'id' => 'cot-title', + 'desc' => sprintf( + /* translators: %1$s = tag, %2$s = tag. */ + __( '%1$sWARNING:%2$s This feature is currently under development and may cause database instability. For contributors only.', 'woocommerce' ), + '', + '' + ), + ); + + $sync_status = $this->data_synchronizer->get_sync_status(); + $sync_is_pending = 0 !== $sync_status['current_pending_count']; $settings[] = array( - 'title' => __( 'Enable tables usage', 'woocommerce' ), - 'desc' => __( 'Use the custom orders tables as the main orders data store.', 'woocommerce' ), + 'title' => __( 'Data store for orders', 'woocommerce' ), 'id' => self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'default' => 'no', - 'type' => 'checkbox', + 'type' => 'radio', + 'options' => array( + 'yes' => __( 'Use the WooCommerce orders tables', 'woocommerce' ), + 'no' => __( 'Use the WordPress posts table', 'woocommerce' ), + ), 'checkboxgroup' => 'start', + 'disabled' => $sync_is_pending ? array( 'yes', 'no' ) : array(), ); + + if ( $sync_is_pending ) { + $initial_pending_count = $sync_status['initial_pending_count']; + if ( $initial_pending_count ) { + $text = sprintf( + /* translators: %1$s=current number of orders pending sync, %2$s=initial number of orders pending sync */ + __( 'There are %1$s orders (out of a total of %2$s) pending sync!', 'woocommerce' ), + $sync_status['current_pending_count'], + $initial_pending_count + ); + } else { + $text = sprintf( + /* translators: %1$s=current number of orders pending sync */ + __( 'There are %1$s orders pending sync!', 'woocommerce' ), + $sync_status['current_pending_count'] + ); + } + + if ( $this->data_synchronizer->pending_data_sync_is_in_progress() ) { + $text .= __( '
Syncrhonization for these orders is currently in progress.', 'woocommerce' ); + } + + $text .= __( "
The authoritative table can't be changed until sync completes.", 'woocommerce' ); + + $settings[] = array( + 'type' => 'info', + 'id' => 'cot-out-of-sync-warning', + 'css' => 'color: #C00000', + 'text' => $text, + ); + } + + $settings[] = array( + 'desc' => __( 'Keep the posts table and the orders tables synchronized', 'woocommerce' ), + 'id' => DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION, + 'type' => 'checkbox', + ); + + if ( $sync_is_pending ) { + if ( $this->data_synchronizer->data_sync_is_enabled() ) { + $message = $this->custom_orders_table_usage_is_enabled() ? + __( 'Switch to using the posts table as the authoritative data store for orders when sync finishes', 'woocommerce' ) : + __( 'Switch to using the orders table as the authoritative data store for orders when sync finishes', 'woocommerce' ); + $settings[] = array( + 'desc' => $message, + 'id' => DataSynchronizer::AUTO_FLIP_AUTHORITATIVE_TABLE_ROLES_OPTION, + 'type' => 'checkbox', + ); + } + } } else { - $title_item['desc'] = sprintf( - /* translators: %1$s = tag, %2$s = tag. */ - __( 'Create the tables first by going to %1$sWooCommerce > Status > Tools%2$s and running %1$sCreate the custom orders tables%2$s.', 'woocommerce' ), - '', - '' + $settings[] = array( + 'title' => __( 'Custom orders tables', 'woocommerce' ), + 'type' => 'title', + 'desc' => sprintf( + /* translators: %1$s = tag, %2$s = tag. */ + __( 'Create the tables first by going to %1$sWooCommerce > Status > Tools%2$s and running %1$sCreate the custom orders tables%2$s.', 'woocommerce' ), + '', + '' + ), ); - $settings[] = $title_item; } $settings[] = array( 'type' => 'sectionend' ); return $settings; } + + /** + * Handler for the individual setting updated hook. + * + * @param string $option Setting name. + * @param mixed $old_value Old value of the setting. + * @param mixed $value New value of the setting. + */ + private function process_updated_option( $option, $old_value, $value ) { + if ( DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION === $option && 'no' === $value ) { + $this->data_synchronizer->cleanup_synchronization_state(); + } + } + + /** + * Handler for the setting pre-update hook. + * We use it to verify that authoritative orders table switch doesn't happen while sync is pending. + * + * @param string $option Setting name. + * @param mixed $old_value Old value of the setting. + * @param mixed $value New value of the setting. + * + * @throws \Exception Attempt to change the authoritative orders table while orders sync is pending. + */ + private function process_pre_update_option( $option, $old_value, $value ) { + if ( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION !== $option || $value === $old_value || false === $old_value ) { + return $value; + } + + $sync_is_pending = 0 !== $this->data_synchronizer->get_current_orders_pending_sync_count(); + if ( $sync_is_pending ) { + throw new \Exception( "The authoritative table for orders storage can't be changed while there are orders out of sync" ); + } + + return $value; + } + + /** + * Handler for the synchronization finished hook. + * Here we switch the authoritative table if needed. + */ + private function process_sync_finished() { + if ( $this->auto_flip_authoritative_table_enabled() ) { + return; + } + + update_option( DataSynchronizer::AUTO_FLIP_AUTHORITATIVE_TABLE_ROLES_OPTION, 'no' ); + + if ( $this->custom_orders_table_usage_is_enabled() ) { + update_option( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'no' ); + } else { + update_option( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'yes' ); + } + } + + /** + * Is the automatic authoritative table switch setting set? + * + * @return bool + */ + private function auto_flip_authoritative_table_enabled(): bool { + return 'yes' === get_option( DataSynchronizer::AUTO_FLIP_AUTHORITATIVE_TABLE_ROLES_OPTION ); + } + + /** + * Handler for the all settings updated hook. + */ + private function process_options_updated() { + $data_sync_is_enabled = $this->data_synchronizer->data_sync_is_enabled(); + + // Disabling the sync implies disabling the automatic authoritative table switch too. + if ( ! $data_sync_is_enabled && $this->auto_flip_authoritative_table_enabled() ) { + update_option( DataSynchronizer::AUTO_FLIP_AUTHORITATIVE_TABLE_ROLES_OPTION, 'no' ); + } + + // Enabling the sync implies starting it too, if needed. + // We do this check here, and not in process_pre_update_option, so that if for some reason + // the setting is enabled but no sync is in process, sync will start by just saving the + // settings even without modifying them. + if ( $data_sync_is_enabled && ! $this->data_synchronizer->pending_data_sync_is_in_progress() ) { + $this->data_synchronizer->start_synchronizing_pending_orders(); + } + } } diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php index 19894cfcb18..579a419e43b 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php @@ -17,6 +17,13 @@ defined( 'ABSPATH' ) || exit; */ class DataSynchronizer { + const ORDERS_DATA_SYNC_ENABLED_OPTION = 'woocommerce_custom_orders_table_data_sync_enabled'; + const INITIAL_ORDERS_PENDING_SYNC_COUNT_OPTION = 'woocommerce_initial_orders_pending_sync_count'; + const AUTO_FLIP_AUTHORITATIVE_TABLE_ROLES_OPTION = 'woocommerce_auto_flip_authoritative_table_roles'; + const PENDING_SYNC_IS_IN_PROGRESS_OPTION = 'woocommerce_custom_orders_table_pending_sync_in_progress'; + const ORDERS_SYNC_SCHEDULED_ACTION_CALLBACK = 'woocommerce_run_orders_sync_callback'; + const PENDING_SYNCHRONIZATION_FINISHED_ACTION = 'woocommerce_orders_sync_finished'; + /** * The data store object to use. * @@ -31,7 +38,17 @@ class DataSynchronizer { */ private $database_util; - // TODO: Add a constructor to handle hooks as appropriate. + /** + * Class constructor. + */ + public function __construct() { + add_action( + self::ORDERS_SYNC_SCHEDULED_ACTION_CALLBACK, + function() { + $this->do_pending_orders_synchronization(); + } + ); + } /** * Class initialization, invoked by the DI container. @@ -74,5 +91,113 @@ class DataSynchronizer { } } + /** + * Is the data sync between old and new tables currently enabled? + * + * @return bool + */ + public function data_sync_is_enabled(): bool { + return 'yes' === get_option( self::ORDERS_DATA_SYNC_ENABLED_OPTION ); + } + /** + * Is a sync process currently in progress? + * + * @return bool + */ + public function pending_data_sync_is_in_progress(): bool { + return 'yes' === get_option( self::PENDING_SYNC_IS_IN_PROGRESS_OPTION ); + } + + /** + * Get the current sync process status. + * The information is meaningful only if pending_data_sync_is_in_progress return true. + * + * @return array + */ + public function get_sync_status() { + return array( + 'initial_pending_count' => (int) get_option( self::INITIAL_ORDERS_PENDING_SYNC_COUNT_OPTION, 0 ), + 'current_pending_count' => $this->get_current_orders_pending_sync_count(), + 'auto_flip' => 'yes' === get_option( self::AUTO_FLIP_AUTHORITATIVE_TABLE_ROLES_OPTION ), + 'sync_in_progress' => $this->pending_data_sync_is_in_progress(), + ); + } + + /** + * Calculate how many orders need to be synchronized currently. + */ + public function get_current_orders_pending_sync_count(): int { + // TODO: get this value by querying the database. + return get_option( 'woocommerce_fake_orders_pending_sync_count', 0 ); + } + + /** + * Start an orders synchronization process. + * This will setup the appropriate status information and schedule the first synchronization batch. + */ + public function start_synchronizing_pending_orders() { + $initial_pending_count = $this->get_current_orders_pending_sync_count(); + if ( 0 === $initial_pending_count ) { + return; + } + + update_option( self::INITIAL_ORDERS_PENDING_SYNC_COUNT_OPTION, $initial_pending_count ); + + $queue = WC()->get_instance_of( \WC_Queue::class ); + $queue->cancel_all( self::ORDERS_SYNC_SCHEDULED_ACTION_CALLBACK ); + + update_option( self::PENDING_SYNC_IS_IN_PROGRESS_OPTION, 'yes' ); + $this->schedule_pending_orders_synchronization(); + } + + /** + * Schedule the next orders synchronization batch. + */ + private function schedule_pending_orders_synchronization() { + $queue = WC()->get_instance_of( \WC_Queue::class ); + $queue->schedule_single( + WC()->call_function( 'time' ) + 1, + self::ORDERS_SYNC_SCHEDULED_ACTION_CALLBACK, + array(), + 'woocommerce-db-updates' + ); + } + + /** + * Run one orders synchronization batch. + */ + private function do_pending_orders_synchronization() { + if ( ! $this->pending_data_sync_is_in_progress() ) { + return; + } + + // TODO: Syncrhonize a batch of orders. + $fake_count = (int) get_option( 'woocommerce_fake_orders_pending_sync_count', 0 ); + if ( $fake_count > 0 ) { + update_option( 'woocommerce_fake_orders_pending_sync_count', $fake_count - 1 ); + } + + if ( 0 === $this->get_current_orders_pending_sync_count() ) { + $this->cleanup_synchronization_state(); + + /** + * Hook to signal that the orders tables synchronization process has finised (nothing left to synchronize). + */ + do_action( self::PENDING_SYNCHRONIZATION_FINISHED_ACTION ); + } else { + $this->schedule_pending_orders_synchronization(); + } + } + + /** + * Cleanup all the synchronization status information, + * because the process has been disabled by the user via settings, + * or because there's nothing left to syncrhonize. + */ + public function cleanup_synchronization_state() { + delete_option( self::INITIAL_ORDERS_PENDING_SYNC_COUNT_OPTION ); + delete_option( self::PENDING_SYNC_IS_IN_PROGRESS_OPTION ); + delete_option( self::AUTO_FLIP_AUTHORITATIVE_TABLE_ROLES_OPTION ); + } } From c49280b91ad97c94c414d84be52bd205a4ea42f4 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Wed, 9 Mar 2022 12:09:19 +0100 Subject: [PATCH 007/386] Implement DataSynchronizer::get_current_orders_pending_sync_count Use real SQL to get the count of unsynced orders. --- .../DataStores/Orders/DataSynchronizer.php | 61 +++++++++++++++++-- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php index 579a419e43b..07d5160922e 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php @@ -24,6 +24,9 @@ class DataSynchronizer { const ORDERS_SYNC_SCHEDULED_ACTION_CALLBACK = 'woocommerce_run_orders_sync_callback'; const PENDING_SYNCHRONIZATION_FINISHED_ACTION = 'woocommerce_orders_sync_finished'; + // TODO: Remove the usage of the fake pending orders count once development of the feature is complete. + const FAKE_ORDERS_PENDING_SYNC_COUNT_OPTION = 'woocommerce_fake_orders_pending_sync_count'; + /** * The data store object to use. * @@ -126,10 +129,53 @@ class DataSynchronizer { /** * Calculate how many orders need to be synchronized currently. + * + * If an option whose name is given by self::FAKE_ORDERS_PENDING_SYNC_COUNT_OPTION exists, + * then the value of that option is returned. This is temporary, to ease testing the feature + * while it is in development. + * + * Otherwise a database query is performed to get how many orders match one of the following: + * + * - Existing in the authoritative table but not in the backup table. + * - Existing in both tables, but they have a different update date. */ public function get_current_orders_pending_sync_count(): int { - // TODO: get this value by querying the database. - return get_option( 'woocommerce_fake_orders_pending_sync_count', 0 ); + global $wpdb; + + // TODO: Remove the usage of the fake pending orders count once development of the feature is complete. + $count = get_option( self::FAKE_ORDERS_PENDING_SYNC_COUNT_OPTION ); + if ( false !== $count ) { + return (int) $count; + } + + $orders_table = $wpdb->prefix . 'wc_orders'; + + if ( 'yes' === get_option( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION ) ) { + $missing_orders_count_sql = "SELECT COUNT(1) FROM $orders_table WHERE post_id IS NULL"; + } else { + $missing_orders_count_sql = " +SELECT COUNT(1) FROM ( + SELECT posts.ID,orders.post_id FROM $wpdb->posts posts + LEFT JOIN $orders_table orders ON posts.ID = orders.post_id + WHERE posts.post_type='shop_order' AND orders.post_id IS NULL + AND posts.post_status != 'auto-draft' +) x"; + + } + + $sql = " +SELECT( + ($missing_orders_count_sql) + + + (SELECT COUNT(1) FROM ( + SELECT orders.post_id FROM $orders_table orders + JOIN $wpdb->posts posts on posts.ID = orders.post_id + WHERE orders.date_updated_gmt != posts.post_modified_gmt + ) x) +) count"; + + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + return (int) $wpdb->get_var( $wpdb->prepare( $sql ) ); } /** @@ -172,10 +218,13 @@ class DataSynchronizer { return; } - // TODO: Syncrhonize a batch of orders. - $fake_count = (int) get_option( 'woocommerce_fake_orders_pending_sync_count', 0 ); - if ( $fake_count > 0 ) { - update_option( 'woocommerce_fake_orders_pending_sync_count', $fake_count - 1 ); + // TODO: Remove the usage of the fake pending orders count once development of the feature is complete. + $fake_count = get_option( self::FAKE_ORDERS_PENDING_SYNC_COUNT_OPTION ); + if ( false !== $fake_count ) { + update_option( 'woocommerce_fake_orders_pending_sync_count', (int) $fake_count - 1 ); + // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedElse + } else { + // TODO: Synchronize a batch of orders. } if ( 0 === $this->get_current_orders_pending_sync_count() ) { From 65b716346d039424b4a5f0ac82ffd9721b9e7611 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Thu, 10 Mar 2022 11:41:58 +0100 Subject: [PATCH 008/386] Add the DataSynchronizer::get_ids_of_orders_pending_sync method --- .../DataStores/Orders/DataSynchronizer.php | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php index 07d5160922e..802b69aeab2 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php @@ -24,6 +24,11 @@ class DataSynchronizer { const ORDERS_SYNC_SCHEDULED_ACTION_CALLBACK = 'woocommerce_run_orders_sync_callback'; const PENDING_SYNCHRONIZATION_FINISHED_ACTION = 'woocommerce_orders_sync_finished'; + // Allowed values for $type in get_ids_of_orders_pending_sync method. + const ID_TYPE_MISSING_IN_ORDERS_TABLE = 0; + const ID_TYPE_MISSING_IN_POSTS_TABLE = 1; + const ID_TYPE_DIFFERENT_UPDATE_DATE = 2; + // TODO: Remove the usage of the fake pending orders count once development of the feature is complete. const FAKE_ORDERS_PENDING_SYNC_COUNT_OPTION = 'woocommerce_fake_orders_pending_sync_count'; @@ -160,7 +165,6 @@ SELECT COUNT(1) FROM ( WHERE posts.post_type='shop_order' AND orders.post_id IS NULL AND posts.post_status != 'auto-draft' ) x"; - } $sql = " @@ -178,6 +182,54 @@ SELECT( return (int) $wpdb->get_var( $wpdb->prepare( $sql ) ); } + /** + * Get a list of ids of orders than are out of sync. + * + * Valid values for $type are: + * + * ID_TYPE_MISSING_IN_ORDERS_TABLE: orders that exist in posts table but not in orders table. Returns post ids. + * ID_TYPE_MISSING_IN_POSTS_TABLE: orders that exist in orders table but not in posts table. Returns ids from orders table. + * ID_TYPE_DIFFERENT_UPDATE_DATE: orders that exist in both tables but have different last update dates. Returns ids from orders table. + * + * @param int $type One of ID_TYPE_MISSING_IN_ORDERS_TABLE, ID_TYPE_MISSING_IN_POSTS_TABLE, ID_TYPE_DIFFERENT_UPDATE_DATE. + * @param int $limit Maximum number of ids to return. + * @return array An array of order or post ids. + * @throws \Exception Invalid parameter. + */ + private function get_ids_of_orders_pending_sync( int $type, int $limit ) { + global $wpdb; + + if ( $limit < 1 ) { + throw new \Exception( '$limit must be at least 1' ); + } + + $orders_table = $wpdb->prefix . 'wc_orders'; + + switch ( $type ) { + case self::ID_TYPE_MISSING_IN_ORDERS_TABLE: + $sql = " +SELECT posts.ID FROM $wpdb->posts posts +LEFT JOIN $orders_table orders ON posts.ID = orders.post_id +WHERE posts.post_type='shop_order' AND orders.post_id IS NULL +AND posts.post_status != 'auto-draft'"; + break; + case self::ID_TYPE_MISSING_IN_POSTS_TABLE: + $sql = "SELECT id FROM $orders_table WHERE post_id IS NULL"; + break; + case self::ID_TYPE_DIFFERENT_UPDATE_DATE: + $sql = " +SELECT orders.id FROM $orders_table orders +JOIN $wpdb->posts posts on posts.ID = orders.post_id +WHERE orders.date_updated_gmt != posts.post_modified_gmt"; + break; + default: + throw new \Exception( 'Invalid $type, must be one of the ID_TYPE_... constants.' ); + } + + // phpcs:ignore WordPress.DB + return array_map( 'intval', $wpdb->get_col( $sql . " LIMIT $limit" ) ); + } + /** * Start an orders synchronization process. * This will setup the appropriate status information and schedule the first synchronization batch. @@ -224,7 +276,7 @@ SELECT( update_option( 'woocommerce_fake_orders_pending_sync_count', (int) $fake_count - 1 ); // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedElse } else { - // TODO: Synchronize a batch of orders. + // TODO: Use get_ids_of_orders_pending_sync to get a batch of order ids and syncrhonize them. } if ( 0 === $this->get_current_orders_pending_sync_count() ) { From 746d6a99b1f9e964332ea82d4ba152a283cb19aa Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Fri, 11 Mar 2022 16:56:31 +0100 Subject: [PATCH 009/386] Improve the "Orders out of sync" warning message in COT settings --- .../DataStores/Orders/CustomOrdersTableController.php | 6 +++--- .../src/Internal/DataStores/Orders/DataSynchronizer.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php b/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php index 338a6e17608..5465fda33f7 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php @@ -338,11 +338,11 @@ class CustomOrdersTableController { } if ( $this->data_synchronizer->pending_data_sync_is_in_progress() ) { - $text .= __( '
Syncrhonization for these orders is currently in progress.', 'woocommerce' ); + $text .= __( "
Synchronization for these orders is currently in progress.
The authoritative table can't be changed until sync completes.", 'woocommerce' ); + } else { + $text .= __( "
The authoritative table can't be changed until these orders are synchronized.", 'woocommerce' ); } - $text .= __( "
The authoritative table can't be changed until sync completes.", 'woocommerce' ); - $settings[] = array( 'type' => 'info', 'id' => 'cot-out-of-sync-warning', diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php index 802b69aeab2..4b91d114c8b 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php @@ -283,7 +283,7 @@ WHERE orders.date_updated_gmt != posts.post_modified_gmt"; $this->cleanup_synchronization_state(); /** - * Hook to signal that the orders tables synchronization process has finised (nothing left to synchronize). + * Hook to signal that the orders tables synchronization process has finished (nothing left to synchronize). */ do_action( self::PENDING_SYNCHRONIZATION_FINISHED_ACTION ); } else { From 71cc725bb5e770f49c0f9afd54ef9d019ce7ac0e Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Wed, 16 Mar 2022 11:00:41 +0100 Subject: [PATCH 010/386] Some minor improvements in the custom orders table implmentation. --- .../admin/class-wc-admin-settings.php | 4 ++-- .../Orders/CustomOrdersTableController.php | 22 +++++++++---------- .../DataStores/Orders/DataSynchronizer.php | 10 ++++----- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-settings.php b/plugins/woocommerce/includes/admin/class-wc-admin-settings.php index 48bbe0e6e2a..a6a2499c7c1 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-settings.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-settings.php @@ -275,7 +275,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) : break; case 'info': - echo ''; + echo ''; echo wp_kses_post( wpautop( wptexturize( $value['text'] ) ) ); echo ''; break; @@ -443,7 +443,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) : name="" value="" type="radio" - + style="" class="" diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php b/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php index 5465fda33f7..baf04735c71 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php @@ -322,19 +322,19 @@ class CustomOrdersTableController { if ( $sync_is_pending ) { $initial_pending_count = $sync_status['initial_pending_count']; + $current_pending_count = $sync_status['current_pending_count']; if ( $initial_pending_count ) { - $text = sprintf( - /* translators: %1$s=current number of orders pending sync, %2$s=initial number of orders pending sync */ - __( 'There are %1$s orders (out of a total of %2$s) pending sync!', 'woocommerce' ), - $sync_status['current_pending_count'], - $initial_pending_count - ); + $text = + sprintf( + /* translators: %1$s=current number of orders pending sync, %2$s=initial number of orders pending sync */ + _n( 'There\'s %1$s order (out of a total of %2$s) pending sync!', 'There are %1$s orders (out of a total of %2$s) pending sync!', $current_pending_count, 'woocommerce' ), + $current_pending_count, + $initial_pending_count + ); } else { - $text = sprintf( - /* translators: %1$s=current number of orders pending sync */ - __( 'There are %1$s orders pending sync!', 'woocommerce' ), - $sync_status['current_pending_count'] - ); + $text = + /* translators: %s=initial number of orders pending sync */ + sprintf( _n( 'There\'s %s order pending sync!', 'There are %s orders pending sync!', $current_pending_count, 'woocommerce' ), $current_pending_count, 'woocommerce' ); } if ( $this->data_synchronizer->pending_data_sync_is_in_progress() ) { diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php index 4b91d114c8b..09bc4bf589c 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php @@ -159,12 +159,10 @@ class DataSynchronizer { $missing_orders_count_sql = "SELECT COUNT(1) FROM $orders_table WHERE post_id IS NULL"; } else { $missing_orders_count_sql = " -SELECT COUNT(1) FROM ( - SELECT posts.ID,orders.post_id FROM $wpdb->posts posts - LEFT JOIN $orders_table orders ON posts.ID = orders.post_id - WHERE posts.post_type='shop_order' AND orders.post_id IS NULL - AND posts.post_status != 'auto-draft' -) x"; +SELECT COUNT( posts.ID ) FROM $wpdb->posts posts +LEFT JOIN $orders_table orders ON posts.ID = orders.post_id +WHERE posts.post_type='shop_order' AND orders.post_id IS NULL +AND posts.post_status != 'auto-draft'"; } $sql = " From bb9feb7d68af2c7065523252cdacf2cd08f2f085 Mon Sep 17 00:00:00 2001 From: Josh Betz Date: Thu, 17 Mar 2022 14:20:17 -0500 Subject: [PATCH 011/386] Add `order_item_display_meta` query parameter By default we will show all meta for order items to maintain backwards compatibility. When this new parameter is set, we will filter out variation meta just as core does on the web. --- .../class-wc-rest-orders-v2-controller.php | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php index b80be00ef60..bc6333dd33e 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php @@ -211,24 +211,26 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller { // Expand meta_data to include user-friendly values. $formatted_meta_data = $item->get_formatted_meta_data( null, true ); - // Filter out product variations - // TODO: Add a query arg to activate this. Default should be to keep variation meta for back-compat - if ( $product ) { - $order_item_name = $data['name']; - $data['meta_data'] = array_filter( $data['meta_data'], function( $meta ) use ( $product, $order_item_name ) { - $meta->key = rawurldecode( (string) $meta->key ); - $meta->value = rawurldecode( (string) $meta->value ); - $attribute_key = str_replace( 'attribute_', '', $meta->key ); - $display_key = wc_attribute_label( $attribute_key, $product ); - $display_value = wp_kses_post( $meta->value ); + // Filter out product variations. + if ( $product && 'true' === $this->request['order_item_display_meta'] ) { + $order_item_name = $data['name']; + $data['meta_data'] = array_filter( + $data['meta_data'], + function( $meta ) use ( $product, $order_item_name ) { + $meta->key = rawurldecode( (string) $meta->key ); + $meta->value = rawurldecode( (string) $meta->value ); + $attribute_key = str_replace( 'attribute_', '', $meta->key ); + $display_key = wc_attribute_label( $attribute_key, $product ); + $display_value = wp_kses_post( $meta->value ); - // Skip items with values already in the product details area of the product name. - if ( $product && $product->is_type( 'variation' ) && wc_is_attribute_in_product_name( $display_value, $order_item_name ) ) { - return false; + // Skip items with values already in the product details area of the product name. + if ( $product && $product->is_type( 'variation' ) && wc_is_attribute_in_product_name( $display_value, $order_item_name ) ) { + return false; + } + + return true; } - - return true; - } ); + ); } $data['meta_data'] = array_map( From 31bcda40b23ac2ee1d8c295f180f7312228f2de5 Mon Sep 17 00:00:00 2001 From: Josh Betz Date: Thu, 17 Mar 2022 14:49:41 -0500 Subject: [PATCH 012/386] Check $product is set This variable is set conditionally, so we need to explicitly check if it is set rather than just checking if it's truthy. --- .../Controllers/Version2/class-wc-rest-orders-v2-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php index e3374f2b6f9..ebbaa118c0e 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php @@ -212,7 +212,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller { $formatted_meta_data = $item->get_all_formatted_meta_data( null ); // Filter out product variations. - if ( $product && 'true' === $this->request['order_item_display_meta'] ) { + if ( isset( $product ) && 'true' === $this->request['order_item_display_meta'] ) { $order_item_name = $data['name']; $data['meta_data'] = array_filter( $data['meta_data'], From 17b18ac68f850bbcb573b0d72e4ec3b9c5c0502e Mon Sep 17 00:00:00 2001 From: Ovidiu Liuta Date: Tue, 22 Mar 2022 14:26:41 +0200 Subject: [PATCH 013/386] adding woocommerce_download_parse_file_path filter --- plugins/woocommerce/includes/class-wc-download-handler.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-download-handler.php b/plugins/woocommerce/includes/class-wc-download-handler.php index 97f0961fed5..be4cd66fb21 100644 --- a/plugins/woocommerce/includes/class-wc-download-handler.php +++ b/plugins/woocommerce/includes/class-wc-download-handler.php @@ -276,9 +276,10 @@ class WC_Download_Handler { // Paths that begin with '//' are always remote URLs. if ( '//' === substr( $file_path, 0, 2 ) ) { + $file_path = (is_ssl() ? 'https:' : 'http:') . $file_path; return array( 'remote_file' => true, - 'file_path' => is_ssl() ? 'https:' . $file_path : 'http:' . $file_path, + 'file_path' => apply_filters( 'woocommerce_download_parse_file_path', $file_path, true ), ); } @@ -299,7 +300,7 @@ class WC_Download_Handler { return array( 'remote_file' => $remote_file, - 'file_path' => $file_path, + 'file_path' => apply_filters( 'woocommerce_download_parse_file_path', $file_path, $remote_file ) ); } From 867f26da8b3c01198188597fb349356fafdfe972 Mon Sep 17 00:00:00 2001 From: Ovidiu Liuta Date: Tue, 22 Mar 2022 14:42:08 +0200 Subject: [PATCH 014/386] woocommerce_download_parse_remote_file_path filter --- plugins/woocommerce/includes/class-wc-download-handler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/class-wc-download-handler.php b/plugins/woocommerce/includes/class-wc-download-handler.php index be4cd66fb21..2783c38ed65 100644 --- a/plugins/woocommerce/includes/class-wc-download-handler.php +++ b/plugins/woocommerce/includes/class-wc-download-handler.php @@ -279,7 +279,7 @@ class WC_Download_Handler { $file_path = (is_ssl() ? 'https:' : 'http:') . $file_path; return array( 'remote_file' => true, - 'file_path' => apply_filters( 'woocommerce_download_parse_file_path', $file_path, true ), + 'file_path' => apply_filters( 'woocommerce_download_parse_remote_file_path', $file_path, true ), ); } From 4c97d8e7573c979194f008db44ca8ebb0000d27e Mon Sep 17 00:00:00 2001 From: Ovidiu Liuta Date: Tue, 22 Mar 2022 14:45:42 +0200 Subject: [PATCH 015/386] Update class-wc-download-handler.php --- plugins/woocommerce/includes/class-wc-download-handler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/class-wc-download-handler.php b/plugins/woocommerce/includes/class-wc-download-handler.php index 2783c38ed65..87d2a2214af 100644 --- a/plugins/woocommerce/includes/class-wc-download-handler.php +++ b/plugins/woocommerce/includes/class-wc-download-handler.php @@ -279,7 +279,7 @@ class WC_Download_Handler { $file_path = (is_ssl() ? 'https:' : 'http:') . $file_path; return array( 'remote_file' => true, - 'file_path' => apply_filters( 'woocommerce_download_parse_remote_file_path', $file_path, true ), + 'file_path' => apply_filters( 'woocommerce_download_parse_remote_file_path', $file_path ), ); } From 9d9b7464d70014bed7e56c283adbe515a368cb4e Mon Sep 17 00:00:00 2001 From: Ovidiu Liuta Date: Tue, 22 Mar 2022 14:46:14 +0200 Subject: [PATCH 016/386] Update class-wc-download-handler.php --- plugins/woocommerce/includes/class-wc-download-handler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/class-wc-download-handler.php b/plugins/woocommerce/includes/class-wc-download-handler.php index 87d2a2214af..789bc19ef97 100644 --- a/plugins/woocommerce/includes/class-wc-download-handler.php +++ b/plugins/woocommerce/includes/class-wc-download-handler.php @@ -276,7 +276,7 @@ class WC_Download_Handler { // Paths that begin with '//' are always remote URLs. if ( '//' === substr( $file_path, 0, 2 ) ) { - $file_path = (is_ssl() ? 'https:' : 'http:') . $file_path; + $file_path = ( is_ssl() ? 'https:' : 'http:' ) . $file_path; return array( 'remote_file' => true, 'file_path' => apply_filters( 'woocommerce_download_parse_remote_file_path', $file_path ), From e7f822e8eba18d94a0fcefefbfbb6fbf63667f89 Mon Sep 17 00:00:00 2001 From: Rodrigue Tusse Date: Wed, 23 Mar 2022 15:51:42 +0200 Subject: [PATCH 017/386] Changes to class-wc-auth that allows it to work with sso auth --- .../woocommerce/includes/class-wc-auth.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/plugins/woocommerce/includes/class-wc-auth.php b/plugins/woocommerce/includes/class-wc-auth.php index 23fdf0861ce..38aede56ad0 100644 --- a/plugins/woocommerce/includes/class-wc-auth.php +++ b/plugins/woocommerce/includes/class-wc-auth.php @@ -324,6 +324,36 @@ class WC_Auth { // Login endpoint. if ( 'login' === $route && ! is_user_logged_in() ) { + /** + * If a merchant is using the WordPress SSO (handled through Jetpack) + * to manage their authorisation then it is likely they'll find that + * their username and password do not work through this form. We + * instead need to redirect them to the WordPress login so that they + * can then be redirected back here with a valid token. + */ + + // Check if Jetpack is installed and activated. + if ( class_exists( 'Jetpack' ) && Jetpack::connection()->is_active() ) { + + // Check if the user is using the WordPress.com SSO. + if ( Jetpack::is_module_active( 'sso' ) ) { + + $redirect_url = $this->build_url( $data, 'authorize' ); + + // Build the SSO URL. + $login_url = Jetpack_SSO::get_instance()->build_sso_button_url( + array( + 'redirect_to' => rawurlencode( esc_url_raw( $redirect_url ) ), + 'action' => 'login', + ) + ); + + // Perform the redirect. + wp_safe_redirect( $login_url ); + exit; + } + } + wc_get_template( 'auth/form-login.php', array( From 939d429389c5a4d07627149d8099be72cb7e813b Mon Sep 17 00:00:00 2001 From: Josh Betz Date: Wed, 23 Mar 2022 11:09:50 -0500 Subject: [PATCH 018/386] Simplify product variation filtering Fixes an issue where we were conditionally mutating the meta key and value. This was originally copy/pasted from somewhere else. In this context, we don't need all of these values, so the simpler approach is good. Co-authored-by: Barry Hughes <3594411+barryhughes@users.noreply.github.com> --- .../Version2/class-wc-rest-orders-v2-controller.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php index ebbaa118c0e..421a55e0c11 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php @@ -217,11 +217,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller { $data['meta_data'] = array_filter( $data['meta_data'], function( $meta ) use ( $product, $order_item_name ) { - $meta->key = rawurldecode( (string) $meta->key ); - $meta->value = rawurldecode( (string) $meta->value ); - $attribute_key = str_replace( 'attribute_', '', $meta->key ); - $display_key = wc_attribute_label( $attribute_key, $product ); - $display_value = wp_kses_post( $meta->value ); + $display_value = wp_kses_post( rawurldecode( (string) $meta->value ) ); // Skip items with values already in the product details area of the product name. if ( $product && $product->is_type( 'variation' ) && wc_is_attribute_in_product_name( $display_value, $order_item_name ) ) { From f0c18b668391f5481959ee6ec0580c6f403ada8e Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Fri, 25 Mar 2022 06:00:03 -0700 Subject: [PATCH 019/386] Update documentation links. --- plugins/woocommerce/includes/class-wc-product-download.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-product-download.php b/plugins/woocommerce/includes/class-wc-product-download.php index a0f47b516e7..416d49eed94 100644 --- a/plugins/woocommerce/includes/class-wc-product-download.php +++ b/plugins/woocommerce/includes/class-wc-product-download.php @@ -216,7 +216,7 @@ class WC_Product_Download implements ArrayAccess { /* translators: %1$s is the downloadable file path, %2$s is an opening link tag, %3%s is a closing link tag. */ __( 'The downloadable file %1$s cannot be used: it is not located in an approved directory. Please contact a site administrator and request their approval. %2$sLearn more.%3$s', 'woocommerce' ), '' . $download_file . '', - '', // @todo update to working link (see https://github.com/Automattic/woocommerce/issues/181) + '', '' ) ); @@ -229,7 +229,7 @@ class WC_Product_Download implements ArrayAccess { /* translators: %1$s is the downloadable file path, %2$s is an opening link tag, %3%s is a closing link tag. */ __( 'The downloadable file %1$s cannot be used: it is not located in an approved directory. Please contact a site administrator for help. %2$sLearn more.%3$s', 'woocommerce' ), '' . $download_file . '', - '', // @todo update to working link (see https://github.com/Automattic/woocommerce/issues/181) + '', '' ) ); From cee42b726446f1cc35aef44685a5d7b4504b2810 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Thu, 24 Mar 2022 17:14:00 -0700 Subject: [PATCH 020/386] Enhance directory traversal handling/resolution. --- .../src/Internal/Utilities/URL.php | 81 ++++++++++++++++--- .../php/src/Internal/Utilities/URLTest.php | 65 +++++++++++++-- 2 files changed, 128 insertions(+), 18 deletions(-) diff --git a/plugins/woocommerce/src/Internal/Utilities/URL.php b/plugins/woocommerce/src/Internal/Utilities/URL.php index bf35943617e..0b64c3c0b8b 100644 --- a/plugins/woocommerce/src/Internal/Utilities/URL.php +++ b/plugins/woocommerce/src/Internal/Utilities/URL.php @@ -124,8 +124,11 @@ class URL { * without touching the filesystem. */ private function process_path() { - $segments = explode( '/', $this->components['path'] ); - $this->is_absolute = substr( $this->components['path'], 0, 1 ) === '/'; + $segments = explode( '/', $this->components['path'] ); + $this->is_absolute = substr( $this->components['path'], 0, 1 ) === '/' || ! empty( $this->components['host'] ); + $is_directory = substr( $this->components['path'], -1, 1 ) === '/' && strlen( $this->components['path'] ) > 1; + $resolve_traversals = 'file' !== $this->components['scheme'] || $this->is_absolute; + $retain_traversals = false; // Clean the path. foreach ( $segments as $part ) { @@ -137,19 +140,39 @@ class URL { // Directory traversals created with percent-encoding syntax should also be detected. $is_traversal = str_ireplace( '%2e', '.', $part ) === '..'; - // Unwind directory traversals. - if ( $is_traversal && count( $this->path_parts ) > 0 ) { - $this->path_parts = array_slice( $this->path_parts, 0, count( $this->path_parts ) - 1 ); - continue; + // Resolve directory traversals (if allowed: see further comment relating to this). + if ( $resolve_traversals && $is_traversal ) { + if ( count( $this->path_parts ) > 0 && ! $retain_traversals ) { + $this->path_parts = array_slice( $this->path_parts, 0, count( $this->path_parts ) - 1 ); + continue; + } elseif ( $this->is_absolute ) { + continue; + } } + /* + * Consider allowing directory traversals to be resolved (ie, the process that converts 'foo/bar/../baz' to + * 'foo/baz'). + * + * 1. We are only concerned with file URLs, for all other types unwinding of traversals is already allowed. + * 2. This is a 'one time' and unidirectional operation. We only wish to flip from false to true, and we + * never wish to do this more than once. + * 3. We only flip the switch after we have examined all leading '..' traversal segments. + */ + if ( false === $resolve_traversals && '..' !== $part && 'file' === $this->components['scheme'] && ! $this->is_absolute ) { + $resolve_traversals = true; + } + + // At this point, if we are committing a traversal to the path then we will wish to retain the next traversal, too. + $retain_traversals = $resolve_traversals && '..' === $part; + // Retain this part of the path. $this->path_parts[] = $part; } // Reform the path from the processed segments, appending a leading slash if it is absolute and restoring // the Windows drive letter if we have one. - $this->components['path'] = ( $this->is_absolute ? '/' : '' ) . implode( '/', $this->path_parts ); + $this->components['path'] = ( $this->is_absolute ? '/' : '' ) . implode( '/', $this->path_parts ) . ( $is_directory ? '/' : '' ); } /** @@ -187,16 +210,47 @@ class URL { * this is set to 1 (parent). 2 will yield the grand-parent, 3 will yield the great * grand-parent, etc. * + * If a level is specified that exceeds the number of path segments, this method will + * return false. + * * @param int $level Used to indicate the level of parent. * - * @return string + * @return string|false */ - public function get_parent_url( int $level = 1 ): string { + public function get_parent_url( int $level = 1 ) { if ( $level < 1 ) { $level = 1; } - $parent_path = implode( '/', array_slice( $this->path_parts, 0, count( $this->path_parts ) - $level ) ) . '/'; + $parent_path_parts_to_keep = count( $this->path_parts ) - $level; + + /* + * With the exception of file URLs, we do not allow obtaining (grand-)parent directories that require + * us to describe them using directory traversals. For example, given "http://hostname/foo/bar/baz.png" we do + * not permit determining anything more than 2 levels up (we cannot go beyond "http://hostname/"). + */ + if ( 'file' !== $this->components['scheme'] && $parent_path_parts_to_keep < 0 ) { + return false; + } + + // In the specific case of an absolute filepath describing the root directory, there can be no parent. + if ( 'file' === $this->components['scheme'] && $this->is_absolute && empty( $this->path_parts ) ) { + return false; + } + + if ( $parent_path_parts_to_keep >= 0 ) { + $parent_path = implode( '/', array_slice( $this->path_parts, 0, $parent_path_parts_to_keep ) ); + } else { + // For relative filepaths only, we use traversals to describe the requested parent. + $parent_path = untrailingslashit( str_repeat( '../', $parent_path_parts_to_keep * -1 ) ); + } + + if ( $this->is_relative() && '' === $parent_path ) { + $parent_path = '.'; + } + + // Append a trailing slash, since a parent is always a directory. The only exception is the current working directory. + $parent_path .= '/'; // For absolute paths, apply a leading slash (does not apply if we have a root path). if ( $this->is_absolute && 0 !== strpos( $parent_path, '/' ) ) { @@ -219,12 +273,17 @@ class URL { $scheme = null !== $this->components['scheme'] ? $this->components['scheme'] . '://' : ''; $host = null !== $this->components['host'] ? $this->components['host'] : ''; $port = null !== $this->components['port'] ? ':' . $this->components['port'] : ''; + $path = $path_override ?? $this->get_path(); + + // Special handling for hostless URLs (typically, filepaths) referencing the current working directory. + if ( '' === $host && ( '' === $path || '.' === $path ) ) { + $path = './'; + } $user = null !== $this->components['user'] ? $this->components['user'] : ''; $pass = null !== $this->components['pass'] ? ':' . $this->components['pass'] : ''; $user_pass = ( ! empty( $user ) || ! empty( $pass ) ) ? $user . $pass . '@' : ''; - $path = $path_override ?? $this->get_path(); $query = null !== $this->components['query'] ? '?' . $this->components['query'] : ''; $fragment = null !== $this->components['fragment'] ? '#' . $this->components['fragment'] : ''; diff --git a/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php b/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php index 415e12e2a2d..d265330f534 100644 --- a/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php +++ b/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php @@ -42,7 +42,19 @@ class URLTest extends WC_Unit_Test_Case { $this->assertEquals( '../should/remain/relative', ( new URL( 'relative/../../should/remain/relative' ) )->get_path(), - 'Simplifies a relative path containing directory traversals to the extent possible (without inspecting the filesystem).' + 'Simplifies a relative path containing directory traversals to the extent possible (without inspecting the filesystem - scenario #1).' + ); + + $this->assertEquals( + '../../should/remain/relative', + ( new URL( 'relative/../../../should/remain/relative' ) )->get_path(), + 'Simplifies a relative path containing directory traversals to the extent possible (without inspecting the filesystem - scenario #2).' + ); + + $this->assertEquals( + 'file:///foo/bar/baz', + ( new URL( '/../foo/bar/baz/bazooka/../../baz' ) )->get_url(), + 'Directory traversals are appropriately resolved even in complex cases with multiple separate traversals. When the original path is absolute, the output will be absolute.' ); } @@ -119,10 +131,9 @@ class URLTest extends WC_Unit_Test_Case { } public function test_can_obtain_parent_url() { - $this->assertEquals( - 'file:///', + $this->assertFalse( ( new URL( '/' ) )->get_parent_url(), - 'The parent of root directory "/" is "/".' + 'Root directory "/" is considered to have no parent.' ); $this->assertEquals( @@ -131,10 +142,9 @@ class URLTest extends WC_Unit_Test_Case { 'The parent URL will be trailingslashed.' ); - $this->assertEquals( - 'https://example.com/', + $this->assertFalse( ( new URL( 'https://example.com' ) )->get_parent_url(), - 'The host name (for non-file URLs) is distinct from the path and will not be removed.' + 'In the case of non-file URLs, if we only have a host name and no path then the parent cannot be derived.' ); } @@ -174,4 +184,45 @@ class URLTest extends WC_Unit_Test_Case { 'All parent URLs can be derived for a filepath, up to and including the root directory plus drive letter (Windows).' ); } + + public function test_obtaining_parent_urls_from_relative_urls() { + $this->assertEquals( + array( + 'file://relative/to/', + 'file://relative/', + 'file://./', + ), + ( new URL( 'relative/to/abspath' ) )->get_all_parent_urls(), + 'When obtaining all parent URLs for a relative filepath, we never return the root directory and never return a URL containing traversals. ' + ); + + $this->assertEquals( + 'file://./', + ( new URL( 'just-a-file.png' ) )->get_parent_url(), + 'The parent URL of an unqualified, relative file is simply an empty relative path (generally, though not always, this is equivalent to ABSPATH).' + ); + + $this->assertEquals( + 'file://../../', + ( new URL( '../../relatively-placed-file.pdf' ) )->get_parent_url() + ); + + $this->assertEquals( + 'file://../', + ( new URL( 'relatively-placed-file.pdf' ) )->get_parent_url( 2 ), + 'For filepaths, we can successfully determine the (grand-)parent directories of relative filepaths (when explicitly requested).' + ); + + $this->assertEquals( + 'file://../../', + ( new URL( 'relatively-placed-file.pdf' ) )->get_parent_url( 3 ), + 'For filepaths, we can successfully determine the (grand-)parent directories of relative filepaths (when explicitly requested).' + ); + + $this->assertEquals( + 'file://../foo/bar/baz', + ( new URL( '../foo/bar/cat/dog/../../baz' ) )->get_url(), + 'Directory traversals are appropriately resolved even in complex cases with multiple separate traversals.' + ); + } } From 1aecb64be5b89f58b4063f700691be4454ea38f3 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Fri, 25 Mar 2022 04:07:15 -0700 Subject: [PATCH 021/386] Cover further scenarios relating to relative URLs and traversals. --- .../src/Internal/Utilities/URL.php | 38 ++++++++++++++---- .../php/src/Internal/Utilities/URLTest.php | 39 ++++++++++++++++++- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/plugins/woocommerce/src/Internal/Utilities/URL.php b/plugins/woocommerce/src/Internal/Utilities/URL.php index 0b64c3c0b8b..a9ad45c9118 100644 --- a/plugins/woocommerce/src/Internal/Utilities/URL.php +++ b/plugins/woocommerce/src/Internal/Utilities/URL.php @@ -34,6 +34,13 @@ class URL { */ private $is_absolute; + /** + * If the URL (or filepath) represents a directory. + * + * @var bool + */ + private $is_directory; + /** * The components of the URL's path. * @@ -126,7 +133,7 @@ class URL { private function process_path() { $segments = explode( '/', $this->components['path'] ); $this->is_absolute = substr( $this->components['path'], 0, 1 ) === '/' || ! empty( $this->components['host'] ); - $is_directory = substr( $this->components['path'], -1, 1 ) === '/' && strlen( $this->components['path'] ) > 1; + $this->is_directory = substr( $this->components['path'], -1, 1 ) === '/' && strlen( $this->components['path'] ) > 1; $resolve_traversals = 'file' !== $this->components['scheme'] || $this->is_absolute; $retain_traversals = false; @@ -172,7 +179,7 @@ class URL { // Reform the path from the processed segments, appending a leading slash if it is absolute and restoring // the Windows drive letter if we have one. - $this->components['path'] = ( $this->is_absolute ? '/' : '' ) . implode( '/', $this->path_parts ) . ( $is_directory ? '/' : '' ); + $this->components['path'] = ( $this->is_absolute ? '/' : '' ) . implode( '/', $this->path_parts ) . ( $this->is_directory ? '/' : '' ); } /** @@ -193,6 +200,15 @@ class URL { $max_parent = count( $this->path_parts ); $parents = array(); + /* + * If we are looking at a relative path that begins with at least one traversal (example: "../../foo") + * then we should only return one parent URL (otherwise, we'd potentially have to return an infinite + * number of parent URLs since we can't know how far the tree extends). + */ + if ( $max_parent > 0 && ! $this->is_absolute && '..' === $this->path_parts[0] ) { + $max_parent = 1; + } + for ( $level = 1; $level <= $max_parent; $level++ ) { $parents[] = $this->get_parent_url( $level ); } @@ -222,7 +238,8 @@ class URL { $level = 1; } - $parent_path_parts_to_keep = count( $this->path_parts ) - $level; + $parts_count = count( $this->path_parts ); + $parent_path_parts_to_keep = $parts_count - $level; /* * With the exception of file URLs, we do not allow obtaining (grand-)parent directories that require @@ -238,11 +255,16 @@ class URL { return false; } - if ( $parent_path_parts_to_keep >= 0 ) { - $parent_path = implode( '/', array_slice( $this->path_parts, 0, $parent_path_parts_to_keep ) ); - } else { + if ( $parts_count > 0 && '..' === $this->path_parts[0] ) { + // In the case where we have a filepath already starting with one or more traversals, we need to add additional traversals. + $last_traversal = max( array_keys( $this->path_parts, '..', true ) ) + ( $this->is_directory ? 1 : 0 ); + $parent_path = str_repeat( '../', $level ) . join( '/', array_slice( $this->path_parts, 0, $last_traversal ) ); + } elseif ( $parent_path_parts_to_keep < 0 ) { // For relative filepaths only, we use traversals to describe the requested parent. $parent_path = untrailingslashit( str_repeat( '../', $parent_path_parts_to_keep * -1 ) ); + } else { + // Otherwise, in a very simple case, we just remove existing parts. + $parent_path = implode( '/', array_slice( $this->path_parts, 0, $parent_path_parts_to_keep ) ); } if ( $this->is_relative() && '' === $parent_path ) { @@ -257,7 +279,9 @@ class URL { $parent_path = '/' . $parent_path; } - return $this->get_url( $this->get_path( $parent_path ) ); + // Form the parent URL, then process it exactly as we would any other URL for consistency. + $parent_url = $this->get_url( $this->get_path( $parent_path ) ); + return ( new self( $parent_url ) )->get_url(); } /** diff --git a/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php b/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php index d265330f534..84103e7fde5 100644 --- a/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php +++ b/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php @@ -133,7 +133,12 @@ class URLTest extends WC_Unit_Test_Case { public function test_can_obtain_parent_url() { $this->assertFalse( ( new URL( '/' ) )->get_parent_url(), - 'Root directory "/" is considered to have no parent.' + 'Root directory "/" is considered to have no parent (scenario #1).' + ); + + $this->assertFalse( + ( new URL( '/' ) )->get_parent_url( 2 ), + 'Root directory "/" is considered to have no parent (scenario #2).' ); $this->assertEquals( @@ -196,6 +201,38 @@ class URLTest extends WC_Unit_Test_Case { 'When obtaining all parent URLs for a relative filepath, we never return the root directory and never return a URL containing traversals. ' ); + $this->assertEquals( + array( + 'file://../../' + ), + ( new URL( '../../some.file' ) )->get_all_parent_urls(), + 'When obtaining all parent URLs for a path that begins with directory traversals, we only go up one more level.' + ); + + $this->assertEquals( + 'file://../../', + ( new URL( '../' ) )->get_parent_url(), + 'If a relative *directory* beginning with a traversal is provided, we can successfully derive its parent (scenario #1).' + ); + + $this->assertEquals( + 'file://../../../', + ( new URL( '../' ) )->get_parent_url( 2 ), + 'If a relative *directory* beginning with a traversal is provided, we can successfully derive its parent (scenario #2).' + ); + + $this->assertEquals( + 'file://../../../', + ( new URL( '../../some.file' ) )->get_parent_url( 2 ), + 'If the grandparent of a relative path that begins with one or more traversals is requested, we should receive the expected result (scenario #1).' + ); + + $this->assertEquals( + 'file://../../../../', + ( new URL( '../../some.file' ) )->get_parent_url( 3 ), + 'If the grandparent of a relative path that begins with one or more traversals is requested, we should receive the expected result (scenario #2).' + ); + $this->assertEquals( 'file://./', ( new URL( 'just-a-file.png' ) )->get_parent_url(), From 114480c23c37018ba9a5179d8b680b81e20a4ca7 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Fri, 25 Mar 2022 04:12:46 -0700 Subject: [PATCH 022/386] Tweak commentary. --- plugins/woocommerce/src/Internal/Utilities/URL.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/src/Internal/Utilities/URL.php b/plugins/woocommerce/src/Internal/Utilities/URL.php index a9ad45c9118..16a367550a7 100644 --- a/plugins/woocommerce/src/Internal/Utilities/URL.php +++ b/plugins/woocommerce/src/Internal/Utilities/URL.php @@ -161,7 +161,8 @@ class URL { * Consider allowing directory traversals to be resolved (ie, the process that converts 'foo/bar/../baz' to * 'foo/baz'). * - * 1. We are only concerned with file URLs, for all other types unwinding of traversals is already allowed. + * 1. For this decision point, we are only concerned with relative filepaths (in all other cases, + * $resolve_traversals will already be true). * 2. This is a 'one time' and unidirectional operation. We only wish to flip from false to true, and we * never wish to do this more than once. * 3. We only flip the switch after we have examined all leading '..' traversal segments. @@ -170,7 +171,10 @@ class URL { $resolve_traversals = true; } - // At this point, if we are committing a traversal to the path then we will wish to retain the next traversal, too. + /* + * Set a flag indicating that traversals should be retained. This is done to ensure we don't prematurely + * discard traversals at the start of the path. + */ $retain_traversals = $resolve_traversals && '..' === $part; // Retain this part of the path. From 80397d4f73d2258b6368d672971af10897c3c8c2 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Fri, 25 Mar 2022 04:14:49 -0700 Subject: [PATCH 023/386] Improve test description. --- .../woocommerce/tests/php/src/Internal/Utilities/URLTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php b/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php index 84103e7fde5..168b5920781 100644 --- a/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php +++ b/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php @@ -198,7 +198,7 @@ class URLTest extends WC_Unit_Test_Case { 'file://./', ), ( new URL( 'relative/to/abspath' ) )->get_all_parent_urls(), - 'When obtaining all parent URLs for a relative filepath, we never return the root directory and never return a URL containing traversals. ' + 'When obtaining all parent URLs for a relative filepath, we never return the root directory and never return a URL containing traversals if there were none to begin with.' ); $this->assertEquals( From a41ae705dd75fa97166c1ec5f7bffd73407c7509 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Fri, 25 Mar 2022 10:47:05 -0700 Subject: [PATCH 024/386] Rename the is_directory property to give a better sense of its role. --- .../src/Internal/Utilities/URL.php | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/plugins/woocommerce/src/Internal/Utilities/URL.php b/plugins/woocommerce/src/Internal/Utilities/URL.php index 16a367550a7..aa4102d92ed 100644 --- a/plugins/woocommerce/src/Internal/Utilities/URL.php +++ b/plugins/woocommerce/src/Internal/Utilities/URL.php @@ -35,11 +35,15 @@ class URL { private $is_absolute; /** - * If the URL (or filepath) represents a directory. + * If the URL (or filepath) represents a directory other than the root directory. + * + * This is useful at different points in the process, when deciding whether to re-apply + * a trailing slash at the end of processing or when we need to calculate how many + * directory traversals are needed to form a (grand-)parent URL. * * @var bool */ - private $is_directory; + private $is_non_root_directory; /** * The components of the URL's path. @@ -131,11 +135,11 @@ class URL { * without touching the filesystem. */ private function process_path() { - $segments = explode( '/', $this->components['path'] ); - $this->is_absolute = substr( $this->components['path'], 0, 1 ) === '/' || ! empty( $this->components['host'] ); - $this->is_directory = substr( $this->components['path'], -1, 1 ) === '/' && strlen( $this->components['path'] ) > 1; - $resolve_traversals = 'file' !== $this->components['scheme'] || $this->is_absolute; - $retain_traversals = false; + $segments = explode( '/', $this->components['path'] ); + $this->is_absolute = substr( $this->components['path'], 0, 1 ) === '/' || ! empty( $this->components['host'] ); + $this->is_non_root_directory = substr( $this->components['path'], -1, 1 ) === '/' && strlen( $this->components['path'] ) > 1; + $resolve_traversals = 'file' !== $this->components['scheme'] || $this->is_absolute; + $retain_traversals = false; // Clean the path. foreach ( $segments as $part ) { @@ -183,7 +187,7 @@ class URL { // Reform the path from the processed segments, appending a leading slash if it is absolute and restoring // the Windows drive letter if we have one. - $this->components['path'] = ( $this->is_absolute ? '/' : '' ) . implode( '/', $this->path_parts ) . ( $this->is_directory ? '/' : '' ); + $this->components['path'] = ( $this->is_absolute ? '/' : '' ) . implode( '/', $this->path_parts ) . ( $this->is_non_root_directory ? '/' : '' ); } /** @@ -261,7 +265,7 @@ class URL { if ( $parts_count > 0 && '..' === $this->path_parts[0] ) { // In the case where we have a filepath already starting with one or more traversals, we need to add additional traversals. - $last_traversal = max( array_keys( $this->path_parts, '..', true ) ) + ( $this->is_directory ? 1 : 0 ); + $last_traversal = max( array_keys( $this->path_parts, '..', true ) ) + ( $this->is_non_root_directory ? 1 : 0 ); $parent_path = str_repeat( '../', $level ) . join( '/', array_slice( $this->path_parts, 0, $last_traversal ) ); } elseif ( $parent_path_parts_to_keep < 0 ) { // For relative filepaths only, we use traversals to describe the requested parent. From b46f28f4e21395f96c3210e93a4a5ce9ef618c4b Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Tue, 29 Mar 2022 14:09:53 -0700 Subject: [PATCH 025/386] Improve path resolution, add additional tests, clean-up tests (use data-providers). --- .../src/Internal/Utilities/URL.php | 21 +- .../php/src/Internal/Utilities/URLTest.php | 356 +++++++----------- 2 files changed, 162 insertions(+), 215 deletions(-) diff --git a/plugins/woocommerce/src/Internal/Utilities/URL.php b/plugins/woocommerce/src/Internal/Utilities/URL.php index aa4102d92ed..340caa71c34 100644 --- a/plugins/woocommerce/src/Internal/Utilities/URL.php +++ b/plugins/woocommerce/src/Internal/Utilities/URL.php @@ -144,7 +144,7 @@ class URL { // Clean the path. foreach ( $segments as $part ) { // Drop empty segments. - if ( strlen( $part ) === 0 ) { + if ( strlen( $part ) === 0 || '.' === $part ) { continue; } @@ -185,6 +185,12 @@ class URL { $this->path_parts[] = $part; } + // Protect against empty relative paths. + if ( count( $this->path_parts ) === 0 && ! $this->is_absolute ) { + $this->path_parts = array( '.' ); + $this->is_non_root_directory = true; + } + // Reform the path from the processed segments, appending a leading slash if it is absolute and restoring // the Windows drive letter if we have one. $this->components['path'] = ( $this->is_absolute ? '/' : '' ) . implode( '/', $this->path_parts ) . ( $this->is_non_root_directory ? '/' : '' ); @@ -263,9 +269,16 @@ class URL { return false; } - if ( $parts_count > 0 && '..' === $this->path_parts[0] ) { - // In the case where we have a filepath already starting with one or more traversals, we need to add additional traversals. - $last_traversal = max( array_keys( $this->path_parts, '..', true ) ) + ( $this->is_non_root_directory ? 1 : 0 ); + // Handle cases where the path starts with one or more 'dot segments'. Since the path has already been + // processed, we can be confident that any such segments are at the start of the path. + if ( $parts_count > 0 && ( '.' === $this->path_parts[0] || '..' === $this->path_parts[0] ) ) { + // Determine the index of the last dot segment (ex: given the path '/../../foo' it would be 1). + $single_dots = array_keys( $this->path_parts, '.', true ); + $double_dots = array_keys( $this->path_parts, '..', true ); + $max_dot_index = max( array_merge( $single_dots, $double_dots ) ); + + // Prepend the required number of traversals and discard unnessary trailing segments. + $last_traversal = $max_dot_index + ( $this->is_non_root_directory ? 1 : 0 ); $parent_path = str_repeat( '../', $level ) . join( '/', array_slice( $this->path_parts, 0, $last_traversal ) ); } elseif ( $parent_path_parts_to_keep < 0 ) { // For relative filepaths only, we use traversals to describe the requested parent. diff --git a/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php b/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php index 168b5920781..659da935540 100644 --- a/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php +++ b/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php @@ -26,240 +26,174 @@ class URLTest extends WC_Unit_Test_Case { ); } - public function test_directory_traversal_resolution() { - $this->assertEquals( - '/var/foo/foobar', - ( new URL( '/var/foo/bar/baz/../../foobar' ) )->get_path(), - 'Correctly resolves a path containing a directory traversal.' - ); + /** + * @dataProvider path_expectations + * + * @param string $source_path + * @param string $expected_resolution + */ + public function test_path_resolution( $source_path, $expected_resolution ) { + $this->assertEquals( $expected_resolution, ( new URL( $source_path ) )->get_path() ); + } - $this->assertEquals( - '/bazbar', - ( new URL( '/var/foo/../../../../bazbar' ) )->get_path(), - 'Correctly resolves a path containing a directory traversal, even if the traversals attempt to backtrack beyond the root directory.' - ); - - $this->assertEquals( - '../should/remain/relative', - ( new URL( 'relative/../../should/remain/relative' ) )->get_path(), - 'Simplifies a relative path containing directory traversals to the extent possible (without inspecting the filesystem - scenario #1).' - ); - - $this->assertEquals( - '../../should/remain/relative', - ( new URL( 'relative/../../../should/remain/relative' ) )->get_path(), - 'Simplifies a relative path containing directory traversals to the extent possible (without inspecting the filesystem - scenario #2).' - ); - - $this->assertEquals( - 'file:///foo/bar/baz', - ( new URL( '/../foo/bar/baz/bazooka/../../baz' ) )->get_url(), - 'Directory traversals are appropriately resolved even in complex cases with multiple separate traversals. When the original path is absolute, the output will be absolute.' + /** + * Expectations when requesting the path of a URL. + * + * @return string[][] + */ + public function path_expectations(): array { + return array( + array( '/var/foo/bar/baz/../../foobar', '/var/foo/foobar' ), + array( '/var/foo/../../../../bazbar', '/bazbar' ), + array( '././././.', './' ), + array( 'empty/segments//are/stripped', 'empty/segments/are/stripped' ), + array( '///nonempty/ /whitespace/ /is//kept', '/nonempty/ /whitespace/ /is/kept' ), + array( 'relative/../../should/remain/relative', '../should/remain/relative' ), + array( 'relative/../../../should/remain/relative', '../../should/remain/relative' ), + array( 'c:\\Windows\Server\HTTP\dump.xml', 'c:/Windows/Server/HTTP/dump.xml') ); } - public function test_can_get_normalized_string_representation() { - $this->assertEquals( - 'foo/bar/baz', - ( new URL( 'foo/bar//baz' ) )->get_path(), - 'Empty segments are discarded, remains as a relative path.' - ); + /** + * @dataProvider url_expectations + * + * @param string $source_url + * @param string $expected_resolution + */ + public function test_url_resolution( $source_url, $expected_resolution ) { + $this->assertEquals( $expected_resolution, ( new URL( $source_url ) )->get_url() ); + } - $this->assertEquals( - '/foo/ /bar/ /baz/foobarbaz', - ( new URL( '///foo/ /bar/ /baz//foobarbaz' ) )->get_path(), - 'Empty segments are discarded, non-empty segments containing only whitespace are preserved, remains as an absolute path.' - ); - - $this->assertEquals( - 'c:/Windows/Server/HTTP/dump.xml', - ( new URL( 'c:\\Windows\Server\HTTP\dump.xml' ) )->get_path(), - 'String representations of Windows filepaths have forward slash separators and preserve the drive letter.' + /** + * Expectations when resolving URLs. + * + * @return string[][] + */ + public function url_expectations(): array { + return array( + array( '/../foo/bar/baz/bazooka/../../baz', 'file:///foo/bar/baz' ), + array( './a/b/c/./../././../b/c', 'file://a/b/c' ), + array( 'relative/path', 'file://relative/path' ), + array( '/absolute/path', 'file:///absolute/path' ), + array( '/var/www/network/%2econfig', 'file:///var/www/network/%2econfig' ), + array( '///foo', 'file:///foo' ), + array( '~/foo.txt', 'file://~/foo.txt' ), + array( 'baz///foo', 'file://baz/foo' ), + array( 'file:///etc/foo/bar', 'file:///etc/foo/bar' ), + array( 'foo://bar', 'foo://bar/' ), + array( 'foo://bar/baz-file', 'foo://bar/baz-file' ), + array( 'foo://bar/baz-dir/', 'foo://bar/baz-dir/' ), + array( 'https://foo.bar/parent/.%2e/asset.txt', 'https://foo.bar/asset.txt' ), + array( 'https://foo.bar/parent/%2E./asset.txt', 'https://foo.bar/asset.txt' ), + array( 'https://foo.bar/parent/%2E%2e/asset.txt', 'https://foo.bar/asset.txt' ), + array( 'https://foo.bar/parent/%2E.%2fasset.txt', 'https://foo.bar/parent/%2E.%2fasset.txt' ), + array( 'http://localhost?../../bar', 'http://localhost/?../../bar' ), ); } - public function test_can_get_normalized_url_representation() { - $this->assertEquals( - 'file://relative/path', - ( new URL( 'relative/path' ) )->get_url(), - 'Can obtain a URL representation of a relative filepath, even when the initial string was a plain filepath.' - ); + /** + * @dataProvider parent_url_expectations + * + * @param string $source_path + * @param int $parent_level + * @param string|false $expectation + */ + public function test_can_obtain_parent_url( string $source_path, int $parent_level, $expectation ) { + $this->assertEquals( $expectation, ( new URL( $source_path ) )->get_parent_url( $parent_level ) ); + } - $this->assertEquals( - 'file:///absolute/path', - ( new URL( '/absolute/path' ) )->get_url(), - 'Can obtain a URL representation of an absolute filepath, even when the initial string was a plain filepath.' - ); - - $this->assertEquals( - 'file:///etc/foo/bar', - ( new URL( 'file:///etc/foo/bar' ) )->get_url(), - 'Can obtain a URL representation of a filepath, when the source filepath was also expressed as a URL.' + /** + * Expectations when resolving (grand-)parent URLs. + * + * @return array[] + */ + public function parent_url_expectations(): array { + return array( + array( '/', 1, false ), + array( '/', 2, false ), + array( './', 1, 'file://../' ), + array( '../', 1, 'file://../../' ), + array( 'relative-file.png', 1, 'file://./' ), + array( 'relative-file.png', 2, 'file://../' ), + array( '/var/dev/', 1, 'file:///var/' ), + array( '/var/../dev/./../foo/bar', 1, 'file:///foo/' ), + array( 'https://example.com', 1, false ), + array( 'https://example.com/foo', 1, 'https://example.com/' ), + array( 'https://example.com/foo/bar/baz/../cat/', 2, 'https://example.com/foo/' ), + array( 'https://example.com/foo/bar/baz/%2E%2E/dog/', 2, 'https://example.com/foo/' ), + array( 'file://./', 1, 'file://../' ), + array( 'file://./', 2, 'file://../../' ), + array( 'file://../../foo', 1, 'file://../../' ), + array( 'file://../../foo', 2, 'file://../../../' ), + array( 'file://../../', 1, 'file://../../../' ), + array( 'file://./../', 2, 'file://../../../' ), ); } - public function test_handling_of_percent_encoded_periods() { - $this->assertEquals( - 'https://foo.bar/asset.txt', - ( new URL( 'https://foo.bar/parent/.%2e/asset.txt' ) )->get_url(), - 'Directory traversals expressed using percent-encoding are still resolved (lowercase, one encoded period).' - ); - - $this->assertEquals( - 'https://foo.bar/asset.txt', - ( new URL( 'https://foo.bar/parent/%2E./asset.txt' ) )->get_url(), - 'Directory traversals expressed using percent-encoding are still resolved (uppercase, one encoded period).' - ); - - $this->assertEquals( - 'https://foo.bar/asset.txt', - ( new URL( 'https://foo.bar/parent/%2E%2e/asset.txt' ) )->get_url(), - 'Directory traversals expressed using percent-encoding are still resolved (mixed case, both periods encoded).' - ); - - $this->assertEquals( - 'https://foo.bar/parent/%2E.%2fasset.txt', - ( new URL( 'https://foo.bar/parent/%2E.%2fasset.txt' ) )->get_url(), - 'If the forward slash after a double period is URL encoded, there is no directory traversal (since this means the slash is a part of the segment and is not a separator).' - ); - - $this->assertEquals( - 'file:///var/www/network/%2econfig', - ( new URL( '/var/www/network/%2econfig' ) )->get_url(), - 'Use of percent-encoding in URLs is accepted and unnecessary conversion does not take place.' - ); + /** + * @dataProvider all_parent_url_expectations + * + * @param string $source_path + * @param array $expectation + */ + public function test_can_obtain_all_parent_urls( string $source_path, array $expectation ) { + $this->assertEquals( $expectation, ( new URL( $source_path ) )->get_all_parent_urls() ); } - public function test_can_obtain_parent_url() { - $this->assertFalse( - ( new URL( '/' ) )->get_parent_url(), - 'Root directory "/" is considered to have no parent (scenario #1).' - ); - - $this->assertFalse( - ( new URL( '/' ) )->get_parent_url( 2 ), - 'Root directory "/" is considered to have no parent (scenario #2).' - ); - - $this->assertEquals( - 'file:///var/', - ( new URL( '/var/dev/' ) )->get_parent_url(), - 'The parent URL will be trailingslashed.' - ); - - $this->assertFalse( - ( new URL( 'https://example.com' ) )->get_parent_url(), - 'In the case of non-file URLs, if we only have a host name and no path then the parent cannot be derived.' - ); - } - - public function test_can_obtain_all_parent_urls() { - $this->assertEquals( + /** + * Expectations when obtaining all possible parent URLs of a given URL/path. + * + * @return array[] + */ + public function all_parent_urL_expectations(): array { + return array( array( - 'https://local.web/wp-content/uploads/woocommerce_uploads/pdf_bucket/', - 'https://local.web/wp-content/uploads/woocommerce_uploads/', - 'https://local.web/wp-content/uploads/', - 'https://local.web/wp-content/', - 'https://local.web/', + 'https://local.web/wp-content/uploads/woocommerce_uploads/pdf_bucket/secret-sauce.pdf', + array( + 'https://local.web/wp-content/uploads/woocommerce_uploads/pdf_bucket/', + 'https://local.web/wp-content/uploads/woocommerce_uploads/', + 'https://local.web/wp-content/uploads/', + 'https://local.web/wp-content/', + 'https://local.web/', + ), + ), + array( + '/srv/websites/my.wp.site/public/test-file.doc', + array( + 'file:///srv/websites/my.wp.site/public/', + 'file:///srv/websites/my.wp.site/', + 'file:///srv/websites/', + 'file:///srv/', + 'file:///', + ), + ), + array( + 'C:\\Documents\\Web\\TestSite\\BackgroundTrack.mp3', + array( + 'file://C:/Documents/Web/TestSite/', + 'file://C:/Documents/Web/', + 'file://C:/Documents/', + 'file://C:/', + ), ), - ( new URL( 'https://local.web/wp-content/uploads/woocommerce_uploads/pdf_bucket/secret-sauce.pdf' ) )->get_all_parent_urls(), - 'All parent URLs can be derived, but the host name is never stripped.' - ); - - $this->assertEquals( array( - 'file:///srv/websites/my.wp.site/public/', - 'file:///srv/websites/my.wp.site/', - 'file:///srv/websites/', - 'file:///srv/', 'file:///', + array(), ), - ( new URL( '/srv/websites/my.wp.site/public/test-file.doc' ) )->get_all_parent_urls(), - 'All parent URLs can be derived for a filepath, up to and including the root directory.' - ); - - $this->assertEquals( array( - 'file://C:/Documents/Web/TestSite/', - 'file://C:/Documents/Web/', - 'file://C:/Documents/', - 'file://C:/', + 'relative/to/abspath', + array( + 'file://relative/to/', + 'file://relative/', + 'file://./', + ), ), - ( new URL( 'C:\\Documents\\Web\\TestSite\\BackgroundTrack.mp3' ) )->get_all_parent_urls(), - 'All parent URLs can be derived for a filepath, up to and including the root directory plus drive letter (Windows).' - ); - } - - public function test_obtaining_parent_urls_from_relative_urls() { - $this->assertEquals( array( - 'file://relative/to/', - 'file://relative/', - 'file://./', + '../../some.file', + array( + 'file://../../' + ), ), - ( new URL( 'relative/to/abspath' ) )->get_all_parent_urls(), - 'When obtaining all parent URLs for a relative filepath, we never return the root directory and never return a URL containing traversals if there were none to begin with.' - ); - - $this->assertEquals( - array( - 'file://../../' - ), - ( new URL( '../../some.file' ) )->get_all_parent_urls(), - 'When obtaining all parent URLs for a path that begins with directory traversals, we only go up one more level.' - ); - - $this->assertEquals( - 'file://../../', - ( new URL( '../' ) )->get_parent_url(), - 'If a relative *directory* beginning with a traversal is provided, we can successfully derive its parent (scenario #1).' - ); - - $this->assertEquals( - 'file://../../../', - ( new URL( '../' ) )->get_parent_url( 2 ), - 'If a relative *directory* beginning with a traversal is provided, we can successfully derive its parent (scenario #2).' - ); - - $this->assertEquals( - 'file://../../../', - ( new URL( '../../some.file' ) )->get_parent_url( 2 ), - 'If the grandparent of a relative path that begins with one or more traversals is requested, we should receive the expected result (scenario #1).' - ); - - $this->assertEquals( - 'file://../../../../', - ( new URL( '../../some.file' ) )->get_parent_url( 3 ), - 'If the grandparent of a relative path that begins with one or more traversals is requested, we should receive the expected result (scenario #2).' - ); - - $this->assertEquals( - 'file://./', - ( new URL( 'just-a-file.png' ) )->get_parent_url(), - 'The parent URL of an unqualified, relative file is simply an empty relative path (generally, though not always, this is equivalent to ABSPATH).' - ); - - $this->assertEquals( - 'file://../../', - ( new URL( '../../relatively-placed-file.pdf' ) )->get_parent_url() - ); - - $this->assertEquals( - 'file://../', - ( new URL( 'relatively-placed-file.pdf' ) )->get_parent_url( 2 ), - 'For filepaths, we can successfully determine the (grand-)parent directories of relative filepaths (when explicitly requested).' - ); - - $this->assertEquals( - 'file://../../', - ( new URL( 'relatively-placed-file.pdf' ) )->get_parent_url( 3 ), - 'For filepaths, we can successfully determine the (grand-)parent directories of relative filepaths (when explicitly requested).' - ); - - $this->assertEquals( - 'file://../foo/bar/baz', - ( new URL( '../foo/bar/cat/dog/../../baz' ) )->get_url(), - 'Directory traversals are appropriately resolved even in complex cases with multiple separate traversals.' ); } } From cdbbca5b1b7c5f77dde8471d1ff03d2a5f7de776 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Fri, 25 Mar 2022 05:11:37 -0700 Subject: [PATCH 026/386] Support shortcode-based downloadable files (as used by S3 Downloads extension). --- .../includes/class-wc-product-download.php | 12 ++++- .../src/Internal/Utilities/URL.php | 34 +++++++++----- .../class-wc-product-downloads-test.php | 44 +++++++++++++++++++ 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-product-download.php b/plugins/woocommerce/includes/class-wc-product-download.php index a0f47b516e7..a80059b83ef 100644 --- a/plugins/woocommerce/includes/class-wc-product-download.php +++ b/plugins/woocommerce/includes/class-wc-product-download.php @@ -201,7 +201,17 @@ class WC_Product_Download implements ArrayAccess { return; } - $download_file = $this->get_file(); + $download_file = $this->get_file(); + + /** + * Controls whether shortcodes should be resolved and validated using the Approved Download Directory feature. + * + * @param bool $should_validate + */ + if ( apply_filters( 'woocommerce_product_downloads_approved_directory_validation_for_shortcodes', true ) && 'shortcode' === $this->get_type_of_file_path() ) { + $download_file = do_shortcode( $download_file ); + } + $is_site_administrator = is_multisite() ? current_user_can( 'manage_sites' ) : current_user_can( 'manage_options' ); $valid_storage_directory = $download_directories->is_valid_path( $download_file ); diff --git a/plugins/woocommerce/src/Internal/Utilities/URL.php b/plugins/woocommerce/src/Internal/Utilities/URL.php index 340caa71c34..26b429e9f2a 100644 --- a/plugins/woocommerce/src/Internal/Utilities/URL.php +++ b/plugins/woocommerce/src/Internal/Utilities/URL.php @@ -300,8 +300,16 @@ class URL { $parent_path = '/' . $parent_path; } - // Form the parent URL, then process it exactly as we would any other URL for consistency. - $parent_url = $this->get_url( $this->get_path( $parent_path ) ); + // Form the parent URL (ditching the query and fragment, if set). + $parent_url = $this->get_url( + array( + 'path' => $parent_path, + 'query' => null, + 'fragment' => null, + ) + ); + + // We process the parent URL through a fresh instance of this class, for consistency. return ( new self( $parent_url ) )->get_url(); } @@ -310,27 +318,29 @@ class URL { * * Borrows from https://www.php.net/manual/en/function.parse-url.php#106731 * - * @param string $path_override If provided this will be used as the URL path. + * @param array $component_overrides If provided, these will override values set in $this->components. * * @return string */ - public function get_url( string $path_override = null ): string { - $scheme = null !== $this->components['scheme'] ? $this->components['scheme'] . '://' : ''; - $host = null !== $this->components['host'] ? $this->components['host'] : ''; - $port = null !== $this->components['port'] ? ':' . $this->components['port'] : ''; - $path = $path_override ?? $this->get_path(); + public function get_url( array $component_overrides = array() ): string { + $components = array_merge( $this->components, $component_overrides ); + + $scheme = null !== $components['scheme'] ? $components['scheme'] . '://' : ''; + $host = null !== $components['host'] ? $components['host'] : ''; + $port = null !== $components['port'] ? ':' . $components['port'] : ''; + $path = $this->get_path( $components['path'] ); // Special handling for hostless URLs (typically, filepaths) referencing the current working directory. if ( '' === $host && ( '' === $path || '.' === $path ) ) { $path = './'; } - $user = null !== $this->components['user'] ? $this->components['user'] : ''; - $pass = null !== $this->components['pass'] ? ':' . $this->components['pass'] : ''; + $user = null !== $components['user'] ? $components['user'] : ''; + $pass = null !== $components['pass'] ? ':' . $components['pass'] : ''; $user_pass = ( ! empty( $user ) || ! empty( $pass ) ) ? $user . $pass . '@' : ''; - $query = null !== $this->components['query'] ? '?' . $this->components['query'] : ''; - $fragment = null !== $this->components['fragment'] ? '#' . $this->components['fragment'] : ''; + $query = null !== $components['query'] ? '?' . $components['query'] : ''; + $fragment = null !== $components['fragment'] ? '#' . $components['fragment'] : ''; return $scheme . $user_pass . $host . $port . $path . $query . $fragment; } diff --git a/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php b/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php index 0cb0ad4dc15..ba3d9eb484e 100644 --- a/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php +++ b/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php @@ -63,4 +63,48 @@ class WC_Product_Download_Test extends WC_Unit_Test_Case { $this->expectExceptionMessage( 'cannot be used: it is not located in an approved directory' ); $download->check_is_valid(); } + + /** + * Test handling of filepaths described via shortcodes in relation to the Approved Download Directory + * feature. This is to simulate scenarios such as encountered when using the S3 Downloads extension. + */ + public function test_shortcode_resolution_for_approved_directory_rules() { + /** @var Download_Directories $download_directories */ + $download_directories = wc_get_container()->get( Download_Directories::class ); + $download_directories->set_mode( Download_Directories::MODE_ENABLED ); + $dynamic_filepath = 'https://fast.reliable.external.fileserver.com/bucket-123/textbook.pdf'; + + // We select an admin user because we wish to automatically add Approved Directory rules. + $admin_user = wp_insert_user( array( 'user_login' => uniqid(), 'role' => 'administrator', 'user_pass' => 'x' ) ); + wp_set_current_user( $admin_user ); + + add_shortcode( 'dynamic-download', function () { + return 'https://fast.reliable.external.fileserver.com/bucket-123/textbook.pdf'; + } ); + + $this->assertFalse( + $download_directories->is_valid_path( $dynamic_filepath ), + 'Confirm the filepath returned by the test URL is not yet valid.' + ); + + $download = new WC_Product_Download(); + $download->set_file( '[dynamic-download]' ); + + $this->assertNull( + $download->check_is_valid(), + 'The downloadable file successfully validates (if it did not, an exception would be thrown).' + ); + + $this->assertTrue( + $download_directories->is_valid_path( $dynamic_filepath ), + 'Confirm the filepath returned by the test URL is now considered valid.' + ); + + remove_shortcode( 'dynamic-download' ); + + // Now the shortcode is removed (perhaps the parent plugin has been removed/disabled) it will not resolve + // and so the filepath will not validate. + $this->expectException( 'Error' ); + $download_directories->check_is_valid(); + } } From e1a943cdb33892ec111b6e709930f2f488e90197 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Fri, 25 Mar 2022 05:51:32 -0700 Subject: [PATCH 027/386] Add shortcode resolution to sync (approved download directories). --- .../ApprovedDirectories/Synchronize.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Internal/ProductDownloads/ApprovedDirectories/Synchronize.php b/plugins/woocommerce/src/Internal/ProductDownloads/ApprovedDirectories/Synchronize.php index 0db1da8bd5e..d44c0404991 100644 --- a/plugins/woocommerce/src/Internal/ProductDownloads/ApprovedDirectories/Synchronize.php +++ b/plugins/woocommerce/src/Internal/ProductDownloads/ApprovedDirectories/Synchronize.php @@ -219,7 +219,18 @@ class Synchronize { $parent_url = _x( 'invalid URL', 'Approved product download URLs migration', 'woocommerce' ); try { - $parent_url = ( new URL( $downloadable->get_file() ) )->get_parent_url(); + $download_file = $downloadable->get_file(); + + /** + * Controls whether shortcodes should be resolved and validated using the Approved Download Directory feature. + * + * @param bool $should_validate + */ + if ( apply_filters( 'woocommerce_product_downloads_approved_directory_validation_for_shortcodes', true ) && 'shortcode' === $downloadable->get_type_of_file_path() ) { + $download_file = do_shortcode( $download_file ); + } + + $parent_url = ( new URL( $download_file ) )->get_parent_url(); $this->register->add_approved_directory( $parent_url, false ); } catch ( Exception $e ) { wc_get_logger()->log( From 98dee7a196821106bcdacf4845aa28a6c8ede12b Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Wed, 30 Mar 2022 10:53:20 -0700 Subject: [PATCH 028/386] Don't trigger beforeunload notice inappropriately (re embedded WP_List_Tables). --- plugins/woocommerce/legacy/js/admin/settings.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/legacy/js/admin/settings.js b/plugins/woocommerce/legacy/js/admin/settings.js index 702b0ae1a19..29276907af6 100644 --- a/plugins/woocommerce/legacy/js/admin/settings.js +++ b/plugins/woocommerce/legacy/js/admin/settings.js @@ -70,8 +70,14 @@ // Edit prompt $( function() { var changed = false; + let $check_column = $( '.wp-list-table .check-column' ); + + $( 'input, textarea, select, checkbox' ).on( 'change', function( event ) { + // Toggling WP List Table checkboxes should not trigger navigation warnings. + if ( $check_column.length && $check_column.has( event.target ) ) { + return; + } - $( 'input, textarea, select, checkbox' ).on( 'change', function() { if ( ! changed ) { window.onbeforeunload = function() { return params.i18n_nav_warning; @@ -80,7 +86,7 @@ } }); - $( '.submit :input' ).on( 'click', function() { + $( '.submit :input, input#search-submit' ).on( 'click', function() { window.onbeforeunload = ''; }); }); From 43e36a945dd2fbc688e1cd1f53754938feec26bb Mon Sep 17 00:00:00 2001 From: Kamil Date: Wed, 30 Mar 2022 22:04:56 +0200 Subject: [PATCH 029/386] change `wp_safe_redirect` location from user dashboard to `edit-account` endpoint --- plugins/woocommerce/includes/class-wc-form-handler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/class-wc-form-handler.php b/plugins/woocommerce/includes/class-wc-form-handler.php index b182f6a6de0..3ec4872d871 100644 --- a/plugins/woocommerce/includes/class-wc-form-handler.php +++ b/plugins/woocommerce/includes/class-wc-form-handler.php @@ -347,7 +347,7 @@ class WC_Form_Handler { do_action( 'woocommerce_save_account_details', $user->ID ); - wp_safe_redirect( wc_get_page_permalink( 'myaccount' ) ); + wp_safe_redirect( wc_get_endpoint_url( 'edit-account', '', wc_get_page_permalink( 'myaccount' ) ) ); exit; } } From 843d981198a5060db6c9af7c306a57de1c0ac274 Mon Sep 17 00:00:00 2001 From: Jonathan Sadowski Date: Thu, 31 Mar 2022 20:42:54 -0500 Subject: [PATCH 030/386] Adapt check-changelogger-use from Jetpack for usage in WooCommerce monorepo --- tools/monorepo/check-changelogger-use.php | 229 ++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 tools/monorepo/check-changelogger-use.php diff --git a/tools/monorepo/check-changelogger-use.php b/tools/monorepo/check-changelogger-use.php new file mode 100644 index 00000000000..3eaeef83896 --- /dev/null +++ b/tools/monorepo/check-changelogger-use.php @@ -0,0 +1,229 @@ + +Checks that a monorepo commit contains a Changelogger change entry for each +project touched. + --debug, -v Display verbose output. + --list Just list projects, no explanatory output. + Base git ref to compare for changed files. + Head git ref to compare for changed files. +EOH; + exit( 1 ); +} + +$idx = 0; +$verbose = false; +$list = false; +$base = null; +$head = null; +for ( $i = 1; $i < $argc; $i++ ) { + switch ( $argv[ $i ] ) { + case '-v': + case '--debug': + $verbose = true; + break; + case '--list': + $list = true; + break; + case '-h': + case '--help': + usage(); + break; + default: + if ( substr( $argv[ $i ], 0, 1 ) !== '-' ) { + switch ( $idx++ ) { + case 0: + $base = $argv[ $i ]; + break; + case 1: + $head = $argv[ $i ]; + break; + default: + fprintf( STDERR, "\e[1;31mToo many arguments.\e[0m\n" ); + usage(); + } + } else { + fprintf( STDERR, "\e[1;31mUnrecognized parameter `%s`.\e[0m\n", $argv[ $i ] ); + usage(); + } + break; + } +} + +if ( null === $head ) { + fprintf( STDERR, "\e[1;31mBase and head refs are required.\e[0m\n" ); + usage(); +} + +if ( $verbose ) { + /** + * Output debug info. + * + * @param array ...$args Arguments to printf. A newline is automatically appended. + */ + function debug( ...$args ) { + if ( getenv( 'CI' ) ) { + $args[0] = "\e[34m${args[0]}\e[0m\n"; + } else { + $args[0] = "\e[1;30m${args[0]}\e[0m\n"; + } + fprintf( STDERR, ...$args ); + } +} else { + /** + * Do not output debug info. + */ + function debug() { + } +} + +$base_path = dirname( dirname( __DIR__ ) ); + +// Read workspace.json file to find potential composer files. +try { + $workspace = json_decode( file_get_contents( $base_path . '/workspace.json' ), true, 10, JSON_THROW_ON_ERROR ); +} catch ( Exception $e ) { + $workspace = false; +} +if ( ! $workspace || ! is_array( $workspace['projects'] ) ) { + debug( 'Unable to parse workspace file' ); + exit( 1 ); +} + +$composer_projects = array(); +foreach( $workspace['projects'] as $project => $directory ) { + if ( file_exists( $base_path . '/' . $directory . '/composer.json' ) ) { + $composer_projects[] = $directory; + } +} + +// Find projects that use changelogger, and read the relevant config. +$changelogger_projects = array(); +foreach ( $composer_projects as $project_path ) { + try { + $data = json_decode( file_get_contents( $base_path . '/' . $project_path . '/composer.json' ), true, 512, JSON_THROW_ON_ERROR ); + if ( + ! isset( $data['require']['automattic/jetpack-changelogger'] ) && + ! isset( $data['require-dev']['automattic/jetpack-changelogger'] ) + ) { + continue; + } + } catch ( Exception $e ) { + continue; + } + $data = isset( $data['extra']['changelogger'] ) ? $data['extra']['changelogger'] : array(); + $data += array( + 'changelog' => 'CHANGELOG.md', + 'changes-dir' => 'changelog', + ); + $changelogger_projects[ $project_path ] = $data; +} + +// Process the diff. +debug( 'Checking diff from %s...%s.', $base, $head ); +$pipes = null; +$p = proc_open( + sprintf( 'git -c core.quotepath=off diff --no-renames --name-only %s...%s', escapeshellarg( $base ), escapeshellarg( $head ) ), + array( array( 'pipe', 'r' ), array( 'pipe', 'w' ), STDERR ), + $pipes +); +if ( ! $p ) { + exit( 1 ); +} +fclose( $pipes[0] ); + +$ok_projects = array(); +$touched_projects = array(); +// phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition +while ( ( $line = fgets( $pipes[1] ) ) ) { + $line = trim( $line ); + + $project_match = false; + foreach( $composer_projects as $path ) { + if ( substr( $line, 0, strlen( $path ) + 1 ) === $path . '/' ) { + $project_match = $path; + break; + } + } + + if ( false === $project_match ) { + debug( 'Ignoring non-project file %s.', $line ); + continue; + } + + if ( ! isset( $changelogger_projects[ $project_match ] ) ) { + debug( 'Ignoring file %s, project %s does not use changelogger.', $line, $project_match ); + continue; + } + if ( basename( $line ) === $changelogger_projects[ $project_match ]['changelog'] ) { + debug( 'Ignoring changelog file %s.', $line ); + continue; + } + if ( dirname( $line ) === $changelogger_projects[ $project_match ]['changes-dir'] ) { + if ( '.' === basename( $line )[0] ) { + debug( 'Ignoring changes dir dotfile %s.', $line ); + } else { + debug( 'PR touches file %s, marking %s as having a change file.', $line, $project_match ); + $ok_projects[ $project_match ] = true; + } + continue; + } + + debug( 'PR touches file %s, marking %s as touched.', $line, $project_match ); + if ( ! isset( $touched_projects[ $project_match ] ) ) { + $touched_projects[ $project_match ][] = $line; + } +} + +fclose( $pipes[1] ); +$status = proc_close( $p ); +if ( $status ) { + exit( $status ); +} + +// Output. +ksort( $touched_projects ); +$exit = 0; +foreach ( $touched_projects as $slug => $files ) { + if ( empty( $ok_projects[ $slug ] ) ) { + if ( $list ) { + echo "$slug\n"; + } elseif ( getenv( 'CI' ) ) { + printf( "---\n" ); // Bracket message containing newlines for better visibility in GH's logs. + printf( + "::error::Project %s is being changed, but no change file in %s is touched!%%0A%%0AUse `pnpm nx affected --target=changelog` to add a change file.\n", + $slug, + "$slug/{$changelogger_projects[ $slug ]['changes-dir']}/", + $slug + ); + printf( "---\n" ); + $exit = 1; + } else { + printf( + "\e[1;31mProject %s is being changed, but no change file in %s is touched!\e[0m\n", + $slug, + "$slug/{$changelogger_projects[ $slug ]['changes-dir']}/" + ); + $exit = 1; + } + } +} +if ( $exit && ! getenv( 'CI' ) && ! $list ) { + printf( "\e[32mUse `pnpm nx affected --target=changelog` to add a change file for each project.\e[0m\n" ); +} + +exit( $exit ); \ No newline at end of file From e87cd7128b3c469d8e6c784ad677ec6c14371623 Mon Sep 17 00:00:00 2001 From: Jonathan Sadowski Date: Thu, 31 Mar 2022 21:24:22 -0500 Subject: [PATCH 031/386] Add github workflow --- .github/workflows/changelogger.yml | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/changelogger.yml diff --git a/.github/workflows/changelogger.yml b/.github/workflows/changelogger.yml new file mode 100644 index 00000000000..77f97632bd0 --- /dev/null +++ b/.github/workflows/changelogger.yml @@ -0,0 +1,40 @@ +name: Changelogger +on: pull_request +concurrency: + group: changelogger-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: true +jobs: + changelogger_used: + name: Changelogger use + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 10 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + tools: composer:'2.3.2' + + - name: Get Composer cache directory + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Use composer cache + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Check change files are touched for touched projects + env: + BASE: ${{ github.event.pull_request.base.sha }} + HEAD: ${{ github.event.pull_request.head.sha }} + run: tools/check-changelogger-use.php --debug "$BASE" "$HEAD" \ No newline at end of file From 2c4e37319290a42f5f190f4009ad27ce2ed2d49e Mon Sep 17 00:00:00 2001 From: Jonathan Sadowski Date: Thu, 31 Mar 2022 21:53:12 -0500 Subject: [PATCH 032/386] Fix github action and correct error in detecting change file --- .github/workflows/changelogger.yml | 16 +--------------- tools/monorepo/check-changelogger-use.php | 4 ++-- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/.github/workflows/changelogger.yml b/.github/workflows/changelogger.yml index 77f97632bd0..887930b1e4a 100644 --- a/.github/workflows/changelogger.yml +++ b/.github/workflows/changelogger.yml @@ -18,23 +18,9 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: '7.4' - tools: composer:'2.3.2' - - - name: Get Composer cache directory - id: composer-cache - run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Use composer cache - uses: actions/cache@v2 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-composer- - name: Check change files are touched for touched projects env: BASE: ${{ github.event.pull_request.base.sha }} HEAD: ${{ github.event.pull_request.head.sha }} - run: tools/check-changelogger-use.php --debug "$BASE" "$HEAD" \ No newline at end of file + run: php tools/monorepo/check-changelogger-use.php --debug "$BASE" "$HEAD" \ No newline at end of file diff --git a/tools/monorepo/check-changelogger-use.php b/tools/monorepo/check-changelogger-use.php index 3eaeef83896..cfcc8bac982 100644 --- a/tools/monorepo/check-changelogger-use.php +++ b/tools/monorepo/check-changelogger-use.php @@ -127,8 +127,8 @@ foreach ( $composer_projects as $project_path ) { } $data = isset( $data['extra']['changelogger'] ) ? $data['extra']['changelogger'] : array(); $data += array( - 'changelog' => 'CHANGELOG.md', - 'changes-dir' => 'changelog', + 'changelog' => $project_path . '/CHANGELOG.md', + 'changes-dir' => $project_patch . '/changelog', ); $changelogger_projects[ $project_path ] = $data; } From b244db0e44df0281378914f0f9b9e6e3b5c44884 Mon Sep 17 00:00:00 2001 From: Jonathan Sadowski Date: Fri, 1 Apr 2022 12:43:18 -0500 Subject: [PATCH 033/386] Update arguments parsing to use getopt, and add path option --- tools/monorepo/check-changelogger-use.php | 89 ++++++++++++----------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/tools/monorepo/check-changelogger-use.php b/tools/monorepo/check-changelogger-use.php index cfcc8bac982..04d0c42847d 100644 --- a/tools/monorepo/check-changelogger-use.php +++ b/tools/monorepo/check-changelogger-use.php @@ -14,61 +14,54 @@ function usage() { global $argv; echo << +USAGE: {$argv[0]} [--debug|-v] [--list] [-p |--path=] Checks that a monorepo commit contains a Changelogger change entry for each project touched. - --debug, -v Display verbose output. - --list Just list projects, no explanatory output. - Base git ref to compare for changed files. - Head git ref to compare for changed files. + --debug, -v Display verbose output. + --list Just list projects, no explanatory output. + --path=, Project path to check for changed files. + -p + Base git ref to compare for changed files. + Head git ref to compare for changed files. EOH; exit( 1 ); } -$idx = 0; -$verbose = false; -$list = false; -$base = null; -$head = null; -for ( $i = 1; $i < $argc; $i++ ) { - switch ( $argv[ $i ] ) { - case '-v': - case '--debug': - $verbose = true; - break; - case '--list': - $list = true; - break; - case '-h': - case '--help': - usage(); - break; - default: - if ( substr( $argv[ $i ], 0, 1 ) !== '-' ) { - switch ( $idx++ ) { - case 0: - $base = $argv[ $i ]; - break; - case 1: - $head = $argv[ $i ]; - break; - default: - fprintf( STDERR, "\e[1;31mToo many arguments.\e[0m\n" ); - usage(); - } - } else { - fprintf( STDERR, "\e[1;31mUnrecognized parameter `%s`.\e[0m\n", $argv[ $i ] ); - usage(); - } - break; - } +// Options followed by a single colon have a required value. +$short_options = 'vhp:'; +$long_options = array( + 'debug', + 'list', + 'help', + 'path:', +); +$options = getopt( $short_options, $long_options, $remain_index ); +$arg_count = count( $argv ) - $remain_index; + +if ( isset( $options['h'] ) || isset( $options['help'] ) ) { + usage(); } -if ( null === $head ) { +$list = isset( $options['l'] ) || isset( $options['list'] ); +$verbose = isset( $options['v'] ) || isset( $options['debug'] ); +$path = false; +if ( isset( $options['p'] ) || isset( $options['path'] ) ) { + $path = isset( $options['path'] ) ? $options['path'] : $options['p']; +} + +if ( $arg_count > 2 ) { + fprintf( STDERR, "\e[1;31mToo many arguments.\e[0m\n" ); + usage(); +} + +if ( $arg_count < 2 ) { fprintf( STDERR, "\e[1;31mBase and head refs are required.\e[0m\n" ); usage(); } +$base = $argv[ count( $argv ) - 2 ]; +$head = $argv[ count( $argv ) - 1 ]; + if ( $verbose ) { /** * Output debug info. @@ -106,11 +99,19 @@ if ( ! $workspace || ! is_array( $workspace['projects'] ) ) { $composer_projects = array(); foreach( $workspace['projects'] as $project => $directory ) { + if ( $path && $directory !== $path ) { + continue; + } if ( file_exists( $base_path . '/' . $directory . '/composer.json' ) ) { $composer_projects[] = $directory; } } +if ( $path && ! count( $composer_projects ) ) { + debug( sprintf( 'The provided project path, %s, did not contain a composer file.', $path ) ); + exit( 1 ); +} + // Find projects that use changelogger, and read the relevant config. $changelogger_projects = array(); foreach ( $composer_projects as $project_path ) { @@ -128,7 +129,7 @@ foreach ( $composer_projects as $project_path ) { $data = isset( $data['extra']['changelogger'] ) ? $data['extra']['changelogger'] : array(); $data += array( 'changelog' => $project_path . '/CHANGELOG.md', - 'changes-dir' => $project_patch . '/changelog', + 'changes-dir' => $project_path . '/changelog', ); $changelogger_projects[ $project_path ] = $data; } From b997f18dd86a4aa42bde40b1012fd5a406eeaa1b Mon Sep 17 00:00:00 2001 From: Jonathan Sadowski Date: Fri, 1 Apr 2022 13:12:16 -0500 Subject: [PATCH 034/386] Add newline to EOF --- .github/workflows/changelogger.yml | 2 +- tools/monorepo/check-changelogger-use.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/changelogger.yml b/.github/workflows/changelogger.yml index 887930b1e4a..c5cbc521bc5 100644 --- a/.github/workflows/changelogger.yml +++ b/.github/workflows/changelogger.yml @@ -23,4 +23,4 @@ jobs: env: BASE: ${{ github.event.pull_request.base.sha }} HEAD: ${{ github.event.pull_request.head.sha }} - run: php tools/monorepo/check-changelogger-use.php --debug "$BASE" "$HEAD" \ No newline at end of file + run: php tools/monorepo/check-changelogger-use.php --debug "$BASE" "$HEAD" diff --git a/tools/monorepo/check-changelogger-use.php b/tools/monorepo/check-changelogger-use.php index 04d0c42847d..fc9a0118c62 100644 --- a/tools/monorepo/check-changelogger-use.php +++ b/tools/monorepo/check-changelogger-use.php @@ -227,4 +227,4 @@ if ( $exit && ! getenv( 'CI' ) && ! $list ) { printf( "\e[32mUse `pnpm nx affected --target=changelog` to add a change file for each project.\e[0m\n" ); } -exit( $exit ); \ No newline at end of file +exit( $exit ); From b903b84edb96fca1740d6a9bbd545a8c5c6a4f98 Mon Sep 17 00:00:00 2001 From: AlexTech01 <72891399+AlexTech01@users.noreply.github.com> Date: Sun, 3 Apr 2022 08:54:45 -0500 Subject: [PATCH 035/386] Change is_file_valid_csv to wc_is_file_valid_csv This pull request replaces the class-specific is_file_valid_csv() function with the global wc_is_file_valid_csv() function as requested by the todo that was previously above the is_file_valid_csv() function on the previous line 89. --- .../class-wc-product-csv-importer-controller.php | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php b/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php index 848b36ad77b..97acf002e73 100644 --- a/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php +++ b/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php @@ -86,7 +86,6 @@ class WC_Product_CSV_Importer_Controller { /** * Check whether a file is a valid CSV file. * - * @todo Replace this method with wc_is_file_valid_csv() function. * @param string $file File path. * @param bool $check_path Whether to also check the file is located in a valid location (Default: true). * @return bool @@ -98,17 +97,7 @@ class WC_Product_CSV_Importer_Controller { * @param bool $check_import_file_path If the import file path should be checked. * @param string $file Path of the file to be checked. */ - if ( $check_path && apply_filters( 'woocommerce_product_csv_importer_check_import_file_path', true, $file ) && false !== stripos( $file, '://' ) ) { - return false; - } - - $valid_filetypes = self::get_valid_csv_filetypes(); - $filetype = wp_check_filetype( $file, $valid_filetypes ); - if ( in_array( $filetype['type'], $valid_filetypes, true ) ) { - return true; - } - - return false; + return wc_is_file_valid_csv($file, $check_path); } /** From 885516f30213ad17b27890148e2b3be71f44bc9e Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 4 Apr 2022 11:54:34 -0300 Subject: [PATCH 036/386] Slight improvements for loading of the task list --- .../client/tasks/fills/purchase.js | 43 +++++++++++-------- .../woocommerce-admin/client/tasks/tasks.tsx | 42 ++++-------------- .../client/two-column-tasks/index.js | 22 +--------- .../Features/OnboardingTasks/TaskList.php | 10 +++++ 4 files changed, 45 insertions(+), 72 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/purchase.js b/plugins/woocommerce-admin/client/tasks/fills/purchase.js index 31a894c9158..72ae6efcd48 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/purchase.js +++ b/plugins/woocommerce-admin/client/tasks/fills/purchase.js @@ -15,7 +15,7 @@ import { ONBOARDING_STORE_NAME, PLUGINS_STORE_NAME } from '@woocommerce/data'; import CartModal from '../../dashboard/components/cart-modal'; import { getCategorizedOnboardingProducts } from '../../dashboard/utils'; -const PurchaseTaskItem = () => { +const PurchaseTaskItem = ( { defaultTaskItem } ) => { const [ cartModalOpen, setCartModalOpen ] = useState( false ); const { installedPlugins, productTypes, profileItems } = useSelect( @@ -47,25 +47,32 @@ const PurchaseTaskItem = () => { installedPlugins ); const { remainingProducts } = groupedProducts; + const DefaultTaskItem = defaultTaskItem; + return ( + <> + { + if ( remainingProducts.length ) { + toggleCartModal(); + } + } } + /> + { cartModalOpen && ( + toggleCartModal() } + onClickPurchaseLater={ () => toggleCartModal() } + /> + ) } + + ); +}; + +const PurchaseTaskItemFill = () => { return ( - { ( { defaultTaskItem: DefaultTaskItem } ) => ( - <> - { - if ( remainingProducts.length ) { - toggleCartModal(); - } - } } - /> - { cartModalOpen && ( - toggleCartModal() } - onClickPurchaseLater={ () => toggleCartModal() } - /> - ) } - + { ( { defaultTaskItem } ) => ( + ) } ); @@ -73,5 +80,5 @@ const PurchaseTaskItem = () => { registerPlugin( 'woocommerce-admin-task-purchase', { scope: 'woocommerce-tasks', - render: PurchaseTaskItem, + render: PurchaseTaskItemFill, } ); diff --git a/plugins/woocommerce-admin/client/tasks/tasks.tsx b/plugins/woocommerce-admin/client/tasks/tasks.tsx index 8e0e61be921..118597fcfa5 100644 --- a/plugins/woocommerce-admin/client/tasks/tasks.tsx +++ b/plugins/woocommerce-admin/client/tasks/tasks.tsx @@ -18,26 +18,15 @@ import { Task } from './task'; import { TasksPlaceholder } from './placeholder'; import './tasks.scss'; import { TaskListProps } from './task-list'; +import { TaskList } from './task-list'; +import { TaskList as TwoColumnTaskList } from '../two-column-tasks/task-list'; import '../two-column-tasks/style.scss'; export type TasksProps = { query: { task?: string }; }; -const TaskList = lazy( - () => import( /* webpackChunkName: "task-list" */ './task-list' ) -); - -const TwoColumnTaskList = lazy( - () => - import( - /* webpackChunkName: "two-column-task-list" */ '../two-column-tasks/task-list' - ) -); - -function getTaskListComponent( - taskListId: string -): React.LazyExoticComponent< React.FC< TaskListProps > > { +function getTaskListComponent( taskListId: string ): React.FC< TaskListProps > { switch ( taskListId ) { case 'setup_experiment_1': return TwoColumnTaskList; @@ -56,9 +45,9 @@ export const Tasks: React.FC< TasksProps > = ( { query } ) => { const { isResolving, taskLists } = useSelect( ( select ) => { return { - isResolving: select( ONBOARDING_STORE_NAME ).isResolving( - 'getTaskLists' - ), + isResolving: ! select( + ONBOARDING_STORE_NAME + ).hasFinishedResolution( 'getTaskLists' ), taskLists: select( ONBOARDING_STORE_NAME ).getTaskLists(), }; } ); @@ -131,17 +120,7 @@ export const Tasks: React.FC< TasksProps > = ( { query } ) => { : ! id.endsWith( 'two_column' ) ) .map( ( taskList ) => { - const { - id, - eventPrefix, - isComplete, - isHidden, - isVisible, - isToggleable, - title, - tasks, - displayProgressHeader, - } = taskList; + const { id, isHidden, isVisible, isToggleable } = taskList; if ( ! isVisible ) { return null; @@ -153,18 +132,13 @@ export const Tasks: React.FC< TasksProps > = ( { query } ) => { { isToggleable && ( diff --git a/plugins/woocommerce-admin/client/two-column-tasks/index.js b/plugins/woocommerce-admin/client/two-column-tasks/index.js index 11288fe0d30..4a3b315235c 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/index.js +++ b/plugins/woocommerce-admin/client/two-column-tasks/index.js @@ -3,7 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; -import { ONBOARDING_STORE_NAME, OPTIONS_STORE_NAME } from '@woocommerce/data'; +import { ONBOARDING_STORE_NAME } from '@woocommerce/data'; /** * Internal dependencies @@ -14,25 +14,7 @@ import TaskList from './task-list'; import TaskListPlaceholder from './placeholder'; import { Task } from '../tasks/task'; -const taskDashboardSelect = ( select ) => { - const { getOption, hasFinishedResolution } = select( OPTIONS_STORE_NAME ); - - return { - keepCompletedTaskList: getOption( - 'woocommerce_task_list_keep_completed' - ), - isResolving: ! hasFinishedResolution( 'getOption', [ - 'woocommerce_task_list_keep_completed', - ] ), - }; -}; - const TaskDashboard = ( { query, twoColumns } ) => { - const { - keepCompletedTaskList, - isResolving: isResolvingOptions, - } = useSelect( taskDashboardSelect ); - const { task } = query; const { isResolving, taskLists } = useSelect( ( select ) => { @@ -104,7 +86,7 @@ const TaskDashboard = ( { query, twoColumns } ) => { id={ taskList.id } eventName="tasklist" twoColumns={ twoColumns } - keepCompletedTaskList={ keepCompletedTaskList } + keepCompletedTaskList={ taskList.keepCompletedTaskList } dismissedTasks={ dismissedTasks || [] } isComplete={ isTaskListComplete } query={ query } diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php index ddfa282d70e..0cbc9b73465 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php @@ -329,6 +329,15 @@ class TaskList { return $this->get_list_id() . '_tasklist_' . $event_name; } + /** + * Returns option to keep completed task list. + * + * @return string + */ + public function get_keep_completed_task_list() { + return get_option( 'woocommerce_task_list_keep_completed', 'no' ); + } + /** * Remove reminder bar four weeks after store creation. */ @@ -364,6 +373,7 @@ class TaskList { ), 'eventPrefix' => $this->prefix_event( '' ), 'displayProgressHeader' => $this->display_progress_header, + 'keepCompletedTaskList' => $this->get_keep_completed_task_list(), ); } } From b68a835d3a1a1e594625aee3e9253864f5a5f8cf Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 4 Apr 2022 13:07:25 -0300 Subject: [PATCH 037/386] Add displayed task lists to admin settings data --- .../Admin/Features/OnboardingTasks/TaskLists.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php index 436adcff3ff..8363250cd36 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php @@ -70,6 +70,7 @@ class TaskLists { self::init_default_lists(); add_action( 'admin_init', array( __CLASS__, 'set_active_task' ), 5 ); add_action( 'init', array( __CLASS__, 'init_tasks' ) ); + add_filter( 'woocommerce_admin_shared_settings', array( __CLASS__, 'task_list_preloaded_settings' ), 20 ); } /** @@ -321,7 +322,7 @@ class TaskLists { return array_filter( self::get_lists(), function ( $task_list ) { - return ! $task_list->is_hidden(); + return $task_list->is_visible(); } ); } @@ -373,4 +374,17 @@ class TaskLists { return null; } + + /** + * Add visible list ids to component settings. + * + * @param array $settings Component settings. + * + * @return array + */ + public static function task_list_preloaded_settings( $settings ) { + $settings['visibleTaskListIds'] = array_keys( self::get_visible() ); + + return $settings; + } } From 28c31b7c382a72fba9edc8e01b36c3f8e22b42fb Mon Sep 17 00:00:00 2001 From: Jonathan Sadowski Date: Mon, 4 Apr 2022 13:23:12 -0500 Subject: [PATCH 038/386] Move changelogger-specific workflow to a linting workflow for monorepo --- .github/workflows/{changelogger.yml => pr-lint-monorepo.php} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{changelogger.yml => pr-lint-monorepo.php} (91%) diff --git a/.github/workflows/changelogger.yml b/.github/workflows/pr-lint-monorepo.php similarity index 91% rename from .github/workflows/changelogger.yml rename to .github/workflows/pr-lint-monorepo.php index c5cbc521bc5..dff1644e697 100644 --- a/.github/workflows/changelogger.yml +++ b/.github/workflows/pr-lint-monorepo.php @@ -1,4 +1,4 @@ -name: Changelogger +name: Run lint checks potentially affecting projects across the monorepo on: pull_request concurrency: group: changelogger-${{ github.event_name }}-${{ github.ref }} From 3f05bdb90ad5bba46d9d54a852b1033ebd3aa31c Mon Sep 17 00:00:00 2001 From: Jonathan Sadowski Date: Mon, 4 Apr 2022 13:29:44 -0500 Subject: [PATCH 039/386] Fix typo on git workflow description and filename --- .../workflows/{pr-lint-monorepo.php => pr-lint-monorepo.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{pr-lint-monorepo.php => pr-lint-monorepo.yml} (91%) diff --git a/.github/workflows/pr-lint-monorepo.php b/.github/workflows/pr-lint-monorepo.yml similarity index 91% rename from .github/workflows/pr-lint-monorepo.php rename to .github/workflows/pr-lint-monorepo.yml index dff1644e697..f4a40ce8f31 100644 --- a/.github/workflows/pr-lint-monorepo.php +++ b/.github/workflows/pr-lint-monorepo.yml @@ -1,4 +1,4 @@ -name: Run lint checks potentially affecting projects across the monorepo +name: Run lint checks potentially affected projects across the monorepo on: pull_request concurrency: group: changelogger-${{ github.event_name }}-${{ github.ref }} From 4d6aab73df4954e41d284277475d0a1dc97cc3e2 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 4 Apr 2022 15:38:27 -0300 Subject: [PATCH 040/386] Add placeholder for task list experiment one --- .../client/homescreen/layout.js | 3 ++- .../woocommerce-admin/client/tasks/tasks.tsx | 26 ++++++++++++++++--- .../{placeholder.js => placeholder.tsx} | 10 ++++++- 3 files changed, 33 insertions(+), 6 deletions(-) rename plugins/woocommerce-admin/client/two-column-tasks/{placeholder.js => placeholder.tsx} (87%) diff --git a/plugins/woocommerce-admin/client/homescreen/layout.js b/plugins/woocommerce-admin/client/homescreen/layout.js index 68521a58958..34fd8c4cc32 100644 --- a/plugins/woocommerce-admin/client/homescreen/layout.js +++ b/plugins/woocommerce-admin/client/homescreen/layout.js @@ -42,6 +42,7 @@ import { WelcomeModal } from './welcome-modal'; import { useHeadercardExperimentHook } from './hooks/use-headercard-experiment-hook'; import './style.scss'; import '../dashboard/style.scss'; +import { getAdminSetting } from '~/utils/admin-settings'; const Tasks = lazy( () => import( /* webpackChunkName: "tasks" */ '../tasks' ) @@ -337,7 +338,7 @@ export default compose( shouldShowWelcomeModal, shouldShowWelcomeFromCalypsoModal, isTaskListHidden: getTaskList( 'setup' )?.isHidden, - hasTaskList: !! taskLists.find( ( list ) => list.isVisible ), + hasTaskList: getAdminSetting( 'visibleTaskListIds', [] ).length > 0, taskListComplete: getTaskList( 'setup' )?.isComplete, installTimestamp, installTimestampHasResolved, diff --git a/plugins/woocommerce-admin/client/tasks/tasks.tsx b/plugins/woocommerce-admin/client/tasks/tasks.tsx index 118597fcfa5..5370e426e3d 100644 --- a/plugins/woocommerce-admin/client/tasks/tasks.tsx +++ b/plugins/woocommerce-admin/client/tasks/tasks.tsx @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { MenuGroup, MenuItem } from '@wordpress/components'; import { check } from '@wordpress/icons'; -import { Fragment, useEffect, lazy, Suspense } from '@wordpress/element'; +import { Fragment, useEffect, Suspense } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { ONBOARDING_STORE_NAME, OPTIONS_STORE_NAME } from '@woocommerce/data'; import { useExperiment } from '@woocommerce/explat'; @@ -15,12 +15,14 @@ import { recordEvent } from '@woocommerce/tracks'; */ import { DisplayOption } from '~/activity-panel/display-options'; import { Task } from './task'; -import { TasksPlaceholder } from './placeholder'; +import { TasksPlaceholder, TasksPlaceholderProps } from './placeholder'; import './tasks.scss'; import { TaskListProps } from './task-list'; import { TaskList } from './task-list'; import { TaskList as TwoColumnTaskList } from '../two-column-tasks/task-list'; +import TwoColumnTaskListPlaceholder from '../two-column-tasks/placeholder'; import '../two-column-tasks/style.scss'; +import { getAdminSetting } from '~/utils/admin-settings'; export type TasksProps = { query: { task?: string }; @@ -35,6 +37,17 @@ function getTaskListComponent( taskListId: string ): React.FC< TaskListProps > { } } +function getTaskListPlaceholderComponent( + taskListId: string +): React.FC< TasksPlaceholderProps > { + switch ( taskListId ) { + case 'setup_experiment_1': + return TwoColumnTaskListPlaceholder; + default: + return TasksPlaceholder; + } +} + export const Tasks: React.FC< TasksProps > = ( { query } ) => { const { task } = query; const { hideTaskList } = useDispatch( ONBOARDING_STORE_NAME ); @@ -97,8 +110,13 @@ export const Tasks: React.FC< TasksProps > = ( { query } ) => { return null; } + const taskListIds = getAdminSetting( 'visibleTaskListIds', [] ); + const TaskListPlaceholderComponent = getTaskListPlaceholderComponent( + taskListIds[ 0 ] + ); + if ( isResolving ) { - return ; + return ; } if ( currentTask ) { @@ -110,7 +128,7 @@ export const Tasks: React.FC< TasksProps > = ( { query } ) => { } if ( isLoadingExperiment ) { - return ; + return ; } return taskLists diff --git a/plugins/woocommerce-admin/client/two-column-tasks/placeholder.js b/plugins/woocommerce-admin/client/two-column-tasks/placeholder.tsx similarity index 87% rename from plugins/woocommerce-admin/client/two-column-tasks/placeholder.js rename to plugins/woocommerce-admin/client/two-column-tasks/placeholder.tsx index 70358aee2ca..2cbea8720db 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/placeholder.js +++ b/plugins/woocommerce-admin/client/two-column-tasks/placeholder.tsx @@ -8,7 +8,15 @@ import classnames from 'classnames'; */ import './style.scss'; -const TaskListPlaceholder = ( props ) => { +type TasksPlaceholderProps = { + numTasks?: number; + twoColumns?: boolean; + query: { + task?: string; + }; +}; + +const TaskListPlaceholder: React.FC< TasksPlaceholderProps > = ( props ) => { const { numTasks = 5, twoColumns = false } = props; return ( From 217bec894297b9e15188288f97866a9649df8744 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 4 Apr 2022 15:58:50 -0300 Subject: [PATCH 041/386] Fix lint errors --- plugins/woocommerce-admin/client/homescreen/layout.js | 3 +-- plugins/woocommerce-admin/client/tasks/tasks.tsx | 3 +-- plugins/woocommerce-admin/client/two-column-tasks/index.js | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce-admin/client/homescreen/layout.js b/plugins/woocommerce-admin/client/homescreen/layout.js index 34fd8c4cc32..282572d6b7b 100644 --- a/plugins/woocommerce-admin/client/homescreen/layout.js +++ b/plugins/woocommerce-admin/client/homescreen/layout.js @@ -286,8 +286,7 @@ export default compose( const { getOption, hasFinishedResolution } = select( OPTIONS_STORE_NAME ); - const { getTaskList, getTaskLists } = select( ONBOARDING_STORE_NAME ); - const taskLists = getTaskLists(); + const { getTaskList } = select( ONBOARDING_STORE_NAME ); const welcomeFromCalypsoModalDismissed = getOption( WELCOME_FROM_CALYPSO_MODAL_DISMISSED_OPTION_NAME ) !== diff --git a/plugins/woocommerce-admin/client/tasks/tasks.tsx b/plugins/woocommerce-admin/client/tasks/tasks.tsx index 5370e426e3d..500661cb650 100644 --- a/plugins/woocommerce-admin/client/tasks/tasks.tsx +++ b/plugins/woocommerce-admin/client/tasks/tasks.tsx @@ -17,8 +17,7 @@ import { DisplayOption } from '~/activity-panel/display-options'; import { Task } from './task'; import { TasksPlaceholder, TasksPlaceholderProps } from './placeholder'; import './tasks.scss'; -import { TaskListProps } from './task-list'; -import { TaskList } from './task-list'; +import { TaskListProps, TaskList } from './task-list'; import { TaskList as TwoColumnTaskList } from '../two-column-tasks/task-list'; import TwoColumnTaskListPlaceholder from '../two-column-tasks/placeholder'; import '../two-column-tasks/style.scss'; diff --git a/plugins/woocommerce-admin/client/two-column-tasks/index.js b/plugins/woocommerce-admin/client/two-column-tasks/index.js index 4a3b315235c..d09c973f864 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/index.js +++ b/plugins/woocommerce-admin/client/two-column-tasks/index.js @@ -54,7 +54,7 @@ const TaskDashboard = ( { query, twoColumns } ) => { return null; } - if ( isResolving || isResolvingOptions || ! taskLists[ 0 ] ) { + if ( isResolving || ! taskLists[ 0 ] ) { return ; } From e4803ef40fb1e9afec02a91e3b0b267d8c4472f1 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 4 Apr 2022 16:43:15 -0300 Subject: [PATCH 042/386] Fix tests --- .../woocommerce-admin/client/tasks/tasks.tsx | 21 ++++++++----------- .../client/tasks/test/tasks.test.tsx | 11 ++++++---- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/tasks.tsx b/plugins/woocommerce-admin/client/tasks/tasks.tsx index 500661cb650..6d450e63a43 100644 --- a/plugins/woocommerce-admin/client/tasks/tasks.tsx +++ b/plugins/woocommerce-admin/client/tasks/tasks.tsx @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { MenuGroup, MenuItem } from '@wordpress/components'; import { check } from '@wordpress/icons'; -import { Fragment, useEffect, Suspense } from '@wordpress/element'; +import { Fragment, useEffect } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { ONBOARDING_STORE_NAME, OPTIONS_STORE_NAME } from '@woocommerce/data'; import { useExperiment } from '@woocommerce/explat'; @@ -147,17 +147,14 @@ export const Tasks: React.FC< TasksProps > = ( { query } ) => { return ( - - - + { isToggleable && ( { jest.mock( '@woocommerce/explat' ); jest.mock( '@woocommerce/tracks' ); -jest.mock( '../task-list', () => ( { id } ) =>
task-list:{ id }
); -jest.mock( '../../two-column-tasks/task-list', () => ( { id } ) => ( -
two-column-list:{ id }
-) ); +jest.mock( '../task-list', () => ( { + TaskList: ( { id } ) =>
task-list:{ id }
, +} ) ); + +jest.mock( '../../two-column-tasks/task-list', () => ( { + TaskList: ( { id } ) =>
two-column-list:{ id }
, +} ) ); jest.mock( '../task', () => ( { Task: ( { query } ) =>
task:{ query.task }
, From 0204010b75b960a9be6fd2f7093bbabad50df5fd Mon Sep 17 00:00:00 2001 From: Christopher Allford Date: Mon, 4 Apr 2022 15:18:30 -0700 Subject: [PATCH 043/386] Moved `wp-env` File Rather than having a root `wp-env` environment, each project should define what it needs. This prevents problems with shared WordPress installations and plugin conflicts during development. --- .wp-env.json => plugins/woocommerce/.wp-env.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .wp-env.json => plugins/woocommerce/.wp-env.json (80%) diff --git a/.wp-env.json b/plugins/woocommerce/.wp-env.json similarity index 80% rename from .wp-env.json rename to plugins/woocommerce/.wp-env.json index 76975dbd699..848e60698c8 100644 --- a/.wp-env.json +++ b/plugins/woocommerce/.wp-env.json @@ -1,7 +1,7 @@ { "phpVersion": "7.4", "core": null, - "plugins": [ "./plugins/woocommerce" ], + "plugins": [ "." ], "config": { "JETPACK_AUTOLOAD_DEV": true, "WP_DEBUG_LOG": true, From 277b1acdf5a6292edd3dad8af215702b9594635c Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 5 Apr 2022 14:45:13 +0800 Subject: [PATCH 044/386] Downgrade chalk to v4 --- plugins/woocommerce-admin/package.json | 2 +- pnpm-lock.yaml | 29 +++++++++++--------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 50d499eea0c..a21d959a0dd 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -222,7 +222,7 @@ "babel-loader": "^8.2.3", "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-es2015-template-literals": "^6.22.0", - "chalk": "^5.0.0", + "chalk": "^4.1.2", "comment-parser": "^1.3.0", "concurrently": "^7.0.0", "config": "^3.3.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3554369126f..87d9d2c2986 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -889,9 +889,9 @@ importers: qs: 6.10.3 devDependencies: '@babel/core': 7.17.8 + '@babel/runtime': 7.17.7 '@wordpress/eslint-plugin': 11.0.1_7c040a9b494a33cf8bf9079642892fb1 eslint: 8.12.0 - '@babel/runtime': 7.17.7 jest: 27.5.1 jest-cli: 27.5.1 rimraf: 3.0.2 @@ -928,9 +928,9 @@ importers: packages/js/number: specifiers: '@babel/core': ^7.17.5 + '@babel/runtime': ^7.17.2 '@wordpress/eslint-plugin': ^11.0.0 eslint: ^8.12.0 - '@babel/runtime': ^7.17.2 jest: ^27.5.1 jest-cli: ^27.5.1 locutus: ^2.0.16 @@ -941,9 +941,9 @@ importers: locutus: 2.0.16 devDependencies: '@babel/core': 7.17.8 + '@babel/runtime': 7.17.7 '@wordpress/eslint-plugin': 11.0.1_7c040a9b494a33cf8bf9079642892fb1 eslint: 8.12.0 - '@babel/runtime': 7.17.7 jest: 27.5.1 jest-cli: 27.5.1 rimraf: 3.0.2 @@ -1265,7 +1265,7 @@ importers: babel-loader: ^8.2.3 babel-plugin-transform-class-properties: ^6.24.1 babel-plugin-transform-es2015-template-literals: ^6.22.0 - chalk: ^5.0.0 + chalk: ^4.1.2 classnames: ^2.3.1 comment-parser: ^1.3.0 concurrently: ^7.0.0 @@ -1473,7 +1473,7 @@ importers: babel-loader: 8.2.3_fa907c5a4f16ccc493e21649ccc59574 babel-plugin-transform-class-properties: 6.24.1 babel-plugin-transform-es2015-template-literals: 6.22.0 - chalk: 5.0.1 + chalk: 4.1.2 comment-parser: 1.3.0 concurrently: 7.0.0 config: 3.3.7 @@ -3812,7 +3812,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.0 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 dev: true /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.16.12: @@ -3821,7 +3821,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.12 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.17.8: resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} @@ -3829,7 +3829,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.8 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.12.9: resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} @@ -3944,7 +3944,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.0 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 dev: true /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.16.12: @@ -3953,7 +3953,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.12 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.17.8: resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} @@ -3961,7 +3961,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.8 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.16.0: resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} @@ -17684,11 +17684,6 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 - /chalk/5.0.1: - resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - dev: true - /change-case/2.3.1: resolution: {integrity: sha1-LE/ePwY7tB0AzWjg1aCdthy+iU8=} dependencies: @@ -37084,7 +37079,7 @@ packages: serialize-javascript: 6.0.0 source-map: 0.6.1 terser: 5.10.0_acorn@8.7.0 - webpack: 5.70.0 + webpack: 5.70.0_webpack-cli@3.3.12 transitivePeerDependencies: - acorn dev: true From bab72ea51baec439b8577fbbcd57cc657107e85e Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 5 Apr 2022 16:59:25 +0800 Subject: [PATCH 045/386] Fix continue setup prompt for adding first product appeared too early --- .../src/Admin/Features/OnboardingTasks/Tasks/Products.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php index f632fa01ce0..d3e657d5ba6 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php @@ -98,7 +98,7 @@ class Products extends Task { return; } - if ( ! $this->is_active() || $this->is_complete() ) { + if ( ! $this->is_active() || ! $this->is_complete() ) { return; } @@ -112,6 +112,9 @@ class Products extends Task { WC_VERSION, true ); + + // Clear the active task transient to only show notice once per active session. + delete_transient( self::ACTIVE_TASK_TRANSIENT ); } /** From be8e0744e5883559ef228605d4ea63e4623871e8 Mon Sep 17 00:00:00 2001 From: erikdemarco <42645368+erikdemarco@users.noreply.github.com> Date: Tue, 20 Jul 2021 13:37:11 +0700 Subject: [PATCH 046/386] Add new hook to order-tracking Add additional hook for order tracking, So developer can add additional field for example adding captcha field. --- plugins/woocommerce/templates/order/form-tracking.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce/templates/order/form-tracking.php b/plugins/woocommerce/templates/order/form-tracking.php index d26f1a062a9..decff842e77 100644 --- a/plugins/woocommerce/templates/order/form-tracking.php +++ b/plugins/woocommerce/templates/order/form-tracking.php @@ -21,14 +21,18 @@ global $post; ?>
- + +

+ +

+
From 28a5f7866b129849d6a1f060f5b63eb76f849604 Mon Sep 17 00:00:00 2001 From: erikdemarco <42645368+erikdemarco@users.noreply.github.com> Date: Thu, 24 Mar 2022 07:07:27 +0700 Subject: [PATCH 047/386] Update form-tracking.php --- .../templates/order/form-tracking.php | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce/templates/order/form-tracking.php b/plugins/woocommerce/templates/order/form-tracking.php index decff842e77..712106eb8df 100644 --- a/plugins/woocommerce/templates/order/form-tracking.php +++ b/plugins/woocommerce/templates/order/form-tracking.php @@ -21,18 +21,35 @@ global $post; ?>
- + +

- - + +

- - + + +
From 680ad9ceb88540d2237dece4c7834416c7c836b9 Mon Sep 17 00:00:00 2001 From: "Jorge A. Torres" Date: Tue, 5 Apr 2022 13:55:25 -0300 Subject: [PATCH 048/386] Add docblocks to woocommerce_order_tracking_form hooks. --- .../templates/order/form-tracking.php | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/plugins/woocommerce/templates/order/form-tracking.php b/plugins/woocommerce/templates/order/form-tracking.php index 712106eb8df..10cdde7713f 100644 --- a/plugins/woocommerce/templates/order/form-tracking.php +++ b/plugins/woocommerce/templates/order/form-tracking.php @@ -21,35 +21,41 @@ global $post; ?>
- - - +

- -

- - - +
From e72bc47535b91cb07127c455e9d84dfbcc1bc302 Mon Sep 17 00:00:00 2001 From: "Jorge A. Torres" Date: Tue, 5 Apr 2022 14:02:40 -0300 Subject: [PATCH 049/386] =?UTF-8?q?Bump=20=E2=80=98form-tracking=E2=80=99?= =?UTF-8?q?=20template=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/woocommerce/templates/order/form-tracking.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/templates/order/form-tracking.php b/plugins/woocommerce/templates/order/form-tracking.php index 10cdde7713f..2b2ce8de6b3 100644 --- a/plugins/woocommerce/templates/order/form-tracking.php +++ b/plugins/woocommerce/templates/order/form-tracking.php @@ -12,7 +12,7 @@ * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce\Templates - * @version 3.6.0 + * @version 6.5.0 */ defined( 'ABSPATH' ) || exit; From c90922d9e4ce702d5f13fd032a23954635ba9002 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 5 Apr 2022 14:38:17 -0300 Subject: [PATCH 050/386] Fix PHP warning of missing default value in spec --- .../src/Admin/RemoteInboxNotifications/OptionRuleProcessor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/OptionRuleProcessor.php b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/OptionRuleProcessor.php index 57ad552174a..3ef30a9b97e 100644 --- a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/OptionRuleProcessor.php +++ b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/OptionRuleProcessor.php @@ -42,7 +42,7 @@ class OptionRuleProcessor implements RuleProcessorInterface { } if ( isset( $rule->transformers ) && is_array( $rule->transformers ) ) { - $option_value = TransformerService::apply( $option_value, $rule->transformers, $rule->default ); + $option_value = TransformerService::apply( $option_value, $rule->transformers, $default ); } return ComparisonOperation::compare( From 407b22513367c1b0bd59100b66f9addf51f064ae Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Wed, 6 Apr 2022 14:08:04 +1200 Subject: [PATCH 051/386] Move feature flag config files to Woocommerce plugin to support unit test execution in the wp-env environment. --- plugins/woocommerce-admin/package.json | 2 +- .../bin/generate-feature-config.php | 4 ++-- .../client/admin}/config/core.json | 0 .../client/admin}/config/development.json | 0 .../client/admin}/config/plugin.json | 0 5 files changed, 3 insertions(+), 3 deletions(-) rename plugins/{woocommerce-admin => woocommerce}/bin/generate-feature-config.php (86%) rename plugins/{woocommerce-admin => woocommerce/client/admin}/config/core.json (100%) rename plugins/{woocommerce-admin => woocommerce/client/admin}/config/development.json (100%) rename plugins/{woocommerce-admin => woocommerce/client/admin}/config/plugin.json (100%) diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 50d499eea0c..eb44110a6e3 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -25,7 +25,7 @@ "build": "pnpm run build:feature-config && cross-env NODE_ENV=production webpack", "analyze": "cross-env NODE_ENV=production ANALYZE=true webpack", "postbuild": "pnpm run -s i18n:pot && pnpm run -s i18n:build", - "build:feature-config": "php bin/generate-feature-config.php", + "build:feature-config": "php ../woocommerce/bin/generate-feature-config.php", "build:packages": "cross-env NODE_ENV=production pnpm run:packages -- build", "build:release": "./bin/build-plugin-zip.sh", "clean": "rimraf ./dist && pnpm run:packages -- clean --parallel", diff --git a/plugins/woocommerce-admin/bin/generate-feature-config.php b/plugins/woocommerce/bin/generate-feature-config.php similarity index 86% rename from plugins/woocommerce-admin/bin/generate-feature-config.php rename to plugins/woocommerce/bin/generate-feature-config.php index 7174b0110b9..0ab6248a35c 100644 --- a/plugins/woocommerce-admin/bin/generate-feature-config.php +++ b/plugins/woocommerce/bin/generate-feature-config.php @@ -17,7 +17,7 @@ $phase = getenv( 'WC_ADMIN_PHASE' ); if ( ! in_array( $phase, array( 'development', 'plugin', 'core' ), true ) ) { $phase = 'plugin'; // Default to plugin when running `pnpm run build`. } -$config_json = file_get_contents( __DIR__ . '/../config/' . $phase . '.json' ); +$config_json = file_get_contents( __DIR__ . '/../client/admin/config/' . $phase . '.json' ); $config = json_decode( $config_json ); $write = " Date: Wed, 6 Apr 2022 10:01:41 +0800 Subject: [PATCH 052/386] Remove uninstall.php & woocommerce-admin.php --- plugins/woocommerce-admin/uninstall.php | 23 --- .../woocommerce-admin/woocommerce-admin.php | 143 ------------------ 2 files changed, 166 deletions(-) delete mode 100644 plugins/woocommerce-admin/uninstall.php delete mode 100755 plugins/woocommerce-admin/woocommerce-admin.php diff --git a/plugins/woocommerce-admin/uninstall.php b/plugins/woocommerce-admin/uninstall.php deleted file mode 100644 index e349b99deb9..00000000000 --- a/plugins/woocommerce-admin/uninstall.php +++ /dev/null @@ -1,23 +0,0 @@ - -
-

- composer install', - '' . esc_html( str_replace( ABSPATH, '', __DIR__ ) ) . '' - ); - ?> -

-
-

'; - printf( - /* Translators: %1$s is referring to a php constant name, %2$s is referring to the wp-config.php file. */ - esc_html__( 'WooCommerce Admin development mode requires the %1$s constant to be defined and true in your %2$s file. Otherwise you are loading the admin package from WooCommerce core.', 'woocommerce-admin' ), - 'JETPACK_AUTOLOAD_DEV', - 'wp-config.php' - ); - echo '

'; - } - ); -} - -/** - * If we're missing expected files, notify users that the plugin needs to be built. - */ -if ( ! woocommerce_admin_check_build_files() ) { - add_action( - 'admin_notices', - function() { - echo '

'; - printf( - /* Translators: %1$s, %2$s, and %3$s are all build commands to be run in order. */ - esc_html__( 'You have installed a development version of WooCommerce Admin which requires files to be built. From the plugin directory, run %1$s and %2$s to install dependencies, then %3$s to build the files.', 'woocommerce-admin' ), - 'composer install', - 'pnpm install', - 'pnpm run build' - ); - printf( - /* translators: 1: URL of GitHub Repository build page */ - esc_html__( 'Or you can download a pre-built version of the plugin by visiting the releases page in the repository.', 'woocommerce-admin' ), - 'https://github.com/woocommerce/woocommerce-admin/releases' - ); - echo '

'; - } - ); -} - -FeaturePlugin::instance()->init(); From bf91c3cca0d029cb62223684385f802369385a29 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 6 Apr 2022 10:01:57 +0800 Subject: [PATCH 053/386] Change mainFile in Gruntfile.js --- plugins/woocommerce-admin/Gruntfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce-admin/Gruntfile.js b/plugins/woocommerce-admin/Gruntfile.js index 610e8164a89..3f0fd4dbdbf 100755 --- a/plugins/woocommerce-admin/Gruntfile.js +++ b/plugins/woocommerce-admin/Gruntfile.js @@ -1,5 +1,5 @@ /* eslint-disable */ -module.exports = function( grunt ) { +module.exports = function ( grunt ) { 'use strict'; // Project configuration @@ -9,7 +9,7 @@ module.exports = function( grunt ) { options: { domainPath: '/languages', exclude: [ '.git/*', 'bin/*', 'node_modules/*', 'tests/*' ], - mainFile: 'woocommerce-admin.php', + mainFile: '../woocommerce/woocommerce.php', potFilename: 'woocommerce-admin.pot', potHeaders: { poedit: true, From 0c366289e0cf69322736ed738a4f45427c22c86c Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 6 Apr 2022 10:03:55 +0800 Subject: [PATCH 054/386] Remove woocommerce-admin.php from tests --- plugins/woocommerce-admin/tests/bootstrap.php | 1 - .../woocommerce-admin/plugin-version.php | 21 +------------------ 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/plugins/woocommerce-admin/tests/bootstrap.php b/plugins/woocommerce-admin/tests/bootstrap.php index 3ebb080a43f..7d7832339e5 100755 --- a/plugins/woocommerce-admin/tests/bootstrap.php +++ b/plugins/woocommerce-admin/tests/bootstrap.php @@ -97,7 +97,6 @@ class WC_Admin_Unit_Tests_Bootstrap { require_once $this->wc_core_dir . '/woocommerce.php'; require $this->plugin_dir . '/vendor/autoload.php'; - require $this->plugin_dir . '/woocommerce-admin.php'; } /** diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php index 4104bf21cb7..2130805079e 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php @@ -13,25 +13,6 @@ * @since 3.6.4 */ class WC_Admin_Tests_Plugin_Version extends WP_UnitTestCase { - /** - * Ensure that all version numbers match. - */ - public function test_version_numbers() { - // Get package.json version. - $package_json = file_get_contents( '../woocommerce-admin/package.json' ); - $package = json_decode( $package_json ); - - // Get main plugin file header version. - $plugin = get_file_data( '../woocommerce-admin/woocommerce-admin.php', array( 'Version' => 'Version' ) ); - - // Get plugin DB version. - $db_version = defined( 'WC_ADMIN_VERSION_NUMBER' ) ? constant( 'WC_ADMIN_VERSION_NUMBER' ) : false; - - // Compare all versions to the package.json value. - $this->assertEquals( $package->version, $plugin['Version'], 'Plugin header version does not match package.json' ); - $this->assertEquals( $package->version, $db_version, 'DB version constant does not match package.json' ); - } - /** * Ensure that a DB version callback is defined when there are updates. */ @@ -45,7 +26,7 @@ class WC_Admin_Tests_Plugin_Version extends WP_UnitTestCase { } // Verify there is a version update callback for each version. - $version_string = str_replace( '.', '', $version ); + $version_string = str_replace( '.', '', $version ); $expected_callback = "wc_admin_update_{$version_string}_db_version"; $this->assertContains( $expected_callback, $version_callbacks, "Expected DB update callback {$expected_callback}() was not found." ); From 8da48065fd2154d90f6ee5e4e5f57c354ee5aadb Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 6 Apr 2022 10:17:41 +0800 Subject: [PATCH 055/386] Remove de-activation logic when the Core version is not met --- .../src/Admin/Composer/Package.php | 4 -- .../src/Internal/Admin/FeaturePlugin.php | 71 ------------------- 2 files changed, 75 deletions(-) diff --git a/plugins/woocommerce/src/Admin/Composer/Package.php b/plugins/woocommerce/src/Admin/Composer/Package.php index 9d12915973c..5d27cb01d2f 100644 --- a/plugins/woocommerce/src/Admin/Composer/Package.php +++ b/plugins/woocommerce/src/Admin/Composer/Package.php @@ -62,10 +62,6 @@ class Package { } $feature_plugin_instance = FeaturePlugin::instance(); - $satisfied_dependencies = is_callable( array( $feature_plugin_instance, 'has_satisfied_dependencies' ) ) && $feature_plugin_instance->has_satisfied_dependencies(); - if ( ! $satisfied_dependencies ) { - return; - } // Indicate to the feature plugin that the core package exists. if ( ! defined( 'WC_ADMIN_PACKAGE_EXISTS' ) ) { diff --git a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php index 66d8195a41d..98cd6b3ae4e 100644 --- a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php +++ b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php @@ -113,12 +113,6 @@ class FeaturePlugin { return; } - // Check if we are deactivating due to dependencies not being satisfied. - // If WooCommerce is disabled we can't include files that depend upon it. - if ( ! $this->has_satisfied_dependencies() ) { - return; - } - $this->includes(); ReportsSync::clear_queued_actions(); Notes::clear_queued_actions(); @@ -134,12 +128,6 @@ class FeaturePlugin { public function on_plugins_loaded() { $this->load_plugin_textdomain(); - if ( ! $this->has_satisfied_dependencies() ) { - add_action( 'admin_init', array( $this, 'deactivate_self' ) ); - add_action( 'admin_notices', array( $this, 'render_dependencies_notice' ) ); - return; - } - $this->hooks(); $this->includes(); } @@ -229,65 +217,6 @@ class FeaturePlugin { WCAdminAssets::get_instance(); } - /** - * Get an array of dependency error messages. - * - * @return array - */ - protected function get_dependency_errors() { - $errors = array(); - $wordpress_version = get_bloginfo( 'version' ); - $minimum_wordpress_version = '5.4'; - $minimum_woocommerce_version = '4.8'; - $wordpress_minimum_met = version_compare( $wordpress_version, $minimum_wordpress_version, '>=' ); - $woocommerce_minimum_met = class_exists( 'WooCommerce' ) && version_compare( WC_VERSION, $minimum_woocommerce_version, '>=' ); - - if ( ! $woocommerce_minimum_met ) { - $errors[] = sprintf( - /* translators: 1: URL of WooCommerce plugin, 2: The minimum WooCommerce version number */ - __( 'The WooCommerce Admin feature plugin requires WooCommerce %2$s or greater to be installed and active.', 'woocommerce' ), - 'https://wordpress.org/plugins/woocommerce/', - $minimum_woocommerce_version - ); - } - - if ( ! $wordpress_minimum_met ) { - $errors[] = sprintf( - /* translators: 1: URL of WordPress.org, 2: The minimum WordPress version number */ - __( 'The WooCommerce Admin feature plugin requires WordPress %2$s or greater to be installed and active.', 'woocommerce' ), - 'https://wordpress.org/', - $minimum_wordpress_version - ); - } - - return $errors; - } - - /** - * Returns true if all dependencies for the wc-admin plugin are loaded. - * - * @return bool - */ - public function has_satisfied_dependencies() { - $dependency_errors = $this->get_dependency_errors(); - return 0 === count( $dependency_errors ); - } - - /** - * Deactivates this plugin. - */ - public function deactivate_self() { - deactivate_plugins( plugin_basename( WC_ADMIN_PLUGIN_FILE ) ); - unset( $_GET['activate'] ); // phpcs:ignore CSRF ok. - } - - /** - * Notify users of the plugin requirements. - */ - public function render_dependencies_notice() { - $message = $this->get_dependency_errors(); - printf( '

%s

', implode( ' ', $message ) ); /* phpcs:ignore xss ok. */ - } /** * Overwrites the allowed features array using a local `feature-config.php` file. From 73ba1fc4b241007d84f4c67ae011e218e0905ddd Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 6 Apr 2022 11:06:53 +0800 Subject: [PATCH 056/386] Add changelog --- plugins/woocommerce/changelog/update-simpliy-init-routine | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/update-simpliy-init-routine diff --git a/plugins/woocommerce/changelog/update-simpliy-init-routine b/plugins/woocommerce/changelog/update-simpliy-init-routine new file mode 100644 index 00000000000..43026464398 --- /dev/null +++ b/plugins/woocommerce/changelog/update-simpliy-init-routine @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +simplify the WooCommerce Admin init routine. #32489 From 659bf2d43cf613d5e878506fb913adacb39eeb38 Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Wed, 6 Apr 2022 15:25:18 +1200 Subject: [PATCH 057/386] add additional script to core --- plugins/woocommerce/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index 1f20ec694d6..406c5eb4a0b 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -14,6 +14,7 @@ "scripts": { "preinstall": "npx only-allow pnpm", "build": "./bin/build-zip.sh", + "build:feature-config": "php bin/generate-feature-config.php", "build:core": "pnpm nx build woocommerce-admin && pnpm nx build woocommerce-legacy-assets && pnpm run makepot", "build:zip": "pnpm run build", "lint:js": "eslint assets/js --ext=js", From ab673dd14b7d91b15534b95cc8144cf977b2f27d Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Wed, 6 Apr 2022 15:27:37 +1200 Subject: [PATCH 058/386] Call Core's script from root level --- .github/workflows/ci.yml | 2 +- .github/workflows/pr-code-coverage.yml | 2 +- .github/workflows/pr-unit-tests.yml | 2 +- plugins/woocommerce/project.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b54e225a1b..ac8ab5115ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: - name: Build Admin feature config working-directory: ./ - run: pnpm nx build:feature-config woocommerce-admin + run: pnpm nx build:feature-config woocommerce - name: Add PHP8 Compatibility. run: | diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 816398c1854..c32ef4a016a 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -61,7 +61,7 @@ jobs: - name: Build Admin feature config working-directory: ./ run: | - pnpm nx build:feature-config woocommerce-admin + pnpm nx build:feature-config woocommerce - name: Init DB and WP run: pnpm nx install-unit-test-db woocommerce diff --git a/.github/workflows/pr-unit-tests.yml b/.github/workflows/pr-unit-tests.yml index ac5a5b8de58..9dcaedf3ee4 100644 --- a/.github/workflows/pr-unit-tests.yml +++ b/.github/workflows/pr-unit-tests.yml @@ -69,7 +69,7 @@ jobs: - name: Build Admin feature config working-directory: ./ run: | - pnpm nx build:feature-config woocommerce-admin + pnpm nx build:feature-config woocommerce - name: Add PHP8 Compatibility. run: | diff --git a/plugins/woocommerce/project.json b/plugins/woocommerce/project.json index 9fbc742dca1..bc0737df1d6 100644 --- a/plugins/woocommerce/project.json +++ b/plugins/woocommerce/project.json @@ -57,7 +57,7 @@ "executor": "@nrwl/workspace:run-commands", "options": { "commands": [ - "pnpm nx build:feature-config woocommerce-admin", + "pnpm nx build:feature-config woocommerce", "pnpm nx watch-assets woocommerce" ] } From 0b2e92233811481458d216b036cc9fac972fddbd Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Wed, 6 Apr 2022 15:48:24 +1200 Subject: [PATCH 059/386] changelog --- .../woocommerce/changelog/fix-feature-config-file-location | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-feature-config-file-location diff --git a/plugins/woocommerce/changelog/fix-feature-config-file-location b/plugins/woocommerce/changelog/fix-feature-config-file-location new file mode 100644 index 00000000000..e366c8e91d2 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-feature-config-file-location @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Move feature flag config files to Woocommerce plugin to support unit test execution in the wp-env environment. From a154c26ea466fb899d10d9468c0e31de345b83ca Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Wed, 6 Apr 2022 16:01:36 +1200 Subject: [PATCH 060/386] fix legacy tests path --- plugins/woocommerce/tests/legacy/bootstrap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/tests/legacy/bootstrap.php b/plugins/woocommerce/tests/legacy/bootstrap.php index 6c29298d0a5..d4f0aeead67 100644 --- a/plugins/woocommerce/tests/legacy/bootstrap.php +++ b/plugins/woocommerce/tests/legacy/bootstrap.php @@ -259,7 +259,7 @@ class WC_Unit_Tests_Bootstrap { * @return array Filtered feature flags. */ public function add_development_features( $flags ) { - $config = json_decode( file_get_contents( $this->plugin_dir . '/../woocommerce-admin/config/development.json' ) ); // @codingStandardsIgnoreLine. + $config = json_decode( file_get_contents( $this->plugin_dir . 'client/admin/config/development.json' ) ); // @codingStandardsIgnoreLine. foreach ( $config->features as $feature => $bool ) { $flags[ $feature ] = $bool; } From fd4a4a5604de63d835fb2bd71cc2cdf388042ab8 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 31 Mar 2022 12:23:26 +0800 Subject: [PATCH 061/386] Add is_offline flag and split payment sections --- packages/js/data/src/plugins/types.ts | 1 + .../fills/PaymentGatewaySuggestions/index.js | 24 +++++++++++++++---- .../PaymentGatewaySuggestions/test/index.js | 2 ++ .../DefaultPaymentGateways.php | 2 ++ 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/js/data/src/plugins/types.ts b/packages/js/data/src/plugins/types.ts index 0988f81972a..54a0299ba39 100644 --- a/packages/js/data/src/plugins/types.ts +++ b/packages/js/data/src/plugins/types.ts @@ -39,6 +39,7 @@ export type Plugin = { recommendation_priority?: number; is_visible?: boolean; is_local_partner?: boolean; + is_offline?: boolean; }; type PaypalOnboardingState = 'unknown' | 'start' | 'progressive' | 'onboarded'; diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js index a908df8304f..a74092fb4a5 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js @@ -179,7 +179,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { return gateway; }, [ isResolving, query, paymentGateways ] ); - const [ wcPayGateway, enabledGateways, additionalGateways ] = useMemo( + const [ wcPayGateway, enabledGateways, offlineGateways, additionalGateways ] = useMemo( () => Array.from( paymentGateways.values() ) .sort( ( a, b ) => { @@ -196,7 +196,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { } ) .reduce( ( all, gateway ) => { - const [ wcPay, enabled, additional ] = all; + const [ wcPay, enabled, offline, additional ] = all; // WCPay is handled separately when not installed and configured if ( @@ -207,13 +207,15 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { wcPay.push( gateway ); } else if ( gateway.enabled ) { enabled.push( gateway ); + } else if ( gateway.is_offline ) { + offline.push( gateway ); } else { additional.push( gateway ); } return all; }, - [ [], [], [] ] + [ [], [], [], [] ] ), [ paymentGateways ] ); @@ -250,14 +252,26 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { { !! additionalGateways.length && ( ) } + + { !! offlineGateways.length && ( + + ) } ); }; diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js index eae438fe47b..386dd514510 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js @@ -49,6 +49,7 @@ const paymentGatewaySuggestions = [ image: 'http://localhost:8888/wp-content/plugins/woocommerce-admin/images/onboarding/cod.svg', is_visible: true, + is_offline: true, }, { id: 'bacs', @@ -57,6 +58,7 @@ const paymentGatewaySuggestions = [ image: 'http://localhost:8888/wp-content/plugins/woocommerce-admin/images/onboarding/bacs.svg', is_visible: true, + is_offline: true, }, { id: 'woocommerce_payments:non-us', diff --git a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php index 1b1a76e74e2..64ef1e33512 100644 --- a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php +++ b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php @@ -198,6 +198,7 @@ class DefaultPaymentGateways { 'is_visible' => array( self::get_rules_for_cbd( false ), ), + 'is_offline' => true, ), array( 'id' => 'bacs', @@ -207,6 +208,7 @@ class DefaultPaymentGateways { 'is_visible' => array( self::get_rules_for_cbd( false ), ), + 'is_offline' => true, ), array( 'id' => 'woocommerce_payments', From fb89df5f2c1594f2810986a9587da308e334a354 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 31 Mar 2022 12:26:22 +0800 Subject: [PATCH 062/386] Rename set up button to get started --- .../fills/PaymentGatewaySuggestions/components/Action.js | 2 +- .../PaymentGatewaySuggestions/components/List/test/list.js | 4 ++-- .../PaymentGatewaySuggestions/components/Setup/Configure.js | 2 +- .../components/Setup/test/configure.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Action.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Action.js index e16ff3644b1..90c0486ecbc 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Action.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Action.js @@ -24,7 +24,7 @@ export const Action = ( { markConfigured, onSetUp = () => {}, onSetupCallback, - setupButtonText = __( 'Set up', 'woocommerce' ), + setupButtonText = __( 'Get started', 'woocommerce-admin' ), } ) => { const [ isBusy, setIsBusy ] = useState( false ); diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/test/list.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/test/list.js index 8e7d4caca25..6bd6e4e07e0 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/test/list.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/test/list.js @@ -49,7 +49,7 @@ describe( 'PaymentGatewaySuggestions > List', () => { expect( queryByRole( 'button' ) ).toHaveTextContent( 'Enable' ); } ); - it( 'should display the "Set up" button when setup is required', () => { + it( 'should display the "Get started" button when setup is required', () => { const props = { ...defaultProps, paymentGateways: [ @@ -63,7 +63,7 @@ describe( 'PaymentGatewaySuggestions > List', () => { const { queryByRole } = render( ); - expect( queryByRole( 'button' ) ).toHaveTextContent( 'Set up' ); + expect( queryByRole( 'button' ) ).toHaveTextContent( 'Get started' ); } ); it( 'should display the SetupRequired component when appropriate', () => { diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/Configure.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/Configure.js index 528d62f63a3..5b5e45b5f92 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/Configure.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/Configure.js @@ -156,7 +156,7 @@ export const Configure = ( { markConfigured, paymentGateway } ) => {

) } ); diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/test/configure.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/test/configure.js index daacf3cc803..81d63b0930e 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/test/configure.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/test/configure.js @@ -81,7 +81,7 @@ describe( 'Configure', () => { const { container } = render( ); const button = container.querySelector( 'a' ); - expect( button.textContent ).toBe( 'Set up' ); + expect( button.textContent ).toBe( 'Get started' ); expect( button.href ).toBe( mockGateway.settingsUrl ); } ); } ); From a3bd1e5109d7fc95a57883ce3334c7eb7bbb0ad2 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 31 Mar 2022 13:57:38 +0800 Subject: [PATCH 063/386] Add see more button --- .../components/List/List.js | 8 ++++++- .../fills/PaymentGatewaySuggestions/index.js | 24 +++++++++++++++++-- .../payment-gateway-suggestions.scss | 9 +++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.js index 9375f94938a..924ae456415 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { Card, CardHeader } from '@wordpress/components'; +import { Card, CardHeader, CardFooter } from '@wordpress/components'; /** * Internal dependencies @@ -15,6 +15,7 @@ export const List = ( { markConfigured, recommendation, paymentGateways, + footerLink, } ) => { return ( @@ -30,6 +31,11 @@ export const List = ( { /> ); } ) } + { footerLink ? ( + { footerLink } + ) : ( + '' + ) } ); }; diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js index a74092fb4a5..1d2f5160e71 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js @@ -13,6 +13,8 @@ import { useMemo, useCallback, useEffect } from '@wordpress/element'; import { registerPlugin } from '@wordpress/plugins'; import { WooOnboardingTask } from '@woocommerce/onboarding'; import { getNewPath } from '@woocommerce/navigation'; +import { Button } from '@wordpress/components'; +import ExternalIcon from 'gridicons/dist/external'; /** * Internal dependencies @@ -24,6 +26,9 @@ import { getPluginSlug } from '~/utils'; import './plugins/Bacs'; import './payment-gateway-suggestions.scss'; +const SEE_MORE_LINK = + 'https://woocommerce.com/product-category/woocommerce-extensions/payment-gateways/?utm_source=payments_recommendations'; + const comparePaymentGatewaysByPriority = ( a, b ) => a.recommendation_priority - b.recommendation_priority; @@ -179,7 +184,12 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { return gateway; }, [ isResolving, query, paymentGateways ] ); - const [ wcPayGateway, enabledGateways, offlineGateways, additionalGateways ] = useMemo( + const [ + wcPayGateway, + enabledGateways, + offlineGateways, + additionalGateways, + ] = useMemo( () => Array.from( paymentGateways.values() ) .sort( ( a, b ) => { @@ -258,7 +268,17 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { recommendation={ recommendation } paymentGateways={ additionalGateways } markConfigured={ markConfigured } - /> + footerLink={ + + } + >
) } { !! offlineGateways.length && ( diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/payment-gateway-suggestions.scss b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/payment-gateway-suggestions.scss index 0716dc3d3fa..8e408b15e20 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/payment-gateway-suggestions.scss +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/payment-gateway-suggestions.scss @@ -22,6 +22,15 @@ margin: 0; } + + .components-card__footer { + a.components-button { + .gridicon { + margin-left: 4px; + } + } + } + .woocommerce-task-payment__recommended-pill { border: 1px solid $studio-gray-5; border-radius: 28px; From 0f554436da200fce975928e2ebfed714ddb80d1b Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Mon, 4 Apr 2022 18:12:20 +0800 Subject: [PATCH 064/386] Broke wcpay task to different sections, added toggle component, added other payment methods toggle --- .../components/List/List.js | 2 +- .../components/Toggle/Toggle.js | 41 +++++++ .../components/Toggle/Toggle.scss | 9 ++ .../components/Toggle/index.js | 1 + .../fills/PaymentGatewaySuggestions/index.js | 100 ++++++++++-------- 5 files changed, 108 insertions(+), 45 deletions(-) create mode 100644 plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.js create mode 100644 plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.scss create mode 100644 plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/index.js diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.js index 924ae456415..d9442b284f7 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.js @@ -19,7 +19,7 @@ export const List = ( { } ) => { return ( - { heading } + { heading ? { heading } : null } { paymentGateways.map( ( paymentGateway ) => { const { id } = paymentGateway; return ( diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.js new file mode 100644 index 00000000000..d87e6fa6ff9 --- /dev/null +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.js @@ -0,0 +1,41 @@ +/** + * External dependencies + */ +import { Button } from '@wordpress/components'; +import ChevronUpIcon from 'gridicons/dist/chevron-up'; +import ChevronDownIcon from 'gridicons/dist/chevron-down'; +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import './Toggle.scss'; + +export const Toggle = ( { children, heading } ) => { + const [ isOpen, setIsOpen ] = useState( false ); + const onToggle = () => { + setIsOpen( ! isOpen ); + }; + + return ( +
+ + { isOpen ? children : null } +
+ ); +}; + +// export default Toggle; diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.scss b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.scss new file mode 100644 index 00000000000..b1605309872 --- /dev/null +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.scss @@ -0,0 +1,9 @@ +.woocommerce-task-payments { + .toggle-button { + margin: $gap-small 0; + + .gridicon { + margin-left: 4px; + } + } +} diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/index.js new file mode 100644 index 00000000000..87fdc434811 --- /dev/null +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/index.js @@ -0,0 +1 @@ +export { Toggle } from './Toggle'; diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js index 1d2f5160e71..99d9f2effea 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js @@ -21,6 +21,7 @@ import ExternalIcon from 'gridicons/dist/external'; */ import { List, Placeholder as ListPlaceholder } from './components/List'; import { Setup, Placeholder as SetupPlaceholder } from './components/Setup'; +import { Toggle } from './components/Toggle/Toggle'; import { WCPaySuggestion } from './components/WCPay'; import { getPluginSlug } from '~/utils'; import './plugins/Bacs'; @@ -230,6 +231,8 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { [ paymentGateways ] ); + const isEligibleWCPay = !! wcPayGateway.length; + if ( query.id && ! currentGateway ) { return ; } @@ -243,54 +246,63 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { ); } + const enabledSection = !! enabledGateways.length && ( + + ); + + const additionalSection = !! additionalGateways.length && ( + + { __( 'See more', 'woocommerce' ) } + + + } + > + ); + + const offlineSection = !! offlineGateways.length && ( + + ); + return (
{ ! paymentGateways.size && } - { !! wcPayGateway.length && ( - - ) } - - { !! enabledGateways.length && ( - - ) } - - { !! additionalGateways.length && ( - - { __( 'See more', 'woocommerce-admin' ) } - - - } - > - ) } - - { !! offlineGateways.length && ( - + { isEligibleWCPay ? ( + <> + + + { enabledSection } + { additionalSection } + { offlineSection } + + + ) : ( + <> + { enabledSection } + { additionalSection } + { offlineSection } + ) }
); From 5ff23bf10331accfefb98a9910eb065ec3c8ab65 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Mon, 4 Apr 2022 18:38:07 +0800 Subject: [PATCH 065/386] Add tracks and small refactor to toggle --- .../components/Toggle/Toggle.js | 17 +++++++++-------- .../fills/PaymentGatewaySuggestions/index.js | 18 +++++++++++++++++- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.js index d87e6fa6ff9..d096360b166 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.js @@ -11,29 +11,30 @@ import { useState } from '@wordpress/element'; */ import './Toggle.scss'; -export const Toggle = ( { children, heading } ) => { - const [ isOpen, setIsOpen ] = useState( false ); - const onToggle = () => { - setIsOpen( ! isOpen ); +export const Toggle = ( { children, heading, onToggle } ) => { + const [ isShow, setIsShow ] = useState( false ); + const onClick = () => { + onToggle( isShow ); + setIsShow( ! isShow ); }; return (
- { isOpen ? children : null } + { isShow ? children : null }
); }; diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js index 99d9f2effea..99d4df681a6 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js @@ -161,6 +161,16 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { [ paymentGateways ] ); + const trackSeeMore = () => { + recordEvent( 'tasklist_payment_see_more', {} ); + }; + + const trackToggle = ( isShow ) => { + recordEvent( 'tasklist_payment_show_toggle', { + toggle: isShow ? 'hide' : 'show', + } ); + }; + const recommendation = useMemo( () => Array.from( paymentGateways.values() ) @@ -265,7 +275,12 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { paymentGateways={ additionalGateways } markConfigured={ markConfigured } footerLink={ - @@ -291,6 +306,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { { enabledSection } { additionalSection } From c596222069b4fa9e23da15aa4d516ba968d2777a Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Tue, 5 Apr 2022 11:01:03 +0800 Subject: [PATCH 066/386] Refactor tests --- .../PaymentGatewaySuggestions/test/index.js | 102 ++++++++++++++++-- 1 file changed, 94 insertions(+), 8 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js index 386dd514510..7df91e7a82a 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js @@ -85,8 +85,12 @@ const paymentGatewaySuggestions = [ }, ]; +const paymentGatewaySuggestionsWithoutWCPay = paymentGatewaySuggestions.filter( + ( p ) => p.title !== 'WooCommerce Payments' +); + describe( 'PaymentGatewaySuggestions', () => { - test( 'should render payment gateway lists', () => { + test( 'should render only WCPay if its suggested', () => { const onComplete = jest.fn(); const query = {}; useSelect.mockImplementation( () => ( { @@ -111,6 +115,38 @@ describe( 'PaymentGatewaySuggestions', () => { ( e ) => e.textContent ); + expect( paymentTitles ).toEqual( [] ); + + expect( + container.getElementsByTagName( 'title' )[ 0 ].textContent + ).toBe( 'WooCommerce Payments' ); + } ); + + test( 'should render all payment gateways if no WCPay', () => { + const onComplete = jest.fn(); + const query = {}; + useSelect.mockImplementation( () => ( { + isResolving: false, + getPaymentGateway: jest.fn(), + paymentGatewaySuggestions: paymentGatewaySuggestionsWithoutWCPay, + installedPaymentGateways: [], + } ) ); + + const { container } = render( + + ); + + const paymentTitleElements = container.querySelectorAll( + '.woocommerce-task-payment__title' + ); + + const paymentTitles = Array.from( paymentTitleElements ).map( + ( e ) => e.textContent + ); + expect( paymentTitles ).toEqual( [ 'Stripe', 'PayPal Payments', @@ -118,10 +154,6 @@ describe( 'PaymentGatewaySuggestions', () => { 'Cash on delivery', 'Direct bank transfer', ] ); - - expect( - container.getElementsByTagName( 'title' )[ 0 ].textContent - ).toBe( 'WooCommerce Payments' ); } ); test( 'should the payment gateway offline options at the bottom', () => { @@ -130,7 +162,7 @@ describe( 'PaymentGatewaySuggestions', () => { useSelect.mockImplementation( () => ( { isResolving: false, getPaymentGateway: jest.fn(), - paymentGatewaySuggestions, + paymentGatewaySuggestions: paymentGatewaySuggestionsWithoutWCPay, installedPaymentGateways: [], } ) ); @@ -156,7 +188,7 @@ describe( 'PaymentGatewaySuggestions', () => { useSelect.mockImplementation( () => ( { isResolving: false, getPaymentGateway: jest.fn(), - paymentGatewaySuggestions, + paymentGatewaySuggestions: paymentGatewaySuggestionsWithoutWCPay, installedPaymentGateways: [ { id: 'ppcp-gateway', @@ -186,7 +218,7 @@ describe( 'PaymentGatewaySuggestions', () => { useSelect.mockImplementation( () => ( { isResolving: false, getPaymentGateway: jest.fn(), - paymentGatewaySuggestions, + paymentGatewaySuggestions: paymentGatewaySuggestionsWithoutWCPay, installedPaymentGateways: [ { id: 'ppcp-gateway', @@ -213,4 +245,58 @@ describe( 'PaymentGatewaySuggestions', () => { selected: 'ppcp_gateway', } ); } ); + + test( 'should record event correctly when other payment methods is clicked', () => { + const onComplete = jest.fn(); + const query = {}; + useSelect.mockImplementation( () => ( { + isResolving: false, + getPaymentGateway: jest.fn(), + paymentGatewaySuggestions, + installedPaymentGateways: [], + } ) ); + + render( + + ); + + fireEvent.click( screen.getByText( 'Other payment methods' ) ); + + // By default it's hidden, so when toggle it shows. + expect( recordEvent ).toHaveBeenCalledWith( + 'tasklist_payment_show_toggle', + { + toggle: 'show', + } + ); + } ); + + test( 'should record event correctly when see more is clicked', () => { + const onComplete = jest.fn(); + const query = {}; + useSelect.mockImplementation( () => ( { + isResolving: false, + getPaymentGateway: jest.fn(), + paymentGatewaySuggestions, + installedPaymentGateways: [], + } ) ); + + render( + + ); + + fireEvent.click( screen.getByText( 'Other payment methods' ) ); + fireEvent.click( screen.getByText( 'See more' ) ); + + expect( recordEvent ).toHaveBeenCalledWith( + 'tasklist_payment_see_more', + {} + ); + } ); } ); From 1980fac6fd06f03a3074a3306fabe90014b1ef10 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Wed, 6 Apr 2022 12:25:18 +0800 Subject: [PATCH 067/386] Revamp WCPay suggestion in payments task --- .../src/components/WCPayCard/WCPayCard.js | 139 +++++++++---- .../src/components/WCPayCard/WCPayCard.scss | 51 ++++- .../onboarding/src/images/wcpay-benefit-1.js | 134 +++++++++++++ .../onboarding/src/images/wcpay-benefit-2.js | 102 ++++++++++ .../onboarding/src/images/wcpay-benefit-3.js | 188 ++++++++++++++++++ .../onboarding/src/images/wcpay-hero-image.js | 140 +++++++++++++ .../components/WCPay/Suggestion.js | 50 ++--- .../payment-gateway-suggestions.scss | 1 - 8 files changed, 735 insertions(+), 70 deletions(-) create mode 100644 packages/js/onboarding/src/images/wcpay-benefit-1.js create mode 100644 packages/js/onboarding/src/images/wcpay-benefit-2.js create mode 100644 packages/js/onboarding/src/images/wcpay-benefit-3.js create mode 100644 packages/js/onboarding/src/images/wcpay-hero-image.js diff --git a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js index 88a79a7ad1c..6f4fde45af7 100644 --- a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js +++ b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js @@ -1,63 +1,126 @@ /** * External dependencies */ -import { Card, CardBody, CardHeader, CardFooter } from '@wordpress/components'; +import { Card, CardBody, CardFooter } from '@wordpress/components'; import { Text } from '@woocommerce/experimental'; import { createElement } from '@wordpress/element'; import { Link } from '@woocommerce/components'; +import interpolateComponents from '@automattic/interpolate-components'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import { WCPayAcceptedMethods } from '../WCPayAcceptedMethods'; -import WCPayLogo from '../../images/wcpay-logo'; - -export const WCPayCardHeader = ( { - logoWidth = 196, - logoHeight = 41, - children, -} ) => ( - - - { children } - -); +import WCPayHeroImage from '../../images/wcpay-hero-image'; +import WCPayBenefit1 from '../../images/wcpay-benefit-1'; +import WCPayBenefit2 from '../../images/wcpay-benefit-2'; +import WCPayBenefit3 from '../../images/wcpay-benefit-3'; export const WCPayCardBody = ( { - description, heading, + children, onLinkClick = () => {}, } ) => ( - { heading && { heading } } - - - { description } -
- - { __( 'Learn more', 'woocommerce' ) } - -
- - +
+
+ { heading && ( + + { heading } + + ) } + + { interpolateComponents( { + mixedString: __( + 'By using WooCommerce Payments you agree to be bound by our {{tosLink /}} and acknowledge that you have read our {{privacyLink /}}', + 'woocommerce' + ), + components: { + tosLink: ( + + { __( 'Terms of Service', 'woocommerce' ) } + + ), + privacyLink: ( + + { __( 'Privacy Policy', 'woocommerce' ) } + + ), + }, + } ) } +
+
+
{ children }
+
+
+ +
+
); -export const WCPayCardFooter = ( { children } ) => ( - { children } +export const WCPayCardFooter = () => ( + + + ); export const WCPayCard = ( { children } ) => { - return { children }; + return ( + + { children } + + ); +}; + +export const WCPayBenefitCard = () => { + return ( + + +
+
+ + { __( + 'Offer your customers their preferred way to pay including debit and credit card payments, Apple Pay, Sofort, SEPA, iDeal and many more.', + 'woocommerce' + ) } +
+
+ + { __( + 'Sell to international markets and accept more than 135 currencies with local payment methods.', + 'woocommerce' + ) } +
+
+ + { __( + 'Earn and manage recurring revenue and get automatic deposits into your nominated bank account.', + 'woocommerce' + ) } +
+
+
+
+ ); }; diff --git a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss index c21e835ff68..21b1407c54c 100644 --- a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss +++ b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss @@ -1,6 +1,44 @@ .woocommerce-task-payments .woocommerce-task-payment-wcpay { + + .vstack, .hstack { + display: flex; + &.content-center { + justify-content: center; + } + + &.content-around { + justify-content: space-around; + } + + &.flex-1 { + flex: 1; + } + } + + .vstack { + flex-direction: column; + } + + .hstack { + flex-direction: row; + } + + .wcpay-hero-image { + margin-right: -32px; + margin-bottom: -28px; + } + + .woocommerce-task-payment-wcpay__heading { + letter-spacing: 0.6px; + max-width: 250px; + margin-right: $gap-small; + margin-bottom: $gap; + } + .woocommerce-task-payment-wcpay__description { - font-size: 16px; + font-size: 13px; + max-width: 325px; + margin-right: $gap-small; margin-bottom: $gap-large; } @@ -40,4 +78,15 @@ color: #40464d; } } + + .woocommerce-task-payment-wcpay__benefit { + svg { + margin: 0 auto; + } + max-width: 170px; + text-align: center; + font-size: 15px; + line-height: 24px; + letter-spacing: normal; + } } diff --git a/packages/js/onboarding/src/images/wcpay-benefit-1.js b/packages/js/onboarding/src/images/wcpay-benefit-1.js new file mode 100644 index 00000000000..f2e7ee9497b --- /dev/null +++ b/packages/js/onboarding/src/images/wcpay-benefit-1.js @@ -0,0 +1,134 @@ +/** + * External dependencies + */ +import { createElement } from '@wordpress/element'; + +export default ( { width = 141, height = 148, ...props } ) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/packages/js/onboarding/src/images/wcpay-benefit-2.js b/packages/js/onboarding/src/images/wcpay-benefit-2.js new file mode 100644 index 00000000000..3bcd83d6d99 --- /dev/null +++ b/packages/js/onboarding/src/images/wcpay-benefit-2.js @@ -0,0 +1,102 @@ +/** + * External dependencies + */ +import { createElement } from '@wordpress/element'; + +export default ( { width = 140, height = 148, ...props } ) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/packages/js/onboarding/src/images/wcpay-benefit-3.js b/packages/js/onboarding/src/images/wcpay-benefit-3.js new file mode 100644 index 00000000000..9d29102f3e8 --- /dev/null +++ b/packages/js/onboarding/src/images/wcpay-benefit-3.js @@ -0,0 +1,188 @@ +/** + * External dependencies + */ +import { createElement } from '@wordpress/element'; + +export default ( { width = 157, height = 148, ...props } ) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/packages/js/onboarding/src/images/wcpay-hero-image.js b/packages/js/onboarding/src/images/wcpay-hero-image.js new file mode 100644 index 00000000000..2d70f3d126d --- /dev/null +++ b/packages/js/onboarding/src/images/wcpay-hero-image.js @@ -0,0 +1,140 @@ +/** + * External dependencies + */ +import { createElement } from '@wordpress/element'; + +export default ( { width = 293, height = 275, ...props } ) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/WCPay/Suggestion.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/WCPay/Suggestion.js index ee4ad87a861..786ee03c58b 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/WCPay/Suggestion.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/WCPay/Suggestion.js @@ -3,15 +3,13 @@ */ import { __ } from '@wordpress/i18n'; import interpolateComponents from '@automattic/interpolate-components'; -import { Link, Pill } from '@woocommerce/components'; +import { Link } from '@woocommerce/components'; import { recordEvent } from '@woocommerce/tracks'; -import { Text } from '@woocommerce/experimental'; import { WCPayCard, - WCPayCardHeader, WCPayCardFooter, WCPayCardBody, - SetupRequired, + WCPayBenefitCard, } from '@woocommerce/onboarding'; import { useDispatch } from '@wordpress/data'; @@ -41,7 +39,6 @@ const TosPrompt = () => export const Suggestion = ( { paymentGateway, onSetupCallback = null } ) => { const { - description, id, needsSetup, installed, @@ -62,27 +59,14 @@ export const Suggestion = ( { paymentGateway, onSetupCallback = null } ) => { } return ( - - - { installed && needsSetup ? ( - - ) : ( - { __( 'Recommended', 'woocommerce' ) } - ) } - - - { - recordEvent( 'tasklist_payment_learn_more' ); - } } - /> - - - <> - - - + <> + + { + recordEvent( 'tasklist_payment_learn_more' ); + } } + > { isRecommended={ true } isInstalled={ isInstalled } hasPlugins={ true } - setupButtonText={ __( 'Get started', 'woocommerce' ) } + setupButtonText={ + installed && needsSetup + ? __( 'Finish setup', 'woocommerce' ) + : __( 'Install', 'woocommerce' ) + } onSetupCallback={ onSetupCallback } /> - - - + + + + + ); }; diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/payment-gateway-suggestions.scss b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/payment-gateway-suggestions.scss index 8e408b15e20..8298e321dbe 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/payment-gateway-suggestions.scss +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/payment-gateway-suggestions.scss @@ -22,7 +22,6 @@ margin: 0; } - .components-card__footer { a.components-button { .gridicon { From 1f8988675fc48fc3d5f9076996efd57d2b36657d Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Wed, 6 Apr 2022 13:43:08 +0800 Subject: [PATCH 068/386] Some adjustment to card footer --- .../js/onboarding/src/components/WCPayAcceptedMethods.js | 4 ++-- .../js/onboarding/src/components/WCPayCard/WCPayCard.scss | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/js/onboarding/src/components/WCPayAcceptedMethods.js b/packages/js/onboarding/src/components/WCPayAcceptedMethods.js index f104c24e4e9..7fc42131c2d 100644 --- a/packages/js/onboarding/src/components/WCPayAcceptedMethods.js +++ b/packages/js/onboarding/src/components/WCPayAcceptedMethods.js @@ -21,8 +21,8 @@ import UnionPay from '../images/cards/unionpay.js'; export const WCPayAcceptedMethods = () => ( <> - - { __( 'Accepted payment methods', 'woocommerce' ) } + + { __( 'Accepted payment methods include:', 'woocommerce' ) }
diff --git a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss index 21b1407c54c..fd950b5b4f7 100644 --- a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss +++ b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss @@ -60,6 +60,14 @@ margin-top: $gap; margin-left: 0; } + + &, h3 { + color: #757575; + } + + svg { + height: 24px; + } } .components-card__body { From 3050cacc39d941544055350a0a50b58b7be99586 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Wed, 6 Apr 2022 13:51:26 +0800 Subject: [PATCH 069/386] Minor adjustments --- packages/js/onboarding/src/images/wcpay-benefit-3.js | 2 +- .../client/tasks/fills/PaymentGatewaySuggestions/index.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/js/onboarding/src/images/wcpay-benefit-3.js b/packages/js/onboarding/src/images/wcpay-benefit-3.js index 9d29102f3e8..80e16205927 100644 --- a/packages/js/onboarding/src/images/wcpay-benefit-3.js +++ b/packages/js/onboarding/src/images/wcpay-benefit-3.js @@ -7,7 +7,7 @@ export default ( { width = 157, height = 148, ...props } ) => ( { heading={ __( 'Other payment methods', 'woocommerce' ) } onToggle={ trackToggle } > - { enabledSection } { additionalSection } + { enabledSection } { offlineSection } ) : ( <> - { enabledSection } { additionalSection } + { enabledSection } { offlineSection } ) } From ffdf1e6c02381c0446a5878a720da88d0723f07e Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Wed, 6 Apr 2022 16:25:08 +0800 Subject: [PATCH 070/386] More adjustments --- .../src/components/WCPayCard/WCPayCard.js | 4 ++-- .../onboarding/src/images/wcpay-benefit-1.js | 2 +- .../onboarding/src/images/wcpay-benefit-2.js | 2 +- .../onboarding/src/images/wcpay-benefit-3.js | 2 +- .../components/WCPay/Suggestion.js | 19 ------------------- 5 files changed, 5 insertions(+), 24 deletions(-) diff --git a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js index 6f4fde45af7..458fd0669c9 100644 --- a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js +++ b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js @@ -49,7 +49,7 @@ export const WCPayCardBody = ( { target="_blank" type="external" rel="noreferrer" - href="https://woocommerce.com/payments/?utm_medium=product" + href="https://wordpress.com/tos/" onClick={ onLinkClick } > { __( 'Terms of Service', 'woocommerce' ) } @@ -60,7 +60,7 @@ export const WCPayCardBody = ( { target="_blank" type="external" rel="noreferrer" - href="https://woocommerce.com/payments/?utm_medium=product" + href="https://automattic.com/privacy/" onClick={ onLinkClick } > { __( 'Privacy Policy', 'woocommerce' ) } diff --git a/packages/js/onboarding/src/images/wcpay-benefit-1.js b/packages/js/onboarding/src/images/wcpay-benefit-1.js index f2e7ee9497b..e0dc078c0f7 100644 --- a/packages/js/onboarding/src/images/wcpay-benefit-1.js +++ b/packages/js/onboarding/src/images/wcpay-benefit-1.js @@ -7,7 +7,7 @@ export default ( { width = 141, height = 148, ...props } ) => ( ( ( - interpolateComponents( { - mixedString: __( - 'Upon clicking "Get started", you agree to the {{link}}Terms of service{{/link}}. Next we’ll ask you to share a few details about your business to create your account.', - 'woocommerce' - ), - components: { - link: ( - - ), - }, - } ); - export const Suggestion = ( { paymentGateway, onSetupCallback = null } ) => { const { id, From 16c28e3599b24068dae85868ae465af14ce750be Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Wed, 6 Apr 2022 16:41:27 +0800 Subject: [PATCH 071/386] Fix css --- .../src/components/WCPayCard/WCPayCard.js | 5 +- .../src/components/WCPayCard/WCPayCard.scss | 187 +++++++++--------- 2 files changed, 101 insertions(+), 91 deletions(-) diff --git a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js index 458fd0669c9..a865588b22f 100644 --- a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js +++ b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js @@ -95,7 +95,10 @@ export const WCPayCard = ( { children } ) => { export const WCPayBenefitCard = () => { return ( - +
diff --git a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss index fd950b5b4f7..9d375486176 100644 --- a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss +++ b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss @@ -1,100 +1,107 @@ -.woocommerce-task-payments .woocommerce-task-payment-wcpay { +.woocommerce-task-payments { + .woocommerce-task-payment-wcpay { + .vstack, + .hstack { + display: flex; + &.content-center { + justify-content: center; + } - .vstack, .hstack { - display: flex; - &.content-center { - justify-content: center; + &.content-around { + justify-content: space-around; + } + + &.flex-1 { + flex: 1; + } } - &.content-around { - justify-content: space-around; + .vstack { + flex-direction: column; } - &.flex-1 { - flex: 1; + .hstack { + flex-direction: row; + } + + .wcpay-hero-image { + margin-right: -32px; + margin-bottom: -28px; + } + + .woocommerce-task-payment-wcpay__heading { + letter-spacing: 0.6px; + max-width: 250px; + margin-right: $gap-small; + margin-bottom: $gap; + } + + .woocommerce-task-payment-wcpay__description { + font-size: 13px; + max-width: 325px; + margin-right: $gap-small; + margin-bottom: $gap-large; + } + + .components-card__header { + margin-bottom: $gap-small; + justify-content: flex-start; + padding: 25px; + + .woocommerce-pill { + margin-left: $gap-small; + } + } + + .components-card__footer { + flex-direction: column; + align-items: flex-start; + + .components-button { + margin-top: $gap; + margin-left: 0; + } + + &, + h3 { + color: #757575; + } + + svg { + height: 24px; + } + } + + .components-card__body { + h2 { + margin: 0 0 20px 0; + } + } + + .woocommerce-task-payment-wcpay__accepted { + display: flex; + margin-top: $gap-small; + flex-wrap: wrap; + gap: $gap-small; + + h3 { + color: #40464d; + } + } + + .woocommerce-task-payment-wcpay__benefit { + svg { + margin: 0 auto; + } + max-width: 170px; + text-align: center; + font-size: 15px; + line-height: 24px; + letter-spacing: normal; } } - .vstack { - flex-direction: column; - } - - .hstack { - flex-direction: row; - } - - .wcpay-hero-image { - margin-right: -32px; - margin-bottom: -28px; - } - - .woocommerce-task-payment-wcpay__heading { - letter-spacing: 0.6px; - max-width: 250px; - margin-right: $gap-small; - margin-bottom: $gap; - } - - .woocommerce-task-payment-wcpay__description { - font-size: 13px; - max-width: 325px; - margin-right: $gap-small; - margin-bottom: $gap-large; - } - - .components-card__header { - margin-bottom: $gap-small; - justify-content: flex-start; - padding: 25px; - - .woocommerce-pill { - margin-left: $gap-small; - } - } - - .components-card__footer { - flex-direction: column; - align-items: flex-start; - - .components-button { - margin-top: $gap; - margin-left: 0; - } - - &, h3 { - color: #757575; - } - - svg { - height: 24px; - } - } - - .components-card__body { - h2 { - margin: 0 0 20px 0; - } - } - - .woocommerce-task-payment-wcpay__accepted { - display: flex; - margin-top: $gap-small; - flex-wrap: wrap; - gap: $gap-small; - - h3 { - color: #40464d; - } - } - - .woocommerce-task-payment-wcpay__benefit { - svg { - margin: 0 auto; - } - max-width: 170px; - text-align: center; - font-size: 15px; - line-height: 24px; - letter-spacing: normal; + .woocommerce-task-payment-wcpay__benefits-card { + margin-bottom: 0; } } From ae44908f33e983b37314423f66f19532441eca4d Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 30 Mar 2022 15:41:59 +0800 Subject: [PATCH 072/386] Move .husky to root path Add .husky --- .../woocommerce-admin/.husky/pre-push => .husky/post-merge | 2 +- .husky/post-push | 4 ++++ {plugins/woocommerce-admin/.husky => .husky}/pre-commit | 0 {plugins/woocommerce/bin => bin}/post-merge.sh | 0 {plugins/woocommerce/bin => bin}/pre-push.sh | 0 5 files changed, 5 insertions(+), 1 deletion(-) rename plugins/woocommerce-admin/.husky/pre-push => .husky/post-merge (61%) create mode 100755 .husky/post-push rename {plugins/woocommerce-admin/.husky => .husky}/pre-commit (100%) rename {plugins/woocommerce/bin => bin}/post-merge.sh (100%) rename {plugins/woocommerce/bin => bin}/pre-push.sh (100%) diff --git a/plugins/woocommerce-admin/.husky/pre-push b/.husky/post-merge similarity index 61% rename from plugins/woocommerce-admin/.husky/pre-push rename to .husky/post-merge index db541032602..0b5aa72c9d6 100755 --- a/plugins/woocommerce-admin/.husky/pre-push +++ b/.husky/post-merge @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -node bin/pre-push-hook.js +./bin/post-merge.sh diff --git a/.husky/post-push b/.husky/post-push new file mode 100755 index 00000000000..a8877f5c054 --- /dev/null +++ b/.husky/post-push @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +./bin/post-push.sh diff --git a/plugins/woocommerce-admin/.husky/pre-commit b/.husky/pre-commit similarity index 100% rename from plugins/woocommerce-admin/.husky/pre-commit rename to .husky/pre-commit diff --git a/plugins/woocommerce/bin/post-merge.sh b/bin/post-merge.sh similarity index 100% rename from plugins/woocommerce/bin/post-merge.sh rename to bin/post-merge.sh diff --git a/plugins/woocommerce/bin/pre-push.sh b/bin/pre-push.sh similarity index 100% rename from plugins/woocommerce/bin/pre-push.sh rename to bin/pre-push.sh From 7c4c98c91d4025aacaac85d7ea06c4b833dad07e Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 30 Mar 2022 15:44:51 +0800 Subject: [PATCH 073/386] Fix bin/post-merge.sh & bin/pre-push.sh --- .husky/{post-push => pre-push} | 2 +- bin/post-merge.sh | 4 +++- bin/pre-push.sh | 39 ++++++++++++++++------------------ 3 files changed, 22 insertions(+), 23 deletions(-) rename .husky/{post-push => pre-push} (68%) diff --git a/.husky/post-push b/.husky/pre-push similarity index 68% rename from .husky/post-push rename to .husky/pre-push index a8877f5c054..e52fd7c5635 100755 --- a/.husky/post-push +++ b/.husky/pre-push @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -./bin/post-push.sh +./bin/pre-push.sh diff --git a/bin/post-merge.sh b/bin/post-merge.sh index b7d9ac1bab4..ea9143d568d 100755 --- a/bin/post-merge.sh +++ b/bin/post-merge.sh @@ -9,5 +9,7 @@ runOnChange() { fi } -runOnChange "package-lock.json" "pnpm install" +runOnChange "pnpm-lock.yaml" "pnpm install" runOnChange "composer.lock" "SKIP_UPDATE_TEXTDOMAINS=true composer install" +runOnChange "plugins/woocommerce/composer.lock" "SKIP_UPDATE_TEXTDOMAINS=true composer --working-dir=plugins/woocommerce install" +runOnChange "plugins/woocommerce-beta-tester/composer.lock" "SKIP_UPDATE_TEXTDOMAINS=true composer --working-dir=plugins/woocommerce-beta-tester install" diff --git a/bin/pre-push.sh b/bin/pre-push.sh index 8dc3a6e0f9b..9b2346b136f 100755 --- a/bin/pre-push.sh +++ b/bin/pre-push.sh @@ -1,27 +1,24 @@ #!/bin/sh PROTECTED_BRANCH="trunk" -REMOTE_REF=$(echo "$HUSKY_GIT_STDIN" | cut -d " " -f 3) - -if [ -n "$REMOTE_REF" ]; then - if [ "refs/heads/${PROTECTED_BRANCH}" = "$REMOTE_REF" ]; then - if [ "$TERM" = "dumb" ]; then - >&2 echo "Sorry, you are unable to push to trunk using a GUI client! Please use git CLI." - exit 1 - fi - - printf "%sYou're about to push to trunk, is that what you intended? [y/N]: %s" "$(tput setaf 3)" "$(tput sgr0)" - read -r PROCEED < /dev/tty - echo - - if [ "$(echo "${PROCEED:-n}" | tr "[:upper:]" "[:lower:]")" = "y" ]; then - echo "$(tput setaf 2)Brace yourself! Pushing to the trunk branch...$(tput sgr0)" - echo - exit 0 - fi - - echo "$(tput setaf 2)Push to trunk cancelled!$(tput sgr0)" - echo +CURRENT_BRANCH=$(git branch --show-current) +if [ $PROTECTED_BRANCH = $CURRENT_BRANCH ]; then + if [ "$TERM" = "dumb" ]; then + >&2 echo "Sorry, you are unable to push to $PROTECTED_BRANCH using a GUI client! Please use git CLI." exit 1 fi + + printf "%sYou're about to push to $PROTECTED_BRANCH, is that what you intended? [y/N]: %s" "$(tput setaf 3)" "$(tput sgr0)" + read -r PROCEED < /dev/tty + echo + + if [ "$(echo "${PROCEED:-n}" | tr "[:upper:]" "[:lower:]")" = "y" ]; then + echo "$(tput setaf 2)Brace yourself! Pushing to the $PROTECTED_BRANCH branch...$(tput sgr0)" + echo + exit 0 + fi + + echo "$(tput setaf 2)Push to $PROTECTED_BRANCH cancelled!$(tput sgr0)" + echo + exit 1 fi From a4ece1b7b9c71684379229140d4419be5456d68f Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 30 Mar 2022 15:47:27 +0800 Subject: [PATCH 074/386] Remove husky lint-staged from plugins/* --- plugins/woocommerce-admin/package.json | 2 -- plugins/woocommerce-beta-tester/package.json | 22 -------------- plugins/woocommerce/package.json | 30 +------------------- 3 files changed, 1 insertion(+), 53 deletions(-) diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 50d499eea0c..374bbccfffd 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -241,11 +241,9 @@ "grunt": "^1.4.1", "grunt-checktextdomain": "^1.0.1", "grunt-wp-i18n": "^1.0.3", - "husky": "^7.0.0", "jest": "^27.5.1", "jest-environment-jsdom": "~27.5.0", "jest-environment-node": "^27.5.1", - "lint-staged": "^12.3.5", "md5": "^2.3.0", "merge-config": "^2.0.0", "mini-css-extract-plugin": "^2.6.0", diff --git a/plugins/woocommerce-beta-tester/package.json b/plugins/woocommerce-beta-tester/package.json index b2bf1ba26da..2c4c3b89233 100644 --- a/plugins/woocommerce-beta-tester/package.json +++ b/plugins/woocommerce-beta-tester/package.json @@ -11,8 +11,6 @@ "homepage": "http://github.com/woocommerce/woocommerce-beta-tester", "devDependencies": { "eslint": "5.16.0", - "husky": "1.3.1", - "lint-staged": "8.1.5", "uglify-js": "^3.5.3" }, "assets": { @@ -32,26 +30,6 @@ "node": ">=10.15.0", "npm": ">=6.4.1" }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, - "lint-staged": { - "linters": { - "*.php": [ - "php -d display_errors=1 -l", - "composer run-script phpcs-pre-commit" - ], - "*.js": [ - "eslint --fix", - "git add" - ] - }, - "ignore": [ - "*.min.js" - ] - }, "woorelease": { "svn_reauth": "true", "wp_org_slug": "woocommerce-beta-tester" diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index 1f20ec694d6..c6a827e7ef9 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -27,8 +27,7 @@ "test:e2e-dev": "pnpx wc-e2e test:e2e-dev", "test:unit": "./vendor/bin/phpunit -c ./phpunit.xml", "makepot": "composer run-script makepot", - "packages:fix:textdomain": "node ./bin/package-update-textdomain.js", - "git:update-hooks": "rm -r .git/hooks && mkdir -p .git/hooks && node ./node_modules/husky/husky.js install" + "packages:fix:textdomain": "node ./bin/package-update-textdomain.js" }, "devDependencies": { "@babel/cli": "7.12.8", @@ -60,10 +59,8 @@ "eslint-config-wpcalypso": "5.0.0", "eslint-plugin-jest": "23.20.0", "github-contributors-list": "https://github.com/woocommerce/github-contributors-list/tarball/master", - "husky": "4.3.0", "istanbul": "1.0.0-alpha.2", "jest": "^25.1.0", - "lint-staged": "9.5.0", "mocha": "7.2.0", "prettier": "npm:wp-prettier@2.0.5", "stylelint": "^13.8.0", @@ -76,31 +73,6 @@ "node": "^16.13.1", "pnpm": "^6.24.2" }, - "husky": { - "hooks": { - "post-merge": "./bin/post-merge.sh", - "pre-commit": "lint-staged", - "pre-push": "./bin/pre-push.sh" - } - }, - "lint-staged": { - "*.php": [ - "php -d display_errors=1 -l", - "composer run-script phpcs-pre-commit" - ], - "*.scss": [ - "stylelint --syntax=scss --fix", - "git add" - ], - "*.js": [ - "eslint --fix", - "git add" - ], - "*.ts": [ - "eslint --fix", - "git add" - ] - }, "browserslist": [ "> 0.1%", "ie 8", From 14a2f9a8d89e92a79bcdbd6f73d4f43937ea4531 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 30 Mar 2022 15:58:25 +0800 Subject: [PATCH 075/386] Remove git:update-hooks from woocommerce/project.json --- plugins/woocommerce/project.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/plugins/woocommerce/project.json b/plugins/woocommerce/project.json index 9fbc742dca1..79c76fb2462 100644 --- a/plugins/woocommerce/project.json +++ b/plugins/woocommerce/project.json @@ -142,12 +142,6 @@ "script": "packages:fix:textdomain" } }, - "git-update-hooks": { - "executor": "@nrwl/workspace:run-script", - "options": { - "script": "git:update-hooks" - } - }, "make-collection": { "executor": "@nrwl/workspace:run-script", "options": { From f2dcc1f705b16d43b34a3f5d9b7dce302a2d576c Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 30 Mar 2022 16:32:38 +0800 Subject: [PATCH 076/386] Add git:update-hooks to postinstall --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f7f9789ea7..4b93a796730 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "url": "https://github.com/woocommerce/woocommerce/issues" }, "scripts": { - "preinstall": "npx only-allow pnpm" + "preinstall": "npx only-allow pnpm", + "postinstall": "pnpm git:update-hooks", + "git:update-hooks": "rm -r .git/hooks && mkdir -p .git/hooks && husky install" }, "devDependencies": { "@automattic/nx-composer": "^0.1.0", From 7e146cf35382ce8a4f3ddbe9827145baecf37cb9 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 5 Apr 2022 11:57:07 +0800 Subject: [PATCH 077/386] Remove post-merge.sh --- bin/post-merge.sh | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100755 bin/post-merge.sh diff --git a/bin/post-merge.sh b/bin/post-merge.sh deleted file mode 100755 index ea9143d568d..00000000000 --- a/bin/post-merge.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -changedFiles="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)" - -runOnChange() { - if echo "$changedFiles" | grep -q "$1" - then - eval "$2" - fi -} - -runOnChange "pnpm-lock.yaml" "pnpm install" -runOnChange "composer.lock" "SKIP_UPDATE_TEXTDOMAINS=true composer install" -runOnChange "plugins/woocommerce/composer.lock" "SKIP_UPDATE_TEXTDOMAINS=true composer --working-dir=plugins/woocommerce install" -runOnChange "plugins/woocommerce-beta-tester/composer.lock" "SKIP_UPDATE_TEXTDOMAINS=true composer --working-dir=plugins/woocommerce-beta-tester install" From 01edee8d90f494f90e5c18f607adc54eaf09b20f Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 30 Mar 2022 16:39:32 +0800 Subject: [PATCH 078/386] Remove lint-staged.config.js --- .../woocommerce-admin/lint-staged.config.js | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 plugins/woocommerce-admin/lint-staged.config.js diff --git a/plugins/woocommerce-admin/lint-staged.config.js b/plugins/woocommerce-admin/lint-staged.config.js deleted file mode 100644 index 0dc6e3b021a..00000000000 --- a/plugins/woocommerce-admin/lint-staged.config.js +++ /dev/null @@ -1,47 +0,0 @@ -module.exports = { - '*.scss': [ 'pnpm run lint:css-fix' ], - 'client/**/*.(t|j)s?(x)': [ - 'wp-scripts format-js', - 'wp-scripts lint-js', - 'pnpm run test-staged', - ], - 'packages/**/*.(t|j)s?(x)': ( packageFiles ) => { - const globalScripts = [ - `wp-scripts format-js ${ packageFiles.join( ' ' ) }`, - `wp-scripts lint-js ${ packageFiles.join( ' ' ) }`, - ]; - - const filesByPackage = packageFiles.reduce( - ( packages, packageFile ) => { - const packageNameMatch = packageFile.match( - /\/packages\/([a-z0-9\-]+)\// - ); - - if ( ! packageNameMatch ) { - return packages; - } - - const packageName = packageNameMatch[ 1 ]; - - if ( Array.isArray( packages[ packageName ] ) ) { - packages[ packageName ].push( packageFile ); - } else { - packages[ packageName ] = [ packageFile ]; - } - - return packages; - }, - {} - ); - - const workspaceScripts = Object.keys( filesByPackage ).map( - ( packageName ) => - `pnpm --filter @woocommerce/${ packageName } run test-staged -- ${ filesByPackage[ - packageName - ].join( ' ' ) }` - ); - - return globalScripts.concat( workspaceScripts ); - }, - '*.php': [ 'php -d display_errors=1 -l', 'composer run-script phpcs' ], -}; From d7df8ff8ab68f4c0d7e1fb804246990f955782f8 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 5 Apr 2022 11:30:52 +0800 Subject: [PATCH 079/386] Set up husky Update pnpm-lock.yaml --- .husky/post-merge | 3 +- .husky/pre-commit | 2 +- package.json | 1 + pnpm-lock.yaml | 617 +--------------------------------------------- 4 files changed, 16 insertions(+), 607 deletions(-) diff --git a/.husky/post-merge b/.husky/post-merge index 0b5aa72c9d6..48bc4affda4 100755 --- a/.husky/post-merge +++ b/.husky/post-merge @@ -1,4 +1,5 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -./bin/post-merge.sh +pnpm install +pnpm nx affected --target="composer-install" --base=ORIG_HEAD --head=HEAD diff --git a/.husky/pre-commit b/.husky/pre-commit index 5e592735698..aa52ea8822c 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -pnpm exec lint-staged +pnpm nx affected:lint --uncommitted diff --git a/package.json b/package.json index 4b93a796730..277da8b5ba9 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@wordpress/prettier-config": "^1.1.1", "chalk": "^4.1.2", "glob": "^7.2.0", + "husky": "^7.0.4", "jest": "^27.3.1", "mkdirp": "^1.0.4", "node-stream-zip": "^1.15.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3554369126f..0e2932d41c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,7 @@ importers: '@wordpress/prettier-config': ^1.1.1 chalk: ^4.1.2 glob: ^7.2.0 + husky: ^7.0.4 jest: ^27.3.1 lodash: ^4.17.21 mkdirp: ^1.0.4 @@ -48,6 +49,7 @@ importers: '@wordpress/prettier-config': 1.1.1 chalk: 4.1.2 glob: 7.2.0 + husky: 7.0.4 jest: 27.3.1 mkdirp: 1.0.4 node-stream-zip: 1.15.0 @@ -889,9 +891,9 @@ importers: qs: 6.10.3 devDependencies: '@babel/core': 7.17.8 + '@babel/runtime': 7.17.7 '@wordpress/eslint-plugin': 11.0.1_7c040a9b494a33cf8bf9079642892fb1 eslint: 8.12.0 - '@babel/runtime': 7.17.7 jest: 27.5.1 jest-cli: 27.5.1 rimraf: 3.0.2 @@ -928,9 +930,9 @@ importers: packages/js/number: specifiers: '@babel/core': ^7.17.5 + '@babel/runtime': ^7.17.2 '@wordpress/eslint-plugin': ^11.0.0 eslint: ^8.12.0 - '@babel/runtime': ^7.17.2 jest: ^27.5.1 jest-cli: ^27.5.1 locutus: ^2.0.16 @@ -941,9 +943,9 @@ importers: locutus: 2.0.16 devDependencies: '@babel/core': 7.17.8 + '@babel/runtime': 7.17.7 '@wordpress/eslint-plugin': 11.0.1_7c040a9b494a33cf8bf9079642892fb1 eslint: 8.12.0 - '@babel/runtime': 7.17.7 jest: 27.5.1 jest-cli: 27.5.1 rimraf: 3.0.2 @@ -1098,10 +1100,8 @@ importers: eslint-config-wpcalypso: 5.0.0 eslint-plugin-jest: 23.20.0 github-contributors-list: https://github.com/woocommerce/github-contributors-list/tarball/master - husky: 4.3.0 istanbul: 1.0.0-alpha.2 jest: ^25.1.0 - lint-staged: 9.5.0 mocha: 7.2.0 prettier: npm:wp-prettier@2.0.5 stylelint: ^13.8.0 @@ -1139,10 +1139,8 @@ importers: eslint-config-wpcalypso: 5.0.0_eslint@6.8.0 eslint-plugin-jest: 23.20.0_eslint@6.8.0+typescript@3.9.7 github-contributors-list: '@github.com/woocommerce/github-contributors-list/tarball/master' - husky: 4.3.0 istanbul: 1.0.0-alpha.2 jest: 25.5.4 - lint-staged: 9.5.0 mocha: 7.2.0 prettier: /wp-prettier/2.0.5 stylelint: 13.13.1 @@ -1292,11 +1290,9 @@ importers: grunt-checktextdomain: ^1.0.1 grunt-wp-i18n: ^1.0.3 history: ^4.10.1 - husky: ^7.0.0 jest: ^27.5.1 jest-environment-jsdom: ~27.5.0 jest-environment-node: ^27.5.1 - lint-staged: ^12.3.5 lodash: ^4.17.21 md5: ^2.3.0 memize: ^1.1.0 @@ -1492,11 +1488,9 @@ importers: grunt: 1.4.1 grunt-checktextdomain: 1.0.1_grunt@1.4.1 grunt-wp-i18n: 1.0.3 - husky: 7.0.4 jest: 27.5.1 jest-environment-jsdom: 27.5.1 jest-environment-node: 27.5.1 - lint-staged: 12.3.7 md5: 2.3.0 merge-config: 2.0.0 mini-css-extract-plugin: 2.6.0_webpack@5.70.0 @@ -1533,13 +1527,9 @@ importers: plugins/woocommerce-beta-tester: specifiers: eslint: 5.16.0 - husky: 1.3.1 - lint-staged: 8.1.5 uglify-js: ^3.5.3 devDependencies: eslint: 5.16.0 - husky: 1.3.1 - lint-staged: 8.1.5 uglify-js: 3.14.5 plugins/woocommerce/legacy: @@ -3812,7 +3802,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.0 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 dev: true /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.16.12: @@ -3821,7 +3811,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.12 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.17.8: resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} @@ -3829,7 +3819,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.8 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.12.9: resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} @@ -3944,7 +3934,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.0 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 dev: true /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.16.12: @@ -3953,7 +3943,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.12 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.17.8: resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} @@ -3961,7 +3951,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.8 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.16.0: resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} @@ -6198,12 +6188,6 @@ packages: core-js-pure: 3.19.1 regenerator-runtime: 0.13.9 - /@babel/runtime/7.0.0: - resolution: {integrity: sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==} - dependencies: - regenerator-runtime: 0.12.1 - dev: true - /@babel/runtime/7.15.4: resolution: {integrity: sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==} engines: {node: '>=6.9.0'} @@ -8808,22 +8792,6 @@ packages: lodash.merge: 4.6.2 postcss: 5.2.18 - /@samverschueren/stream-to-observable/0.3.1_rxjs@6.6.7: - resolution: {integrity: sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==} - engines: {node: '>=6'} - peerDependencies: - rxjs: '*' - zen-observable: '*' - peerDependenciesMeta: - rxjs: - optional: true - zen-observable: - optional: true - dependencies: - any-observable: 0.3.0 - rxjs: 6.6.7 - dev: true - /@sindresorhus/is/0.14.0: resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} engines: {node: '>=6'} @@ -15669,11 +15637,6 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - /ansi-styles/6.1.0: - resolution: {integrity: sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==} - engines: {node: '>=12'} - dev: true - /ansi-to-html/0.6.15: resolution: {integrity: sha512-28ijx2aHJGdzbs+O5SNQF65r6rrKYnkuwTYm8lZlChuoJ9P1vVzIpWO20sQTqTPDXYp6NFwk326vApTtLVFXpQ==} engines: {node: '>=8.0.0'} @@ -15690,11 +15653,6 @@ packages: engines: {node: '>=12.13'} dev: false - /any-observable/0.3.0: - resolution: {integrity: sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==} - engines: {node: '>=6'} - dev: true - /anymatch/2.0.0: resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==} dependencies: @@ -17979,14 +17937,6 @@ packages: colors: 1.4.0 dev: true - /cli-truncate/0.2.1: - resolution: {integrity: sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=} - engines: {node: '>=0.10.0'} - dependencies: - slice-ansi: 0.0.4 - string-width: 1.0.2 - dev: true - /cli-truncate/2.1.0: resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} engines: {node: '>=8'} @@ -17995,14 +17945,6 @@ packages: string-width: 4.2.3 dev: true - /cli-truncate/3.1.0: - resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - slice-ansi: 5.0.0 - string-width: 5.1.2 - dev: true - /cli-width/2.2.1: resolution: {integrity: sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==} dev: true @@ -18318,10 +18260,6 @@ packages: resolution: {integrity: sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=} dev: true - /compare-versions/3.6.0: - resolution: {integrity: sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==} - dev: true - /component-emitter/1.3.0: resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} @@ -19306,10 +19244,6 @@ packages: whatwg-url: 8.7.0 dev: true - /date-fns/1.30.1: - resolution: {integrity: sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==} - dev: true - /date-fns/2.28.0: resolution: {integrity: sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==} engines: {node: '>=0.11'} @@ -19388,19 +19322,6 @@ packages: ms: 2.1.2 supports-color: 8.1.1 - /debug/4.3.3_supports-color@9.2.1: - resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - supports-color: 9.2.1 - dev: true - /debuglog/1.0.1: resolution: {integrity: sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=} dev: true @@ -19544,18 +19465,6 @@ packages: is-descriptor: 1.0.2 isobject: 3.0.1 - /del/3.0.0: - resolution: {integrity: sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=} - engines: {node: '>=4'} - dependencies: - globby: 6.1.0 - is-path-cwd: 1.0.0 - is-path-in-cwd: 1.0.1 - p-map: 1.2.0 - pify: 3.0.0 - rimraf: 2.7.1 - dev: true - /del/4.1.1: resolution: {integrity: sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==} engines: {node: '>=6'} @@ -19569,20 +19478,6 @@ packages: rimraf: 2.7.1 dev: true - /del/5.1.0: - resolution: {integrity: sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==} - engines: {node: '>=8'} - dependencies: - globby: 10.0.1 - graceful-fs: 4.2.9 - is-glob: 4.0.3 - is-path-cwd: 2.2.0 - is-path-inside: 3.0.3 - p-map: 3.0.0 - rimraf: 3.0.2 - slash: 3.0.0 - dev: true - /del/6.0.0: resolution: {integrity: sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==} engines: {node: '>=10'} @@ -20015,10 +19910,6 @@ packages: stream-shift: 1.0.1 dev: true - /eastasianwidth/0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true - /ecc-jsbn/0.1.2: resolution: {integrity: sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=} dependencies: @@ -20062,11 +19953,6 @@ packages: /electron-to-chromium/1.4.88: resolution: {integrity: sha512-oA7mzccefkvTNi9u7DXmT0LqvhnOiN2BhSrKerta7HeUC1cLoIwtbf2wL+Ah2ozh5KQd3/1njrGrwDBXx6d14Q==} - /elegant-spinner/1.0.1: - resolution: {integrity: sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=} - engines: {node: '>=0.10.0'} - dev: true - /element-resize-detector/1.2.4: resolution: {integrity: sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg==} dependencies: @@ -21989,21 +21875,6 @@ packages: signal-exit: 3.0.7 strip-eof: 1.0.0 - /execa/2.1.0: - resolution: {integrity: sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==} - engines: {node: ^8.12.0 || >=9.7.0} - dependencies: - cross-spawn: 7.0.3 - get-stream: 5.2.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 3.1.0 - onetime: 5.1.2 - p-finally: 2.0.1 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - dev: true - /execa/3.4.0: resolution: {integrity: sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==} engines: {node: ^8.12.0 || >=9.7.0} @@ -22576,13 +22447,6 @@ packages: path-exists: 4.0.0 dev: true - /find-versions/3.2.0: - resolution: {integrity: sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==} - engines: {node: '>=6'} - dependencies: - semver-regex: 2.0.0 - dev: true - /find-yarn-workspace-root/2.0.0: resolution: {integrity: sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==} dependencies: @@ -22717,11 +22581,6 @@ packages: readable-stream: 2.3.7 dev: true - /fn-name/2.0.1: - resolution: {integrity: sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=} - engines: {node: '>=0.10.0'} - dev: true - /follow-redirects/1.14.5: resolution: {integrity: sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==} engines: {node: '>=4.0'} @@ -23129,17 +22988,6 @@ packages: engines: {node: '>=6'} dev: true - /g-status/2.0.2: - resolution: {integrity: sha512-kQoE9qH+T1AHKgSSD0Hkv98bobE90ILQcXAF4wvGgsr7uFqNvwmh8j+Lq3l0RVt3E3HjSbv2B9biEGcEtpHLCA==} - engines: {node: '>=6'} - dependencies: - arrify: 1.0.1 - matcher: 1.1.1 - simple-git: 1.132.0 - transitivePeerDependencies: - - supports-color - dev: true - /gauge/3.0.2: resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} engines: {node: '>=10'} @@ -23206,10 +23054,6 @@ packages: has: 1.0.3 has-symbols: 1.0.2 - /get-own-enumerable-property-symbols/3.0.2: - resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} - dev: true - /get-package-type/0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -24616,42 +24460,6 @@ packages: ms: 2.1.3 dev: true - /husky/1.3.1: - resolution: {integrity: sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg==} - engines: {node: '>=6'} - hasBin: true - requiresBuild: true - dependencies: - cosmiconfig: 5.2.1 - execa: 1.0.0 - find-up: 3.0.0 - get-stdin: 6.0.0 - is-ci: 2.0.0 - pkg-dir: 3.0.0 - please-upgrade-node: 3.2.0 - read-pkg: 4.0.1 - run-node: 1.0.0 - slash: 2.0.0 - dev: true - - /husky/4.3.0: - resolution: {integrity: sha512-tTMeLCLqSBqnflBZnlVDhpaIMucSGaYyX6855jM4AguGeWCeSzNdb1mfyWduTZ3pe3SJVvVWGL0jO1iKZVPfTA==} - engines: {node: '>=10'} - hasBin: true - requiresBuild: true - dependencies: - chalk: 4.1.2 - ci-info: 2.0.0 - compare-versions: 3.6.0 - cosmiconfig: 7.0.1 - find-versions: 3.2.0 - opencollective-postinstall: 2.0.3 - pkg-dir: 4.2.0 - please-upgrade-node: 3.2.0 - slash: 3.0.0 - which-pm-runs: 1.0.0 - dev: true - /husky/7.0.4: resolution: {integrity: sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==} engines: {node: '>=12'} @@ -24839,11 +24647,6 @@ packages: resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=} engines: {node: '>=0.8.19'} - /indent-string/3.2.0: - resolution: {integrity: sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=} - engines: {node: '>=4'} - dev: true - /indent-string/4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} @@ -25285,11 +25088,6 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - /is-fullwidth-code-point/4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} - dev: true - /is-function/1.0.2: resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==} dev: true @@ -25404,30 +25202,11 @@ packages: resolution: {integrity: sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==} dev: true - /is-observable/1.1.0: - resolution: {integrity: sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==} - engines: {node: '>=4'} - dependencies: - symbol-observable: 1.2.0 - dev: true - - /is-path-cwd/1.0.0: - resolution: {integrity: sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=} - engines: {node: '>=0.10.0'} - dev: true - /is-path-cwd/2.2.0: resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} engines: {node: '>=6'} dev: true - /is-path-in-cwd/1.0.1: - resolution: {integrity: sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==} - engines: {node: '>=0.10.0'} - dependencies: - is-path-inside: 1.0.1 - dev: true - /is-path-in-cwd/2.1.0: resolution: {integrity: sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==} engines: {node: '>=6'} @@ -25488,10 +25267,6 @@ packages: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} dev: true - /is-promise/2.2.2: - resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} - dev: true - /is-promise/4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} dev: false @@ -25514,11 +25289,6 @@ packages: call-bind: 1.0.2 has-tostringtag: 1.0.0 - /is-regexp/1.0.0: - resolution: {integrity: sha1-/S2INUXEa6xaYz57mgnof6LLUGk=} - engines: {node: '>=0.10.0'} - dev: true - /is-regexp/2.1.0: resolution: {integrity: sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==} engines: {node: '>=6'} @@ -28455,159 +28225,11 @@ packages: uc.micro: 1.0.6 dev: true - /lint-staged/12.3.7: - resolution: {integrity: sha512-/S4D726e2GIsDVWIk1XGvheCaDm1SJRQp8efamZFWJxQMVEbOwSysp7xb49Oo73KYCdy97mIWinhlxcoNqIfIQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - dependencies: - cli-truncate: 3.1.0 - colorette: 2.0.16 - commander: 8.3.0 - debug: 4.3.3_supports-color@9.2.1 - execa: 5.1.1 - lilconfig: 2.0.4 - listr2: 4.0.5 - micromatch: 4.0.4 - normalize-path: 3.0.0 - object-inspect: 1.12.0 - pidtree: 0.5.0 - string-argv: 0.3.1 - supports-color: 9.2.1 - yaml: 1.10.2 - transitivePeerDependencies: - - enquirer - dev: true - - /lint-staged/8.1.5: - resolution: {integrity: sha512-e5ZavfnSLcBJE1BTzRTqw6ly8OkqVyO3GL2M6teSmTBYQ/2BuueD5GIt2RPsP31u/vjKdexUyDCxSyK75q4BDA==} - hasBin: true - dependencies: - chalk: 2.4.2 - commander: 2.20.3 - cosmiconfig: 5.2.1 - debug: 3.2.7 - dedent: 0.7.0 - del: 3.0.0 - execa: 1.0.0 - find-parent-dir: 0.3.1 - g-status: 2.0.2 - is-glob: 4.0.3 - is-windows: 1.0.2 - listr: 0.14.3 - listr-update-renderer: 0.5.0_listr@0.14.3 - lodash: 4.17.21 - log-symbols: 2.2.0 - micromatch: 3.1.10 - npm-which: 3.0.1 - p-map: 1.2.0 - path-is-inside: 1.0.2 - pify: 3.0.0 - please-upgrade-node: 3.2.0 - staged-git-files: 1.1.2 - string-argv: 0.0.2 - stringify-object: 3.3.0 - yup: 0.26.10 - transitivePeerDependencies: - - supports-color - - zen-observable - dev: true - - /lint-staged/9.5.0: - resolution: {integrity: sha512-nawMob9cb/G1J98nb8v3VC/E8rcX1rryUYXVZ69aT9kde6YWX+uvNOEHY5yf2gcWcTJGiD0kqXmCnS3oD75GIA==} - hasBin: true - dependencies: - chalk: 2.4.2 - commander: 2.20.3 - cosmiconfig: 5.2.1 - debug: 4.3.3 - dedent: 0.7.0 - del: 5.1.0 - execa: 2.1.0 - listr: 0.14.3 - log-symbols: 3.0.0 - micromatch: 4.0.4 - normalize-path: 3.0.0 - please-upgrade-node: 3.2.0 - string-argv: 0.3.1 - stringify-object: 3.3.0 - transitivePeerDependencies: - - supports-color - - zen-observable - dev: true - /liquid-json/0.3.1: resolution: {integrity: sha1-kVWhgTbYprJhXl8W+aJEira1Duo=} engines: {node: '>=4'} dev: false - /listr-silent-renderer/1.1.1: - resolution: {integrity: sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=} - engines: {node: '>=4'} - dev: true - - /listr-update-renderer/0.5.0_listr@0.14.3: - resolution: {integrity: sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==} - engines: {node: '>=6'} - peerDependencies: - listr: ^0.14.2 - dependencies: - chalk: 1.1.3 - cli-truncate: 0.2.1 - elegant-spinner: 1.0.1 - figures: 1.7.0 - indent-string: 3.2.0 - listr: 0.14.3 - log-symbols: 1.0.2 - log-update: 2.3.0 - strip-ansi: 3.0.1 - dev: true - - /listr-verbose-renderer/0.5.0: - resolution: {integrity: sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==} - engines: {node: '>=4'} - dependencies: - chalk: 2.4.2 - cli-cursor: 2.1.0 - date-fns: 1.30.1 - figures: 2.0.0 - dev: true - - /listr/0.14.3: - resolution: {integrity: sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==} - engines: {node: '>=6'} - dependencies: - '@samverschueren/stream-to-observable': 0.3.1_rxjs@6.6.7 - is-observable: 1.1.0 - is-promise: 2.2.2 - is-stream: 1.1.0 - listr-silent-renderer: 1.1.1 - listr-update-renderer: 0.5.0_listr@0.14.3 - listr-verbose-renderer: 0.5.0 - p-map: 2.1.0 - rxjs: 6.6.7 - transitivePeerDependencies: - - zen-observable - dev: true - - /listr2/4.0.5: - resolution: {integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==} - engines: {node: '>=12'} - peerDependencies: - enquirer: '>= 2.3.0 < 3' - peerDependenciesMeta: - enquirer: - optional: true - dependencies: - cli-truncate: 2.1.0 - colorette: 2.0.16 - log-update: 4.0.0 - p-map: 4.0.0 - rfdc: 1.3.0 - rxjs: 7.5.5 - through: 2.3.8 - wrap-ansi: 7.0.0 - dev: true - /livereload-js/2.4.0: resolution: {integrity: sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==} dev: true @@ -28884,20 +28506,6 @@ packages: /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - /log-symbols/1.0.2: - resolution: {integrity: sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=} - engines: {node: '>=0.10.0'} - dependencies: - chalk: 1.1.3 - dev: true - - /log-symbols/2.2.0: - resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==} - engines: {node: '>=4'} - dependencies: - chalk: 2.4.2 - dev: true - /log-symbols/3.0.0: resolution: {integrity: sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==} engines: {node: '>=8'} @@ -28913,25 +28521,6 @@ packages: is-unicode-supported: 0.1.0 dev: true - /log-update/2.3.0: - resolution: {integrity: sha1-iDKP19HOeTiykoN0bwsbwSayRwg=} - engines: {node: '>=4'} - dependencies: - ansi-escapes: 3.2.0 - cli-cursor: 2.1.0 - wrap-ansi: 3.0.1 - dev: true - - /log-update/4.0.0: - resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} - engines: {node: '>=10'} - dependencies: - ansi-escapes: 4.3.2 - cli-cursor: 3.1.0 - slice-ansi: 4.0.0 - wrap-ansi: 6.2.0 - dev: true - /lolex/5.1.2: resolution: {integrity: sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==} dependencies: @@ -29207,13 +28796,6 @@ packages: hasBin: true dev: true - /matcher/1.1.1: - resolution: {integrity: sha512-+BmqxWIubKTRKNWx/ahnCkk3mG8m7OturVlqq6HiojGJTd5hVYbgZm6WzcYPCoB+KBT4Vd6R7WSRG2OADNaCjg==} - engines: {node: '>=4'} - dependencies: - escape-string-regexp: 1.0.5 - dev: true - /mathml-tag-names/2.1.3: resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} dev: true @@ -30358,14 +29940,6 @@ packages: npm-normalize-package-bin: 1.0.1 dev: true - /npm-path/2.0.4: - resolution: {integrity: sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==} - engines: {node: '>=0.8'} - hasBin: true - dependencies: - which: 1.3.1 - dev: true - /npm-pick-manifest/6.1.1: resolution: {integrity: sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA==} dependencies: @@ -30411,29 +29985,12 @@ packages: dependencies: path-key: 2.0.1 - /npm-run-path/3.1.0: - resolution: {integrity: sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==} - engines: {node: '>=8'} - dependencies: - path-key: 3.1.1 - dev: true - /npm-run-path/4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} dependencies: path-key: 3.1.1 - /npm-which/3.0.1: - resolution: {integrity: sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=} - engines: {node: '>=4.2.0'} - hasBin: true - dependencies: - commander: 2.20.3 - npm-path: 2.0.4 - which: 1.3.1 - dev: true - /npmlog/5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} dependencies: @@ -30914,11 +30471,6 @@ packages: p-limit: 3.1.0 dev: true - /p-map/1.2.0: - resolution: {integrity: sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==} - engines: {node: '>=4'} - dev: true - /p-map/2.1.0: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} @@ -31340,12 +30892,6 @@ packages: hasBin: true dev: true - /pidtree/0.5.0: - resolution: {integrity: sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==} - engines: {node: '>=0.10'} - hasBin: true - dev: true - /pify/2.3.0: resolution: {integrity: sha1-7RQaasBDqEnqWISY59yosVMw6Qw=} engines: {node: '>=0.10.0'} @@ -31405,12 +30951,6 @@ packages: find-up: 5.0.0 dev: true - /please-upgrade-node/3.2.0: - resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==} - dependencies: - semver-compare: 1.0.0 - dev: true - /plur/4.0.0: resolution: {integrity: sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==} engines: {node: '>=10'} @@ -32929,10 +32469,6 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 - /property-expr/1.5.1: - resolution: {integrity: sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g==} - dev: true - /property-information/5.6.0: resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==} dependencies: @@ -34143,15 +33679,6 @@ packages: normalize-package-data: 2.5.0 path-type: 3.0.0 - /read-pkg/4.0.1: - resolution: {integrity: sha1-ljYlN48+HE1IyFhytabsfV0JMjc=} - engines: {node: '>=6'} - dependencies: - normalize-package-data: 2.5.0 - parse-json: 4.0.0 - pify: 3.0.0 - dev: true - /read-pkg/5.2.0: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} engines: {node: '>=8'} @@ -34391,10 +33918,6 @@ packages: resolution: {integrity: sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==} dev: true - /regenerator-runtime/0.12.1: - resolution: {integrity: sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==} - dev: true - /regenerator-runtime/0.13.9: resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} @@ -34871,10 +34394,6 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - /rfdc/1.3.0: - resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} - dev: true - /rgb-regex/1.0.1: resolution: {integrity: sha1-wODWiC3w4jviVKR16O3UGRX+rrE=} @@ -35017,12 +34536,6 @@ packages: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} - /run-node/1.0.0: - resolution: {integrity: sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==} - engines: {node: '>=4'} - hasBin: true - dev: true - /run-parallel/1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: @@ -35061,12 +34574,6 @@ packages: tslib: 2.3.1 dev: true - /rxjs/7.5.5: - resolution: {integrity: sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==} - dependencies: - tslib: 2.3.1 - dev: true - /safe-buffer/5.1.1: resolution: {integrity: sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==} dev: true @@ -35322,10 +34829,6 @@ packages: node-forge: 0.10.0 dev: true - /semver-compare/1.0.0: - resolution: {integrity: sha1-De4hahyUGrN+nvsXiPavxf9VN/w=} - dev: true - /semver-diff/2.1.0: resolution: {integrity: sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=} engines: {node: '>=0.10.0'} @@ -35340,11 +34843,6 @@ packages: semver: 6.3.0 dev: true - /semver-regex/2.0.0: - resolution: {integrity: sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==} - engines: {node: '>=6'} - dev: true - /semver/5.7.1: resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} hasBin: true @@ -35572,14 +35070,6 @@ packages: /signal-exit/3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - /simple-git/1.132.0: - resolution: {integrity: sha512-xauHm1YqCTom1sC9eOjfq3/9RKiUA9iPnxBbrY2DdL8l4ADMu0jjM5l5lphQP5YWNqAL2aXC/OeuQ76vHtW5fg==} - dependencies: - debug: 4.3.3 - transitivePeerDependencies: - - supports-color - dev: true - /simple-html-tokenizer/0.5.11: resolution: {integrity: sha512-C2WEK/Z3HoSFbYq8tI7ni3eOo/NneSPRoPpcM7WdLjFOArFuyXEjAoCdOC3DgMfRyziZQ1hCNR4mrNdWEvD0og==} dev: false @@ -35605,11 +35095,6 @@ packages: engines: {node: '>=12'} dev: true - /slice-ansi/0.0.4: - resolution: {integrity: sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=} - engines: {node: '>=0.10.0'} - dev: true - /slice-ansi/2.1.0: resolution: {integrity: sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==} engines: {node: '>=6'} @@ -35636,14 +35121,6 @@ packages: is-fullwidth-code-point: 3.0.0 dev: true - /slice-ansi/5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} - dependencies: - ansi-styles: 6.1.0 - is-fullwidth-code-point: 4.0.0 - dev: true - /smart-buffer/4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -35924,11 +35401,6 @@ packages: resolution: {integrity: sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==} dev: true - /staged-git-files/1.1.2: - resolution: {integrity: sha512-0Eyrk6uXW6tg9PYkhi/V/J4zHp33aNyi2hOCmhFLqLTIhbgqWn5jlSzI+IU0VqrZq6+DbHcabQl/WP6P3BG0QA==} - hasBin: true - dev: true - /state-toggle/1.0.3: resolution: {integrity: sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==} dev: true @@ -35986,16 +35458,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /string-argv/0.0.2: - resolution: {integrity: sha1-2sMECGkMIfPDYwo/86BYd73L1zY=} - engines: {node: '>=0.6.19'} - dev: true - - /string-argv/0.3.1: - resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} - engines: {node: '>=0.6.19'} - dev: true - /string-hash/1.1.3: resolution: {integrity: sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=} dev: true @@ -36060,15 +35522,6 @@ packages: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - /string-width/5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.0.1 - dev: true - /string.prototype.matchall/4.0.6: resolution: {integrity: sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg==} dependencies: @@ -36133,15 +35586,6 @@ packages: dependencies: safe-buffer: 5.2.1 - /stringify-object/3.3.0: - resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} - engines: {node: '>=4'} - dependencies: - get-own-enumerable-property-symbols: 3.0.2 - is-obj: 1.0.1 - is-regexp: 1.0.0 - dev: true - /strip-ansi/2.0.1: resolution: {integrity: sha1-32LBqpTtLxFOHQ8h/R1QSCt5pg4=} engines: {node: '>=0.10.0'} @@ -36731,11 +36175,6 @@ packages: dependencies: has-flag: 4.0.0 - /supports-color/9.2.1: - resolution: {integrity: sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==} - engines: {node: '>=12'} - dev: true - /supports-hyperlinks/2.2.0: resolution: {integrity: sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==} engines: {node: '>=8'} @@ -36792,11 +36231,6 @@ packages: upper-case: 1.1.3 dev: true - /symbol-observable/1.2.0: - resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==} - engines: {node: '>=0.10.0'} - dev: true - /symbol-tree/3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -37084,7 +36518,7 @@ packages: serialize-javascript: 6.0.0 source-map: 0.6.1 terser: 5.10.0_acorn@8.7.0 - webpack: 5.70.0 + webpack: 5.70.0_webpack-cli@3.3.12 transitivePeerDependencies: - acorn dev: true @@ -37425,10 +36859,6 @@ packages: engines: {node: '>=0.6'} dev: true - /toposort/2.0.2: - resolution: {integrity: sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=} - dev: true - /tough-cookie/2.5.0: resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} engines: {node: '>=0.8'} @@ -39384,10 +38814,6 @@ packages: /which-module/2.0.0: resolution: {integrity: sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=} - /which-pm-runs/1.0.0: - resolution: {integrity: sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=} - dev: true - /which-pm/2.0.0: resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} engines: {node: '>=8.15'} @@ -39502,14 +38928,6 @@ packages: strip-ansi: 3.0.1 dev: true - /wrap-ansi/3.0.1: - resolution: {integrity: sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=} - engines: {node: '>=4'} - dependencies: - string-width: 2.1.1 - strip-ansi: 4.0.0 - dev: true - /wrap-ansi/5.1.0: resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==} engines: {node: '>=6'} @@ -39933,17 +39351,6 @@ packages: wrap-ansi: 2.1.0 dev: true - /yup/0.26.10: - resolution: {integrity: sha512-keuNEbNSnsOTOuGCt3UJW69jDE3O4P+UHAakO7vSeFMnjaitcmlbij/a3oNb9g1Y1KvSKH/7O1R2PQ4m4TRylw==} - dependencies: - '@babel/runtime': 7.0.0 - fn-name: 2.0.1 - lodash: 4.17.21 - property-expr: 1.5.1 - synchronous-promise: 2.0.15 - toposort: 2.0.2 - dev: true - /zwitch/1.0.5: resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} dev: true From 44b4008849ab1434d92c0fe51752962c0f67cc45 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 6 Apr 2022 16:57:09 +0800 Subject: [PATCH 080/386] Install lint-staged --- package.json | 1 + pnpm-lock.yaml | 152 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 143 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 277da8b5ba9..2ff6a3d8460 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "glob": "^7.2.0", "husky": "^7.0.4", "jest": "^27.3.1", + "lint-staged": "^12.3.7", "mkdirp": "^1.0.4", "node-stream-zip": "^1.15.0", "prettier": "npm:wp-prettier@^2.2.1-beta-1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0e2932d41c3..bb0942ed732 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,7 @@ importers: glob: ^7.2.0 husky: ^7.0.4 jest: ^27.3.1 + lint-staged: ^12.3.7 lodash: ^4.17.21 mkdirp: ^1.0.4 node-stream-zip: ^1.15.0 @@ -51,6 +52,7 @@ importers: glob: 7.2.0 husky: 7.0.4 jest: 27.3.1 + lint-staged: 12.3.7 mkdirp: 1.0.4 node-stream-zip: 1.15.0 prettier: /wp-prettier/2.2.1-beta-1 @@ -1765,7 +1767,7 @@ packages: '@babel/traverse': 7.16.3 '@babel/types': 7.16.0 convert-source-map: 1.8.0 - debug: 4.3.2 + debug: 4.3.3 gensync: 1.0.0-beta.2 json5: 2.2.0 semver: 6.3.0 @@ -2206,7 +2208,7 @@ packages: '@babel/helper-module-imports': 7.16.0 '@babel/helper-plugin-utils': 7.14.5 '@babel/traverse': 7.16.3 - debug: 4.3.2 + debug: 4.3.3 lodash.debounce: 4.0.8 resolve: 1.20.0 semver: 6.3.0 @@ -2224,7 +2226,7 @@ packages: '@babel/helper-module-imports': 7.16.0 '@babel/helper-plugin-utils': 7.14.5 '@babel/traverse': 7.16.3 - debug: 4.3.2 + debug: 4.3.3 lodash.debounce: 4.0.8 resolve: 1.20.0 semver: 6.3.0 @@ -2242,7 +2244,7 @@ packages: '@babel/helper-module-imports': 7.16.0 '@babel/helper-plugin-utils': 7.14.5 '@babel/traverse': 7.16.3 - debug: 4.3.2 + debug: 4.3.3 lodash.debounce: 4.0.8 resolve: 1.20.0 semver: 6.3.0 @@ -6234,7 +6236,7 @@ packages: '@babel/helper-split-export-declaration': 7.16.0 '@babel/parser': 7.16.4 '@babel/types': 7.16.0 - debug: 4.3.2 + debug: 4.3.3 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -6676,7 +6678,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.2 + debug: 4.3.3 espree: 9.0.0 globals: 13.12.0 ignore: 4.0.6 @@ -6756,7 +6758,7 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.2 + debug: 4.3.3 minimatch: 3.0.4 transitivePeerDependencies: - supports-color @@ -13273,7 +13275,7 @@ packages: dependencies: '@typescript-eslint/types': 5.4.0 '@typescript-eslint/visitor-keys': 5.4.0 - debug: 4.3.2 + debug: 4.3.3 globby: 11.0.4 is-glob: 4.0.3 semver: 7.3.5 @@ -15637,6 +15639,11 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + /ansi-styles/6.1.0: + resolution: {integrity: sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==} + engines: {node: '>=12'} + dev: true + /ansi-to-html/0.6.15: resolution: {integrity: sha512-28ijx2aHJGdzbs+O5SNQF65r6rrKYnkuwTYm8lZlChuoJ9P1vVzIpWO20sQTqTPDXYp6NFwk326vApTtLVFXpQ==} engines: {node: '>=8.0.0'} @@ -17945,6 +17952,14 @@ packages: string-width: 4.2.3 dev: true + /cli-truncate/3.1.0: + resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + slice-ansi: 5.0.0 + string-width: 5.1.2 + dev: true + /cli-width/2.2.1: resolution: {integrity: sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==} dev: true @@ -19322,6 +19337,19 @@ packages: ms: 2.1.2 supports-color: 8.1.1 + /debug/4.3.3_supports-color@9.2.2: + resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + supports-color: 9.2.2 + dev: true + /debuglog/1.0.1: resolution: {integrity: sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=} dev: true @@ -19910,6 +19938,10 @@ packages: stream-shift: 1.0.1 dev: true + /eastasianwidth/0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + /ecc-jsbn/0.1.2: resolution: {integrity: sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=} dependencies: @@ -24719,7 +24751,7 @@ packages: react-devtools-core: 4.22.0 react-reconciler: 0.26.2_react@17.0.2 scheduler: 0.20.2 - signal-exit: 3.0.5 + signal-exit: 3.0.7 slice-ansi: 3.0.0 stack-utils: 2.0.5 string-width: 4.2.3 @@ -25088,6 +25120,11 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + /is-fullwidth-code-point/4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + dev: true + /is-function/1.0.2: resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==} dev: true @@ -25603,7 +25640,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.3.2 + debug: 4.3.3 istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -28225,11 +28262,53 @@ packages: uc.micro: 1.0.6 dev: true + /lint-staged/12.3.7: + resolution: {integrity: sha512-/S4D726e2GIsDVWIk1XGvheCaDm1SJRQp8efamZFWJxQMVEbOwSysp7xb49Oo73KYCdy97mIWinhlxcoNqIfIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + dependencies: + cli-truncate: 3.1.0 + colorette: 2.0.16 + commander: 8.3.0 + debug: 4.3.3_supports-color@9.2.2 + execa: 5.1.1 + lilconfig: 2.0.4 + listr2: 4.0.5 + micromatch: 4.0.4 + normalize-path: 3.0.0 + object-inspect: 1.12.0 + pidtree: 0.5.0 + string-argv: 0.3.1 + supports-color: 9.2.2 + yaml: 1.10.2 + transitivePeerDependencies: + - enquirer + dev: true + /liquid-json/0.3.1: resolution: {integrity: sha1-kVWhgTbYprJhXl8W+aJEira1Duo=} engines: {node: '>=4'} dev: false + /listr2/4.0.5: + resolution: {integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==} + engines: {node: '>=12'} + peerDependencies: + enquirer: '>= 2.3.0 < 3' + peerDependenciesMeta: + enquirer: + optional: true + dependencies: + cli-truncate: 2.1.0 + colorette: 2.0.16 + log-update: 4.0.0 + p-map: 4.0.0 + rfdc: 1.3.0 + rxjs: 7.5.5 + through: 2.3.8 + wrap-ansi: 7.0.0 + dev: true + /livereload-js/2.4.0: resolution: {integrity: sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==} dev: true @@ -28521,6 +28600,16 @@ packages: is-unicode-supported: 0.1.0 dev: true + /log-update/4.0.0: + resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} + engines: {node: '>=10'} + dependencies: + ansi-escapes: 4.3.2 + cli-cursor: 3.1.0 + slice-ansi: 4.0.0 + wrap-ansi: 6.2.0 + dev: true + /lolex/5.1.2: resolution: {integrity: sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==} dependencies: @@ -30892,6 +30981,12 @@ packages: hasBin: true dev: true + /pidtree/0.5.0: + resolution: {integrity: sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==} + engines: {node: '>=0.10'} + hasBin: true + dev: true + /pify/2.3.0: resolution: {integrity: sha1-7RQaasBDqEnqWISY59yosVMw6Qw=} engines: {node: '>=0.10.0'} @@ -34394,6 +34489,10 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + /rfdc/1.3.0: + resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} + dev: true + /rgb-regex/1.0.1: resolution: {integrity: sha1-wODWiC3w4jviVKR16O3UGRX+rrE=} @@ -34574,6 +34673,12 @@ packages: tslib: 2.3.1 dev: true + /rxjs/7.5.5: + resolution: {integrity: sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==} + dependencies: + tslib: 2.3.1 + dev: true + /safe-buffer/5.1.1: resolution: {integrity: sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==} dev: true @@ -35121,6 +35226,14 @@ packages: is-fullwidth-code-point: 3.0.0 dev: true + /slice-ansi/5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.1.0 + is-fullwidth-code-point: 4.0.0 + dev: true + /smart-buffer/4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -35458,6 +35571,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /string-argv/0.3.1: + resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} + engines: {node: '>=0.6.19'} + dev: true + /string-hash/1.1.3: resolution: {integrity: sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=} dev: true @@ -35522,6 +35640,15 @@ packages: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + /string-width/5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.0.1 + dev: true + /string.prototype.matchall/4.0.6: resolution: {integrity: sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg==} dependencies: @@ -36175,6 +36302,11 @@ packages: dependencies: has-flag: 4.0.0 + /supports-color/9.2.2: + resolution: {integrity: sha512-XC6g/Kgux+rJXmwokjm9ECpD6k/smUoS5LKlUCcsYr4IY3rW0XyAympon2RmxGrlnZURMpg5T18gWDP9CsHXFA==} + engines: {node: '>=12'} + dev: true + /supports-hyperlinks/2.2.0: resolution: {integrity: sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==} engines: {node: '>=8'} From 5bfcaa7f3f091db5c64b692b2b94b046a0c2760a Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 6 Apr 2022 16:57:29 +0800 Subject: [PATCH 081/386] Use lint-staged in pre-commit hook --- .husky/pre-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index aa52ea8822c..5e592735698 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -pnpm nx affected:lint --uncommitted +pnpm exec lint-staged From 2b1f54b09d24cb9ba283f5381b6acf0d442fe114 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 6 Apr 2022 16:58:17 +0800 Subject: [PATCH 082/386] Set up lint-staged configuration for each package --- packages/js/admin-e2e-tests/package.json | 5 +++++ packages/js/api-core-tests/package.json | 5 +++++ packages/js/api/package.json | 5 +++++ packages/js/components/package.json | 6 ++++++ packages/js/csv-export/package.json | 6 ++++++ packages/js/currency/package.json | 6 ++++++ packages/js/customer-effort-score/package.json | 6 ++++++ packages/js/data/package.json | 6 ++++++ packages/js/date/package.json | 6 ++++++ .../js/dependency-extraction-webpack-plugin/package.json | 5 +++++ packages/js/e2e-core-tests/package.json | 5 +++++ packages/js/e2e-environment/package.json | 5 +++++ packages/js/e2e-utils/package.json | 5 +++++ packages/js/experimental/package.json | 6 ++++++ packages/js/explat/package.json | 6 ++++++ packages/js/js-tests/package.json | 5 +++++ packages/js/navigation/package.json | 6 ++++++ packages/js/notices/package.json | 5 +++++ packages/js/number/package.json | 6 ++++++ packages/js/onboarding/package.json | 5 +++++ packages/js/style-build/package.json | 5 +++++ packages/js/tracks/package.json | 5 +++++ plugins/woocommerce-admin/package.json | 9 +++++++++ plugins/woocommerce-beta-tester/package.json | 9 +++++++++ plugins/woocommerce/package.json | 9 +++++++++ 25 files changed, 147 insertions(+) diff --git a/packages/js/admin-e2e-tests/package.json b/packages/js/admin-e2e-tests/package.json index be6a8efe6d2..200ee2170f9 100644 --- a/packages/js/admin-e2e-tests/package.json +++ b/packages/js/admin-e2e-tests/package.json @@ -56,5 +56,10 @@ "clean": "pnpm exec rimraf tsconfig.tsbuildinfo build build-*", "lint": "eslint src", "prepack": "pnpm run clean && pnpm run build" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix" + ] } } diff --git a/packages/js/api-core-tests/package.json b/packages/js/api-core-tests/package.json index 4c4bf1fa0d3..f83420e77d3 100644 --- a/packages/js/api-core-tests/package.json +++ b/packages/js/api-core-tests/package.json @@ -33,5 +33,10 @@ }, "bin": { "wc-api-tests": "bin/wc-api-tests.sh" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix" + ] } } diff --git a/packages/js/api/package.json b/packages/js/api/package.json index af4790ba749..913125e6bcd 100644 --- a/packages/js/api/package.json +++ b/packages/js/api/package.json @@ -52,5 +52,10 @@ }, "publishConfig": { "access": "public" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix" + ] } } diff --git a/packages/js/components/package.json b/packages/js/components/package.json index b9ec712baa5..abdca8364fc 100644 --- a/packages/js/components/package.json +++ b/packages/js/components/package.json @@ -123,5 +123,11 @@ "test:nobuild": "jest --config ./jest.config.json", "test:update-snapshots": "pnpm run test:nobuild -- --updateSnapshot", "test-staged": "jest --bail --config ./jest.config.json --findRelatedTests" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix", + "pnpm test-staged" + ] } } diff --git a/packages/js/csv-export/package.json b/packages/js/csv-export/package.json index 50722c87690..3db27db855d 100644 --- a/packages/js/csv-export/package.json +++ b/packages/js/csv-export/package.json @@ -46,5 +46,11 @@ "rimraf": "^3.0.2", "ts-jest": "^27.1.3", "typescript": "^4.6.2" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix", + "pnpm test-staged" + ] } } diff --git a/packages/js/currency/package.json b/packages/js/currency/package.json index 80661eca8bd..bcaadf03b03 100644 --- a/packages/js/currency/package.json +++ b/packages/js/currency/package.json @@ -49,5 +49,11 @@ "rimraf": "^3.0.2", "ts-jest": "^27.1.3", "typescript": "^4.6.2" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix", + "pnpm test-staged" + ] } } diff --git a/packages/js/customer-effort-score/package.json b/packages/js/customer-effort-score/package.json index c1bd32fd97c..b21e073edfe 100644 --- a/packages/js/customer-effort-score/package.json +++ b/packages/js/customer-effort-score/package.json @@ -70,5 +70,11 @@ "test": "pnpm run build && pnpm run test:nobuild", "test:nobuild": "jest --config ./jest.config.json", "test-staged": "jest --bail --config ./jest.config.json --findRelatedTests" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix", + "pnpm test-staged" + ] } } diff --git a/packages/js/data/package.json b/packages/js/data/package.json index 1308e6827d0..1096444c615 100644 --- a/packages/js/data/package.json +++ b/packages/js/data/package.json @@ -70,5 +70,11 @@ "test": "pnpm run build && pnpm run test:nobuild", "test:nobuild": "jest --config ./jest.config.json", "test-staged": "jest --bail --config ./jest.config.json --findRelatedTests" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix", + "pnpm test-staged" + ] } } diff --git a/packages/js/date/package.json b/packages/js/date/package.json index eb6240d2f7c..9fa0935dfad 100644 --- a/packages/js/date/package.json +++ b/packages/js/date/package.json @@ -52,5 +52,11 @@ "test": "pnpm run build && pnpm run test:nobuild", "test:nobuild": "jest --config ./jest.config.json", "test-staged": "jest --bail --config ./jest.config.json --findRelatedTests" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix", + "pnpm test-staged" + ] } } diff --git a/packages/js/dependency-extraction-webpack-plugin/package.json b/packages/js/dependency-extraction-webpack-plugin/package.json index eccc6705f2b..e280009130c 100644 --- a/packages/js/dependency-extraction-webpack-plugin/package.json +++ b/packages/js/dependency-extraction-webpack-plugin/package.json @@ -34,5 +34,10 @@ "typescript": "^4.6.2", "webpack": "^5.70.0", "webpack-cli": "^3.3.12" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix" + ] } } diff --git a/packages/js/e2e-core-tests/package.json b/packages/js/e2e-core-tests/package.json index bbc55e8eadc..475cb1aa8fe 100644 --- a/packages/js/e2e-core-tests/package.json +++ b/packages/js/e2e-core-tests/package.json @@ -48,5 +48,10 @@ "clean": "rm -rf ./build ./build-module", "compile": "node ./../bin/build.js", "build": "./bin/build.sh && pnpm run clean && pnpm run compile" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix" + ] } } diff --git a/packages/js/e2e-environment/package.json b/packages/js/e2e-environment/package.json index 07de1318f83..f30f3a535c2 100644 --- a/packages/js/e2e-environment/package.json +++ b/packages/js/e2e-environment/package.json @@ -77,5 +77,10 @@ }, "bin": { "wc-e2e": "bin/wc-e2e.sh" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix" + ] } } diff --git a/packages/js/e2e-utils/package.json b/packages/js/e2e-utils/package.json index 867a4bab905..f83dd1c01cb 100644 --- a/packages/js/e2e-utils/package.json +++ b/packages/js/e2e-utils/package.json @@ -45,5 +45,10 @@ "build": "pnpm run clean && pnpm run compile", "prepare": "pnpm run build", "lint": "eslint src" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix" + ] } } diff --git a/packages/js/experimental/package.json b/packages/js/experimental/package.json index 8a7530f6085..c75114da92e 100644 --- a/packages/js/experimental/package.json +++ b/packages/js/experimental/package.json @@ -83,5 +83,11 @@ "test": "pnpm run build && pnpm run test:nobuild", "test:nobuild": "jest --config ./jest.config.json", "test-staged": "jest --bail --config ./jest.config.json --findRelatedTests" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix", + "pnpm test-staged" + ] } } diff --git a/packages/js/explat/package.json b/packages/js/explat/package.json index 533172d328f..a2145aef476 100644 --- a/packages/js/explat/package.json +++ b/packages/js/explat/package.json @@ -54,5 +54,11 @@ "test": "pnpm run build && pnpm run test:nobuild", "test:nobuild": "jest --config ./jest.config.json", "test-staged": "jest --bail --config ./jest.config.json --findRelatedTests" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix", + "pnpm test-staged" + ] } } diff --git a/packages/js/js-tests/package.json b/packages/js/js-tests/package.json index c6de210f262..d8e7895f022 100644 --- a/packages/js/js-tests/package.json +++ b/packages/js/js-tests/package.json @@ -40,5 +40,10 @@ "rimraf": "^3.0.2", "ts-jest": "^27.1.3", "typescript": "^4.6.2" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix" + ] } } diff --git a/packages/js/navigation/package.json b/packages/js/navigation/package.json index c4b578c2cc6..75f25e62743 100644 --- a/packages/js/navigation/package.json +++ b/packages/js/navigation/package.json @@ -57,5 +57,11 @@ "rimraf": "^3.0.2", "ts-jest": "^27.1.3", "typescript": "^4.6.2" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix", + "pnpm test-staged" + ] } } diff --git a/packages/js/notices/package.json b/packages/js/notices/package.json index ca89ed63893..b316ab1899a 100644 --- a/packages/js/notices/package.json +++ b/packages/js/notices/package.json @@ -51,5 +51,10 @@ "rimraf": "^3.0.2", "ts-jest": "^27.1.3", "typescript": "^4.6.2" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix" + ] } } diff --git a/packages/js/number/package.json b/packages/js/number/package.json index d7daf0f01d0..92843dff466 100644 --- a/packages/js/number/package.json +++ b/packages/js/number/package.json @@ -45,5 +45,11 @@ "rimraf": "^3.0.2", "ts-jest": "^27.1.3", "typescript": "^4.6.2" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix", + "pnpm test-staged" + ] } } diff --git a/packages/js/onboarding/package.json b/packages/js/onboarding/package.json index 8ee2642b336..5119a30f58e 100644 --- a/packages/js/onboarding/package.json +++ b/packages/js/onboarding/package.json @@ -59,5 +59,10 @@ "start": "concurrently \"tsc --build --watch\" \"webpack --watch\"", "prepack": "pnpm run clean && pnpm run build", "lint": "eslint src" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix" + ] } } diff --git a/packages/js/style-build/package.json b/packages/js/style-build/package.json index 96926105d6c..aca30b6c530 100644 --- a/packages/js/style-build/package.json +++ b/packages/js/style-build/package.json @@ -42,5 +42,10 @@ "ts-jest": "^27.1.3", "typescript": "^4.6.2", "webpack": "^5.70.0" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix" + ] } } diff --git a/packages/js/tracks/package.json b/packages/js/tracks/package.json index 314b4c9abe5..cf5eca20e78 100644 --- a/packages/js/tracks/package.json +++ b/packages/js/tracks/package.json @@ -42,5 +42,10 @@ "rimraf": "^3.0.2", "ts-jest": "^27.1.3", "typescript": "^4.6.2" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "eslint --fix" + ] } } diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 374bbccfffd..b2bb9d530ba 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -48,6 +48,7 @@ "lint:css": "stylelint '**/*.scss'", "lint:css-fix": "stylelint '**/*.scss' --fix --ip 'storybook/wordpress'", "lint:js": "wp-scripts lint-js ./client --ext=js,ts,tsx", + "lint:js-pre-commit": "wp-scripts lint-js --ext=js,ts,tsx", "lint:js-packages": "wp-scripts lint-js ../../packages/js --ext=js,ts,tsx", "lint:js-fix": "pnpm run lint:js -- --fix --ext=js,ts,tsx", "lint:php": "./vendor/bin/phpcs --standard=phpcs.xml.dist $(git ls-files | grep .php$)", @@ -280,6 +281,14 @@ "peerDependencies": { "@wordpress/data": "^6.3.0" }, + "lint-staged": { + "*.scss": [ "pnpm lint:css-fix" ], + "client/**/*.(t|j)s?(x)": [ + "pnpm reformat-files", + "pnpm wp-scripts lint-js", + "pnpm test-staged" + ] + }, "engines": { "node": "^16.13.1", "pnpm": "^6.24.2" diff --git a/plugins/woocommerce-beta-tester/package.json b/plugins/woocommerce-beta-tester/package.json index 2c4c3b89233..c9044a0682f 100644 --- a/plugins/woocommerce-beta-tester/package.json +++ b/plugins/woocommerce-beta-tester/package.json @@ -33,5 +33,14 @@ "woorelease": { "svn_reauth": "true", "wp_org_slug": "woocommerce-beta-tester" + }, + "lint-staged": { + "*.php": [ + "php -d display_errors=1 -l", + "composer --working-dir=./plugins/woocommerce-beta-tester run-script phpcs-pre-commit" + ], + "!(*min).js": [ + "eslint --fix" + ] } } diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index c6a827e7ef9..6ae8e8e0ace 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -69,6 +69,15 @@ "webpack-cli": "3.3.12", "wp-textdomain": "1.0.1" }, + "lint-staged": { + "*.php": [ + "php -d display_errors=1 -l", + "composer --working-dir=./plugins/woocommerce run-script phpcs-pre-commit" + ], + "!(*min).js": [ + "eslint --fix" + ] + }, "engines": { "node": "^16.13.1", "pnpm": "^6.24.2" From f50379d8a2c5a493c6b3df1d5e09de6a8aa269d3 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Wed, 6 Apr 2022 19:15:08 +0800 Subject: [PATCH 083/386] Update all onboarding card images --- .../src/components/WCPayAcceptedMethods.js | 15 +- .../src/components/WCPayCard/WCPayCard.scss | 17 +- .../js/onboarding/src/images/cards/amex.js | 23 +- .../onboarding/src/images/cards/applepay.js | 21 +- packages/js/onboarding/src/images/cards/cb.js | 33 ++- .../js/onboarding/src/images/cards/diners.js | 251 ++---------------- .../onboarding/src/images/cards/discover.js | 148 ++++++++--- .../js/onboarding/src/images/cards/giropay.js | 49 ++++ .../onboarding/src/images/cards/googlepay.js | 47 ++-- .../js/onboarding/src/images/cards/jcb.js | 72 +++-- .../onboarding/src/images/cards/mastercard.js | 35 ++- .../js/onboarding/src/images/cards/sofort.js | 35 +++ .../onboarding/src/images/cards/unionpay.js | 103 +------ .../js/onboarding/src/images/cards/visa.js | 35 ++- 14 files changed, 413 insertions(+), 471 deletions(-) create mode 100644 packages/js/onboarding/src/images/cards/giropay.js create mode 100644 packages/js/onboarding/src/images/cards/sofort.js diff --git a/packages/js/onboarding/src/components/WCPayAcceptedMethods.js b/packages/js/onboarding/src/components/WCPayAcceptedMethods.js index 7fc42131c2d..a79a1031c23 100644 --- a/packages/js/onboarding/src/components/WCPayAcceptedMethods.js +++ b/packages/js/onboarding/src/components/WCPayAcceptedMethods.js @@ -10,14 +10,16 @@ import { __ } from '@wordpress/i18n'; */ import Visa from '../images/cards/visa.js'; import MasterCard from '../images/cards/mastercard.js'; -import Maestro from '../images/cards/maestro.js'; import Amex from '../images/cards/amex.js'; import ApplePay from '../images/cards/applepay.js'; +import GooglePay from '../images/cards/googlepay.js'; import CB from '../images/cards/cb.js'; import DinersClub from '../images/cards/diners.js'; import Discover from '../images/cards/discover.js'; import JCB from '../images/cards/jcb.js'; import UnionPay from '../images/cards/unionpay.js'; +import GiroPay from '../images/cards/giropay.js'; +import Sofort from '../images/cards/sofort.js'; export const WCPayAcceptedMethods = () => ( <> @@ -28,14 +30,19 @@ export const WCPayAcceptedMethods = () => (
- - + + + + - + +
+ & more. +
); diff --git a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss index 9d375486176..57a2ee5305a 100644 --- a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss +++ b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss @@ -79,14 +79,17 @@ } .woocommerce-task-payment-wcpay__accepted { - display: flex; - margin-top: $gap-small; - flex-wrap: wrap; - gap: $gap-small; - h3 { color: #40464d; } + + display: flex; + flex-wrap: wrap; + margin-top: $gap-small; + margin-left: 0; + gap: 8px; + justify-content: flex-start; + align-items: flex-end; } .woocommerce-task-payment-wcpay__benefit { @@ -99,6 +102,10 @@ line-height: 24px; letter-spacing: normal; } + + .woocommerce-task-payment-wcpay__accepted__and-more { + white-space: nowrap; + } } .woocommerce-task-payment-wcpay__benefits-card { diff --git a/packages/js/onboarding/src/images/cards/amex.js b/packages/js/onboarding/src/images/cards/amex.js index daa085352db..49facfe9d6e 100644 --- a/packages/js/onboarding/src/images/cards/amex.js +++ b/packages/js/onboarding/src/images/cards/amex.js @@ -5,26 +5,25 @@ import { createElement } from '@wordpress/element'; export default () => ( - + ); diff --git a/packages/js/onboarding/src/images/cards/applepay.js b/packages/js/onboarding/src/images/cards/applepay.js index 74febe62299..edb77a8a8e8 100644 --- a/packages/js/onboarding/src/images/cards/applepay.js +++ b/packages/js/onboarding/src/images/cards/applepay.js @@ -5,26 +5,25 @@ import { createElement } from '@wordpress/element'; export default () => ( - + ); diff --git a/packages/js/onboarding/src/images/cards/cb.js b/packages/js/onboarding/src/images/cards/cb.js index da48c9a5b13..b3dcb6cadcd 100644 --- a/packages/js/onboarding/src/images/cards/cb.js +++ b/packages/js/onboarding/src/images/cards/cb.js @@ -5,34 +5,33 @@ import { createElement } from '@wordpress/element'; export default () => ( - + diff --git a/packages/js/onboarding/src/images/cards/diners.js b/packages/js/onboarding/src/images/cards/diners.js index 039bad8b238..5630431f0eb 100644 --- a/packages/js/onboarding/src/images/cards/diners.js +++ b/packages/js/onboarding/src/images/cards/diners.js @@ -5,254 +5,37 @@ import { createElement } from '@wordpress/element'; export default () => ( - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); diff --git a/packages/js/onboarding/src/images/cards/discover.js b/packages/js/onboarding/src/images/cards/discover.js index 51479f917aa..a9e764ba46c 100644 --- a/packages/js/onboarding/src/images/cards/discover.js +++ b/packages/js/onboarding/src/images/cards/discover.js @@ -5,46 +5,134 @@ import { createElement } from '@wordpress/element'; export default () => ( - + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + ); diff --git a/packages/js/onboarding/src/images/cards/giropay.js b/packages/js/onboarding/src/images/cards/giropay.js new file mode 100644 index 00000000000..6979d1911c5 --- /dev/null +++ b/packages/js/onboarding/src/images/cards/giropay.js @@ -0,0 +1,49 @@ +/** + * External dependencies + */ +import { createElement } from '@wordpress/element'; + +export default () => ( + + + + + + + + + +); diff --git a/packages/js/onboarding/src/images/cards/googlepay.js b/packages/js/onboarding/src/images/cards/googlepay.js index 329ec0e3641..d7e00b3b269 100644 --- a/packages/js/onboarding/src/images/cards/googlepay.js +++ b/packages/js/onboarding/src/images/cards/googlepay.js @@ -5,48 +5,61 @@ import { createElement } from '@wordpress/element'; export default () => ( - + ); diff --git a/packages/js/onboarding/src/images/cards/jcb.js b/packages/js/onboarding/src/images/cards/jcb.js index ee332408bba..36c6569289e 100644 --- a/packages/js/onboarding/src/images/cards/jcb.js +++ b/packages/js/onboarding/src/images/cards/jcb.js @@ -5,38 +5,78 @@ import { createElement } from '@wordpress/element'; export default () => ( - + + + + + + + + + + + + + + + + ); diff --git a/packages/js/onboarding/src/images/cards/mastercard.js b/packages/js/onboarding/src/images/cards/mastercard.js index 8fbde439e41..d6755de398d 100644 --- a/packages/js/onboarding/src/images/cards/mastercard.js +++ b/packages/js/onboarding/src/images/cards/mastercard.js @@ -5,38 +5,37 @@ import { createElement } from '@wordpress/element'; export default () => ( - ); diff --git a/packages/js/onboarding/src/images/cards/sofort.js b/packages/js/onboarding/src/images/cards/sofort.js new file mode 100644 index 00000000000..9d651368c38 --- /dev/null +++ b/packages/js/onboarding/src/images/cards/sofort.js @@ -0,0 +1,35 @@ +/** + * External dependencies + */ +import { createElement } from '@wordpress/element'; + +export default () => ( + + + + + + + +); diff --git a/packages/js/onboarding/src/images/cards/unionpay.js b/packages/js/onboarding/src/images/cards/unionpay.js index 0be12014c7b..457b182fbbb 100644 --- a/packages/js/onboarding/src/images/cards/unionpay.js +++ b/packages/js/onboarding/src/images/cards/unionpay.js @@ -5,110 +5,35 @@ import { createElement } from '@wordpress/element'; export default () => ( - - - - - - - - - - ); diff --git a/packages/js/onboarding/src/images/cards/visa.js b/packages/js/onboarding/src/images/cards/visa.js index cd81d8a8fa3..dbae529ccd4 100644 --- a/packages/js/onboarding/src/images/cards/visa.js +++ b/packages/js/onboarding/src/images/cards/visa.js @@ -5,40 +5,39 @@ import { createElement } from '@wordpress/element'; export default () => ( - + ); From a13f15624a1099eb3cd07d94900e0dad1164b618 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 5 Apr 2022 11:57:18 -0300 Subject: [PATCH 084/386] Make sure we also track completion of non visible tasks and fix parent id in WooCommercePayments task --- .../src/Admin/Features/OnboardingTasks/TaskList.php | 7 +++++++ .../OnboardingTasks/Tasks/AdditionalPayments.php | 7 +++++-- .../OnboardingTasks/Tasks/WooCommercePayments.php | 10 +--------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php index ddfa282d70e..e2439ad0d2c 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php @@ -350,6 +350,13 @@ class TaskList { */ public function get_json() { $this->possibly_track_completion(); + // Track completion of non viewable tasks. + foreach ( $this->tasks as $task ) { + if ( ! $task->can_view() ) { + $task->possibly_track_completion(); + } + } + return array( 'id' => $this->get_list_id(), 'title' => $this->title, diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/AdditionalPayments.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/AdditionalPayments.php index 6277a0acd55..e479c4292db 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/AdditionalPayments.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/AdditionalPayments.php @@ -73,8 +73,11 @@ class AdditionalPayments extends Payments { $woocommerce_payments = new WooCommercePayments(); - if ( ! $woocommerce_payments->is_requested() || ( $woocommerce_payments->is_supported() && ! $woocommerce_payments->is_connected() ) ) { - // Hide task if WC Pay is installed via OBW, in supported country, but not connected. + if ( ! $woocommerce_payments->is_requested() || ! $woocommerce_payments->is_supported() || ! $woocommerce_payments->is_connected() ) { + // Hide task if WC Pay is not installed via OBW, or is not connected, or the store is located in a country that is not supported by WC Pay. + return false; + } + if ( $this->get_parent_id() === 'extended_two_column' && WooCommercePayments::is_connected() ) { return false; } diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php index 55b30784880..af95dfcc174 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php @@ -19,15 +19,6 @@ class WooCommercePayments extends Task { return 'woocommerce-payments'; } - /** - * Parent ID. - * - * @return string - */ - public function get_parent_id() { - return 'setup'; - } - /** * Title. * @@ -130,6 +121,7 @@ class WooCommercePayments extends Task { * @return bool */ public static function is_connected() { + return true; if ( class_exists( '\WC_Payments' ) ) { $wc_payments_gateway = \WC_Payments::get_gateway(); return method_exists( $wc_payments_gateway, 'is_connected' ) From a7ec02447196467b54daa1a70f5e00ebc602929d Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 5 Apr 2022 12:09:49 -0300 Subject: [PATCH 085/386] Remove test condition --- .../Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php index af95dfcc174..fa36d7df0dd 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php @@ -121,7 +121,6 @@ class WooCommercePayments extends Task { * @return bool */ public static function is_connected() { - return true; if ( class_exists( '\WC_Payments' ) ) { $wc_payments_gateway = \WC_Payments::get_gateway(); return method_exists( $wc_payments_gateway, 'is_connected' ) From 828e34a9745e5f84e195f76ed08e099048de4130 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 6 Apr 2022 10:08:22 -0300 Subject: [PATCH 086/386] Address PR feedback and fix PHP unit tests --- .../Admin/Features/OnboardingTasks/TaskList.php | 14 +++++--------- .../features/onboarding-tasks/task.php | 2 +- .../features/onboarding-tasks/test-task.php | 9 --------- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php index e2439ad0d2c..f451ea48caf 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php @@ -350,10 +350,11 @@ class TaskList { */ public function get_json() { $this->possibly_track_completion(); - // Track completion of non viewable tasks. + $tasks_json = array(); foreach ( $this->tasks as $task ) { - if ( ! $task->can_view() ) { - $task->possibly_track_completion(); + $json = $task->get_json(); + if ( $json['canView'] ) { + $tasks_json[] = $json; } } @@ -363,12 +364,7 @@ class TaskList { 'isHidden' => $this->is_hidden(), 'isVisible' => $this->is_visible(), 'isComplete' => $this->is_complete(), - 'tasks' => array_map( - function( $task ) { - return $task->get_json(); - }, - $this->get_viewable_tasks() - ), + 'tasks' => $tasks_json, 'eventPrefix' => $this->prefix_event( '' ), 'displayProgressHeader' => $this->display_progress_header, ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php index 88eaeffee34..b9b27a0c1ae 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php @@ -394,7 +394,7 @@ class WC_Admin_Tests_OnboardingTasks_Task extends WC_Unit_Test_Case { */ public function test_get_list_id() { $task = new TestTask( - new TaskList( array( 'id' => 'setup' ) ), + new TaskList( array( 'id' => 'extended' ) ), array( 'id' => 'wc-unit-test-task', ) diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/test-task.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/test-task.php index 2f3b81ba966..262a9503305 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/test-task.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/test-task.php @@ -96,15 +96,6 @@ class TestTask extends Task { return $this->content; } - /** - * Parent ID. - * - * @return string - */ - public function get_parent_id() { - return 'extended'; - } - /** * Level. * From b660f58c1c1b34b9932f0c4135567246b96bac5e Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 6 Apr 2022 10:10:20 -0300 Subject: [PATCH 087/386] Add changelog --- plugins/woocommerce/changelog/fix-32140_wcpay_completion_task | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-32140_wcpay_completion_task diff --git a/plugins/woocommerce/changelog/fix-32140_wcpay_completion_task b/plugins/woocommerce/changelog/fix-32140_wcpay_completion_task new file mode 100644 index 00000000000..d8b06ce4cb0 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-32140_wcpay_completion_task @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix issue where some tasks where not being tracked as completed, when tracking is enabled. #32493 From 4ad2f4ceed349853977fd6e227acea71f5fcc2d4 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 5 Apr 2022 16:25:42 -0300 Subject: [PATCH 088/386] Make use of PaymentGatewaySuggestions logic in WooCommerce Payments task --- .../Tasks/WooCommercePayments.php | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php index 55b30784880..474358768e6 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php @@ -5,6 +5,7 @@ namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks; use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile; use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task; use Automattic\WooCommerce\Admin\PluginsHelper; +use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\Init as Suggestions; /** * WooCommercePayments Task @@ -146,22 +147,14 @@ class WooCommercePayments extends Task { * @return bool */ public static function is_supported() { - return in_array( - WC()->countries->get_base_country(), - array( - 'US', - 'PR', - 'AU', - 'CA', - 'DE', - 'ES', - 'FR', - 'GB', - 'IE', - 'IT', - 'NZ', - ), - true - ); + $suggestions = Suggestions::get_suggestions(); + $suggestion_plugins = array_merge( ...array_filter( array_column( $suggestions, 'plugins' ), function( $plugins ) { + return is_array( $plugins ); + } ) ); + $woocommerce_payments_ids = array_search( 'woocommerce-payments', $suggestion_plugins, true ); + if ( false !== $woocommerce_payments_ids ) { + return true; + } + return false; } } From 1cd3511061055ba6a2c4bccee8457b17d5759db1 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 5 Apr 2022 16:52:20 -0300 Subject: [PATCH 089/386] Add changelog --- plugins/woocommerce/changelog/fix-wcpay_task | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-wcpay_task diff --git a/plugins/woocommerce/changelog/fix-wcpay_task b/plugins/woocommerce/changelog/fix-wcpay_task new file mode 100644 index 00000000000..03a6800fa7a --- /dev/null +++ b/plugins/woocommerce/changelog/fix-wcpay_task @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix WooCommerce Payments task not showing up in some supported countries. #32496 From d93731dc57f8837c5518dba6f255bfbb86708117 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 6 Apr 2022 10:17:24 -0300 Subject: [PATCH 090/386] Fix lint errors --- .../OnboardingTasks/Tasks/WooCommercePayments.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php index 474358768e6..ef92e92693c 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php @@ -147,10 +147,15 @@ class WooCommercePayments extends Task { * @return bool */ public static function is_supported() { - $suggestions = Suggestions::get_suggestions(); - $suggestion_plugins = array_merge( ...array_filter( array_column( $suggestions, 'plugins' ), function( $plugins ) { - return is_array( $plugins ); - } ) ); + $suggestions = Suggestions::get_suggestions(); + $suggestion_plugins = array_merge( + ...array_filter( + array_column( $suggestions, 'plugins' ), + function( $plugins ) { + return is_array( $plugins ); + } + ) + ); $woocommerce_payments_ids = array_search( 'woocommerce-payments', $suggestion_plugins, true ); if ( false !== $woocommerce_payments_ids ) { return true; From f82b5d78f1669e2ec50f0567c67bbdc7a88db55c Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Wed, 6 Apr 2022 21:31:50 +0800 Subject: [PATCH 091/386] Fix css and test --- .../src/components/WCPayCard/WCPayCard.js | 27 +++++++++---------- .../src/components/WCPayCard/WCPayCard.scss | 18 ++++++++++--- .../PaymentGatewaySuggestions/test/index.js | 6 +++-- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js index a865588b22f..ca4f5cf634b 100644 --- a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js +++ b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js @@ -17,22 +17,19 @@ import WCPayBenefit1 from '../../images/wcpay-benefit-1'; import WCPayBenefit2 from '../../images/wcpay-benefit-2'; import WCPayBenefit3 from '../../images/wcpay-benefit-3'; -export const WCPayCardBody = ( { - heading, - children, - onLinkClick = () => {}, -} ) => ( +export const WCPayCardBody = ( { children, onLinkClick = () => {} } ) => (
- { heading && ( - - { heading } - - ) } + + { __( + 'Accept payments and manage your business.', + 'woocommerce' + ) } +
{ children }
-
- +
+
diff --git a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss index 57a2ee5305a..65d81ee1a88 100644 --- a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss +++ b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss @@ -3,6 +3,7 @@ .vstack, .hstack { display: flex; + &.content-center { justify-content: center; } @@ -24,9 +25,19 @@ flex-direction: row; } - .wcpay-hero-image { - margin-right: -32px; - margin-bottom: -28px; + .woocommerce-task-payment-wcpay__hero-image-container { + display: flex; + flex-direction: column; + justify-content: flex-end; + + svg { + margin-right: -32px; + margin-bottom: -24px; + } + + @media screen and (max-width: 600px) { + max-width: 200px; + } } .woocommerce-task-payment-wcpay__heading { @@ -96,6 +107,7 @@ svg { margin: 0 auto; } + max-width: 170px; text-align: center; font-size: 15px; diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js index 7df91e7a82a..3fdf6c29efd 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js @@ -118,8 +118,10 @@ describe( 'PaymentGatewaySuggestions', () => { expect( paymentTitles ).toEqual( [] ); expect( - container.getElementsByTagName( 'title' )[ 0 ].textContent - ).toBe( 'WooCommerce Payments' ); + container.getElementsByClassName( + 'woocommerce-task-payment-wcpay' + )[ 0 ].textContent + ).toContain( 'By using WooCommerce Payments' ); } ); test( 'should render all payment gateways if no WCPay', () => { From fcc7143207d6b33d389015dbf2073645ba3d20f2 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Wed, 6 Apr 2022 14:01:36 -0300 Subject: [PATCH 092/386] Add alreadyActivatedExtensions to persisted business extensions --- .../steps/business-details/flows/selective-bundle/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/client/profile-wizard/steps/business-details/flows/selective-bundle/index.js b/plugins/woocommerce-admin/client/profile-wizard/steps/business-details/flows/selective-bundle/index.js index 10dfadf55f3..71ecdb0bf33 100644 --- a/plugins/woocommerce-admin/client/profile-wizard/steps/business-details/flows/selective-bundle/index.js +++ b/plugins/woocommerce-admin/client/profile-wizard/steps/business-details/flows/selective-bundle/index.js @@ -201,7 +201,10 @@ class BusinessDetails extends Component { const promises = [ this.persistProfileItems( { - business_extensions: businessExtensions, + business_extensions: [ + ...businessExtensions, + ...alreadyActivatedExtensions, + ], } ), ]; From fd1bca35404d486bd4ca421fca0243dd048f3255 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Wed, 6 Apr 2022 14:07:55 -0300 Subject: [PATCH 093/386] Add changelog --- plugins/woocommerce/changelog/fix-hidden_wcpay_task | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-hidden_wcpay_task diff --git a/plugins/woocommerce/changelog/fix-hidden_wcpay_task b/plugins/woocommerce/changelog/fix-hidden_wcpay_task new file mode 100644 index 00000000000..cc3627b2ba0 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-hidden_wcpay_task @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +WCPayments task is not visible after installing the plugin #32506 From 30c7586f36b9d38153b38077502d524530ec4caa Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 6 Apr 2022 16:12:56 -0300 Subject: [PATCH 094/386] Update comment --- .../woocommerce-admin/features/onboarding-tasks/task.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php index b9b27a0c1ae..3cdae60afe9 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php @@ -1,6 +1,6 @@ Date: Wed, 6 Apr 2022 16:36:39 -0300 Subject: [PATCH 095/386] Move require_once below use statements --- .../woocommerce-admin/features/onboarding-tasks/task.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php index 3cdae60afe9..ba2ee818c71 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php @@ -5,11 +5,11 @@ * @package WooCommerce\Admin\Tests\OnboardingTasks */ -require_once __DIR__ . '/test-task.php'; - use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task; use Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskList; +require_once __DIR__ . '/test-task.php'; + /** * class WC_Admin_Tests_OnboardingTasks_Task */ From 2f53fbca049c73aefa8bd060d38b60639e670334 Mon Sep 17 00:00:00 2001 From: Joel T Date: Wed, 6 Apr 2022 13:50:24 -0700 Subject: [PATCH 096/386] Updating deasync package to resolve build issue --- plugins/woocommerce/package.json | 2 +- pnpm-lock.yaml | 27 ++++++++++++++------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index 1f20ec694d6..f6a11958edb 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -55,7 +55,7 @@ "chai-as-promised": "7.1.1", "config": "3.3.3", "cross-env": "6.0.3", - "deasync": "0.1.21", + "deasync": "0.1.26", "eslint": "6.8.0", "eslint-config-wpcalypso": "5.0.0", "eslint-plugin-jest": "23.20.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3554369126f..a4b3fc66e1d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -889,9 +889,9 @@ importers: qs: 6.10.3 devDependencies: '@babel/core': 7.17.8 + '@babel/runtime': 7.17.7 '@wordpress/eslint-plugin': 11.0.1_7c040a9b494a33cf8bf9079642892fb1 eslint: 8.12.0 - '@babel/runtime': 7.17.7 jest: 27.5.1 jest-cli: 27.5.1 rimraf: 3.0.2 @@ -928,9 +928,9 @@ importers: packages/js/number: specifiers: '@babel/core': ^7.17.5 + '@babel/runtime': ^7.17.2 '@wordpress/eslint-plugin': ^11.0.0 eslint: ^8.12.0 - '@babel/runtime': ^7.17.2 jest: ^27.5.1 jest-cli: ^27.5.1 locutus: ^2.0.16 @@ -941,9 +941,9 @@ importers: locutus: 2.0.16 devDependencies: '@babel/core': 7.17.8 + '@babel/runtime': 7.17.7 '@wordpress/eslint-plugin': 11.0.1_7c040a9b494a33cf8bf9079642892fb1 eslint: 8.12.0 - '@babel/runtime': 7.17.7 jest: 27.5.1 jest-cli: 27.5.1 rimraf: 3.0.2 @@ -1093,7 +1093,7 @@ importers: chai-as-promised: 7.1.1 config: 3.3.3 cross-env: 6.0.3 - deasync: 0.1.21 + deasync: 0.1.26 eslint: 6.8.0 eslint-config-wpcalypso: 5.0.0 eslint-plugin-jest: 23.20.0 @@ -1134,7 +1134,7 @@ importers: chai-as-promised: 7.1.1_chai@4.2.0 config: 3.3.3 cross-env: 6.0.3 - deasync: 0.1.21 + deasync: 0.1.26 eslint: 6.8.0 eslint-config-wpcalypso: 5.0.0_eslint@6.8.0 eslint-plugin-jest: 23.20.0_eslint@6.8.0+typescript@3.9.7 @@ -3812,7 +3812,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.0 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 dev: true /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.16.12: @@ -3821,7 +3821,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.12 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.17.8: resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} @@ -3829,7 +3829,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.8 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.12.9: resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} @@ -3944,7 +3944,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.0 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 dev: true /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.16.12: @@ -3953,7 +3953,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.12 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.17.8: resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} @@ -3961,7 +3961,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.8 - '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-plugin-utils': 7.14.5 /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.16.0: resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} @@ -19322,8 +19322,8 @@ packages: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} dev: true - /deasync/0.1.21: - resolution: {integrity: sha512-kUmM8Y+PZpMpQ+B4AuOW9k2Pfx/mSupJtxOsLzmnHY2WqZUYRFccFn2RhzPAqt3Xb+sorK/badW2D4zNzqZz5w==} + /deasync/0.1.26: + resolution: {integrity: sha512-YKw0BmJSWxkjtQsbgn6Q9CHSWB7DKMen8vKrgyC006zy0UZ6nWyGidB0IzZgqkVRkOglAeUaFtiRTeLyel72bg==} engines: {node: '>=0.11.0'} requiresBuild: true dependencies: @@ -29940,6 +29940,7 @@ packages: /nan/2.15.0: resolution: {integrity: sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==} + requiresBuild: true optional: true /nanoid/3.1.30: From 9eff273f01b92cf8286222ffe305af7f6cd8e62a Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Thu, 7 Apr 2022 10:13:01 +1200 Subject: [PATCH 097/386] add appropriate slash --- plugins/woocommerce/tests/legacy/bootstrap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/tests/legacy/bootstrap.php b/plugins/woocommerce/tests/legacy/bootstrap.php index d4f0aeead67..cd6e2ca4958 100644 --- a/plugins/woocommerce/tests/legacy/bootstrap.php +++ b/plugins/woocommerce/tests/legacy/bootstrap.php @@ -259,7 +259,7 @@ class WC_Unit_Tests_Bootstrap { * @return array Filtered feature flags. */ public function add_development_features( $flags ) { - $config = json_decode( file_get_contents( $this->plugin_dir . 'client/admin/config/development.json' ) ); // @codingStandardsIgnoreLine. + $config = json_decode( file_get_contents( $this->plugin_dir . '/client/admin/config/development.json' ) ); // @codingStandardsIgnoreLine. foreach ( $config->features as $feature => $bool ) { $flags[ $feature ] = $bool; } From 4180780cf9fb58d01e34ce5d1f743c9a1e3109b6 Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Thu, 7 Apr 2022 11:00:36 +1200 Subject: [PATCH 098/386] create feature config as part of core build --- plugins/woocommerce/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index 406c5eb4a0b..544ac1b7efa 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -15,7 +15,7 @@ "preinstall": "npx only-allow pnpm", "build": "./bin/build-zip.sh", "build:feature-config": "php bin/generate-feature-config.php", - "build:core": "pnpm nx build woocommerce-admin && pnpm nx build woocommerce-legacy-assets && pnpm run makepot", + "build:core": "pnpm run build:feature-config && pnpm nx build woocommerce-admin && pnpm nx build woocommerce-legacy-assets && pnpm run makepot", "build:zip": "pnpm run build", "lint:js": "eslint assets/js --ext=js", "docker:down": "pnpx wc-e2e docker:down", From ce1bc10ea3a604d1c1a730e2eb792dbd12d91efe Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 7 Apr 2022 10:44:04 +0800 Subject: [PATCH 099/386] Add changelog --- plugins/woocommerce/changelog/fix-32399-husky | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-32399-husky diff --git a/plugins/woocommerce/changelog/fix-32399-husky b/plugins/woocommerce/changelog/fix-32399-husky new file mode 100644 index 00000000000..da836bbc7d1 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-32399-husky @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Fix husky git hooks. From 4c4edc1c03ae1f8e7dc3d59945c00821ca2adfd8 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 7 Apr 2022 11:23:04 +0800 Subject: [PATCH 100/386] Fix css --- .../js/onboarding/src/components/WCPayAcceptedMethods.js | 8 ++++---- .../js/onboarding/src/components/WCPayCard/WCPayCard.scss | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/js/onboarding/src/components/WCPayAcceptedMethods.js b/packages/js/onboarding/src/components/WCPayAcceptedMethods.js index a79a1031c23..cea52fdb3d9 100644 --- a/packages/js/onboarding/src/components/WCPayAcceptedMethods.js +++ b/packages/js/onboarding/src/components/WCPayAcceptedMethods.js @@ -22,12 +22,12 @@ import GiroPay from '../images/cards/giropay.js'; import Sofort from '../images/cards/sofort.js'; export const WCPayAcceptedMethods = () => ( - <> +
{ __( 'Accepted payment methods include:', 'woocommerce' ) } -
+
@@ -40,9 +40,9 @@ export const WCPayAcceptedMethods = () => ( -
+
& more.
- +
); diff --git a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss index 65d81ee1a88..6e50f1f3edd 100644 --- a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss +++ b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss @@ -89,7 +89,7 @@ } } - .woocommerce-task-payment-wcpay__accepted { + .woocommerce-task-payment-wcpay__accepted-icons { h3 { color: #40464d; } @@ -115,7 +115,7 @@ letter-spacing: normal; } - .woocommerce-task-payment-wcpay__accepted__and-more { + .woocommerce-task-payment-wcpay__accepted-and-more { white-space: nowrap; } } From 70359c8d11d470e56a5e54c75fd72f2a92fec652 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 7 Apr 2022 14:16:46 +0800 Subject: [PATCH 101/386] Update @wordpress/a11y from ^2.15.3 to ^3.5.0 --- packages/js/notices/package.json | 2 +- plugins/woocommerce-admin/package.json | 2 +- pnpm-lock.yaml | 55 ++++++++++++++++++-------- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/packages/js/notices/package.json b/packages/js/notices/package.json index ca89ed63893..476b26a0789 100644 --- a/packages/js/notices/package.json +++ b/packages/js/notices/package.json @@ -22,7 +22,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@wordpress/a11y": "^2.15.3", + "@wordpress/a11y": "^3.5.0", "@wordpress/data": "^6.3.0", "@wordpress/notices": "^3.3.2" }, diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index a21d959a0dd..17ff33bf8e1 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -96,7 +96,7 @@ "@woocommerce/api": "^0.2.0", "@woocommerce/e2e-environment": "^0.3.0", "@woocommerce/e2e-utils": "^0.2.0", - "@wordpress/a11y": "^2.15.3", + "@wordpress/a11y": "^3.5.0", "@wordpress/api-fetch": "^6.0.1", "@wordpress/base-styles": "^3.6.0", "@wordpress/components": "^19.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87d9d2c2986..661111ef532 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -901,7 +901,7 @@ importers: packages/js/notices: specifiers: '@babel/core': ^7.17.5 - '@wordpress/a11y': ^2.15.3 + '@wordpress/a11y': ^3.5.0 '@wordpress/data': ^6.3.0 '@wordpress/eslint-plugin': ^11.0.0 '@wordpress/notices': ^3.3.2 @@ -912,7 +912,7 @@ importers: ts-jest: ^27.1.3 typescript: ^4.6.2 dependencies: - '@wordpress/a11y': 2.15.3 + '@wordpress/a11y': 3.5.0 '@wordpress/data': 6.4.1 '@wordpress/notices': 3.4.1 devDependencies: @@ -1226,7 +1226,7 @@ importers: '@woocommerce/onboarding': workspace:* '@woocommerce/style-build': workspace:* '@woocommerce/tracks': workspace:* - '@wordpress/a11y': ^2.15.3 + '@wordpress/a11y': ^3.5.0 '@wordpress/api-fetch': ^6.0.1 '@wordpress/babel-plugin-makepot': ^2.1.3 '@wordpress/babel-preset-default': ^6.5.1 @@ -1348,7 +1348,7 @@ importers: '@woocommerce/api': link:../../packages/js/api '@woocommerce/e2e-environment': link:../../packages/js/e2e-environment '@woocommerce/e2e-utils': link:../../packages/js/e2e-utils - '@wordpress/a11y': 2.15.3 + '@wordpress/a11y': 3.5.0 '@wordpress/api-fetch': 6.1.1 '@wordpress/base-styles': 3.6.0 '@wordpress/components': 19.6.1_978f344c876a57c1143ffe356b90df31 @@ -13779,14 +13779,6 @@ packages: react: 17.0.2 dev: true - /@wordpress/a11y/2.15.3: - resolution: {integrity: sha512-uoCznHY3/TaNWeXutLI6juC198ykaBwZ34P51PNHHQqi3WzVoBhFx6AnAR/9Uupl3tZcekefpkVHy7AJHMAPIA==} - dependencies: - '@babel/runtime': 7.17.7 - '@wordpress/dom-ready': 2.13.2 - '@wordpress/i18n': 3.20.0 - dev: false - /@wordpress/a11y/3.4.1: resolution: {integrity: sha512-SjeLO8x/Y/QAcKBrvyJiu8KVAPckRLNwuFfgX7zCGM8vBfg+Depj94Hp55ARLjq0oXHg7EWKxSdzNkvmTz8AIA==} engines: {node: '>=12'} @@ -13796,6 +13788,15 @@ packages: '@wordpress/i18n': 4.4.1 dev: false + /@wordpress/a11y/3.5.0: + resolution: {integrity: sha512-pJyDexol4yFfUNs6BAW1IKftdxZBsxvNRpzmYcwXiFA+1jSnMJFehp0nu47skdzxiHS6CKyLqBks17J+a/GqGA==} + engines: {node: '>=12'} + dependencies: + '@babel/runtime': 7.17.7 + '@wordpress/dom-ready': 3.5.0 + '@wordpress/i18n': 4.5.0 + dev: false + /@wordpress/api-fetch/5.2.6: resolution: {integrity: sha512-AG8KdCHwtYJWR38AAU7nEI+UbumUSqSBthQj3rShLUVyFbYGkQdpwXJJG6vFj7FjIp41zljiyj3K1Fh3cqdaAw==} engines: {node: '>=12'} @@ -14366,14 +14367,15 @@ packages: '@wordpress/hooks': 3.4.1 dev: false - /@wordpress/dom-ready/2.13.2: - resolution: {integrity: sha512-COH7n2uZfBq4FtluSbl37N3nCEcdMXzV42ETCWKUcumiP1Zd3qnkfQKcsxTaHWY8aVt/358RvJ7ghWe3xAd+fg==} + /@wordpress/dom-ready/3.4.1: + resolution: {integrity: sha512-w6DVKKpNwX0XUp0Cuh1OyFyGXLabr47k/ecRHKmQkQh9LdjRew7QvxUHYDN1rejRvq5GqcDb7Gnkz4E6hWIo4Q==} + engines: {node: '>=12'} dependencies: '@babel/runtime': 7.17.7 dev: false - /@wordpress/dom-ready/3.4.1: - resolution: {integrity: sha512-w6DVKKpNwX0XUp0Cuh1OyFyGXLabr47k/ecRHKmQkQh9LdjRew7QvxUHYDN1rejRvq5GqcDb7Gnkz4E6hWIo4Q==} + /@wordpress/dom-ready/3.5.0: + resolution: {integrity: sha512-xhxZx3qH0UoWI3AMvZpB7NnKkHR5m5ifrBlinXM3+kSPQ8bIUkuOi2cFYdCnglPi0a+dd7OahWKFzXwDvgjO1w==} engines: {node: '>=12'} dependencies: '@babel/runtime': 7.17.7 @@ -14716,6 +14718,13 @@ packages: dependencies: '@babel/runtime': 7.17.7 + /@wordpress/hooks/3.5.0: + resolution: {integrity: sha512-6Ko+1rWLq75s2LeZah6e0sJC5lC2nL1M+DDLlP/EZ+YCGZlIKoCvkhVBuCdI2wgIHRPXU56OqLvw85rUjsfDJw==} + engines: {node: '>=12'} + dependencies: + '@babel/runtime': 7.17.7 + dev: false + /@wordpress/html-entities/3.4.1: resolution: {integrity: sha512-wSuwgONTefnhCB9B7mKS+e8islHuCkprfDc+FhqVAa6r5RbVBGvaHUJs8embgdtww7MwBRMnskNf/buQ8Jr02A==} engines: {node: '>=12'} @@ -14749,6 +14758,20 @@ packages: sprintf-js: 1.1.2 tannin: 1.2.0 + /@wordpress/i18n/4.5.0: + resolution: {integrity: sha512-BhOHsgnbWeUWT+23P/vksen0MiZ+OyhemZkKUHtQnelRKY4FnFEYvjp5q9v/O7DY3J0Hqc+Ss4wLNqajRQmMIw==} + engines: {node: '>=12'} + hasBin: true + dependencies: + '@babel/runtime': 7.17.7 + '@wordpress/hooks': 3.5.0 + gettext-parser: 1.4.0 + lodash: 4.17.21 + memize: 1.1.0 + sprintf-js: 1.1.2 + tannin: 1.2.0 + dev: false + /@wordpress/icons/6.3.0: resolution: {integrity: sha512-Vliw7QsFuTsrA05GZov4i3PQiLQOGO97PR2keUeY53fVZdeoJKv/nfDqOZxZCIts5jR2Mfje6P6hc/KlurxsKg==} engines: {node: '>=12'} From c60db1215e576addf13e54e14d654f120f8e343a Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 7 Apr 2022 14:18:13 +0800 Subject: [PATCH 102/386] Update @wordpress/hooks from ^2.12.3 to ^3.5.0 --- packages/js/components/package.json | 2 +- packages/js/data/package.json | 2 +- packages/js/explat/package.json | 2 +- packages/js/navigation/package.json | 2 +- plugins/woocommerce-admin/package.json | 2 +- pnpm-lock.yaml | 32 +++++++++++++------------- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/js/components/package.json b/packages/js/components/package.json index b9ec712baa5..cff938a6107 100644 --- a/packages/js/components/package.json +++ b/packages/js/components/package.json @@ -39,7 +39,7 @@ "@wordpress/deprecated": "^3.3.1", "@wordpress/dom": "^3.3.2", "@wordpress/element": "^4.1.1", - "@wordpress/hooks": "^2.12.3", + "@wordpress/hooks": "^3.5.0", "@wordpress/html-entities": "^3.3.1", "@wordpress/i18n": "^4.3.1", "@wordpress/icons": "^6.3.0", diff --git a/packages/js/data/package.json b/packages/js/data/package.json index 1308e6827d0..f77d1322bae 100644 --- a/packages/js/data/package.json +++ b/packages/js/data/package.json @@ -30,7 +30,7 @@ "@wordpress/data-controls": "^2.3.2", "@wordpress/deprecated": "^3.3.1", "@wordpress/element": "^4.1.1", - "@wordpress/hooks": "^2.12.3", + "@wordpress/hooks": "^3.5.0", "@wordpress/i18n": "^4.3.1", "@wordpress/url": "^3.4.1", "dompurify": "^2.3.6", diff --git a/packages/js/explat/package.json b/packages/js/explat/package.json index 533172d328f..91de23b69c3 100644 --- a/packages/js/explat/package.json +++ b/packages/js/explat/package.json @@ -28,7 +28,7 @@ "@automattic/explat-client": "^0.0.3", "@automattic/explat-client-react-helpers": "^0.0.4", "@wordpress/api-fetch": "^6.0.1", - "@wordpress/hooks": "^2.12.3", + "@wordpress/hooks": "^3.5.0", "cookie": "^0.4.2", "qs": "^6.10.3" }, diff --git a/packages/js/navigation/package.json b/packages/js/navigation/package.json index c4b578c2cc6..af94fb36e60 100644 --- a/packages/js/navigation/package.json +++ b/packages/js/navigation/package.json @@ -25,7 +25,7 @@ "@wordpress/components": "^19.5.0", "@wordpress/compose": "^5.1.2", "@wordpress/element": "^4.1.1", - "@wordpress/hooks": "^2.12.3", + "@wordpress/hooks": "^3.5.0", "@wordpress/notices": "^3.3.2", "@wordpress/url": "^3.4.1", "history": "^4.10.1", diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 17ff33bf8e1..993949bf8c9 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -107,7 +107,7 @@ "@wordpress/dom": "^3.3.2", "@wordpress/dom-ready": "^3.3.1", "@wordpress/element": "^4.1.1", - "@wordpress/hooks": "^2.12.3", + "@wordpress/hooks": "^3.5.0", "@wordpress/html-entities": "^3.3.1", "@wordpress/i18n": "^4.3.1", "@wordpress/icons": "^6.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 661111ef532..acb4d13c849 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -177,7 +177,7 @@ importers: '@wordpress/dom': ^3.3.2 '@wordpress/element': ^4.1.1 '@wordpress/eslint-plugin': ^11.0.0 - '@wordpress/hooks': ^2.12.3 + '@wordpress/hooks': ^3.5.0 '@wordpress/html-entities': ^3.3.1 '@wordpress/i18n': ^4.3.1 '@wordpress/icons': ^6.3.0 @@ -230,7 +230,7 @@ importers: '@wordpress/deprecated': 3.4.1 '@wordpress/dom': 3.4.1 '@wordpress/element': 4.2.1 - '@wordpress/hooks': 2.12.3 + '@wordpress/hooks': 3.5.0 '@wordpress/html-entities': 3.4.1 '@wordpress/i18n': 4.4.1 '@wordpress/icons': 6.3.0 @@ -428,7 +428,7 @@ importers: '@wordpress/deprecated': ^3.3.1 '@wordpress/element': ^4.1.1 '@wordpress/eslint-plugin': ^11.0.0 - '@wordpress/hooks': ^2.12.3 + '@wordpress/hooks': ^3.5.0 '@wordpress/i18n': ^4.3.1 '@wordpress/url': ^3.4.1 dompurify: ^2.3.6 @@ -451,7 +451,7 @@ importers: '@wordpress/data-controls': 2.4.1 '@wordpress/deprecated': 3.4.1 '@wordpress/element': 4.2.1 - '@wordpress/hooks': 2.12.3 + '@wordpress/hooks': 3.5.0 '@wordpress/i18n': 4.4.1 '@wordpress/url': 3.5.1 dompurify: 2.3.6 @@ -795,7 +795,7 @@ importers: '@types/qs': ^6.9.7 '@wordpress/api-fetch': ^6.0.1 '@wordpress/eslint-plugin': ^11.0.0 - '@wordpress/hooks': ^2.12.3 + '@wordpress/hooks': ^3.5.0 cookie: ^0.4.2 eslint: ^8.12.0 jest: ^27.5.1 @@ -808,7 +808,7 @@ importers: '@automattic/explat-client': 0.0.3 '@automattic/explat-client-react-helpers': 0.0.4 '@wordpress/api-fetch': 6.1.1 - '@wordpress/hooks': 2.12.3 + '@wordpress/hooks': 3.5.0 cookie: 0.4.2 qs: 6.10.3 devDependencies: @@ -866,7 +866,7 @@ importers: '@wordpress/compose': ^5.1.2 '@wordpress/element': ^4.1.1 '@wordpress/eslint-plugin': ^11.0.0 - '@wordpress/hooks': ^2.12.3 + '@wordpress/hooks': ^3.5.0 '@wordpress/notices': ^3.3.2 '@wordpress/url': ^3.4.1 eslint: ^8.12.0 @@ -882,7 +882,7 @@ importers: '@wordpress/components': 19.6.1_@babel+core@7.17.8 '@wordpress/compose': 5.2.1 '@wordpress/element': 4.2.1 - '@wordpress/hooks': 2.12.3 + '@wordpress/hooks': 3.5.0 '@wordpress/notices': 3.4.1 '@wordpress/url': 3.5.1 history: 4.10.1 @@ -1242,7 +1242,7 @@ importers: '@wordpress/dom-ready': ^3.3.1 '@wordpress/element': ^4.1.1 '@wordpress/eslint-plugin': ^11.0.0 - '@wordpress/hooks': ^2.12.3 + '@wordpress/hooks': ^3.5.0 '@wordpress/html-entities': ^3.3.1 '@wordpress/i18n': ^4.3.1 '@wordpress/icons': ^6.3.0 @@ -1359,7 +1359,7 @@ importers: '@wordpress/dom': 3.4.1 '@wordpress/dom-ready': 3.4.1 '@wordpress/element': 4.2.1 - '@wordpress/hooks': 2.12.3 + '@wordpress/hooks': 3.5.0 '@wordpress/html-entities': 3.4.1 '@wordpress/i18n': 4.4.1 '@wordpress/icons': 6.3.0 @@ -13983,7 +13983,7 @@ packages: '@wordpress/deprecated': 3.4.1 '@wordpress/dom': 3.4.1 '@wordpress/element': 4.2.1 - '@wordpress/hooks': 3.4.1 + '@wordpress/hooks': 3.5.0 '@wordpress/html-entities': 3.4.1 '@wordpress/i18n': 4.4.1 '@wordpress/is-shallow-equal': 4.4.1 @@ -14012,7 +14012,7 @@ packages: '@wordpress/deprecated': 3.4.1 '@wordpress/dom': 3.4.1 '@wordpress/element': 4.2.1 - '@wordpress/hooks': 3.4.1 + '@wordpress/hooks': 3.5.0 '@wordpress/html-entities': 3.4.1 '@wordpress/i18n': 4.4.1 '@wordpress/is-shallow-equal': 4.4.1 @@ -14062,7 +14062,7 @@ packages: '@wordpress/dom': 3.4.1 '@wordpress/element': 4.2.1 '@wordpress/escape-html': 2.4.1 - '@wordpress/hooks': 3.4.1 + '@wordpress/hooks': 3.5.0 '@wordpress/i18n': 4.4.1 '@wordpress/icons': 8.0.1 '@wordpress/is-shallow-equal': 4.4.1 @@ -14115,7 +14115,7 @@ packages: '@wordpress/dom': 3.4.1 '@wordpress/element': 4.2.1 '@wordpress/escape-html': 2.4.1 - '@wordpress/hooks': 3.4.1 + '@wordpress/hooks': 3.5.0 '@wordpress/i18n': 4.4.1 '@wordpress/icons': 8.0.1 '@wordpress/is-shallow-equal': 4.4.1 @@ -14364,7 +14364,7 @@ packages: engines: {node: '>=12'} dependencies: '@babel/runtime': 7.17.7 - '@wordpress/hooks': 3.4.1 + '@wordpress/hooks': 3.5.0 dev: false /@wordpress/dom-ready/3.4.1: @@ -14973,7 +14973,7 @@ packages: '@babel/runtime': 7.17.7 '@wordpress/compose': 5.2.1_react@17.0.2 '@wordpress/element': 4.2.1 - '@wordpress/hooks': 3.4.1 + '@wordpress/hooks': 3.5.0 '@wordpress/icons': 8.0.1 lodash: 4.17.21 memize: 1.1.0 From 317dd7c9e92c397214f21c639e4f36e111b7a571 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 7 Apr 2022 14:21:33 +0800 Subject: [PATCH 103/386] Update @wordpress/base-styles from ^3.6.0 to ^4.3.0 --- packages/js/style-build/package.json | 2 +- plugins/woocommerce-admin/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js/style-build/package.json b/packages/js/style-build/package.json index 96926105d6c..2d7cbe02f56 100644 --- a/packages/js/style-build/package.json +++ b/packages/js/style-build/package.json @@ -19,7 +19,7 @@ "main": "index.js", "dependencies": { "@automattic/color-studio": "^2.5.0", - "@wordpress/base-styles": "^3.6.0", + "@wordpress/base-styles": "^4.3.0", "@wordpress/postcss-plugins-preset": "^1.6.0", "css-loader": "^3.6.0", "mini-css-extract-plugin": "^2.6.0", diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 993949bf8c9..07c026f3e66 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -98,7 +98,7 @@ "@woocommerce/e2e-utils": "^0.2.0", "@wordpress/a11y": "^3.5.0", "@wordpress/api-fetch": "^6.0.1", - "@wordpress/base-styles": "^3.6.0", + "@wordpress/base-styles": "^4.3.0", "@wordpress/components": "^19.5.0", "@wordpress/compose": "^5.1.2", "@wordpress/core-data": "^4.1.2", From 344338704501af8315dc18434604b716187d89f1 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 7 Apr 2022 14:24:19 +0800 Subject: [PATCH 104/386] Update @wordpress/icons from ^6.3.0 to ^8.1.0 --- packages/js/components/package.json | 2 +- packages/js/experimental/package.json | 2 +- plugins/woocommerce-admin/package.json | 2 +- pnpm-lock.yaml | 73 +++++++++++++++++++------- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/packages/js/components/package.json b/packages/js/components/package.json index cff938a6107..5246dda39aa 100644 --- a/packages/js/components/package.json +++ b/packages/js/components/package.json @@ -42,7 +42,7 @@ "@wordpress/hooks": "^3.5.0", "@wordpress/html-entities": "^3.3.1", "@wordpress/i18n": "^4.3.1", - "@wordpress/icons": "^6.3.0", + "@wordpress/icons": "^8.1.0", "@wordpress/keycodes": "^3.3.1", "@wordpress/url": "^3.4.1", "@wordpress/viewport": "^4.1.2", diff --git a/packages/js/experimental/package.json b/packages/js/experimental/package.json index 8a7530f6085..225a29476f3 100644 --- a/packages/js/experimental/package.json +++ b/packages/js/experimental/package.json @@ -29,7 +29,7 @@ "@wordpress/components": "^19.5.0", "@wordpress/element": "^4.1.1", "@wordpress/i18n": "^4.3.1", - "@wordpress/icons": "^6.3.0", + "@wordpress/icons": "^8.1.0", "@wordpress/keycodes": "^3.3.1", "classnames": "^2.3.1", "dompurify": "^2.3.6", diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 07c026f3e66..0b26f2c91dd 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -110,7 +110,7 @@ "@wordpress/hooks": "^3.5.0", "@wordpress/html-entities": "^3.3.1", "@wordpress/i18n": "^4.3.1", - "@wordpress/icons": "^6.3.0", + "@wordpress/icons": "^8.1.0", "@wordpress/keycodes": "^3.3.1", "@wordpress/notices": "^3.3.2", "@wordpress/plugins": "^4.1.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index acb4d13c849..e5b72f2030c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -180,7 +180,7 @@ importers: '@wordpress/hooks': ^3.5.0 '@wordpress/html-entities': ^3.3.1 '@wordpress/i18n': ^4.3.1 - '@wordpress/icons': ^6.3.0 + '@wordpress/icons': ^8.1.0 '@wordpress/keycodes': ^3.3.1 '@wordpress/scripts': ^12.6.1 '@wordpress/url': ^3.4.1 @@ -233,7 +233,7 @@ importers: '@wordpress/hooks': 3.5.0 '@wordpress/html-entities': 3.4.1 '@wordpress/i18n': 4.4.1 - '@wordpress/icons': 6.3.0 + '@wordpress/icons': 8.1.0 '@wordpress/keycodes': 3.4.1 '@wordpress/url': 3.5.1 '@wordpress/viewport': 4.2.1 @@ -725,7 +725,7 @@ importers: '@wordpress/element': ^4.1.1 '@wordpress/eslint-plugin': ^11.0.0 '@wordpress/i18n': ^4.3.1 - '@wordpress/icons': ^6.3.0 + '@wordpress/icons': ^8.1.0 '@wordpress/keycodes': ^3.3.1 classnames: ^2.3.1 concurrently: ^7.0.0 @@ -750,7 +750,7 @@ importers: '@wordpress/components': 19.6.1_@babel+core@7.17.8 '@wordpress/element': 4.2.1 '@wordpress/i18n': 4.4.1 - '@wordpress/icons': 6.3.0 + '@wordpress/icons': 8.1.0 '@wordpress/keycodes': 3.4.1 classnames: 2.3.1 dompurify: 2.3.6 @@ -1007,7 +1007,7 @@ importers: specifiers: '@automattic/color-studio': ^2.5.0 '@babel/core': ^7.17.5 - '@wordpress/base-styles': ^3.6.0 + '@wordpress/base-styles': ^4.3.0 '@wordpress/eslint-plugin': ^11.0.0 '@wordpress/postcss-plugins-preset': ^1.6.0 css-loader: ^3.6.0 @@ -1025,7 +1025,7 @@ importers: webpack-rtl-plugin: ^2.0.0 dependencies: '@automattic/color-studio': 2.5.0 - '@wordpress/base-styles': 3.6.0 + '@wordpress/base-styles': 4.3.0 '@wordpress/postcss-plugins-preset': 1.6.0 css-loader: 3.6.0_webpack@5.70.0 mini-css-extract-plugin: 2.6.0_webpack@5.70.0 @@ -1230,7 +1230,7 @@ importers: '@wordpress/api-fetch': ^6.0.1 '@wordpress/babel-plugin-makepot': ^2.1.3 '@wordpress/babel-preset-default': ^6.5.1 - '@wordpress/base-styles': ^3.6.0 + '@wordpress/base-styles': ^4.3.0 '@wordpress/browserslist-config': ^4.1.1 '@wordpress/components': ^19.5.0 '@wordpress/compose': ^5.1.2 @@ -1245,7 +1245,7 @@ importers: '@wordpress/hooks': ^3.5.0 '@wordpress/html-entities': ^3.3.1 '@wordpress/i18n': ^4.3.1 - '@wordpress/icons': ^6.3.0 + '@wordpress/icons': ^8.1.0 '@wordpress/jest-preset-default': ^8.0.1 '@wordpress/keycodes': ^3.3.1 '@wordpress/notices': ^3.3.2 @@ -1350,7 +1350,7 @@ importers: '@woocommerce/e2e-utils': link:../../packages/js/e2e-utils '@wordpress/a11y': 3.5.0 '@wordpress/api-fetch': 6.1.1 - '@wordpress/base-styles': 3.6.0 + '@wordpress/base-styles': 4.3.0 '@wordpress/components': 19.6.1_978f344c876a57c1143ffe356b90df31 '@wordpress/compose': 5.2.1_react@17.0.2 '@wordpress/core-data': 4.2.1_react@17.0.2 @@ -1362,7 +1362,7 @@ importers: '@wordpress/hooks': 3.5.0 '@wordpress/html-entities': 3.4.1 '@wordpress/i18n': 4.4.1 - '@wordpress/icons': 6.3.0 + '@wordpress/icons': 8.1.0 '@wordpress/keycodes': 3.4.1 '@wordpress/notices': 3.4.1_react@17.0.2 '@wordpress/plugins': 4.2.1_react@17.0.2 @@ -13954,6 +13954,10 @@ packages: /@wordpress/base-styles/3.6.0: resolution: {integrity: sha512-6/vXAmc9FSX7Y17UjKgUJoVU++Pv1U1G8uMx7iClRUaLetc7/jj2DD9PTyX/cdJjHr32e3yXuLVT9wfEbo6SEg==} + /@wordpress/base-styles/4.3.0: + resolution: {integrity: sha512-e9Z+txhEQ3zyAHkzzsuYg1ADFhKArz1eGU3ayqCNtCdakrgNjI6Q/sPODI26LlwTmjJPBIJ5wSCBrsDjMhdWqA==} + dev: false + /@wordpress/blob/3.4.1: resolution: {integrity: sha512-rGm7nXaxnsXStIu9v9IjbUOKtE9UzkvgYiJMX5SyVyzAGLOo2Aq759+JNRDLRR0RDkS6igH/G7qBXS6xSgLFgA==} engines: {node: '>=12'} @@ -14492,6 +14496,19 @@ packages: react: 17.0.2 react-dom: 17.0.2_react@17.0.2 + /@wordpress/element/4.3.0: + resolution: {integrity: sha512-QN5qsNN6kzbHgrCL9CG2877iOu01KMEwls1K3iKk43EQ8hr/D/Ms/h5TqfOgF6oIGUR/QUlbeZQJs4zdvEnFOg==} + engines: {node: '>=12'} + dependencies: + '@babel/runtime': 7.17.7 + '@types/react': 17.0.40 + '@types/react-dom': 17.0.13 + '@wordpress/escape-html': 2.5.0 + lodash: 4.17.21 + react: 17.0.2 + react-dom: 17.0.2_react@17.0.2 + dev: false + /@wordpress/escape-html/1.12.2: resolution: {integrity: sha512-FabgSwznhdaUwe6hr1CsGpgxQbzqEoGevv73WIL1B9GvlZ6csRWodgHfWh4P6fYqpzxFL4WYB8wPJ1PdO32XFA==} dependencies: @@ -14511,6 +14528,13 @@ packages: dependencies: '@babel/runtime': 7.17.7 + /@wordpress/escape-html/2.5.0: + resolution: {integrity: sha512-WV4jI6uBPZNxxOQdftiOsx1WgimkjxnwCfx6T+K7Ltfnm78Q5q2P5R98twGOqSVI/rPqtZubv9e7oMDbpp4H2w==} + engines: {node: '>=12'} + dependencies: + '@babel/runtime': 7.17.7 + dev: false + /@wordpress/eslint-plugin/11.0.1_2205da2c9bef219d53091cb9dbc5524c: resolution: {integrity: sha512-HDKwKjOmCaWdyJEtWKRAd0xK/NAXL/ykUP/I8l+zCvzvCXbS1UuixWN09RRzl09tv17JUtPiEqehDilkWRCBZg==} engines: {node: '>=12', npm: '>=6.9'} @@ -14772,15 +14796,6 @@ packages: tannin: 1.2.0 dev: false - /@wordpress/icons/6.3.0: - resolution: {integrity: sha512-Vliw7QsFuTsrA05GZov4i3PQiLQOGO97PR2keUeY53fVZdeoJKv/nfDqOZxZCIts5jR2Mfje6P6hc/KlurxsKg==} - engines: {node: '>=12'} - dependencies: - '@babel/runtime': 7.17.7 - '@wordpress/element': 4.2.1 - '@wordpress/primitives': 3.2.1 - dev: false - /@wordpress/icons/8.0.1: resolution: {integrity: sha512-+K0yWNSMR6d/Q/Zlixw6I7/s8UIeMFuJUET3LhprMGLO3K+t+o/8xqfgXAMf4GiVkc0YV+kXOuCsMMXFFwzi+A==} engines: {node: '>=12'} @@ -14790,6 +14805,15 @@ packages: '@wordpress/primitives': 3.2.1 dev: false + /@wordpress/icons/8.1.0: + resolution: {integrity: sha512-fNq0Mnzzf03uxIwKqQeU/G48wElyypwkhcBZWYQRpmwLZrOR231dxUeK9mzPOSGlYbgM+YKK+k3lzysGmrJU0A==} + engines: {node: '>=12'} + dependencies: + '@babel/runtime': 7.17.7 + '@wordpress/element': 4.3.0 + '@wordpress/primitives': 3.3.0 + dev: false + /@wordpress/is-shallow-equal/4.4.1: resolution: {integrity: sha512-NlcqqrukKe4zT5fCs3O5FVYwqmHhtqM//KqWs7xfIaoz9B07oKZQNZqOrU72mgz7mgRliQumTQHzFM76RO0hZQ==} engines: {node: '>=12'} @@ -14974,7 +14998,7 @@ packages: '@wordpress/compose': 5.2.1_react@17.0.2 '@wordpress/element': 4.2.1 '@wordpress/hooks': 3.5.0 - '@wordpress/icons': 8.0.1 + '@wordpress/icons': 8.1.0 lodash: 4.17.21 memize: 1.1.0 react: 17.0.2 @@ -15027,6 +15051,15 @@ packages: classnames: 2.3.1 dev: false + /@wordpress/primitives/3.3.0: + resolution: {integrity: sha512-iwlFGSaI2RnQF0SxsWJ3KaM0LPdUosI5mb9879JXOh/vAFVObrQdyk5Fv+++vGUzDfxRnxAH68UpJi7nOzcRRA==} + engines: {node: '>=12'} + dependencies: + '@babel/runtime': 7.17.7 + '@wordpress/element': 4.3.0 + classnames: 2.3.1 + dev: false + /@wordpress/priority-queue/2.4.1: resolution: {integrity: sha512-5+pyUvQCQTTkoiccnO5G6AUDxzCKdAiDh3oLbl+qLz3j56iGuLoKWR6L5ySj+knaYIZb4g8expFsbvf2+RcVtw==} engines: {node: '>=12'} From a1a63e943382d03cdb7c379cb3f83f05fcbaf045 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 7 Apr 2022 14:34:42 +0800 Subject: [PATCH 105/386] Update package changelogs --- packages/js/components/CHANGELOG.md | 3 +++ packages/js/data/CHANGELOG.md | 2 ++ packages/js/experimental/CHANGELOG.md | 2 ++ packages/js/explat/CHANGELOG.md | 2 ++ packages/js/navigation/CHANGELOG.md | 2 ++ packages/js/notices/CHANGELOG.md | 2 ++ 6 files changed, 13 insertions(+) diff --git a/packages/js/components/CHANGELOG.md b/packages/js/components/CHANGELOG.md index 798bde85839..1453039b5d5 100644 --- a/packages/js/components/CHANGELOG.md +++ b/packages/js/components/CHANGELOG.md @@ -1,5 +1,8 @@ # Unreleased +- Update dependency `@wordpress/hooks` to ^3.5.0 +- Update dependency `@wordpress/icons` to ^8.1.0 + # 10.0.0 - Replace deprecated wp.compose.withState with wp.element.useState. #8338 - Add missing dependencies. #8349 diff --git a/packages/js/data/CHANGELOG.md b/packages/js/data/CHANGELOG.md index d7acf37a3dd..cb955c6b7e2 100644 --- a/packages/js/data/CHANGELOG.md +++ b/packages/js/data/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- Update dependency `@wordpress/hooks` to ^3.5.0 + # 3.1.0 - Add "moment" to peerDependencies. #8349 diff --git a/packages/js/experimental/CHANGELOG.md b/packages/js/experimental/CHANGELOG.md index 1e9c72b068d..c43445f78c6 100644 --- a/packages/js/experimental/CHANGELOG.md +++ b/packages/js/experimental/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- Update dependency `@wordpress/icons` to ^8.1.0 + # 3.0.1 - Update all js packages with minor/patch version changes. #8392 diff --git a/packages/js/explat/CHANGELOG.md b/packages/js/explat/CHANGELOG.md index 9a709dc19f6..1c2faa25278 100644 --- a/packages/js/explat/CHANGELOG.md +++ b/packages/js/explat/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- Update dependency `@wordpress/hooks` to ^3.5.0 + # 2.1.0 - Add missing dependencies. #8349 diff --git a/packages/js/navigation/CHANGELOG.md b/packages/js/navigation/CHANGELOG.md index b90c2da966b..81fd1afc1cd 100644 --- a/packages/js/navigation/CHANGELOG.md +++ b/packages/js/navigation/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- Update dependency `@wordpress/hooks` to ^3.5.0 + # 7.0.1 - Add missing dependencies. #8349 diff --git a/packages/js/notices/CHANGELOG.md b/packages/js/notices/CHANGELOG.md index c738abfb1d1..e0a22a3fc5c 100644 --- a/packages/js/notices/CHANGELOG.md +++ b/packages/js/notices/CHANGELOG.md @@ -2,6 +2,8 @@ # Unreleased +- Update dependency `@wordpress/a11y` to ^3.5.0 + # 4.0.1 - Update all js packages with minor/patch version changes. #8392 From 3fc88f0a71a24069d1a9568a3335a075b443a53a Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 7 Apr 2022 16:50:40 +0800 Subject: [PATCH 106/386] Update links of woocommerce-admin to https://github.com/woocommerce/woocommerce --- .github/CONTRIBUTING.md | 3 +-- packages/js/README.md | 6 +++--- packages/js/admin-e2e-tests/package.json | 4 ++-- packages/js/components/package.json | 6 +++--- packages/js/components/src/advanced-filters/README.md | 2 +- packages/js/csv-export/package.json | 6 +++--- packages/js/currency/package.json | 6 +++--- packages/js/customer-effort-score/package.json | 6 +++--- packages/js/data/package.json | 6 +++--- packages/js/date/package.json | 6 +++--- .../dependency-extraction-webpack-plugin/package.json | 6 +++--- packages/js/eslint-plugin/package.json | 6 +++--- packages/js/experimental/package.json | 6 +++--- packages/js/explat/package.json | 6 +++--- packages/js/js-tests/package.json | 6 +++--- packages/js/navigation/package.json | 6 +++--- packages/js/number/package.json | 6 +++--- packages/js/onboarding/package.json | 6 +++--- packages/js/style-build/package.json | 6 +++--- packages/js/tracks/package.json | 6 +++--- plugins/woocommerce-admin/bin/starter-pack/README.md | 6 +++--- plugins/woocommerce-admin/docs/data.md | 4 ++-- plugins/woocommerce-admin/docs/features/onboarding.md | 10 +++++----- .../docs/features/payment-gateway-suggestions.md | 6 +++--- .../docs/woocommerce.com/analytics-orders-report.md | 2 +- 25 files changed, 69 insertions(+), 70 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 677999b111b..cbe382c842e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -41,9 +41,8 @@ If you have questions about the process to contribute code or want to discuss de - Make sure to write good and detailed commit messages (see [this post](https://chris.beams.io/posts/git-commit/) for more on this) and follow all the applicable sections of the pull request template. - Please avoid modifying the changelog directly or updating the .pot files. These will be updated by the WooCommerce team. -If you are contributing code to the (Javascript-driven) WooCommerce Admin project or to Gutenberg blocks, note that these are developed in external packages. +If you are contributing code to the (Javascript-driven) to Gutenberg blocks, note that it's developed in external package. -- [WooCommerce Admin](https://github.com/woocommerce/woocommerce-admin) - [Blocks](https://github.com/woocommerce/woocommerce-gutenberg-products-block) ## Feature Requests 🚀 diff --git a/packages/js/README.md b/packages/js/README.md index 25ca7a90007..5f82f22a152 100644 --- a/packages/js/README.md +++ b/packages/js/README.md @@ -36,13 +36,13 @@ To create a new package, add a new folder to `/packages`, containing… "author": "Automattic", "license": "GPL-2.0-or-later", "keywords": [ "wordpress", "woocommerce" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/[_YOUR_PACKAGE_]/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/main/packages/[_YOUR_PACKAGE_]/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/js/admin-e2e-tests/package.json b/packages/js/admin-e2e-tests/package.json index be6a8efe6d2..0435f6505ca 100644 --- a/packages/js/admin-e2e-tests/package.json +++ b/packages/js/admin-e2e-tests/package.json @@ -3,10 +3,10 @@ "version": "1.0.0", "author": "Automattic", "description": "E2E tests for the new WooCommerce interface.", - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/admin-e2e-tests/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/admin-e2e-tests/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "keywords": [ "woocommerce", diff --git a/packages/js/components/package.json b/packages/js/components/package.json index b9ec712baa5..575eab37ccf 100644 --- a/packages/js/components/package.json +++ b/packages/js/components/package.json @@ -9,13 +9,13 @@ "woocommerce", "components" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/components/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/components/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/js/components/src/advanced-filters/README.md b/packages/js/components/src/advanced-filters/README.md index e9cbc16bd8c..72da82a2994 100644 --- a/packages/js/components/src/advanced-filters/README.md +++ b/packages/js/components/src/advanced-filters/README.md @@ -124,7 +124,7 @@ const config = { }; ``` -`type`: A string Autocompleter type used by the [Search Component](https://github.com/woocommerce/woocommerce-admin/tree/main/packages/components/src/search). +`type`: A string Autocompleter type used by the [Search Component](https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/components/src/search). `getLabels`: A function returning a Promise resolving to an array of objects with `id` and `label` properties. ### Date diff --git a/packages/js/csv-export/package.json b/packages/js/csv-export/package.json index 50722c87690..dea69a47967 100644 --- a/packages/js/csv-export/package.json +++ b/packages/js/csv-export/package.json @@ -9,13 +9,13 @@ "woocommerce", "csv" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/csv-export/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/csv-export/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/js/currency/package.json b/packages/js/currency/package.json index 80661eca8bd..638cc6cd419 100644 --- a/packages/js/currency/package.json +++ b/packages/js/currency/package.json @@ -9,13 +9,13 @@ "woocommerce", "currency" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/currency/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/currency/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/js/customer-effort-score/package.json b/packages/js/customer-effort-score/package.json index c1bd32fd97c..bfa584d4539 100644 --- a/packages/js/customer-effort-score/package.json +++ b/packages/js/customer-effort-score/package.json @@ -8,13 +8,13 @@ "wordpress", "woocommerce" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/customer-effort-score/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/customer-effort-score/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/js/data/package.json b/packages/js/data/package.json index 1308e6827d0..a49964b96fa 100644 --- a/packages/js/data/package.json +++ b/packages/js/data/package.json @@ -9,13 +9,13 @@ "woocommerce", "data" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/data/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/data/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/js/date/package.json b/packages/js/date/package.json index eb6240d2f7c..621ca580509 100644 --- a/packages/js/date/package.json +++ b/packages/js/date/package.json @@ -9,13 +9,13 @@ "woocommerce", "date" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/date/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/date/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/js/dependency-extraction-webpack-plugin/package.json b/packages/js/dependency-extraction-webpack-plugin/package.json index eccc6705f2b..6d3bb279fc6 100644 --- a/packages/js/dependency-extraction-webpack-plugin/package.json +++ b/packages/js/dependency-extraction-webpack-plugin/package.json @@ -8,13 +8,13 @@ "wordpress", "woocommerce" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/dependency-extraction-webpack-plugin/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/dependency-extraction-webpack-plugin/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "src/index.js", "dependencies": { diff --git a/packages/js/eslint-plugin/package.json b/packages/js/eslint-plugin/package.json index 787d6b7011d..df23e6f4649 100644 --- a/packages/js/eslint-plugin/package.json +++ b/packages/js/eslint-plugin/package.json @@ -10,14 +10,14 @@ "eslint", "plugin" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/eslint-plugin/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/eslint-plugin/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git", + "url": "https://github.com/woocommerce/woocommerce.git", "directory": "packages/eslint-plugin" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "files": [ "configs", diff --git a/packages/js/experimental/package.json b/packages/js/experimental/package.json index 8a7530f6085..3ce8b604be5 100644 --- a/packages/js/experimental/package.json +++ b/packages/js/experimental/package.json @@ -9,13 +9,13 @@ "woocommerce", "experimental" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/experimental/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/experimental/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/js/explat/package.json b/packages/js/explat/package.json index 533172d328f..7332a9b9b4a 100644 --- a/packages/js/explat/package.json +++ b/packages/js/explat/package.json @@ -10,13 +10,13 @@ "abtest", "explat" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/explat/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/explat/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/js/js-tests/package.json b/packages/js/js-tests/package.json index c6de210f262..a722949e727 100644 --- a/packages/js/js-tests/package.json +++ b/packages/js/js-tests/package.json @@ -4,14 +4,14 @@ "description": "JavaScript test tooling.", "author": "Automattic", "license": "GPL-2.0-or-later", - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/js-tests/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/js-tests/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git", + "url": "https://github.com/woocommerce/woocommerce.git", "directory": "packages/js-tests" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "private": true, "main": "build/util/index.js", diff --git a/packages/js/navigation/package.json b/packages/js/navigation/package.json index c4b578c2cc6..8c7093c52f9 100644 --- a/packages/js/navigation/package.json +++ b/packages/js/navigation/package.json @@ -9,13 +9,13 @@ "woocommerce", "navigation" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/navigation/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/navigation/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/js/number/package.json b/packages/js/number/package.json index d7daf0f01d0..adc37093980 100644 --- a/packages/js/number/package.json +++ b/packages/js/number/package.json @@ -8,13 +8,13 @@ "wordpress", "woocommerce" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/number/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/number/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/js/onboarding/package.json b/packages/js/onboarding/package.json index 8ee2642b336..131e36872d2 100644 --- a/packages/js/onboarding/package.json +++ b/packages/js/onboarding/package.json @@ -9,13 +9,13 @@ "woocommerce", "onboarding" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/onboarding/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/onboarding/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/js/style-build/package.json b/packages/js/style-build/package.json index 96926105d6c..880fc27aafe 100644 --- a/packages/js/style-build/package.json +++ b/packages/js/style-build/package.json @@ -8,13 +8,13 @@ "wordpress", "woocommerce" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/style-build/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/style-build/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "index.js", "dependencies": { diff --git a/packages/js/tracks/package.json b/packages/js/tracks/package.json index 314b4c9abe5..8e3a0f2cbb0 100644 --- a/packages/js/tracks/package.json +++ b/packages/js/tracks/package.json @@ -9,13 +9,13 @@ "woocommerce", "tracks" ], - "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/tracks/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/tracks/README.md", "repository": { "type": "git", - "url": "https://github.com/woocommerce/woocommerce-admin.git" + "url": "https://github.com/woocommerce/woocommerce.git" }, "bugs": { - "url": "https://github.com/woocommerce/woocommerce-admin/issues" + "url": "https://github.com/woocommerce/woocommerce/issues" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/plugins/woocommerce-admin/bin/starter-pack/README.md b/plugins/woocommerce-admin/bin/starter-pack/README.md index 2ab0b68740d..eb549bd5668 100644 --- a/plugins/woocommerce-admin/bin/starter-pack/README.md +++ b/plugins/woocommerce-admin/bin/starter-pack/README.md @@ -5,12 +5,12 @@ Scaffold a modern JavaScript WordPress plugin with WooCommerce tooling. ## Includes - [wp-scripts](https://github.com/WordPress/gutenberg/tree/master/packages/scripts) -- [WooCommerce Dependency Extraction Webpack Plugin](https://github.com/woocommerce/woocommerce-admin/tree/main/packages/dependency-extraction-webpack-plugin) -- [WooCommerce ESLint Plugin with WordPress Prettier](https://github.com/woocommerce/woocommerce-admin/tree/main/packages/eslint-plugin) +- [WooCommerce Dependency Extraction Webpack Plugin](https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/dependency-extraction-webpack-plugin) +- [WooCommerce ESLint Plugin with WordPress Prettier](https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/eslint-plugin) ### Usage -At the root of a [WooCommerce Admin](https://github.com/woocommerce/woocommerce-admin) installation, run the create extension command. +At the root of a [WooCommerce Admin](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-admin) installation, run the create extension command. ``` pnpm run create-wc-extension diff --git a/plugins/woocommerce-admin/docs/data.md b/plugins/woocommerce-admin/docs/data.md index ec1061ddabe..98600969167 100644 --- a/plugins/woocommerce-admin/docs/data.md +++ b/plugins/woocommerce-admin/docs/data.md @@ -1,7 +1,7 @@ Data ==== -WooCommerce Admin data stores implement the [`SqlQuery` class](https://github.com/woocommerce/woocommerce-admin/blob/main/src/API/Reports/SqlQuery.php). +WooCommerce Admin data stores implement the [`SqlQuery` class](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Admin/API/Reports/SqlQuery.php). ### SqlQuery Class @@ -81,4 +81,4 @@ function my_custom_product_stats( $clauses ) { $clauses[] = ', SUM( sample_column ) as sample_total'; return $clauses; } -``` \ No newline at end of file +``` diff --git a/plugins/woocommerce-admin/docs/features/onboarding.md b/plugins/woocommerce-admin/docs/features/onboarding.md index b097764c9ca..a2769e79584 100644 --- a/plugins/woocommerce-admin/docs/features/onboarding.md +++ b/plugins/woocommerce-admin/docs/features/onboarding.md @@ -35,7 +35,7 @@ To power the new onboarding flow client side, new REST API endpoints have been i * `woocommerce_admin_onboarding_plugins_whitelist` filters the list of plugins that can installed & activated via onboarding. This acts as a whitelist so only certain plugins can be used via the `/wc-admin/onboarding/profile/install` and `/wc-admin/onboarding/profile/activate` endpoints. * `woocommerce_admin_onboarding_themes` filters the themes displayed in the profile wizard. * `woocommerce_admin_onboarding_jetpack_connect_redirect_url` filters the Jetpack connection redirect URL outlined in the Jetpack connection section below. -* `woocommerce_admin_onboarding_task_list` filters the list of tasks on the task list dashboard. This allows extensions to add new tasks. See [the extension docs](https://github.com/woocommerce/woocommerce-admin/tree/42015d17a919e8f9e54ba75869c50b04b8dc9241/docs/examples/extensions) for an example of how to do this. +* `woocommerce_admin_onboarding_task_list` filters the list of tasks on the task list dashboard. This allows extensions to add new tasks. See [the extension docs](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-admin/docs/examples/extensions) for an example of how to do this. * `woocommerce_rest_onboarding_profile_collection_params` filters the collection parameters for requests to `/wc-admin/onboarding/profile`. * `woocommerce_rest_onboarding_profile_object_query` filters the query arguments for requests to `/wc-admin/onboarding/profile`. * `woocommerce_rest_onboarding_prepare_onboarding_profile` filters the response for requests to `/wc-admin/onboarding/profile`. @@ -48,7 +48,7 @@ A few new WordPress options have been introduced to store information and settin * `woocommerce_task_list_hidden_lists`. This option houses the task lists that have been hidden from view by the user. * `woocommerce_task_list_welcome_modal_dismissed`. This option is used to show a congratulations modal during the transition between the profile wizard and task list. -We also use existing options from WooCommerce Core or extensions like WooCommerce Shipping & Tax or Stripe. The list below may not be complete, as new tasks are introduced, but you can generally find usage of these by searching for the [getOptions selector](https://github.com/woocommerce/woocommerce-admin/search?q=getOptions&unscoped_q=getOptions). +We also use existing options from WooCommerce Core or extensions like WooCommerce Shipping & Tax or Stripe. The list below may not be complete, as new tasks are introduced, but you can generally find usage of these by searching for the [getOptions selector](https://github.com/woocommerce/woocommerce/search?q=getOptions&unscoped_q=getOptions). * `woocommerce_setup_jetpack_opted_in` and `wc_connect_options` are both used to control Jetpack's Terms of Service opt-in, which is necessary to set for a user during the connection process, so that they can use services like automated tax rates. * `woocommerce_allow_tracking` is used to control Tracks opt-in, allowing us to gather usage data from WooCommerce Admin and WooCommerce core. @@ -59,7 +59,7 @@ We also use existing options from WooCommerce Core or extensions like WooCommerc During the profile wizard, merchants can select paid product type extensions (like WooCommerce Memberships) or a paid theme. To make installation easier and to finish purchasing, it is necessary to make a [WooCommerce.com connection](https://woocommerce.com/document/managing-woocommerce-com-subscriptions/). We also prompt users to connect on the task list if they chose extensions in the profile wizard, but did not finish connecting. -To make the connection from the new onboarding experience possible, we build our own connection endpoints [/wc-admin/plugins/request-wccom-connect](https://github.com/woocommerce/woocommerce-admin/blob/61b771c2643c24334ea062ab3521073beaf50019/src/API/OnboardingPlugins.php#L298-L355) and [/wc-admin/plugins/finish-wccom-connect](https://github.com/woocommerce/woocommerce-admin/blob/61b771c2643c24334ea062ab3521073beaf50019/src/API/OnboardingPlugins.php#L357-L417). +To make the connection from the new onboarding experience possible, we build our own connection endpoints [/wc-admin/plugins/request-wccom-connect](https://github.com/woocommerce/woocommerce/blob/feba6a8dcd55d4f5c7edc05478369c76df082293/plugins/woocommerce/src/Admin/API/Plugins.php#L419-L476) and [/wc-admin/plugins/finish-wccom-connect](https://github.com/woocommerce/woocommerce/blob/feba6a8dcd55d4f5c7edc05478369c76df082293/plugins/woocommerce/src/Admin/API/Plugins.php#L478-L538). Both of these endpoints use WooCommerce Core's `WC_Helper_API` directly. The main difference with our connection (compared to the connection on the subscriptions page) is the addition of two additional query string parameters: @@ -72,7 +72,7 @@ To disconnect from WooCommerce.com, go to `WooCommerce > Extensions > WooCommerc Using Jetpack & WooCommerce Shipping & Tax allows us to offer additional features to new WooCommerce users as well as simplify parts of the setup process. For example, we can do automated tax calculations for certain countries, significantly simplifying the tax task. To make this work, the user needs to be connected to a WordPress.com account. This also means development and testing of these features needs to be done on a Jetpack connected site. Search the MGS & the Feld Guide for additional resources on testing Jetpack with local setups. -We have a special Jetpack connection flow designed specifically for WooCommerce onboarding, so that the user feels that they are connecting as part of a cohesive experience. To access this flow, we have a custom Jetpack connection endpoint [/wc-admin/plugins/connect-jetpack](https://github.com/woocommerce/woocommerce-admin/blob/61b771c2643c24334ea062ab3521073beaf50019/src/API/OnboardingPlugins.php#L273-L296). +We have a special Jetpack connection flow designed specifically for WooCommerce onboarding, so that the user feels that they are connecting as part of a cohesive experience. To access this flow, we have a custom Jetpack connection endpoint [/wc-admin/plugins/connect-jetpack](https://github.com/woocommerce/woocommerce/blob/feba6a8dcd55d4f5c7edc05478369c76df082293/plugins/woocommerce/src/Admin/API/Plugins.php#L395-L417). We use Jetpack's `build_connect_url` function directly, but add the following two query parameters: @@ -115,7 +115,7 @@ If you are running the development version of WooCommerce Admin, and have [`WP_D The `onboarding` feature flag is enabled in the main WooCommerce Admin plugin build. That means the published version of the plugin on WordPress.org contains the onboarding feature, but it is visually off by default. See the "enable onboarding" section above. -Sometimes, it may be necessary to generate a separate build of the plugin between public releases for internal testing or debugging. This can be done using the [building custom plugin builds](https://github.com/woocommerce/woocommerce-admin/blob/main/docs/feature-flags.md#building-custom-plugin-builds) feature of our build system. +Sometimes, it may be necessary to generate a separate build of the plugin between public releases for internal testing or debugging. This can be done using the [building custom plugin builds](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce-admin/docs/features/feature-flags.md#building-custom-plugin-builds) feature of our build system. * Switch to the latest `main` branch and pull down any changes * Run `pnpm run build:release -- --slug onboarding --features '{"onboarding":true}'` diff --git a/plugins/woocommerce-admin/docs/features/payment-gateway-suggestions.md b/plugins/woocommerce-admin/docs/features/payment-gateway-suggestions.md index 54ad567064b..66f8a41a0e2 100644 --- a/plugins/woocommerce-admin/docs/features/payment-gateway-suggestions.md +++ b/plugins/woocommerce-admin/docs/features/payment-gateway-suggestions.md @@ -41,7 +41,7 @@ The data source schema defines the recommended payment gateways and required plu ] ``` -The specs use the [rule processor](https://github.com/woocommerce/woocommerce-admin/blob/main/src/RemoteInboxNotifications/README.md#rule) to determine if a gateway should be shown using the `is_visible` property. +The specs use the [rule processor](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce/src/Admin/RemoteInboxNotifications#rule) to determine if a gateway should be shown using the `is_visible` property. ## Payment Gateway Configs @@ -64,13 +64,13 @@ By default, the client will generate a payment gateway setup form from the setti ### WooPaymentGatewayConfigure -To customize the configuration form used in the payment setup, you can use [WooPaymentGatewayConfigure](https://github.com/woocommerce/woocommerce-admin/tree/main/packages/onboarding/src/components/WooPaymentGatewayConfigure). +To customize the configuration form used in the payment setup, you can use [WooPaymentGatewayConfigure](https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/onboarding/src/components/WooPaymentGatewayConfigure). This will leave the default gateway installation and stepper in place, but allow the form to be customized as needed. ### WooPaymentGatewaySetup -To completely override the stepper and default installation behavior, the gateway can be SlotFilled using [WooPaymentGatewaySetup](https://github.com/woocommerce/woocommerce-admin/tree/main/packages/onboarding/src/components/WooPaymentGatewaySetup). +To completely override the stepper and default installation behavior, the gateway can be SlotFilled using [WooPaymentGatewaySetup](https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/onboarding/src/components/WooPaymentGatewaySetup). ## Post install setup diff --git a/plugins/woocommerce-admin/docs/woocommerce.com/analytics-orders-report.md b/plugins/woocommerce-admin/docs/woocommerce.com/analytics-orders-report.md index 84b097997d9..7e6e0916c6b 100644 --- a/plugins/woocommerce-admin/docs/woocommerce.com/analytics-orders-report.md +++ b/plugins/woocommerce-admin/docs/woocommerce.com/analytics-orders-report.md @@ -2,7 +2,7 @@ The Orders Report provides insight about your store's orders. -By default, orders with non-excluded statuses are listed by order date descending. Excluded statuses can be edited on the [Settings page](https://github.com/woocommerce/woocommerce-admin/blob/main/docs/woocommerce.com/analytics-settings.md#excluded-statuses) +By default, orders with non-excluded statuses are listed by order date descending. Excluded statuses can be edited on the [Settings page](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce-admin/docs/woocommerce.com/analytics-settings.md#excluded-statuses) Refunded orders cannot be excluded from the orders report. Refunded orders have two rows in the report: one for the date of the original order and one for the date of refund. From c4033b14de093d5d7028dc5f8648cdcd667e0276 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 7 Apr 2022 17:21:37 +0800 Subject: [PATCH 107/386] Remove unnecessary --working-dir arg from woocommerce php lint-staged command --- plugins/woocommerce/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index 6ae8e8e0ace..079fd9ccd4f 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -72,7 +72,7 @@ "lint-staged": { "*.php": [ "php -d display_errors=1 -l", - "composer --working-dir=./plugins/woocommerce run-script phpcs-pre-commit" + "composer run-script phpcs-pre-commit" ], "!(*min).js": [ "eslint --fix" From 1c01cc16055e4029817ab5f4e923b094e846564b Mon Sep 17 00:00:00 2001 From: Tom Cafferkey Date: Thu, 3 Mar 2022 10:55:00 +0000 Subject: [PATCH 108/386] Add tracking for theme activation so that we can track active block themes in WooCommerce --- .../tracks/class-wc-site-tracking.php | 2 + .../tracks/events/class-wc-theme-tracking.php | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php diff --git a/plugins/woocommerce/includes/tracks/class-wc-site-tracking.php b/plugins/woocommerce/includes/tracks/class-wc-site-tracking.php index e924d250448..d2bb5297688 100644 --- a/plugins/woocommerce/includes/tracks/class-wc-site-tracking.php +++ b/plugins/woocommerce/includes/tracks/class-wc-site-tracking.php @@ -161,6 +161,7 @@ class WC_Site_Tracking { include_once WC_ABSPATH . 'includes/tracks/events/class-wc-coupons-tracking.php'; include_once WC_ABSPATH . 'includes/tracks/events/class-wc-order-tracking.php'; include_once WC_ABSPATH . 'includes/tracks/events/class-wc-coupon-tracking.php'; + include_once WC_ABSPATH . 'includes/tracks/events/class-wc-theme-tracking.php'; $tracking_classes = array( 'WC_Extensions_Tracking', @@ -172,6 +173,7 @@ class WC_Site_Tracking { 'WC_Coupons_Tracking', 'WC_Order_Tracking', 'WC_Coupon_Tracking', + 'WC_Theme_Tracking', ); foreach ( $tracking_classes as $tracking_class ) { diff --git a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php new file mode 100644 index 00000000000..c1916fd3723 --- /dev/null +++ b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php @@ -0,0 +1,37 @@ + wc_current_theme_is_fse_theme(), + ); + + WC_Tracks::record_event( 'theme_activated', $properties ); + } +} + From e5ac9e4ae6be42da226f4a1e33283064acd0d90c Mon Sep 17 00:00:00 2001 From: Tom Cafferkey Date: Thu, 3 Mar 2022 14:42:32 +0000 Subject: [PATCH 109/386] Track initial sites theme --- .../tracks/events/class-wc-theme-tracking.php | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php index c1916fd3723..880c60976e3 100644 --- a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php +++ b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php @@ -16,22 +16,40 @@ class WC_Theme_Tracking { * Init tracking. */ public function init() { - add_action( 'switch_theme', array( $this, 'track_theme_activated' ) ); + $this->track_initial_theme(); + add_action( 'switch_theme', array( $this, 'track_active_theme' ) ); + } + + /** + * Tracking the initial theme being used for the first time. + */ + public function track_initial_theme() { + $has_been_initially_tracked = get_option( 'has_tracked_default_theme' ); + + if ( $has_been_initially_tracked ) { + return; + } + + $this->track_active_theme(); + add_option( 'has_tracked_default_theme', 'true' ); } /** * Send a Tracks event when a theme is activated so that we can track active block themes. */ - public function track_theme_activated() { - if ( ! function_exists( 'wc_current_theme_is_fse_theme' ) ) { - return; + public function track_active_theme() { + $is_block_theme = false; + $theme_object = wp_get_theme(); + + if ( function_exists( 'wc_current_theme_is_fse_theme' ) ) { + $is_block_theme = wc_current_theme_is_fse_theme(); } $properties = array( - 'block_theme' => wc_current_theme_is_fse_theme(), + 'block_theme' => $is_block_theme, + 'theme_domain' => $theme_object->get( 'TextDomain' ), ); WC_Tracks::record_event( 'theme_activated', $properties ); } } - From aff9255061548c061de0a22c1ade3659a628fb49 Mon Sep 17 00:00:00 2001 From: Tom Cafferkey Date: Thu, 3 Mar 2022 15:10:22 +0000 Subject: [PATCH 110/386] Update event name and option name --- .../tracks/events/class-wc-theme-tracking.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php index 880c60976e3..2002c5e1c6b 100644 --- a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php +++ b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php @@ -17,27 +17,27 @@ class WC_Theme_Tracking { */ public function init() { $this->track_initial_theme(); - add_action( 'switch_theme', array( $this, 'track_active_theme' ) ); + add_action( 'switch_theme', array( $this, 'track_activated_theme' ) ); } /** * Tracking the initial theme being used for the first time. */ public function track_initial_theme() { - $has_been_initially_tracked = get_option( 'has_tracked_default_theme' ); + $has_been_initially_tracked = get_option( 'wc_has_tracked_default_theme' ); if ( $has_been_initially_tracked ) { return; } - $this->track_active_theme(); - add_option( 'has_tracked_default_theme', 'true' ); + $this->track_activated_theme(); + add_option( 'wc_has_tracked_default_theme', 1 ); } /** * Send a Tracks event when a theme is activated so that we can track active block themes. */ - public function track_active_theme() { + public function track_activated_theme() { $is_block_theme = false; $theme_object = wp_get_theme(); @@ -50,6 +50,6 @@ class WC_Theme_Tracking { 'theme_domain' => $theme_object->get( 'TextDomain' ), ); - WC_Tracks::record_event( 'theme_activated', $properties ); + WC_Tracks::record_event( 'woocommerce_activated_theme', $properties ); } } From 50b1d6072922c1f4247ab8559db88b6771bd6430 Mon Sep 17 00:00:00 2001 From: Tom Cafferkey Date: Thu, 3 Mar 2022 15:40:00 +0000 Subject: [PATCH 111/386] Remove woocommerce event prefix because its autoprefixed with wcadmin_ --- .../includes/tracks/events/class-wc-theme-tracking.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php index 2002c5e1c6b..0110d688b73 100644 --- a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php +++ b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php @@ -50,6 +50,6 @@ class WC_Theme_Tracking { 'theme_domain' => $theme_object->get( 'TextDomain' ), ); - WC_Tracks::record_event( 'woocommerce_activated_theme', $properties ); + WC_Tracks::record_event( 'activated_theme', $properties ); } } From 9108b8dbf8505abec666de1ad285da52206056a1 Mon Sep 17 00:00:00 2001 From: Tom Cafferkey Date: Fri, 4 Mar 2022 09:45:16 +0000 Subject: [PATCH 112/386] Update comments --- .../includes/tracks/events/class-wc-theme-tracking.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php index 0110d688b73..4a9168b4e5c 100644 --- a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php +++ b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php @@ -8,7 +8,7 @@ defined( 'ABSPATH' ) || exit; /** - * This class adds actions to track usage of a WooCommerce Order. + * This class adds actions to track usage of themes on a WooCommerce store. */ class WC_Theme_Tracking { @@ -21,7 +21,7 @@ class WC_Theme_Tracking { } /** - * Tracking the initial theme being used for the first time. + * Tracks the sites current theme the first time this code is run, and will only be run once. */ public function track_initial_theme() { $has_been_initially_tracked = get_option( 'wc_has_tracked_default_theme' ); From ab769ebe2cf9bcebe9a37c41512dd092268be972 Mon Sep 17 00:00:00 2001 From: Tom Cafferkey Date: Thu, 24 Mar 2022 08:37:40 +0000 Subject: [PATCH 113/386] Add block theme property to get_theme_info() in class-wc-tracker class --- plugins/woocommerce/includes/class-wc-tracker.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-tracker.php b/plugins/woocommerce/includes/class-wc-tracker.php index a7e9c6fc082..bda59973044 100644 --- a/plugins/woocommerce/includes/class-wc-tracker.php +++ b/plugins/woocommerce/includes/class-wc-tracker.php @@ -181,15 +181,17 @@ class WC_Tracker { * @return array */ public static function get_theme_info() { - $theme_data = wp_get_theme(); - $theme_child_theme = wc_bool_to_string( is_child_theme() ); - $theme_wc_support = wc_bool_to_string( current_theme_supports( 'woocommerce' ) ); + $theme_data = wp_get_theme(); + $theme_child_theme = wc_bool_to_string( is_child_theme() ); + $theme_wc_support = wc_bool_to_string( current_theme_supports( 'woocommerce' ) ); + $theme_is_block_theme = wc_bool_to_string( wc_current_theme_is_fse_theme() ); return array( 'name' => $theme_data->Name, // @phpcs:ignore 'version' => $theme_data->Version, // @phpcs:ignore 'child_theme' => $theme_child_theme, 'wc_support' => $theme_wc_support, + 'block_theme' => $theme_is_block_theme, ); } From 864f348865b770fe4a795cbb6237455cb8fa879f Mon Sep 17 00:00:00 2001 From: tjcafferkey Date: Thu, 7 Apr 2022 10:50:29 +0100 Subject: [PATCH 114/386] Switch TextDomain for Name when tracking block themes --- .../includes/tracks/events/class-wc-theme-tracking.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php index 4a9168b4e5c..38f3cc956da 100644 --- a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php +++ b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php @@ -47,7 +47,7 @@ class WC_Theme_Tracking { $properties = array( 'block_theme' => $is_block_theme, - 'theme_domain' => $theme_object->get( 'TextDomain' ), + 'theme_name' => $theme_object->get( 'Name' ), ); WC_Tracks::record_event( 'activated_theme', $properties ); From 54b9a29db3fe86782521f99f893d945b2c0163ea Mon Sep 17 00:00:00 2001 From: tjcafferkey Date: Thu, 7 Apr 2022 14:23:23 +0100 Subject: [PATCH 115/386] Track theme version --- .../includes/tracks/events/class-wc-theme-tracking.php | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php index 38f3cc956da..2d941c08399 100644 --- a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php +++ b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php @@ -48,6 +48,7 @@ class WC_Theme_Tracking { $properties = array( 'block_theme' => $is_block_theme, 'theme_name' => $theme_object->get( 'Name' ), + 'theme_version' => $theme_object->get( 'Version' ), ); WC_Tracks::record_event( 'activated_theme', $properties ); From 3087cae672fd87228e243e86ab094b2010a6dd20 Mon Sep 17 00:00:00 2001 From: "Jorge A. Torres" Date: Thu, 7 Apr 2022 10:40:45 -0300 Subject: [PATCH 116/386] Add changelog entry --- plugins/woocommerce/changelog/add-block-theme-tracking | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/add-block-theme-tracking diff --git a/plugins/woocommerce/changelog/add-block-theme-tracking b/plugins/woocommerce/changelog/add-block-theme-tracking new file mode 100644 index 00000000000..67a404bdc7c --- /dev/null +++ b/plugins/woocommerce/changelog/add-block-theme-tracking @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Add tracking for block themes and theme switches. From aed64b15e4fe77fbc11fea0da28e93adee7e754d Mon Sep 17 00:00:00 2001 From: "Jorge A. Torres" Date: Thu, 7 Apr 2022 10:44:27 -0300 Subject: [PATCH 117/386] Fix code style issue. --- .../includes/tracks/events/class-wc-theme-tracking.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php index 2d941c08399..51e23a7fceb 100644 --- a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php +++ b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php @@ -46,8 +46,8 @@ class WC_Theme_Tracking { } $properties = array( - 'block_theme' => $is_block_theme, - 'theme_name' => $theme_object->get( 'Name' ), + 'block_theme' => $is_block_theme, + 'theme_name' => $theme_object->get( 'Name' ), 'theme_version' => $theme_object->get( 'Version' ), ); From 7710f07e20fa2badb5fd683e9069dcfc83250cc2 Mon Sep 17 00:00:00 2001 From: "Jorge A. Torres" Date: Thu, 7 Apr 2022 11:03:01 -0300 Subject: [PATCH 118/386] Add changelog entry --- plugins/woocommerce/changelog/add-block-theme-tracking | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/changelog/add-block-theme-tracking b/plugins/woocommerce/changelog/add-block-theme-tracking index 67a404bdc7c..8fc5311f580 100644 --- a/plugins/woocommerce/changelog/add-block-theme-tracking +++ b/plugins/woocommerce/changelog/add-block-theme-tracking @@ -1,4 +1,4 @@ Significance: minor Type: dev -Add tracking for block themes and theme switches. +Add tracking for block themes. From 473d63d043a2a925b90a142454c4dd3e20fd9dd4 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Thu, 7 Apr 2022 16:58:21 -0300 Subject: [PATCH 119/386] Remove can_view condition on tracking completion --- .../woocommerce/src/Admin/Features/OnboardingTasks/Task.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php index f6300aa5ea1..df315157545 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php @@ -358,10 +358,6 @@ abstract class Task { * Track task completion if task is viewable. */ public function possibly_track_completion() { - if ( ! $this->can_view() ) { - return; - } - if ( ! $this->is_complete() ) { return; } From fcf389d921f52614272cb0d302712791d4de035b Mon Sep 17 00:00:00 2001 From: Joel T Date: Thu, 7 Apr 2022 13:08:36 -0700 Subject: [PATCH 120/386] Adding changelog --- plugins/woocommerce/changelog/update-32509_deasync_issue | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 plugins/woocommerce/changelog/update-32509_deasync_issue diff --git a/plugins/woocommerce/changelog/update-32509_deasync_issue b/plugins/woocommerce/changelog/update-32509_deasync_issue new file mode 100644 index 00000000000..c3fabe4aef0 --- /dev/null +++ b/plugins/woocommerce/changelog/update-32509_deasync_issue @@ -0,0 +1,5 @@ +Significance: patch +Type: update +Comment: Simply updating a patch version of the deasync package + + From 3159bde89501af06c7fbe5389510f34a4e13bc29 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Thu, 7 Apr 2022 18:07:00 -0300 Subject: [PATCH 121/386] Add Pinterest to free extensions list --- packages/js/data/src/plugins/constants.ts | 4 ++++ plugins/woocommerce/src/Admin/API/OnboardingProfile.php | 1 + 2 files changed, 5 insertions(+) diff --git a/packages/js/data/src/plugins/constants.ts b/packages/js/data/src/plugins/constants.ts index 1050edb792c..50fbb1985f2 100644 --- a/packages/js/data/src/plugins/constants.ts +++ b/packages/js/data/src/plugins/constants.ts @@ -55,4 +55,8 @@ export const pluginNames = { 'google-listings-and-ads': __( 'Google Listings and Ads', 'woocommerce' ), 'woo-razorpay': __( 'Razorpay', 'woocommerce' ), mailpoet: __( 'MailPoet', 'woocommerce' ), + 'pinterest-for-woocommerce': __( + 'Pinterest for WooCommerce', + 'woocommerce' + ), }; diff --git a/plugins/woocommerce/src/Admin/API/OnboardingProfile.php b/plugins/woocommerce/src/Admin/API/OnboardingProfile.php index a148810ed2e..d30956054d3 100644 --- a/plugins/woocommerce/src/Admin/API/OnboardingProfile.php +++ b/plugins/woocommerce/src/Admin/API/OnboardingProfile.php @@ -397,6 +397,7 @@ class OnboardingProfile extends \WC_REST_Data_Controller { 'creative-mail-by-constant-contact', 'facebook-for-woocommerce', 'google-listings-and-ads', + 'pinterest-for-woocommerce', 'mailpoet', ), 'type' => 'string', From 63f33b86dee6630febb35e8ab6f49d60bb2bc3b4 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Thu, 7 Apr 2022 18:07:47 -0300 Subject: [PATCH 122/386] Add free extensions list fallback --- .../DefaultFreeExtensions.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php b/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php index 6ec1c56dccb..1134defbd16 100644 --- a/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php +++ b/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php @@ -36,6 +36,7 @@ class DefaultFreeExtensions { 'plugins' => [ self::get_plugin( 'mailpoet' ), self::get_plugin( 'google-listings-and-ads' ), + self::get_plugin( 'pinterest-for-woocommerce' ), ], ], [ @@ -52,6 +53,7 @@ class DefaultFreeExtensions { 'title' => __( 'Grow your store', 'woocommerce' ), 'plugins' => [ self::get_plugin( 'google-listings-and-ads:alt' ), + self::get_plugin( 'pinterest-for-woocommerce:alt' ), ], ], ]; @@ -98,6 +100,36 @@ class DefaultFreeExtensions { 'manage_url' => 'admin.php?page=wc-admin&path=%2Fgoogle%2Fstart', 'is_built_by_wc' => true, ], + 'pinterest-for-woocommerce' => [ + 'name' => __( 'Pinterest for WooCommerce', 'woocommerce' ), + 'description' => sprintf( + /* translators: 1: opening product link tag. 2: closing link tag */ + __( 'aaaa Inspire shoppers with %1$sPinterest for WooCommerce%2$s', 'woocommerce' ), + '', + '' + ), + 'image_url' => plugins_url( 'images/onboarding/creative-mail-by-constant-contact.png', WC_ADMIN_PLUGIN_FILE ), + 'manage_url' => 'admin.php?page=pinterest-for-woocommerce', + 'is_visible' => [ + [ + 'type' => 'not', + 'operand' => [ + [ + 'type' => 'plugins_activated', + 'plugins' => [ 'pinterest-for-woocommerce' ], + ], + ], + ], + ], + 'is_built_by_wc' => false, + ], + 'pinterest-for-woocommerce:alt' => [ + 'name' => __( 'Pinterest for WooCommerce', 'woocommerce' ), + 'description' => __( 'Get your products in front of Pinterest users searching for ideas and things to buy. Get started with Pinterest and make your entire product catalog browsable.', 'woocommerce-admin' ), + 'image_url' => plugins_url( 'images/onboarding/creative-mail-by-constant-contact.png', WC_ADMIN_PLUGIN_FILE ), + 'manage_url' => 'admin.php?page=wc-admin&path=%2Fpinterest%2Flanding', + 'is_built_by_wc' => false, + ], 'mailpoet' => [ 'name' => __( 'MailPoet', 'woocommerce' ), 'description' => __( 'Create and send purchase follow-up emails, newsletters, and promotional campaigns straight from your dashboard.', 'woocommerce' ), From a649fe6ae275d68244507233251305e1b727d0de Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 5 Apr 2022 14:30:22 +0800 Subject: [PATCH 123/386] Remove no needed commands and fields in package.json --- plugins/woocommerce-admin/package.json | 35 +++++--------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 759909df858..a5da4f8a2d0 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -1,22 +1,13 @@ { "name": "@woocommerce/admin-library", "version": "3.3.0", - "homepage": "https://woocommerce.github.io/woocommerce-admin/", - "repository": { - "type": "git", - "url": "https://github.com:woocommerce/woocommerce-admin.git" - }, "license": "GPL-3.0-or-later", "author": "Automattic", - "files": [ - "dist/**/*.css", - "dist/**/*.js", - "dist/feature-config-core.php", - "includes/class-wc-admin-loader.php", - "includes/features/**/*.php", - "languages/**/*.json", - "license.txt" - ], + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-admin/README.md", + "repository": { + "type": "git", + "url": "https://github.com:woocommerce/woocommerce.git" + }, "scripts": { "preinstall": "npx only-allow pnpm", "prebuild": "pnpm run install-if-deps-outdated", @@ -27,7 +18,6 @@ "postbuild": "pnpm run -s i18n:pot && pnpm run -s i18n:build", "build:feature-config": "php ../woocommerce/bin/generate-feature-config.php", "build:packages": "cross-env NODE_ENV=production pnpm run:packages -- build", - "build:release": "./bin/build-plugin-zip.sh", "clean": "rimraf ./dist && pnpm run:packages -- clean --parallel", "predev": "pnpm run -s install-if-deps-outdated", "dev": "cross-env WC_ADMIN_PHASE=development pnpm run build:feature-config && cross-env WC_ADMIN_PHASE=development pnpm run build:packages && cross-env WC_ADMIN_PHASE=development webpack", @@ -42,8 +32,6 @@ "i18n:pot": "grunt makepot", "install-if-deps-outdated": "node bin/install-if-deps-outdated.js", "install-if-no-packages": "node bin/install-if-no-packages.js", - "labels:dry": "github-label-sync --labels ./.github/label-sync-config.json --allow-added-labels --dry-run woocommerce/woocommerce-admin", - "labels:sync": "github-label-sync --labels ./.github/label-sync-config.json --allow-added-labels woocommerce/woocommerce-admin", "lint": "pnpm run lint:js && pnpm run lint:css", "lint:css": "stylelint '**/*.scss'", "lint:css-fix": "stylelint '**/*.scss' --fix --ip 'storybook/wordpress'", @@ -51,8 +39,6 @@ "lint:js-pre-commit": "wp-scripts lint-js --ext=js,ts,tsx", "lint:js-packages": "wp-scripts lint-js ../../packages/js --ext=js,ts,tsx", "lint:js-fix": "pnpm run lint:js -- --fix --ext=js,ts,tsx", - "lint:php": "./vendor/bin/phpcs --standard=phpcs.xml.dist $(git ls-files | grep .php$)", - "lint:php-fix": "./vendor/bin/phpcbf --standard=phpcs.xml.dist $(git ls-files | grep .php$)", "ts:check": "tsc --build ./tsconfig.json --pretty", "ts:check:watch": "npm run ts:check -- --watch", "reformat-files": "wp-scripts format-js -- --ignore-path .eslintignore", @@ -68,26 +54,17 @@ "test:client": "jest --config client/jest.config.js", "test:packages": "pnpm run --filter ../../packages/js/ --filter !api-core-tests test", "test": "pnpm nx build @woocommerce/js-tests && pnpm run test:client", - "test:e2e": "pnpm run build && test -z \"$(docker ps | grep woocommerce-admin-e2e)\" || pnpm exec wc-e2e docker:down && pnpm run e2e:docker-up && pnpm exec wc-e2e test:e2e", - "e2e:docker-up": "WC_E2E_FOLDER=../../../ pnpm exec wc-e2e docker:up ./tests/e2e/docker/initialize.sh", "test-staged": "pnpm run test:client -- --bail --findRelatedTests", "test:help": "wp-scripts test-unit-js --help", - "test:php": "docker-compose -f docker/wc-admin-php-test-suite/docker-compose.yml run --rm phpunit", - "posttest:php": "docker-compose -f docker/wc-admin-php-test-suite/docker-compose.yml down", "test:update-snapshots": "pnpm run test:client -- --updateSnapshot && pnpm run --filter @woocommerce/components test:update-snapshots", "test:watch": "tsc --build || concurrently \"pnpm run test:client -- --watch\" \"pnpm run:packages -- test:nobuild --parallel -- --watch\"", - "test:zip": "pnpm run clean && composer i && ./bin/build-test-zip.sh", "example": "webpack --config docs/examples/extensions/examples.config.js --watch", "pre-release": "./bin/pre-release.sh", "create-wc-extension": "node ./bin/starter-pack/starter-pack.js", "storybook": "./bin/import-wp-css-storybook.sh && BABEL_ENV=storybook STORYBOOK=true start-storybook -c ./storybook/.storybook -p 6007 --ci", "storybook-rtl": "USE_RTL_STYLE=true pnpm run storybook", "build-storybook": "build-storybook -c ./storybook/.storybook", - "changelog": "node ./bin/changelog --changelogSrcType='ZENHUB_RELEASE'", - "wp-env-mysql-port": "node ./docker/wc-admin-wp-env/mysql-port.js", - "create-hook-reference": "node ./bin/hook-reference/index.js", - "changelogger": "./vendor/bin/changelogger", - "test-instruction-logger": "./bin/test-instruction-logger/bin/test-instruction-logger" + "create-hook-reference": "node ./bin/hook-reference/index.js" }, "dependencies": { "@automattic/explat-client": "^0.0.3", From 870d52345528115e49cd50a647f3f04f78d399b6 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 5 Apr 2022 14:31:13 +0800 Subject: [PATCH 124/386] Reorder woocommerce admin package.json scripts --- plugins/woocommerce-admin/package.json | 52 +++++++++++++------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index a5da4f8a2d0..1f1719ec77f 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -9,62 +9,62 @@ "url": "https://github.com:woocommerce/woocommerce.git" }, "scripts": { - "preinstall": "npx only-allow pnpm", - "prebuild": "pnpm run install-if-deps-outdated", - "run:packages": "pnpm run --filter ../../packages/js/", - "packages:fix:textdomain": "node ./bin/package-update-textdomain.js", - "build": "pnpm run build:feature-config && cross-env NODE_ENV=production webpack", "analyze": "cross-env NODE_ENV=production ANALYZE=true webpack", + "prebuild": "pnpm run install-if-deps-outdated", + "build": "pnpm run build:feature-config && cross-env NODE_ENV=production webpack", "postbuild": "pnpm run -s i18n:pot && pnpm run -s i18n:build", + "build-storybook": "build-storybook -c ./storybook/.storybook", "build:feature-config": "php ../woocommerce/bin/generate-feature-config.php", "build:packages": "cross-env NODE_ENV=production pnpm run:packages -- build", "clean": "rimraf ./dist && pnpm run:packages -- clean --parallel", + "client:watch": "cross-env WC_ADMIN_PHASE=development pnpm run build:feature-config && cross-env WC_ADMIN_PHASE=development webpack --watch", + "create-hook-reference": "node ./bin/hook-reference/index.js", + "create-wc-extension": "node ./bin/starter-pack/starter-pack.js", "predev": "pnpm run -s install-if-deps-outdated", "dev": "cross-env WC_ADMIN_PHASE=development pnpm run build:feature-config && cross-env WC_ADMIN_PHASE=development pnpm run build:packages && cross-env WC_ADMIN_PHASE=development webpack", - "client:watch": "cross-env WC_ADMIN_PHASE=development pnpm run build:feature-config && cross-env WC_ADMIN_PHASE=development webpack --watch", - "packages:watch": "cross-env WC_ADMIN_PHASE=development pnpm run:packages -- start --parallel", "docs": "./bin/import-wp-css-storybook.sh && BABEL_ENV=storybook STORYBOOK=true pnpm exec build-storybook -c storybook/.storybook -o ./docs/components/storybook", + "example": "webpack --config docs/examples/extensions/examples.config.js --watch", "i18n": "pnpm run -s i18n:js && pnpm run -s i18n:check && pnpm run -s i18n:pot && pnpm run -s i18n:build", "i18n:build": "php bin/combine-pot-files.php languages/woocommerce-admin.po languages/woocommerce-admin.pot", "i18n:check": "grunt checktextdomain", "i18n:js": "pnpm run clean && cross-env NODE_ENV=production babel client packages -o /dev/null", "i18n:json": "./bin/make-i18n-json.sh", "i18n:pot": "grunt makepot", + "preinstall": "npx only-allow pnpm", "install-if-deps-outdated": "node bin/install-if-deps-outdated.js", "install-if-no-packages": "node bin/install-if-no-packages.js", "lint": "pnpm run lint:js && pnpm run lint:css", "lint:css": "stylelint '**/*.scss'", "lint:css-fix": "stylelint '**/*.scss' --fix --ip 'storybook/wordpress'", "lint:js": "wp-scripts lint-js ./client --ext=js,ts,tsx", - "lint:js-pre-commit": "wp-scripts lint-js --ext=js,ts,tsx", - "lint:js-packages": "wp-scripts lint-js ../../packages/js --ext=js,ts,tsx", "lint:js-fix": "pnpm run lint:js -- --fix --ext=js,ts,tsx", - "ts:check": "tsc --build ./tsconfig.json --pretty", - "ts:check:watch": "npm run ts:check -- --watch", - "reformat-files": "wp-scripts format-js -- --ignore-path .eslintignore", + "lint:js-packages": "wp-scripts lint-js ../../packages/js --ext=js,ts,tsx", + "lint:js-pre-commit": "wp-scripts lint-js --ext=js,ts,tsx", "prepack": "pnpm install && pnpm run lint && pnpm run test && cross-env WC_ADMIN_PHASE=core pnpm run build", + "packages:fix:textdomain": "node ./bin/package-update-textdomain.js", + "packages:watch": "cross-env WC_ADMIN_PHASE=development pnpm run:packages -- start --parallel", + "pre-release": "./bin/pre-release.sh", "publish-packages:check": "pnpm run build:packages && pnpm publish --dry-run --filter ../../packages/js/ --publish-branch main --report-summary && cat ../../pnpm-publish-summary.json && rimraf ../../pnpm-publish-summary.json", "publish-packages:dev": "pnpm run build:packages && pnpm publish --filter ../../packages/js/ --publish-branch main --tag next", "publish-packages:prod": "pnpm run build:packages && pnpm publish --filter ../../packages/js/ --publish-branch main", + "reformat-files": "wp-scripts format-js -- --ignore-path .eslintignore", + "run:packages": "pnpm run --filter ../../packages/js/", "prestart": "pnpm run install-if-deps-outdated", "start": "cross-env WC_ADMIN_PHASE=development pnpm run build:packages && cross-env WC_ADMIN_PHASE=development pnpm run build:feature-config && concurrently \"cross-env WC_ADMIN_PHASE=development webpack --watch\" \"cross-env WC_ADMIN_PHASE=development pnpm run:packages -- start --parallel\"", "start:package": "pnpm run:packages -- start --parallel", - "pretest": "pnpm run -s install-if-no-packages", - "test:debug": "node --inspect-brk ./node_modules/.bin/jest --config client/jest.config.js --watch --runInBand --no-cache", - "test:client": "jest --config client/jest.config.js", - "test:packages": "pnpm run --filter ../../packages/js/ --filter !api-core-tests test", - "test": "pnpm nx build @woocommerce/js-tests && pnpm run test:client", - "test-staged": "pnpm run test:client -- --bail --findRelatedTests", - "test:help": "wp-scripts test-unit-js --help", - "test:update-snapshots": "pnpm run test:client -- --updateSnapshot && pnpm run --filter @woocommerce/components test:update-snapshots", - "test:watch": "tsc --build || concurrently \"pnpm run test:client -- --watch\" \"pnpm run:packages -- test:nobuild --parallel -- --watch\"", - "example": "webpack --config docs/examples/extensions/examples.config.js --watch", - "pre-release": "./bin/pre-release.sh", - "create-wc-extension": "node ./bin/starter-pack/starter-pack.js", "storybook": "./bin/import-wp-css-storybook.sh && BABEL_ENV=storybook STORYBOOK=true start-storybook -c ./storybook/.storybook -p 6007 --ci", "storybook-rtl": "USE_RTL_STYLE=true pnpm run storybook", - "build-storybook": "build-storybook -c ./storybook/.storybook", - "create-hook-reference": "node ./bin/hook-reference/index.js" + "pretest": "pnpm run -s install-if-no-packages", + "test": "pnpm nx build @woocommerce/js-tests && pnpm run test:client", + "test-staged": "pnpm run test:client -- --bail --findRelatedTests", + "test:client": "jest --config client/jest.config.js", + "test:debug": "node --inspect-brk ./node_modules/.bin/jest --config client/jest.config.js --watch --runInBand --no-cache", + "test:help": "wp-scripts test-unit-js --help", + "test:packages": "pnpm run --filter ../../packages/js/ --filter !api-core-tests test", + "test:update-snapshots": "pnpm run test:client -- --updateSnapshot && pnpm run --filter @woocommerce/components test:update-snapshots", + "test:watch": "tsc --build || concurrently \"pnpm run test:client -- --watch\" \"pnpm run:packages -- test:nobuild --parallel -- --watch\"", + "ts:check": "tsc --build ./tsconfig.json --pretty", + "ts:check:watch": "npm run ts:check -- --watch" }, "dependencies": { "@automattic/explat-client": "^0.0.3", From f3e460f055ee52eac824ad0b67ba4a318ceb3e60 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Fri, 8 Apr 2022 12:35:00 +0800 Subject: [PATCH 125/386] Remove i18 commands and their dependencies i18 routines have been handled inside plugins/woocommerce. --- plugins/woocommerce-admin/Gruntfile.js | 62 ------ plugins/woocommerce-admin/babel.config.js | 11 +- .../bin/combine-pot-files.php | 104 ---------- .../woocommerce-admin/bin/make-i18n-json.sh | 23 --- plugins/woocommerce-admin/languages/README.md | 22 --- plugins/woocommerce-admin/package.json | 11 -- .../src/Internal/Admin/FeaturePlugin.php | 9 - pnpm-lock.yaml | 183 ++---------------- 8 files changed, 19 insertions(+), 406 deletions(-) delete mode 100755 plugins/woocommerce-admin/Gruntfile.js delete mode 100644 plugins/woocommerce-admin/bin/combine-pot-files.php delete mode 100755 plugins/woocommerce-admin/bin/make-i18n-json.sh delete mode 100644 plugins/woocommerce-admin/languages/README.md diff --git a/plugins/woocommerce-admin/Gruntfile.js b/plugins/woocommerce-admin/Gruntfile.js deleted file mode 100755 index 3f0fd4dbdbf..00000000000 --- a/plugins/woocommerce-admin/Gruntfile.js +++ /dev/null @@ -1,62 +0,0 @@ -/* eslint-disable */ -module.exports = function ( grunt ) { - 'use strict'; - - // Project configuration - grunt.initConfig( { - makepot: { - target: { - options: { - domainPath: '/languages', - exclude: [ '.git/*', 'bin/*', 'node_modules/*', 'tests/*' ], - mainFile: '../woocommerce/woocommerce.php', - potFilename: 'woocommerce-admin.pot', - potHeaders: { - poedit: true, - 'x-poedit-keywordslist': true, - }, - type: 'wp-plugin', - updateTimestamp: true, - }, - }, - }, - - checktextdomain: { - options: { - text_domain: 'woocommerce', - keywords: [ - '__:1,2d', - '_e:1,2d', - '_x:1,2c,3d', - 'esc_html__:1,2d', - 'esc_html_e:1,2d', - 'esc_html_x:1,2c,3d', - 'esc_attr__:1,2d', - 'esc_attr_e:1,2d', - 'esc_attr_x:1,2c,3d', - '_ex:1,2c,3d', - '_n:1,2,4d', - '_nx:1,2,4c,5d', - '_n_noop:1,2,3d', - '_nx_noop:1,2,3c,4d', - ], - }, - files: { - src: [ - '**/*.php', // Include all files/ - '!node_modules/**', // Exclude node_modules/ - '!tests/**', // Exclude tests/ - '!vendor/**', // Exclude vendor/ - '!tmp/**', // Exclude tmp/ - ], - expand: true, - }, - }, - } ); - - // Load NPM tasks to be used here. - grunt.loadNpmTasks( 'grunt-wp-i18n' ); - grunt.loadNpmTasks( 'grunt-checktextdomain' ); - - grunt.util.linefeed = '\n'; -}; diff --git a/plugins/woocommerce-admin/babel.config.js b/plugins/woocommerce-admin/babel.config.js index 93a8add31f2..70215466892 100644 --- a/plugins/woocommerce-admin/babel.config.js +++ b/plugins/woocommerce-admin/babel.config.js @@ -22,16 +22,7 @@ module.exports = function ( api ) { ], ignore: [ 'packages/**/node_modules' ], env: { - production: { - plugins: [ - [ - '@wordpress/babel-plugin-makepot', - { - output: 'languages/woocommerce-admin.po', - }, - ], - ], - }, + production: {}, storybook: { plugins: [ diff --git a/plugins/woocommerce-admin/bin/combine-pot-files.php b/plugins/woocommerce-admin/bin/combine-pot-files.php deleted file mode 100644 index fa82f605ddf..00000000000 --- a/plugins/woocommerce-admin/bin/combine-pot-files.php +++ /dev/null @@ -1,104 +0,0 @@ - $original ) { - // Use the complete message section to match strings to be translated. - if ( isset( $originals_1[ $message ] ) ) { - $original = array_merge( $original, $originals_1[ $message ] ); - unset( $originals_1[ $message ] ); - } - - fwrite( $fh, implode( "\n", $original ) ); - fwrite( $fh, "\n" . $message ."\n\n" ); -} - -foreach ( $originals_1 as $message => $original ) { - fwrite( $fh, implode( "\n", $original ) ); - fwrite( $fh, "\n" . $message ."\n\n" ); -} - -fclose( $fh ); - -echo "Created {$target_file}\n"; diff --git a/plugins/woocommerce-admin/bin/make-i18n-json.sh b/plugins/woocommerce-admin/bin/make-i18n-json.sh deleted file mode 100755 index 5bc91bacef1..00000000000 --- a/plugins/woocommerce-admin/bin/make-i18n-json.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -# Check for required version -WPCLI_VERSION=`wp cli version | cut -f2 -d' '` -if [ ${WPCLI_VERSION:0:1} -lt "2" -o ${WPCLI_VERSION:0:1} -eq "2" -a ${WPCLI_VERSION:2:1} -lt "1" ]; then - echo WP-CLI version 2.1.0 or greater is required to make JSON translation files - exit -fi - -# Substitute JS source references with build references -for T in `find languages -name "*.po"` - do - sed \ - -e 's/ client\/[^:]*:/ dist\/app\/index.js:/gp' \ - -e 's/ packages\/components[^:]*:/ dist\/components\/index.js:/gp' \ - -e 's/ packages\/date[^:]*:/ dist\/date\/index.js:/gp' \ - $T | uniq > $T-build - rm $T - mv $T-build $T - done - -# Make the JSON files -wp i18n make-json languages --no-purge \ No newline at end of file diff --git a/plugins/woocommerce-admin/languages/README.md b/plugins/woocommerce-admin/languages/README.md deleted file mode 100644 index 4c0bdd32b0e..00000000000 --- a/plugins/woocommerce-admin/languages/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Languages - -## Contributing a Translation -If you'd like to contribute a translation, please follow the Localizing section in [CONTRIBUTING.md](https://github.com/woocommerce/woocommerce-admin/blob/main/CONTRIBUTING.md). - -## Generating POT - -The generated POT template file is not included in this repository. To create this file locally, follow instructions from [README.md](https://github.com/woocommerce/woocommerce-admin/blob/main/README.md) to install the project, then run the following command: - -``` -pnpm run i18n lang=xx_YY -``` - -After the build completes, you'll find a `woocommerce-admin-xx_YY.po` (eg. `woocommerce-admin-fr_FR.po`) strings file in this directory. - -## Generating JSON - -To generate JSON from your translations, save your translation file in this directory then run the following command: - -``` -pnpm run i18n:json -``` diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 1f1719ec77f..d8270041af0 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -12,7 +12,6 @@ "analyze": "cross-env NODE_ENV=production ANALYZE=true webpack", "prebuild": "pnpm run install-if-deps-outdated", "build": "pnpm run build:feature-config && cross-env NODE_ENV=production webpack", - "postbuild": "pnpm run -s i18n:pot && pnpm run -s i18n:build", "build-storybook": "build-storybook -c ./storybook/.storybook", "build:feature-config": "php ../woocommerce/bin/generate-feature-config.php", "build:packages": "cross-env NODE_ENV=production pnpm run:packages -- build", @@ -24,12 +23,6 @@ "dev": "cross-env WC_ADMIN_PHASE=development pnpm run build:feature-config && cross-env WC_ADMIN_PHASE=development pnpm run build:packages && cross-env WC_ADMIN_PHASE=development webpack", "docs": "./bin/import-wp-css-storybook.sh && BABEL_ENV=storybook STORYBOOK=true pnpm exec build-storybook -c storybook/.storybook -o ./docs/components/storybook", "example": "webpack --config docs/examples/extensions/examples.config.js --watch", - "i18n": "pnpm run -s i18n:js && pnpm run -s i18n:check && pnpm run -s i18n:pot && pnpm run -s i18n:build", - "i18n:build": "php bin/combine-pot-files.php languages/woocommerce-admin.po languages/woocommerce-admin.pot", - "i18n:check": "grunt checktextdomain", - "i18n:js": "pnpm run clean && cross-env NODE_ENV=production babel client packages -o /dev/null", - "i18n:json": "./bin/make-i18n-json.sh", - "i18n:pot": "grunt makepot", "preinstall": "npx only-allow pnpm", "install-if-deps-outdated": "node bin/install-if-deps-outdated.js", "install-if-no-packages": "node bin/install-if-no-packages.js", @@ -183,7 +176,6 @@ "@woocommerce/onboarding": "workspace:*", "@woocommerce/style-build": "workspace:*", "@woocommerce/tracks": "workspace:*", - "@wordpress/babel-plugin-makepot": "^2.1.3", "@wordpress/babel-preset-default": "^6.5.1", "@wordpress/browserslist-config": "^4.1.1", "@wordpress/custom-templated-path-webpack-plugin": "^2.1.2", @@ -216,9 +208,6 @@ "expose-loader": "^3.1.0", "fork-ts-checker-webpack-plugin": "^6.5.0", "fs-extra": "^8.1.0", - "grunt": "^1.4.1", - "grunt-checktextdomain": "^1.0.1", - "grunt-wp-i18n": "^1.0.3", "jest": "^27.5.1", "jest-environment-jsdom": "~27.5.0", "jest-environment-node": "^27.5.1", diff --git a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php index 98cd6b3ae4e..f914a0bfc32 100644 --- a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php +++ b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php @@ -126,8 +126,6 @@ class FeaturePlugin { * @return void */ public function on_plugins_loaded() { - $this->load_plugin_textdomain(); - $this->hooks(); $this->includes(); } @@ -152,13 +150,6 @@ class FeaturePlugin { define( 'WC_ADMIN_VERSION_NUMBER', '3.3.0' ); } - /** - * Load Localisation files. - */ - protected function load_plugin_textdomain() { - load_plugin_textdomain( 'woocommerce-admin', false, basename( dirname( __DIR__ ) ) . '/languages' ); - } - /** * Include WC Admin classes. */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af82cec33c9..9baed0c32c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1228,7 +1228,6 @@ importers: '@woocommerce/tracks': workspace:* '@wordpress/a11y': ^3.5.0 '@wordpress/api-fetch': ^6.0.1 - '@wordpress/babel-plugin-makepot': ^2.1.3 '@wordpress/babel-preset-default': ^6.5.1 '@wordpress/base-styles': ^4.3.0 '@wordpress/browserslist-config': ^4.1.1 @@ -1288,9 +1287,6 @@ importers: github-label-sync: ^2.0.2 grapheme-splitter: ^1.0.4 gridicons: ^3.4.0 - grunt: ^1.4.1 - grunt-checktextdomain: ^1.0.1 - grunt-wp-i18n: ^1.0.3 history: ^4.10.1 jest: ^27.5.1 jest-environment-jsdom: ~27.5.0 @@ -1454,7 +1450,6 @@ importers: '@woocommerce/onboarding': link:../../packages/js/onboarding '@woocommerce/style-build': link:../../packages/js/style-build '@woocommerce/tracks': link:../../packages/js/tracks - '@wordpress/babel-plugin-makepot': 2.1.3_@babel+core@7.17.8 '@wordpress/babel-preset-default': 6.6.1 '@wordpress/browserslist-config': 4.1.2 '@wordpress/custom-templated-path-webpack-plugin': 2.1.2_webpack@5.70.0 @@ -1487,9 +1482,6 @@ importers: expose-loader: 3.1.0_webpack@5.70.0 fork-ts-checker-webpack-plugin: 6.5.0_10568ae13669cc833891d65cd6879aa0 fs-extra: 8.1.0 - grunt: 1.4.1 - grunt-checktextdomain: 1.0.1_grunt@1.4.1 - grunt-wp-i18n: 1.0.3 jest: 27.5.1 jest-environment-jsdom: 27.5.1 jest-environment-node: 27.5.1 @@ -2208,7 +2200,7 @@ packages: '@babel/helper-module-imports': 7.16.0 '@babel/helper-plugin-utils': 7.14.5 '@babel/traverse': 7.16.3 - debug: 4.3.3 + debug: 4.3.2 lodash.debounce: 4.0.8 resolve: 1.20.0 semver: 6.3.0 @@ -2226,7 +2218,7 @@ packages: '@babel/helper-module-imports': 7.16.0 '@babel/helper-plugin-utils': 7.14.5 '@babel/traverse': 7.16.3 - debug: 4.3.3 + debug: 4.3.2 lodash.debounce: 4.0.8 resolve: 1.20.0 semver: 6.3.0 @@ -2244,7 +2236,7 @@ packages: '@babel/helper-module-imports': 7.16.0 '@babel/helper-plugin-utils': 7.14.5 '@babel/traverse': 7.16.3 - debug: 4.3.3 + debug: 4.3.2 lodash.debounce: 4.0.8 resolve: 1.20.0 semver: 6.3.0 @@ -8600,7 +8592,7 @@ packages: react-refresh: 0.11.0 schema-utils: 3.1.1 source-map: 0.7.3 - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0_webpack-cli@4.9.2 dev: true /@pmmmwh/react-refresh-webpack-plugin/0.5.1_92cb4b81c6b9f71cf92f0bdb85e4210c: @@ -8988,7 +8980,7 @@ packages: peerDependencies: '@storybook/addon-actions': '*' dependencies: - '@storybook/addon-actions': 6.4.19 + '@storybook/addon-actions': 6.4.19_react-dom@17.0.2+react@17.0.2 global: 4.4.0 dev: true @@ -11301,7 +11293,7 @@ packages: react-docgen-typescript: 2.2.2_typescript@4.6.2 tslib: 2.3.1 typescript: 4.6.2 - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0_webpack-cli@4.9.2 transitivePeerDependencies: - supports-color dev: true @@ -13275,7 +13267,7 @@ packages: dependencies: '@typescript-eslint/types': 5.4.0 '@typescript-eslint/visitor-keys': 5.4.0 - debug: 4.3.3 + debug: 4.3.2 globby: 11.0.4 is-glob: 4.0.3 semver: 7.3.5 @@ -13836,17 +13828,6 @@ packages: dependencies: '@babel/core': 7.17.8 - /@wordpress/babel-plugin-makepot/2.1.3_@babel+core@7.17.8: - resolution: {integrity: sha512-8ijU4bYUmJuXPnHS47X9Y5OrESLmgx3VVGb+9tNO5hyPoXnZj+ELw9+SB4fJtg0Ur1MDNKRLz4ruJS4Y0tRnNQ==} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.17.8 - '@babel/runtime': 7.17.7 - gettext-parser: 1.4.0 - lodash: 4.17.21 - dev: true - /@wordpress/babel-preset-default/3.0.2: resolution: {integrity: sha512-bsa4piS4GU02isj2XJNUgSEC7MpzdYNy9wOFySrp8G6IHAvwrlwcPEXJf5EuwE8ZqTMmFAzPyKOHFEAx/j+J1A==} engines: {node: '>=8'} @@ -15670,11 +15651,6 @@ packages: engines: {node: '>=12'} dev: true - /ansi-styles/0.2.0: - resolution: {integrity: sha1-NZq0sV3NZLptdHNLcsNjYKmvLBk=} - engines: {node: '>=0.8.0'} - dev: true - /ansi-styles/2.2.1: resolution: {integrity: sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=} engines: {node: '>=0.10.0'} @@ -17657,14 +17633,6 @@ packages: type-detect: 4.0.8 dev: true - /chalk/0.2.1: - resolution: {integrity: sha1-dhPhV1FFshOGSD9/SFql/6jL0Qw=} - engines: {node: '>=0.8.0'} - dependencies: - ansi-styles: 0.2.0 - has-color: 0.1.7 - dev: true - /chalk/1.1.3: resolution: {integrity: sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=} engines: {node: '>=0.10.0'} @@ -22363,7 +22331,7 @@ packages: dependencies: loader-utils: 2.0.2 schema-utils: 3.1.1 - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0_webpack-cli@4.9.2 dev: true /file-loader/6.2.0_webpack@5.64.1: @@ -22570,16 +22538,6 @@ packages: resolve-dir: 1.0.1 dev: true - /findup-sync/4.0.0: - resolution: {integrity: sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==} - engines: {node: '>= 8'} - dependencies: - detect-file: 1.0.0 - is-glob: 4.0.3 - micromatch: 4.0.4 - resolve-dir: 1.0.1 - dev: true - /findup/0.1.5: resolution: {integrity: sha1-itkpozk7rGJ5V6fl3kYjsGsOLOs=} engines: {node: '>=0.6'} @@ -23215,14 +23173,6 @@ packages: encoding: 0.1.13 safe-buffer: 5.2.1 - /gettext-parser/3.1.1: - resolution: {integrity: sha512-vNhWcqXEtZPs5Ft1ReA34g7ByWotpcOIeJvXVy2jF3/G2U9v6W0wG4Z4hXzcU8R//jArqkgHcVCGgGqa4vxVlQ==} - dependencies: - encoding: 0.1.13 - readable-stream: 3.6.0 - safe-buffer: 5.2.1 - dev: true - /github-label-sync/2.0.2: resolution: {integrity: sha512-xDxlGG6s9LVfMNQexatne0bMUrwyYyTma9cC04b82zbEMFoy8rxSlag4eUYYF++ThMxvJp577Wk+uAv0mjRsNg==} engines: {node: '>=12'} @@ -23657,17 +23607,6 @@ packages: /growly/1.3.0: resolution: {integrity: sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=} - /grunt-checktextdomain/1.0.1_grunt@1.4.1: - resolution: {integrity: sha1-slTQHh3pEwBdTbHFMD2QI7mD4Zs=} - engines: {node: '>= 0.8.0'} - peerDependencies: - grunt: '>=0.4.1' - dependencies: - chalk: 0.2.1 - grunt: 1.4.1 - text-table: 0.2.0 - dev: true - /grunt-cli/1.3.2: resolution: {integrity: sha512-8OHDiZZkcptxVXtMfDxJvmN7MVJNE8L/yIcPb4HB7TlyFD1kDvjHrb62uhySsU14wJx9ORMnTuhRMQ40lH/orQ==} engines: {node: '>=4'} @@ -23680,18 +23619,6 @@ packages: v8flags: 3.1.3 dev: true - /grunt-cli/1.4.3: - resolution: {integrity: sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ==} - engines: {node: '>=10'} - hasBin: true - dependencies: - grunt-known-options: 2.0.0 - interpret: 1.1.0 - liftup: 3.0.1 - nopt: 4.0.3 - v8flags: 3.2.0 - dev: true - /grunt-contrib-clean/2.0.0_grunt@1.3.0: resolution: {integrity: sha512-g5ZD3ORk6gMa5ugZosLDQl3dZO7cI3R14U75hTM+dVLVxdMNJCPVmwf9OUt4v4eWgpKKWWoVK9DZc1amJp4nQw==} engines: {node: '>=6'} @@ -23756,11 +23683,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /grunt-known-options/2.0.0: - resolution: {integrity: sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==} - engines: {node: '>=0.10.0'} - dev: true - /grunt-legacy-log-utils/2.1.0: resolution: {integrity: sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==} engines: {node: '>=10'} @@ -23855,14 +23777,6 @@ packages: stylelint: 13.8.0 dev: true - /grunt-wp-i18n/1.0.3: - resolution: {integrity: sha512-CJNbEKeBeOSAPeaJ9B8iCgSwtaG63UR9/uT46a4OsIqnFhOJpeAi138JTlvjfIbnDVoBrzvdrKJe1svveLjUtA==} - engines: {node: '>=0.12.0'} - dependencies: - grunt: 1.4.1 - node-wp-i18n: 1.2.6 - dev: true - /grunt/1.3.0: resolution: {integrity: sha512-6ILlMXv11/4cxuhSMfSU+SfvbxrPuqZrAtLN64+tZpQ3DAKfSQPQHRbTjSbdtxfyQhGZPtN0bDZJ/LdCM5WXXA==} engines: {node: '>=8'} @@ -23885,28 +23799,6 @@ packages: rimraf: 3.0.2 dev: true - /grunt/1.4.1: - resolution: {integrity: sha512-ZXIYXTsAVrA7sM+jZxjQdrBOAg7DyMUplOMhTaspMRExei+fD0BTwdWXnn0W5SXqhb/Q/nlkzXclSi3IH55PIA==} - engines: {node: '>=8'} - hasBin: true - dependencies: - dateformat: 3.0.3 - eventemitter2: 0.4.14 - exit: 0.1.2 - findup-sync: 0.3.0 - glob: 7.1.7 - grunt-cli: 1.4.3 - grunt-known-options: 2.0.0 - grunt-legacy-log: 3.0.0 - grunt-legacy-util: 2.0.1 - iconv-lite: 0.4.24 - js-yaml: 3.14.1 - minimatch: 3.0.4 - mkdirp: 1.0.4 - nopt: 3.0.6 - rimraf: 3.0.2 - dev: true - /gruntify-eslint/5.0.0_grunt@1.3.0: resolution: {integrity: sha512-pa2sXHK9+U4dCGdGSIMkpJARNwRStdLBsddNxmSHSSWROUdhWMrXvFWm6pj48zJhyV3Qy068VIuF1seYIvc0cw==} engines: {node: '>=0.10.0'} @@ -23981,11 +23873,6 @@ packages: /has-bigints/1.0.1: resolution: {integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==} - /has-color/0.1.7: - resolution: {integrity: sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=} - engines: {node: '>=0.10.0'} - dev: true - /has-flag/1.0.0: resolution: {integrity: sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=} engines: {node: '>=0.10.0'} @@ -24318,7 +24205,7 @@ packages: pretty-error: 2.1.2 tapable: 1.1.3 util.promisify: 1.0.0 - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0_webpack-cli@4.9.2 dev: true /html-webpack-plugin/5.5.0_webpack@5.70.0: @@ -28285,20 +28172,6 @@ packages: resolve: 1.20.0 dev: true - /liftup/3.0.1: - resolution: {integrity: sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==} - engines: {node: '>=10'} - dependencies: - extend: 3.0.2 - findup-sync: 4.0.0 - fined: 1.2.0 - flagged-respawn: 1.0.1 - is-plain-object: 2.0.4 - object.map: 1.0.1 - rechoir: 0.7.1 - resolve: 1.20.0 - dev: true - /lilconfig/2.0.4: resolution: {integrity: sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==} engines: {node: '>=10'} @@ -29916,19 +29789,6 @@ packages: engines: {node: '>=6'} dev: true - /node-wp-i18n/1.2.6: - resolution: {integrity: sha512-aLutjDB1rMJ3FNlNcs/XjmaejED1/y30uLYQrmkXpeUj1NH/SA6pI94CUz3iI7fbQd63lTGg0YNvOQAT8cWdIw==} - hasBin: true - dependencies: - bluebird: 3.7.2 - gettext-parser: 3.1.1 - glob: 7.2.0 - lodash: 4.17.21 - minimist: 1.2.5 - mkdirp: 1.0.4 - tmp: 0.2.1 - dev: true - /node.extend/2.0.2: resolution: {integrity: sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==} engines: {node: '>=0.4.0'} @@ -31427,7 +31287,7 @@ packages: postcss: 7.0.39 schema-utils: 3.1.1 semver: 7.3.5 - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0_webpack-cli@4.9.2 dev: true /postcss-loader/6.2.0_postcss@8.3.0+webpack@5.64.1: @@ -32900,7 +32760,7 @@ packages: dependencies: loader-utils: 2.0.2 schema-utils: 3.1.1 - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0_webpack-cli@4.9.2 dev: true /raw-loader/4.0.2_webpack@5.64.1: @@ -35882,7 +35742,7 @@ packages: dependencies: loader-utils: 2.0.2 schema-utils: 2.7.1 - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0_webpack-cli@4.9.2 dev: true /style-loader/2.0.0_webpack@5.70.0: @@ -36628,7 +36488,7 @@ packages: serialize-javascript: 5.0.1 source-map: 0.6.1 terser: 5.10.0_acorn@7.4.1 - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0_webpack-cli@4.9.2 webpack-sources: 1.4.3 transitivePeerDependencies: - acorn @@ -36648,7 +36508,7 @@ packages: serialize-javascript: 5.0.1 source-map: 0.6.1 terser: 5.10.0 - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0_webpack-cli@4.9.2 webpack-sources: 1.4.3 transitivePeerDependencies: - acorn @@ -37893,7 +37753,7 @@ packages: loader-utils: 2.0.2 mime-types: 2.1.34 schema-utils: 3.1.1 - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0_webpack-cli@4.9.2 dev: true /url-parse-lax/1.0.0: @@ -38094,13 +37954,6 @@ packages: homedir-polyfill: 1.0.3 dev: true - /v8flags/3.2.0: - resolution: {integrity: sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==} - engines: {node: '>= 0.10'} - dependencies: - homedir-polyfill: 1.0.3 - dev: true - /validate-npm-package-license/3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: @@ -38448,7 +38301,7 @@ packages: mime: 2.5.2 mkdirp: 0.5.5 range-parser: 1.2.1 - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0_webpack-cli@4.9.2 webpack-log: 2.0.0 dev: true @@ -38531,7 +38384,7 @@ packages: peerDependencies: webpack: ^2.0.0 || ^3.0.0 || ^4.0.0 dependencies: - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0_webpack-cli@4.9.2 dev: true /webpack-fix-style-only-entries/0.6.1: @@ -38715,7 +38568,7 @@ packages: tapable: 1.1.3 terser-webpack-plugin: 1.4.5_webpack@4.46.0 watchpack: 1.7.5 - webpack-cli: 3.3.12_webpack@5.70.0 + webpack-cli: 3.3.12_webpack@4.46.0 webpack-sources: 1.4.3 dev: true From 2e09cd824e2a678148f9a0389146444c7d20f835 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Fri, 8 Apr 2022 12:48:14 +0800 Subject: [PATCH 126/386] Add changelog --- .../changelog/dev-32417-clean-up-wc-admin-package-json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/dev-32417-clean-up-wc-admin-package-json diff --git a/plugins/woocommerce/changelog/dev-32417-clean-up-wc-admin-package-json b/plugins/woocommerce/changelog/dev-32417-clean-up-wc-admin-package-json new file mode 100644 index 00000000000..498e9121c9e --- /dev/null +++ b/plugins/woocommerce/changelog/dev-32417-clean-up-wc-admin-package-json @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Remove load_plugin_textdomain method from admin plugin. From b2effe67c99b32a1fbce6060fd18c4bd479c6683 Mon Sep 17 00:00:00 2001 From: Sakri Koskimies Date: Fri, 8 Apr 2022 10:48:12 +0300 Subject: [PATCH 127/386] Fix phpdoc return type for WC_Order_Refund::get_reason --- plugins/woocommerce/includes/class-wc-order-refund.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-order-refund.php b/plugins/woocommerce/includes/class-wc-order-refund.php index c384d044d65..b24ccc60619 100644 --- a/plugins/woocommerce/includes/class-wc-order-refund.php +++ b/plugins/woocommerce/includes/class-wc-order-refund.php @@ -83,7 +83,7 @@ class WC_Order_Refund extends WC_Abstract_Order { * * @since 2.2 * @param string $context What the value is for. Valid values are view and edit. - * @return int|float + * @return string */ public function get_reason( $context = 'view' ) { return $this->get_prop( 'reason', $context ); @@ -219,7 +219,7 @@ class WC_Order_Refund extends WC_Abstract_Order { * Get refund reason. * * @deprecated 3.0 - * @return int|float + * @return string */ public function get_refund_reason() { wc_deprecated_function( 'get_refund_reason', '3.0', 'get_reason' ); From b6310900de5bcfde23e6e26cd449d50c2636eb02 Mon Sep 17 00:00:00 2001 From: Sakri Koskimies Date: Fri, 8 Apr 2022 11:21:53 +0300 Subject: [PATCH 128/386] Fix TableCard component docs --- packages/js/components/src/table/README.md | 4 ++-- packages/js/components/src/table/index.js | 5 +++-- .../client/analytics/components/leaderboard/index.js | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/js/components/src/table/README.md b/packages/js/components/src/table/README.md index d24269043b8..21e30c4a83f 100644 --- a/packages/js/components/src/table/README.md +++ b/packages/js/components/src/table/README.md @@ -67,8 +67,8 @@ Name | Type | Default | Description `downloadable` | Boolean | `false` | Whether the table must be downloadable. If true, the download button will appear `onClickDownload` | Function | `null` | A callback function called when the "download" button is pressed. Optional, if used, the download button will appear `query` | Object | `{}` | An object of the query parameters passed to the page, ex `{ page: 2, per_page: 5 }` -`rowHeader` | One of type: number, bool | `0` | An array of arrays of display/value object pairs (see `Table` props) -`rows` | Array | `[]` | Which column should be the row header, defaults to the first item (`0`) (see `Table` props) +`rowHeader` | One of type: number, bool | `0` | Which column should be the row header, defaults to the first item (`0`) (see `Table` props) +`rows` | Array | `[]` | (required) An array of arrays of display/value object pairs (see `Table` props) `rowsPerPage` | Number | `null` | (required) The total number of rows to display per page `searchBy` | String | `null` | The string to use as a query parameter when searching row items `showMenu` | Boolean | `true` | Boolean to determine whether or not ellipsis menu is shown diff --git a/packages/js/components/src/table/index.js b/packages/js/components/src/table/index.js index 0763f56c16a..b40d94b87d4 100644 --- a/packages/js/components/src/table/index.js +++ b/packages/js/components/src/table/index.js @@ -308,11 +308,12 @@ TableCard.propTypes = { */ query: PropTypes.object, /** - * An array of arrays of display/value object pairs (see `Table` props). + * Which column should be the row header, defaults to the first item (`0`) (but could be set to `1`, if the first col + * is checkboxes, for example). Set to false to disable row headers. */ rowHeader: PropTypes.oneOfType( [ PropTypes.number, PropTypes.bool ] ), /** - * Which column should be the row header, defaults to the first item (`0`) (see `Table` props). + * An array of arrays of display/value object pairs (see `Table` props). */ rows: PropTypes.arrayOf( PropTypes.arrayOf( diff --git a/plugins/woocommerce-admin/client/analytics/components/leaderboard/index.js b/plugins/woocommerce-admin/client/analytics/components/leaderboard/index.js index ce28b7673a4..7ee8b89db32 100644 --- a/plugins/woocommerce-admin/client/analytics/components/leaderboard/index.js +++ b/plugins/woocommerce-admin/client/analytics/components/leaderboard/index.js @@ -121,7 +121,7 @@ Leaderboard.propTypes = { */ query: PropTypes.object, /** - * Which column should be the row header, defaults to the first item (`0`) (see `Table` props). + * An array of arrays of display/value object pairs (see `Table` props). */ rows: PropTypes.arrayOf( PropTypes.arrayOf( From b6611fb7c1ac7d032766366049d194f63509a9bc Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Wed, 6 Apr 2022 13:07:02 -0300 Subject: [PATCH 129/386] Add e2e tests --- .../admin-e2e-tests/src/pages/WcHomescreen.ts | 18 ++++-- .../src/specs/homescreen/welcome-modal.ts | 55 +++++++++++++++++++ .../admin-homescreen/welcome-modal.test.js | 5 ++ 3 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts create mode 100644 plugins/woocommerce/tests/e2e/specs/admin-homescreen/welcome-modal.test.js diff --git a/packages/js/admin-e2e-tests/src/pages/WcHomescreen.ts b/packages/js/admin-e2e-tests/src/pages/WcHomescreen.ts index a79b6852e19..0f2e4ccc480 100644 --- a/packages/js/admin-e2e-tests/src/pages/WcHomescreen.ts +++ b/packages/js/admin-e2e-tests/src/pages/WcHomescreen.ts @@ -24,12 +24,7 @@ export class WcHomescreen extends BasePage { } async possiblyDismissWelcomeModal(): Promise< void > { - const modalText = 'Welcome to your WooCommerce store’s online HQ!'; - const modal = await waitForElementByTextWithoutThrow( - 'h2', - modalText, - 10 - ); + const modal = await this.isWelcomeModalVisible(); if ( modal ) { await this.clickButtonWithText( 'Next' ); @@ -41,6 +36,17 @@ export class WcHomescreen extends BasePage { } } + async isWelcomeModalVisible(): Promise< boolean > { + const modalText = 'Welcome to your WooCommerce store’s online HQ!'; + const modal = await waitForElementByTextWithoutThrow( + 'h2', + modalText, + 10 + ); + return modal; + } + + async getTaskList(): Promise< Array< string | null > > { await page.waitForSelector( '.woocommerce-task-card .woocommerce-task-list__item-title' diff --git a/packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts b/packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts new file mode 100644 index 00000000000..81c26317df6 --- /dev/null +++ b/packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts @@ -0,0 +1,55 @@ +/** + * External dependencies + */ +import { withRestApi } from '@woocommerce/e2e-utils'; + +/** + * Internal dependencies + */ + import { Login } from '../../pages/Login'; + import { OnboardingWizard } from '../../pages/OnboardingWizard'; + import { WcHomescreen } from '../../pages/WcHomescreen'; +import { + removeAllOrders, + unhideTaskList, + runActionScheduler, + updateOption, + resetWooCommerceState, +} from '../../fixtures'; + +/* eslint-disable @typescript-eslint/no-var-requires */ +const { afterAll, beforeAll, describe, it } = require( '@jest/globals' ); +/* eslint-enable @typescript-eslint/no-var-requires */ + +const testAdminHomescreenWelcomeModal = () => { + describe( 'Homescreen welcome modal', () => { + const profileWizard = new OnboardingWizard( page ); + const homeScreen = new WcHomescreen( page ); + const login = new Login( page ); + + beforeAll( async () => { + await login.login(); + await resetWooCommerceState(); + await profileWizard.navigate(); + await profileWizard.skipStoreSetup(); + } ); + + afterAll( async () => { + await withRestApi.deleteAllProducts(); + await removeAllOrders(); + await unhideTaskList( 'setup' ); + await runActionScheduler(); + await updateOption( 'woocommerce_task_list_hidden', 'no' ); + await login.logout(); + } ); + + it( 'should not show welcome modal', async () => { + await homeScreen.isDisplayed(); + await expect( homeScreen.isWelcomeModalVisible() ).resolves.toBe( + false + ); + } ); + } ); +}; + +module.exports = { testAdminHomescreenWelcomeModal }; diff --git a/plugins/woocommerce/tests/e2e/specs/admin-homescreen/welcome-modal.test.js b/plugins/woocommerce/tests/e2e/specs/admin-homescreen/welcome-modal.test.js new file mode 100644 index 00000000000..c85d7381958 --- /dev/null +++ b/plugins/woocommerce/tests/e2e/specs/admin-homescreen/welcome-modal.test.js @@ -0,0 +1,5 @@ +const { + testAdminHomescreenWelcomeModal, +} = require( '@woocommerce/admin-e2e-tests' ); + +testAdminHomescreenWelcomeModal(); From 00578d472fb4c8d8ee649e59864d95e7269d7e85 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Thu, 7 Apr 2022 10:39:03 -0300 Subject: [PATCH 130/386] Add welcome-modal e2e test --- packages/js/admin-e2e-tests/src/specs/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/js/admin-e2e-tests/src/specs/index.ts b/packages/js/admin-e2e-tests/src/specs/index.ts index dcb88e59853..8a2952d63ed 100644 --- a/packages/js/admin-e2e-tests/src/specs/index.ts +++ b/packages/js/admin-e2e-tests/src/specs/index.ts @@ -7,3 +7,4 @@ export * from './tasks/payment'; export * from './tasks/purchase'; export * from './homescreen/task-list'; export * from './homescreen/activity-panel'; +export * from './homescreen/welcome-modal'; From bf12533f0fe1b63baf9df1a36afa92c790560ef1 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Thu, 7 Apr 2022 10:39:16 -0300 Subject: [PATCH 131/386] Clean test --- .../src/specs/homescreen/welcome-modal.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts b/packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts index 81c26317df6..8e69819c149 100644 --- a/packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts +++ b/packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { withRestApi } from '@woocommerce/e2e-utils'; - /** * Internal dependencies */ @@ -10,10 +5,6 @@ import { withRestApi } from '@woocommerce/e2e-utils'; import { OnboardingWizard } from '../../pages/OnboardingWizard'; import { WcHomescreen } from '../../pages/WcHomescreen'; import { - removeAllOrders, - unhideTaskList, - runActionScheduler, - updateOption, resetWooCommerceState, } from '../../fixtures'; @@ -35,11 +26,6 @@ const testAdminHomescreenWelcomeModal = () => { } ); afterAll( async () => { - await withRestApi.deleteAllProducts(); - await removeAllOrders(); - await unhideTaskList( 'setup' ); - await runActionScheduler(); - await updateOption( 'woocommerce_task_list_hidden', 'no' ); await login.logout(); } ); From 956a0dffa711c49623ac5c478f6580a3812cbcea Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Thu, 7 Apr 2022 11:23:46 -0300 Subject: [PATCH 132/386] Fix lint --- packages/js/admin-e2e-tests/src/pages/WcHomescreen.ts | 1 - .../src/specs/homescreen/welcome-modal.ts | 10 ++++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/js/admin-e2e-tests/src/pages/WcHomescreen.ts b/packages/js/admin-e2e-tests/src/pages/WcHomescreen.ts index 0f2e4ccc480..d1c6c90e06a 100644 --- a/packages/js/admin-e2e-tests/src/pages/WcHomescreen.ts +++ b/packages/js/admin-e2e-tests/src/pages/WcHomescreen.ts @@ -46,7 +46,6 @@ export class WcHomescreen extends BasePage { return modal; } - async getTaskList(): Promise< Array< string | null > > { await page.waitForSelector( '.woocommerce-task-card .woocommerce-task-list__item-title' diff --git a/packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts b/packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts index 8e69819c149..60c8c9ba76e 100644 --- a/packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts +++ b/packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts @@ -1,12 +1,10 @@ /** * Internal dependencies */ - import { Login } from '../../pages/Login'; - import { OnboardingWizard } from '../../pages/OnboardingWizard'; - import { WcHomescreen } from '../../pages/WcHomescreen'; -import { - resetWooCommerceState, -} from '../../fixtures'; +import { Login } from '../../pages/Login'; +import { OnboardingWizard } from '../../pages/OnboardingWizard'; +import { WcHomescreen } from '../../pages/WcHomescreen'; +import { resetWooCommerceState } from '../../fixtures'; /* eslint-disable @typescript-eslint/no-var-requires */ const { afterAll, beforeAll, describe, it } = require( '@jest/globals' ); From 49aff7e3dc3f1af9bda68fee8bf0ce6dc99ae6aa Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Fri, 8 Apr 2022 09:17:49 -0300 Subject: [PATCH 133/386] Add changelog --- .../changelog/add-32157_tests_to_disable_welcome_modals | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/add-32157_tests_to_disable_welcome_modals diff --git a/plugins/woocommerce/changelog/add-32157_tests_to_disable_welcome_modals b/plugins/woocommerce/changelog/add-32157_tests_to_disable_welcome_modals new file mode 100644 index 00000000000..21116006a6f --- /dev/null +++ b/plugins/woocommerce/changelog/add-32157_tests_to_disable_welcome_modals @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add E2E tests to disabled welcome modal #32505 From 0ccf740ef6a5c24f43818ff52d26300a5f860c0f Mon Sep 17 00:00:00 2001 From: Bero Date: Fri, 8 Apr 2022 13:25:01 +0200 Subject: [PATCH 134/386] Remove 'WooCommerce Addons Page' user-agent --- .../woocommerce/includes/admin/class-wc-admin-addons.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-addons.php b/plugins/woocommerce/includes/admin/class-wc-admin-addons.php index 9fca11d5ec8..ffe87e84046 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-addons.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-addons.php @@ -38,8 +38,7 @@ class WC_Admin_Addons { $raw_featured = wp_safe_remote_get( 'https://woocommerce.com/wp-json/wccom-extensions/1.0/featured', array( - 'headers' => $headers, - 'user-agent' => 'WooCommerce Addons Page', + 'headers' => $headers, ) ); @@ -82,8 +81,7 @@ class WC_Admin_Addons { $raw_featured = wp_safe_remote_get( 'https://woocommerce.com/wp-json/wccom-extensions/2.0/featured' . $parameter_string, array( - 'headers' => $headers, - 'user-agent' => 'WooCommerce Addons Page', + 'headers' => $headers, ) ); @@ -258,7 +256,7 @@ class WC_Admin_Addons { if ( ! empty( $section->endpoint ) ) { $section_data = get_transient( 'wc_addons_section_' . $section_id ); if ( false === $section_data ) { - $raw_section = wp_safe_remote_get( esc_url_raw( $section->endpoint ), array( 'user-agent' => 'WooCommerce Addons Page' ) ); + $raw_section = wp_safe_remote_get( esc_url_raw( $section->endpoint ) ); if ( ! is_wp_error( $raw_section ) ) { $section_data = json_decode( wp_remote_retrieve_body( $raw_section ) ); From f08edf3f4b9d163050ba5f451813609d4f411074 Mon Sep 17 00:00:00 2001 From: Bero Date: Fri, 8 Apr 2022 14:03:42 +0200 Subject: [PATCH 135/386] Add changelog --- .../remove-32444-featured-request-user-agent-headers | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/remove-32444-featured-request-user-agent-headers diff --git a/plugins/woocommerce/changelog/remove-32444-featured-request-user-agent-headers b/plugins/woocommerce/changelog/remove-32444-featured-request-user-agent-headers new file mode 100644 index 00000000000..8aabb35193e --- /dev/null +++ b/plugins/woocommerce/changelog/remove-32444-featured-request-user-agent-headers @@ -0,0 +1,4 @@ +Significance: minor +Type: tweak + +Remove custom user-agent from featured extensions requests. From 11239494ef45b7b4a41e440137104f7ae204aa88 Mon Sep 17 00:00:00 2001 From: Bero Date: Fri, 8 Apr 2022 14:20:11 +0200 Subject: [PATCH 136/386] Fix Code sniff errors --- .../includes/admin/class-wc-admin-addons.php | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-addons.php b/plugins/woocommerce/includes/admin/class-wc-admin-addons.php index ffe87e84046..b47f5ecd3e1 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-addons.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-addons.php @@ -104,6 +104,7 @@ class WC_Admin_Addons { /* translators: %d: HTTP error code. */ $message = sprintf( esc_html( + /* translators: Error code */ __( 'Our request to the featured API got error code %d.', 'woocommerce' @@ -134,6 +135,13 @@ class WC_Admin_Addons { self::output_featured( $featured ); } + /** + * Check if the error is due to an SSL error + * + * @param string $error_message Error message. + * + * @return bool True if SSL error, false otherwise + */ public static function is_ssl_error( $error_message ) { return false !== stripos( $error_message, 'cURL error 35' ); } @@ -190,14 +198,23 @@ class WC_Admin_Addons { $response_code = (int) wp_remote_retrieve_response_code( $raw_extensions ); if ( 200 !== $response_code ) { do_action( 'woocommerce_page_wc-addons_connection_error', $response_code ); - return new WP_Error( 'error', __( "Our request to the search API got response code $response_code.", 'woocommerce' ) ); + return new WP_Error( + 'error', + sprintf( + esc_html( + /* translators: Error code */ + __( 'Our request to the search API got response code %s.', 'woocommerce' ) + ), + $response_code + ) + ); } $addons = json_decode( wp_remote_retrieve_body( $raw_extensions ) ); if ( ! is_object( $addons ) || ! isset( $addons->products ) ) { do_action( 'woocommerce_page_wc-addons_connection_error', 'Empty or malformed response' ); - return new WP_Error( 'error', __( "Our request to the search API got a malformed response.", 'woocommerce' ) ); + return new WP_Error( 'error', __( 'Our request to the search API got a malformed response.', 'woocommerce' ) ); } return $addons; @@ -961,6 +978,13 @@ class WC_Admin_Addons {
@@ -970,9 +994,9 @@ class WC_Admin_Addons {

WooCommerce.com, where you\'ll find the most popular WooCommerce extensions.', 'woocommerce' From 9e905ea713afc77fa872afbd887df317a2e247bf Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Fri, 8 Apr 2022 14:03:55 -0300 Subject: [PATCH 137/386] Add Pinterest logo --- .../assets/images/onboarding/pinterest.png | Bin 0 -> 4959 bytes .../DefaultFreeExtensions.php | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 plugins/woocommerce/assets/images/onboarding/pinterest.png diff --git a/plugins/woocommerce/assets/images/onboarding/pinterest.png b/plugins/woocommerce/assets/images/onboarding/pinterest.png new file mode 100644 index 0000000000000000000000000000000000000000..2951353d8fdc53724d79af42a3ab830d76260dbd GIT binary patch literal 4959 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 N0H&q`t%R5b{tuJ;;', '' ), - 'image_url' => plugins_url( 'images/onboarding/creative-mail-by-constant-contact.png', WC_ADMIN_PLUGIN_FILE ), + 'image_url' => plugins_url( 'images/onboarding/pinterest.png', WC_ADMIN_PLUGIN_FILE ), 'manage_url' => 'admin.php?page=pinterest-for-woocommerce', 'is_visible' => [ [ @@ -126,7 +126,7 @@ class DefaultFreeExtensions { 'pinterest-for-woocommerce:alt' => [ 'name' => __( 'Pinterest for WooCommerce', 'woocommerce' ), 'description' => __( 'Get your products in front of Pinterest users searching for ideas and things to buy. Get started with Pinterest and make your entire product catalog browsable.', 'woocommerce-admin' ), - 'image_url' => plugins_url( 'images/onboarding/creative-mail-by-constant-contact.png', WC_ADMIN_PLUGIN_FILE ), + 'image_url' => plugins_url( 'images/onboarding/pinterest.png', WC_ADMIN_PLUGIN_FILE ), 'manage_url' => 'admin.php?page=wc-admin&path=%2Fpinterest%2Flanding', 'is_built_by_wc' => false, ], From ecbd8541052ee2efebec7f9e06eeca21e11930a7 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Fri, 8 Apr 2022 15:07:37 -0300 Subject: [PATCH 138/386] Add changelog --- .../changelog/add-32141_pinterest_extension_to_obw | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/add-32141_pinterest_extension_to_obw diff --git a/plugins/woocommerce/changelog/add-32141_pinterest_extension_to_obw b/plugins/woocommerce/changelog/add-32141_pinterest_extension_to_obw new file mode 100644 index 00000000000..83bce430bb6 --- /dev/null +++ b/plugins/woocommerce/changelog/add-32141_pinterest_extension_to_obw @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add Pinterest extension to onboarding wizard and marketing task #32527 From da5dc21acc7db3628d8f65d5d89bd392f2e05f24 Mon Sep 17 00:00:00 2001 From: Christopher Allford Date: Fri, 8 Apr 2022 11:32:14 -0700 Subject: [PATCH 139/386] Replaced Accent Removal The current `iconv` based implementation does not seem to work in all environments. This replaces it with WordPress' native `remove_accents` function. --- plugins/woocommerce/changelog/fix-ascii-uasort | 4 ++++ .../woocommerce/includes/wc-core-functions.php | 15 ++------------- 2 files changed, 6 insertions(+), 13 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-ascii-uasort diff --git a/plugins/woocommerce/changelog/fix-ascii-uasort b/plugins/woocommerce/changelog/fix-ascii-uasort new file mode 100644 index 00000000000..dbd0e8f6d4d --- /dev/null +++ b/plugins/woocommerce/changelog/fix-ascii-uasort @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +String sorting when using different locales. diff --git a/plugins/woocommerce/includes/wc-core-functions.php b/plugins/woocommerce/includes/wc-core-functions.php index 83ffafe812e..177dbf2eff3 100644 --- a/plugins/woocommerce/includes/wc-core-functions.php +++ b/plugins/woocommerce/includes/wc-core-functions.php @@ -1775,19 +1775,8 @@ function wc_uasort_comparison( $a, $b ) { * @return int */ function wc_ascii_uasort_comparison( $a, $b ) { - // 'setlocale' is required for compatibility with PHP 8. - // Without it, 'iconv' will return '?'s instead of transliterated characters. - $prev_locale = setlocale( LC_CTYPE, 0 ); - setlocale( LC_ALL, 'C.UTF-8' ); - - // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged - if ( function_exists( 'iconv' ) && defined( 'ICONV_IMPL' ) && @strcasecmp( ICONV_IMPL, 'unknown' ) !== 0 ) { - $a = @iconv( 'UTF-8', 'ASCII//TRANSLIT//IGNORE', $a ); - $b = @iconv( 'UTF-8', 'ASCII//TRANSLIT//IGNORE', $b ); - } - // phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged - - setlocale( LC_ALL, $prev_locale ); + $a = remove_accents( $a ); + $b = remove_accents( $b ); return strcmp( $a, $b ); } From bc11e57e14888a42ec34a7f77795534e5565f848 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Thu, 31 Mar 2022 17:04:03 -0700 Subject: [PATCH 140/386] Support protocol-less URLs from the URL util. --- .../src/Internal/Utilities/URL.php | 22 +++++++++++-------- .../php/src/Internal/Utilities/URLTest.php | 2 ++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/plugins/woocommerce/src/Internal/Utilities/URL.php b/plugins/woocommerce/src/Internal/Utilities/URL.php index 26b429e9f2a..60963b09ffa 100644 --- a/plugins/woocommerce/src/Internal/Utilities/URL.php +++ b/plugins/woocommerce/src/Internal/Utilities/URL.php @@ -97,8 +97,12 @@ class URL { $this->components['drive'] = $matches[2]; } - // If there is no scheme, assume and prepend "file://". - if ( ! preg_match( '#^[a-z]+://#i', $this->url ) ) { + /* + * If there is no scheme, assume and prepend "file://". An exception is made for cases where the URL simply + * starts with exactly two forward slashes, which indicates 'any scheme' (most commonly, that is used when + * there is freedom to switch between 'http' and 'https'). + */ + if ( ! preg_match( '#^[a-z]+://#i', $this->url ) && ! preg_match( '#^//(?!/)#', $this->url ) ) { $this->url = 'file://' . $this->url; } @@ -115,19 +119,19 @@ class URL { ); } + $this->components = array_merge( $this->components, $parsed_components ); + // File URLs cannot have a host. However, the initial path segment *or* the Windows drive letter // (if present) may be incorrectly be interpreted as the host name. - if ( 'file' === $parsed_components['scheme'] && ! empty( $parsed_components['host'] ) ) { + if ( 'file' === $this->components['scheme'] && ! empty( $this->components['host'] ) ) { // If we do not have a drive letter, then simply merge the host and the path together. if ( null === $this->components['drive'] ) { - $parsed_components['path'] = $parsed_components['host'] . ( $parsed_components['path'] ?? '' ); + $this->components['path'] = $this->components['host'] . ( $this->components['path'] ?? '' ); } - // Always unset the host in this situation. - unset( $parsed_components['host'] ); + // Restore the host to null in this situation. + $this->components['host'] = null; } - - $this->components = array_merge( $this->components, $parsed_components ); } /** @@ -325,7 +329,7 @@ class URL { public function get_url( array $component_overrides = array() ): string { $components = array_merge( $this->components, $component_overrides ); - $scheme = null !== $components['scheme'] ? $components['scheme'] . '://' : ''; + $scheme = null !== $components['scheme'] ? $components['scheme'] . '://' : '//'; $host = null !== $components['host'] ? $components['host'] : ''; $port = null !== $components['port'] ? ':' . $components['port'] : ''; $path = $this->get_path( $components['path'] ); diff --git a/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php b/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php index 659da935540..95ab9838164 100644 --- a/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php +++ b/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php @@ -88,6 +88,8 @@ class URLTest extends WC_Unit_Test_Case { array( 'https://foo.bar/parent/%2E%2e/asset.txt', 'https://foo.bar/asset.txt' ), array( 'https://foo.bar/parent/%2E.%2fasset.txt', 'https://foo.bar/parent/%2E.%2fasset.txt' ), array( 'http://localhost?../../bar', 'http://localhost/?../../bar' ), + array( '//http.or.https/', '//http.or.https/' ), + array( '//schemaless/with-path', '//schemaless/with-path' ), ); } From 6dcb796a890743098e7a3660093f3c0347f3ecb7 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Fri, 8 Apr 2022 11:51:18 -0700 Subject: [PATCH 141/386] No changelog required (but let's satisfy the linter). --- plugins/woocommerce/changelog/fix-protocol-agnostic-urls | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-protocol-agnostic-urls diff --git a/plugins/woocommerce/changelog/fix-protocol-agnostic-urls b/plugins/woocommerce/changelog/fix-protocol-agnostic-urls new file mode 100644 index 00000000000..740508dbf36 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-protocol-agnostic-urls @@ -0,0 +1,5 @@ +Significance: patch +Type: tweak +Comment: Changelog entry not needed, because this is an adjustment to unreleased code. + + From 35a2eb1f9fa48b2591a3fad0c8b8e94c53c3e6f5 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Fri, 8 Apr 2022 16:10:43 -0300 Subject: [PATCH 142/386] Move test to `complete-onboarding-wizard` --- .../complete-onboarding-wizard.ts | 27 +++++++++++++ .../src/specs/homescreen/welcome-modal.ts | 39 ------------------- .../js/admin-e2e-tests/src/specs/index.ts | 1 - .../complete-onboarding-wizard.test.js | 2 + 4 files changed, 29 insertions(+), 40 deletions(-) delete mode 100644 packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts diff --git a/packages/js/admin-e2e-tests/src/specs/activate-and-setup/complete-onboarding-wizard.ts b/packages/js/admin-e2e-tests/src/specs/activate-and-setup/complete-onboarding-wizard.ts index f0772672b86..5a8e48dd0b9 100644 --- a/packages/js/admin-e2e-tests/src/specs/activate-and-setup/complete-onboarding-wizard.ts +++ b/packages/js/admin-e2e-tests/src/specs/activate-and-setup/complete-onboarding-wizard.ts @@ -597,10 +597,37 @@ const testBusinessDetailsForm = () => { } ); }; +const testAdminHomescreenWelcomeModal = () => { + describe( 'Homescreen', () => { + const profileWizard = new OnboardingWizard( page ); + const homeScreen = new WcHomescreen( page ); + const login = new Login( page ); + + beforeAll( async () => { + await login.login(); + await resetWooCommerceState(); + await profileWizard.navigate(); + await profileWizard.skipStoreSetup(); + } ); + + afterAll( async () => { + await login.logout(); + } ); + + it( 'should not show welcome modal', async () => { + await homeScreen.isDisplayed(); + await expect( homeScreen.isWelcomeModalVisible() ).resolves.toBe( + false + ); + } ); + } ); +}; + module.exports = { testAdminOnboardingWizard, testSelectiveBundleWCPay, testDifferentStoreCurrenciesWCPay, testSubscriptionsInclusion, testBusinessDetailsForm, + testAdminHomescreenWelcomeModal, }; diff --git a/packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts b/packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts deleted file mode 100644 index 60c8c9ba76e..00000000000 --- a/packages/js/admin-e2e-tests/src/specs/homescreen/welcome-modal.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Internal dependencies - */ -import { Login } from '../../pages/Login'; -import { OnboardingWizard } from '../../pages/OnboardingWizard'; -import { WcHomescreen } from '../../pages/WcHomescreen'; -import { resetWooCommerceState } from '../../fixtures'; - -/* eslint-disable @typescript-eslint/no-var-requires */ -const { afterAll, beforeAll, describe, it } = require( '@jest/globals' ); -/* eslint-enable @typescript-eslint/no-var-requires */ - -const testAdminHomescreenWelcomeModal = () => { - describe( 'Homescreen welcome modal', () => { - const profileWizard = new OnboardingWizard( page ); - const homeScreen = new WcHomescreen( page ); - const login = new Login( page ); - - beforeAll( async () => { - await login.login(); - await resetWooCommerceState(); - await profileWizard.navigate(); - await profileWizard.skipStoreSetup(); - } ); - - afterAll( async () => { - await login.logout(); - } ); - - it( 'should not show welcome modal', async () => { - await homeScreen.isDisplayed(); - await expect( homeScreen.isWelcomeModalVisible() ).resolves.toBe( - false - ); - } ); - } ); -}; - -module.exports = { testAdminHomescreenWelcomeModal }; diff --git a/packages/js/admin-e2e-tests/src/specs/index.ts b/packages/js/admin-e2e-tests/src/specs/index.ts index 8a2952d63ed..dcb88e59853 100644 --- a/packages/js/admin-e2e-tests/src/specs/index.ts +++ b/packages/js/admin-e2e-tests/src/specs/index.ts @@ -7,4 +7,3 @@ export * from './tasks/payment'; export * from './tasks/purchase'; export * from './homescreen/task-list'; export * from './homescreen/activity-panel'; -export * from './homescreen/welcome-modal'; diff --git a/plugins/woocommerce/tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.js b/plugins/woocommerce/tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.js index 77af197d123..d69d0eb855a 100644 --- a/plugins/woocommerce/tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.js +++ b/plugins/woocommerce/tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.js @@ -4,6 +4,7 @@ const { testDifferentStoreCurrenciesWCPay, testSubscriptionsInclusion, testBusinessDetailsForm, + testAdminHomescreenWelcomeModal, } = require( '@woocommerce/admin-e2e-tests' ); const { withRestApi, IS_RETEST_MODE } = require( '@woocommerce/e2e-utils' ); @@ -17,3 +18,4 @@ testSelectiveBundleWCPay(); testDifferentStoreCurrenciesWCPay(); testSubscriptionsInclusion(); testBusinessDetailsForm(); +testAdminHomescreenWelcomeModal(); From 4449a669c84f850b482a0f3209cb4505e3cc7579 Mon Sep 17 00:00:00 2001 From: Josh Betz Date: Fri, 8 Apr 2022 15:31:03 -0500 Subject: [PATCH 143/386] Add order_item_display_meta description --- .../Version2/class-wc-rest-orders-v2-controller.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php index 421a55e0c11..30b012c9096 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php @@ -1850,6 +1850,13 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller { 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); + $params['order_item_display_meta'] = array( + 'default' => false, + 'description' => __( 'Only show meta which is meant to be displayed for an order.', 'woocomerce' ), + 'type' => 'boolean', + 'sanitize_callback' => 'rest_sanitize_boolean', + 'validate_callback' => 'rest_validate_request_arg', + ); return $params; } From 92a2035896a56c2b3219090e3ec6b4a53a8a0cfd Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Fri, 8 Apr 2022 16:20:44 -0700 Subject: [PATCH 144/386] Update PHPCS ruleset for PHP 7.2. --- phpcs.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpcs.xml b/phpcs.xml index 525a3e938d6..74e638dd71a 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -27,7 +27,7 @@ - + From c5cf0fbd795cf39f40a5de697e56a872c9bb30d3 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Mon, 11 Apr 2022 10:50:26 +0800 Subject: [PATCH 145/386] Fix admin js-tests config path --- packages/js/js-tests/src/setup-globals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/js-tests/src/setup-globals.js b/packages/js/js-tests/src/setup-globals.js index 201fc59265f..b95d9e74d2c 100644 --- a/packages/js/js-tests/src/setup-globals.js +++ b/packages/js/js-tests/src/setup-globals.js @@ -107,7 +107,7 @@ wooCommercePackages.forEach( ( lib ) => { } ); } ); -const config = require( '../../../../plugins/woocommerce-admin/config/development.json' ); +const config = require( '../../../../plugins/woocommerce/client/admin/config/development.json' ); // Check if test is jsdom or node if ( global.window ) { From 90c61e4bada3bb7d2e3306630619afe8e8368fbe Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Mon, 11 Apr 2022 11:51:55 +0800 Subject: [PATCH 146/386] Revert WCPay suggestion UI changes --- .../src/components/WCPayAcceptedMethods.js | 25 +- .../src/components/WCPayCard/WCPayCard.js | 147 +++------- .../src/components/WCPayCard/WCPayCard.scss | 155 +++-------- .../js/onboarding/src/images/cards/amex.js | 23 +- .../onboarding/src/images/cards/applepay.js | 21 +- packages/js/onboarding/src/images/cards/cb.js | 33 +-- .../js/onboarding/src/images/cards/diners.js | 251 ++++++++++++++++-- .../onboarding/src/images/cards/discover.js | 148 +++-------- .../js/onboarding/src/images/cards/giropay.js | 49 ---- .../onboarding/src/images/cards/googlepay.js | 47 ++-- .../js/onboarding/src/images/cards/jcb.js | 72 ++--- .../onboarding/src/images/cards/mastercard.js | 35 +-- .../js/onboarding/src/images/cards/sofort.js | 35 --- .../onboarding/src/images/cards/unionpay.js | 103 ++++++- .../js/onboarding/src/images/cards/visa.js | 35 +-- .../onboarding/src/images/wcpay-benefit-1.js | 134 ---------- .../onboarding/src/images/wcpay-benefit-2.js | 102 ------- .../onboarding/src/images/wcpay-benefit-3.js | 188 ------------- .../onboarding/src/images/wcpay-hero-image.js | 140 ---------- .../components/WCPay/Suggestion.js | 67 +++-- .../PaymentGatewaySuggestions/test/index.js | 6 +- 21 files changed, 599 insertions(+), 1217 deletions(-) delete mode 100644 packages/js/onboarding/src/images/cards/giropay.js delete mode 100644 packages/js/onboarding/src/images/cards/sofort.js delete mode 100644 packages/js/onboarding/src/images/wcpay-benefit-1.js delete mode 100644 packages/js/onboarding/src/images/wcpay-benefit-2.js delete mode 100644 packages/js/onboarding/src/images/wcpay-benefit-3.js delete mode 100644 packages/js/onboarding/src/images/wcpay-hero-image.js diff --git a/packages/js/onboarding/src/components/WCPayAcceptedMethods.js b/packages/js/onboarding/src/components/WCPayAcceptedMethods.js index cea52fdb3d9..f104c24e4e9 100644 --- a/packages/js/onboarding/src/components/WCPayAcceptedMethods.js +++ b/packages/js/onboarding/src/components/WCPayAcceptedMethods.js @@ -10,39 +10,32 @@ import { __ } from '@wordpress/i18n'; */ import Visa from '../images/cards/visa.js'; import MasterCard from '../images/cards/mastercard.js'; +import Maestro from '../images/cards/maestro.js'; import Amex from '../images/cards/amex.js'; import ApplePay from '../images/cards/applepay.js'; -import GooglePay from '../images/cards/googlepay.js'; import CB from '../images/cards/cb.js'; import DinersClub from '../images/cards/diners.js'; import Discover from '../images/cards/discover.js'; import JCB from '../images/cards/jcb.js'; import UnionPay from '../images/cards/unionpay.js'; -import GiroPay from '../images/cards/giropay.js'; -import Sofort from '../images/cards/sofort.js'; export const WCPayAcceptedMethods = () => ( -

- - { __( 'Accepted payment methods include:', 'woocommerce' ) } + <> + + { __( 'Accepted payment methods', 'woocommerce' ) } -
+
+ - - - - + - -
- & more. -
+
-
+ ); diff --git a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js index ca4f5cf634b..88a79a7ad1c 100644 --- a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js +++ b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.js @@ -1,126 +1,63 @@ /** * External dependencies */ -import { Card, CardBody, CardFooter } from '@wordpress/components'; +import { Card, CardBody, CardHeader, CardFooter } from '@wordpress/components'; import { Text } from '@woocommerce/experimental'; import { createElement } from '@wordpress/element'; import { Link } from '@woocommerce/components'; -import interpolateComponents from '@automattic/interpolate-components'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import { WCPayAcceptedMethods } from '../WCPayAcceptedMethods'; -import WCPayHeroImage from '../../images/wcpay-hero-image'; -import WCPayBenefit1 from '../../images/wcpay-benefit-1'; -import WCPayBenefit2 from '../../images/wcpay-benefit-2'; -import WCPayBenefit3 from '../../images/wcpay-benefit-3'; +import WCPayLogo from '../../images/wcpay-logo'; -export const WCPayCardBody = ( { children, onLinkClick = () => {} } ) => ( +export const WCPayCardHeader = ( { + logoWidth = 196, + logoHeight = 41, + children, +} ) => ( + + + { children } + +); + +export const WCPayCardBody = ( { + description, + heading, + onLinkClick = () => {}, +} ) => ( -
-
- - { __( - 'Accept payments and manage your business.', - 'woocommerce' - ) } - - - { interpolateComponents( { - mixedString: __( - 'By using WooCommerce Payments you agree to be bound by our {{tosLink /}} and acknowledge that you have read our {{privacyLink /}}', - 'woocommerce' - ), - components: { - tosLink: ( - - { __( 'Terms of Service', 'woocommerce' ) } - - ), - privacyLink: ( - - { __( 'Privacy Policy', 'woocommerce' ) } - - ), - }, - } ) } -
-
-
{ children }
-
-
- -
-
+ { heading && { heading } } + + + { description } +
+ + { __( 'Learn more', 'woocommerce' ) } + +
+ +
); -export const WCPayCardFooter = () => ( - - - +export const WCPayCardFooter = ( { children } ) => ( + { children } ); export const WCPayCard = ( { children } ) => { - return ( - - { children } - - ); -}; - -export const WCPayBenefitCard = () => { - return ( - - -
-
- - { __( - 'Offer your customers their preferred way to pay including debit and credit card payments, Apple Pay, Sofort, SEPA, iDeal and many more.', - 'woocommerce' - ) } -
-
- - { __( - 'Sell to international markets and accept more than 135 currencies with local payment methods.', - 'woocommerce' - ) } -
-
- - { __( - 'Earn and manage recurring revenue and get automatic deposits into your nominated bank account.', - 'woocommerce' - ) } -
-
-
-
- ); + return { children }; }; diff --git a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss index 6e50f1f3edd..c21e835ff68 100644 --- a/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss +++ b/packages/js/onboarding/src/components/WCPayCard/WCPayCard.scss @@ -1,126 +1,43 @@ -.woocommerce-task-payments { - .woocommerce-task-payment-wcpay { - .vstack, - .hstack { - display: flex; +.woocommerce-task-payments .woocommerce-task-payment-wcpay { + .woocommerce-task-payment-wcpay__description { + font-size: 16px; + margin-bottom: $gap-large; + } - &.content-center { - justify-content: center; - } + .components-card__header { + margin-bottom: $gap-small; + justify-content: flex-start; + padding: 25px; - &.content-around { - justify-content: space-around; - } - - &.flex-1 { - flex: 1; - } - } - - .vstack { - flex-direction: column; - } - - .hstack { - flex-direction: row; - } - - .woocommerce-task-payment-wcpay__hero-image-container { - display: flex; - flex-direction: column; - justify-content: flex-end; - - svg { - margin-right: -32px; - margin-bottom: -24px; - } - - @media screen and (max-width: 600px) { - max-width: 200px; - } - } - - .woocommerce-task-payment-wcpay__heading { - letter-spacing: 0.6px; - max-width: 250px; - margin-right: $gap-small; - margin-bottom: $gap; - } - - .woocommerce-task-payment-wcpay__description { - font-size: 13px; - max-width: 325px; - margin-right: $gap-small; - margin-bottom: $gap-large; - } - - .components-card__header { - margin-bottom: $gap-small; - justify-content: flex-start; - padding: 25px; - - .woocommerce-pill { - margin-left: $gap-small; - } - } - - .components-card__footer { - flex-direction: column; - align-items: flex-start; - - .components-button { - margin-top: $gap; - margin-left: 0; - } - - &, - h3 { - color: #757575; - } - - svg { - height: 24px; - } - } - - .components-card__body { - h2 { - margin: 0 0 20px 0; - } - } - - .woocommerce-task-payment-wcpay__accepted-icons { - h3 { - color: #40464d; - } - - display: flex; - flex-wrap: wrap; - margin-top: $gap-small; - margin-left: 0; - gap: 8px; - justify-content: flex-start; - align-items: flex-end; - } - - .woocommerce-task-payment-wcpay__benefit { - svg { - margin: 0 auto; - } - - max-width: 170px; - text-align: center; - font-size: 15px; - line-height: 24px; - letter-spacing: normal; - } - - .woocommerce-task-payment-wcpay__accepted-and-more { - white-space: nowrap; + .woocommerce-pill { + margin-left: $gap-small; } } - .woocommerce-task-payment-wcpay__benefits-card { - margin-bottom: 0; + .components-card__footer { + flex-direction: column; + align-items: flex-start; + + .components-button { + margin-top: $gap; + margin-left: 0; + } + } + + .components-card__body { + h2 { + margin: 0 0 20px 0; + } + } + + .woocommerce-task-payment-wcpay__accepted { + display: flex; + margin-top: $gap-small; + flex-wrap: wrap; + gap: $gap-small; + + h3 { + color: #40464d; + } } } diff --git a/packages/js/onboarding/src/images/cards/amex.js b/packages/js/onboarding/src/images/cards/amex.js index 49facfe9d6e..daa085352db 100644 --- a/packages/js/onboarding/src/images/cards/amex.js +++ b/packages/js/onboarding/src/images/cards/amex.js @@ -5,25 +5,26 @@ import { createElement } from '@wordpress/element'; export default () => ( - - ); diff --git a/packages/js/onboarding/src/images/cards/applepay.js b/packages/js/onboarding/src/images/cards/applepay.js index edb77a8a8e8..74febe62299 100644 --- a/packages/js/onboarding/src/images/cards/applepay.js +++ b/packages/js/onboarding/src/images/cards/applepay.js @@ -5,25 +5,26 @@ import { createElement } from '@wordpress/element'; export default () => ( - - ); diff --git a/packages/js/onboarding/src/images/cards/cb.js b/packages/js/onboarding/src/images/cards/cb.js index b3dcb6cadcd..da48c9a5b13 100644 --- a/packages/js/onboarding/src/images/cards/cb.js +++ b/packages/js/onboarding/src/images/cards/cb.js @@ -5,33 +5,34 @@ import { createElement } from '@wordpress/element'; export default () => ( - - diff --git a/packages/js/onboarding/src/images/cards/diners.js b/packages/js/onboarding/src/images/cards/diners.js index 5630431f0eb..039bad8b238 100644 --- a/packages/js/onboarding/src/images/cards/diners.js +++ b/packages/js/onboarding/src/images/cards/diners.js @@ -5,37 +5,254 @@ import { createElement } from '@wordpress/element'; export default () => ( - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); diff --git a/packages/js/onboarding/src/images/cards/discover.js b/packages/js/onboarding/src/images/cards/discover.js index a9e764ba46c..51479f917aa 100644 --- a/packages/js/onboarding/src/images/cards/discover.js +++ b/packages/js/onboarding/src/images/cards/discover.js @@ -5,134 +5,46 @@ import { createElement } from '@wordpress/element'; export default () => ( - - - - - - - - - - - - - - - - - - - - - + + - - - - - - + + + - - - - - - - - - ); diff --git a/packages/js/onboarding/src/images/cards/giropay.js b/packages/js/onboarding/src/images/cards/giropay.js deleted file mode 100644 index 6979d1911c5..00000000000 --- a/packages/js/onboarding/src/images/cards/giropay.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * External dependencies - */ -import { createElement } from '@wordpress/element'; - -export default () => ( - - - - - - - - - -); diff --git a/packages/js/onboarding/src/images/cards/googlepay.js b/packages/js/onboarding/src/images/cards/googlepay.js index d7e00b3b269..329ec0e3641 100644 --- a/packages/js/onboarding/src/images/cards/googlepay.js +++ b/packages/js/onboarding/src/images/cards/googlepay.js @@ -5,61 +5,48 @@ import { createElement } from '@wordpress/element'; export default () => ( - - ); diff --git a/packages/js/onboarding/src/images/cards/jcb.js b/packages/js/onboarding/src/images/cards/jcb.js index 36c6569289e..ee332408bba 100644 --- a/packages/js/onboarding/src/images/cards/jcb.js +++ b/packages/js/onboarding/src/images/cards/jcb.js @@ -5,78 +5,38 @@ import { createElement } from '@wordpress/element'; export default () => ( - - - - - - - - - - - - - - - - - ); diff --git a/packages/js/onboarding/src/images/cards/mastercard.js b/packages/js/onboarding/src/images/cards/mastercard.js index d6755de398d..8fbde439e41 100644 --- a/packages/js/onboarding/src/images/cards/mastercard.js +++ b/packages/js/onboarding/src/images/cards/mastercard.js @@ -5,37 +5,38 @@ import { createElement } from '@wordpress/element'; export default () => ( - ); diff --git a/packages/js/onboarding/src/images/cards/sofort.js b/packages/js/onboarding/src/images/cards/sofort.js deleted file mode 100644 index 9d651368c38..00000000000 --- a/packages/js/onboarding/src/images/cards/sofort.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * External dependencies - */ -import { createElement } from '@wordpress/element'; - -export default () => ( - - - - - - - -); diff --git a/packages/js/onboarding/src/images/cards/unionpay.js b/packages/js/onboarding/src/images/cards/unionpay.js index 457b182fbbb..0be12014c7b 100644 --- a/packages/js/onboarding/src/images/cards/unionpay.js +++ b/packages/js/onboarding/src/images/cards/unionpay.js @@ -5,35 +5,110 @@ import { createElement } from '@wordpress/element'; export default () => ( + + + + + + + + + + ); diff --git a/packages/js/onboarding/src/images/cards/visa.js b/packages/js/onboarding/src/images/cards/visa.js index dbae529ccd4..cd81d8a8fa3 100644 --- a/packages/js/onboarding/src/images/cards/visa.js +++ b/packages/js/onboarding/src/images/cards/visa.js @@ -5,39 +5,40 @@ import { createElement } from '@wordpress/element'; export default () => ( - - ); diff --git a/packages/js/onboarding/src/images/wcpay-benefit-1.js b/packages/js/onboarding/src/images/wcpay-benefit-1.js deleted file mode 100644 index e0dc078c0f7..00000000000 --- a/packages/js/onboarding/src/images/wcpay-benefit-1.js +++ /dev/null @@ -1,134 +0,0 @@ -/** - * External dependencies - */ -import { createElement } from '@wordpress/element'; - -export default ( { width = 141, height = 148, ...props } ) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); diff --git a/packages/js/onboarding/src/images/wcpay-benefit-2.js b/packages/js/onboarding/src/images/wcpay-benefit-2.js deleted file mode 100644 index d9ffafc23cd..00000000000 --- a/packages/js/onboarding/src/images/wcpay-benefit-2.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * External dependencies - */ -import { createElement } from '@wordpress/element'; - -export default ( { width = 140, height = 148, ...props } ) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); diff --git a/packages/js/onboarding/src/images/wcpay-benefit-3.js b/packages/js/onboarding/src/images/wcpay-benefit-3.js deleted file mode 100644 index 9d29102f3e8..00000000000 --- a/packages/js/onboarding/src/images/wcpay-benefit-3.js +++ /dev/null @@ -1,188 +0,0 @@ -/** - * External dependencies - */ -import { createElement } from '@wordpress/element'; - -export default ( { width = 157, height = 148, ...props } ) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); diff --git a/packages/js/onboarding/src/images/wcpay-hero-image.js b/packages/js/onboarding/src/images/wcpay-hero-image.js deleted file mode 100644 index 2d70f3d126d..00000000000 --- a/packages/js/onboarding/src/images/wcpay-hero-image.js +++ /dev/null @@ -1,140 +0,0 @@ -/** - * External dependencies - */ -import { createElement } from '@wordpress/element'; - -export default ( { width = 293, height = 275, ...props } ) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/WCPay/Suggestion.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/WCPay/Suggestion.js index 40da21f5430..ee4ad87a861 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/WCPay/Suggestion.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/WCPay/Suggestion.js @@ -2,12 +2,16 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; +import interpolateComponents from '@automattic/interpolate-components'; +import { Link, Pill } from '@woocommerce/components'; import { recordEvent } from '@woocommerce/tracks'; +import { Text } from '@woocommerce/experimental'; import { WCPayCard, + WCPayCardHeader, WCPayCardFooter, WCPayCardBody, - WCPayBenefitCard, + SetupRequired, } from '@woocommerce/onboarding'; import { useDispatch } from '@wordpress/data'; @@ -18,8 +22,26 @@ import { useDispatch } from '@wordpress/data'; import { Action } from '../Action'; import { connectWcpay } from './utils'; +const TosPrompt = () => + interpolateComponents( { + mixedString: __( + 'Upon clicking "Get started", you agree to the {{link}}Terms of service{{/link}}. Next we’ll ask you to share a few details about your business to create your account.', + 'woocommerce' + ), + components: { + link: ( + + ), + }, + } ); + export const Suggestion = ( { paymentGateway, onSetupCallback = null } ) => { const { + description, id, needsSetup, installed, @@ -40,14 +62,27 @@ export const Suggestion = ( { paymentGateway, onSetupCallback = null } ) => { } return ( - <> - - { - recordEvent( 'tasklist_payment_learn_more' ); - } } - > + + + { installed && needsSetup ? ( + + ) : ( + { __( 'Recommended', 'woocommerce' ) } + ) } + + + { + recordEvent( 'tasklist_payment_learn_more' ); + } } + /> + + + <> + + + { isRecommended={ true } isInstalled={ isInstalled } hasPlugins={ true } - setupButtonText={ - installed && needsSetup - ? __( 'Finish setup', 'woocommerce' ) - : __( 'Install', 'woocommerce' ) - } + setupButtonText={ __( 'Get started', 'woocommerce' ) } onSetupCallback={ onSetupCallback } /> - - - - - + + + ); }; diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js index 3fdf6c29efd..7df91e7a82a 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js @@ -118,10 +118,8 @@ describe( 'PaymentGatewaySuggestions', () => { expect( paymentTitles ).toEqual( [] ); expect( - container.getElementsByClassName( - 'woocommerce-task-payment-wcpay' - )[ 0 ].textContent - ).toContain( 'By using WooCommerce Payments' ); + container.getElementsByTagName( 'title' )[ 0 ].textContent + ).toBe( 'WooCommerce Payments' ); } ); test( 'should render all payment gateways if no WCPay', () => { From c8b6bd91e037f33a0ee81054f3d4b06af0975400 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Mon, 11 Apr 2022 14:24:08 +0800 Subject: [PATCH 147/386] Update payment gateway logos --- .../components/List/List.scss | 5 ++-- .../fills/PaymentGatewaySuggestions/index.js | 4 +-- .../images/payment_methods/72x72/affirm.png | Bin 0 -> 972 bytes .../images/payment_methods/72x72/afterpay.png | Bin 0 -> 674 bytes .../payment_methods/72x72/amazonpay.png | Bin 0 -> 1100 bytes .../images/payment_methods/72x72/bacs.png | Bin 0 -> 485 bytes .../images/payment_methods/72x72/cod.png | Bin 0 -> 537 bytes .../images/payment_methods/72x72/klarna.png | Bin 0 -> 710 bytes .../payment_methods/72x72/mercadopago.png | Bin 0 -> 1435 bytes .../images/payment_methods/72x72/mollie.png | Bin 0 -> 637 bytes .../images/payment_methods/72x72/payfast.png | Bin 0 -> 766 bytes .../images/payment_methods/72x72/paypal.png | Bin 0 -> 1229 bytes .../images/payment_methods/72x72/paystack.png | Bin 0 -> 736 bytes .../images/payment_methods/72x72/payu.png | Bin 0 -> 673 bytes .../images/payment_methods/72x72/razorpay.png | Bin 0 -> 706 bytes .../images/payment_methods/72x72/square.png | Bin 0 -> 609 bytes .../images/payment_methods/72x72/stripe.png | Bin 0 -> 2783 bytes .../DefaultPaymentGateways.php | 26 +++++++++--------- 18 files changed, 18 insertions(+), 17 deletions(-) create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/affirm.png create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/afterpay.png create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/amazonpay.png create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/bacs.png create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/cod.png create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/klarna.png create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/mercadopago.png create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/mollie.png create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/payfast.png create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/paypal.png create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/paystack.png create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/payu.png create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/razorpay.png create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/square.png create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/stripe.png diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.scss b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.scss index dd2b03ecafe..cb524da6945 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.scss +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.scss @@ -6,14 +6,15 @@ overflow: hidden; .components-card__media { - width: 170px; + width: 85px; flex-shrink: 0; + align-self: flex-start; img, svg, .is-placeholder { margin: auto; - max-width: 100px; + max-width: 36px; display: block; } diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js index b79adf43dac..9c552cd887d 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js @@ -309,15 +309,15 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { onToggle={ trackToggle } > { additionalSection } - { enabledSection } { offlineSection } + { enabledSection } ) : ( <> { additionalSection } - { enabledSection } { offlineSection } + { enabledSection } ) }
diff --git a/plugins/woocommerce/assets/images/payment_methods/72x72/affirm.png b/plugins/woocommerce/assets/images/payment_methods/72x72/affirm.png new file mode 100644 index 0000000000000000000000000000000000000000..9401ad1b670d7b66138fb5e29451005fcb38e57a GIT binary patch literal 972 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 WLRB?bfTtQ5UkskEelF{r5}E)SrR~H3 literal 0 HcmV?d00001 diff --git a/plugins/woocommerce/assets/images/payment_methods/72x72/cod.png b/plugins/woocommerce/assets/images/payment_methods/72x72/cod.png new file mode 100644 index 0000000000000000000000000000000000000000..f2ba5f688886bb7dc1a34959e4257aecd7899fe0 GIT binary patch literal 537 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAjKY=BRQE0F&A{>RU^AoTy;{~vF^ z1JU=l-+&}gcl-7D5FH_k6F)<4(TBuw%8zZ#$gLM1_d!3+xb`vn%fPdIAux`S- zBZ)bRhkKZg&FvG-U1k-P9@3+y?R7kB>#-?QJa_6XUy#Ga>(A}qx3c$D;o{v9fv`)vGn*n8ekI}~hxp5bKag5yeiwB@F2+*CO9^M9@NUgtSWI(}uwb;e&a zJm%Te!8XS(aFcIK?5vq>7M}l7`VY3-`NjQFPZD1%kga%RmefwO(-S%5S53aD(wsN9 zRW$p$Dm%w2?GDf6ls28-X_CK9bjt-=s~o1=OBrYNZkuv0y0fUEaN9Z|mG{f|Uz$jqwboFyt=akR{04y>B6951J literal 0 HcmV?d00001 diff --git a/plugins/woocommerce/assets/images/payment_methods/72x72/klarna.png b/plugins/woocommerce/assets/images/payment_methods/72x72/klarna.png new file mode 100644 index 0000000000000000000000000000000000000000..a97abed5981cb73b7da2e6b80d1794e0f47d6273 GIT binary patch literal 710 zcmV;%0y+JOP)YT5!d4_>jV1QL%dsAF= zOIBz{O=Lq$V?s(^Jw-$DT>(0#A~=iAqTdz0 z+!pk!%_B@AGtrHbQeIb^!y-|cp-7^rN**!UGKeB9FQSWr9FvePnqoozkSucNMX0tw z7HuRFl10M-hF(Gyr5;9V21L=)Qy9%-n#c|F(vzn^6Kz!K8BBmE+8wlf1F8rnKCb?X zEP{7jl^2jjK_-zXrix(Ymo-hQhZo=)ieR?;a7&EGU&my^hd;WMz z{`#NxEcy>3^A^I@S_rnLhON!wYy6xBaA0G%&Va?%u5%6$gu^h>;2pBi$3EPHD8w-z sjoCt{W^z}0?R`1M$M(w-s=v z&(honpV#T~^*X2APF8_6J#X9W^M;G5n9u3L#nY~`#)5~Y$;{ccxyqcNxU<^reZ=Mr zkjH3mlLe>W^78e~=JBP}>urM#S=w~ET>RkPp|c&-;{o*P}0B|LF8K5yLK zC4pImH|L12#`QHDWMfwJ1|gOjm; zkF7&~sU>x&S#_LVWQB+XbxF~WH8ee%Vm>RaFY+kN7IQxFxcYkKL=f?&TBQ@=HNB*ww|PkLV?Z0ezFjZ{25US(T}|nWr02$bVdfr zWaSjwC)y-Ffn-r3$hF1wy-4~{XwhyPYjvRnDG6isZmM&D1%5_Wbfl))}ysQ9W#4jaYNzBfvM;(@QHdM}zWO8qsYFc@u} z*Q6X=FfXq3UUV$9=GD=#pbeu!$EkS)AMTP9ZxYjgSLUeIMj1jwjAFdImCblsP9#{p;;K02t5FQ$+j8sG@5BIeL29sYq+v2vn{A4R)jjhCWH zgD;d|S&@|i`?NHD1V-ST6)Y>d0qqt0=6B~C-W0*IqW=_(t$-0^tZ1brtfpmb1#}gV zXJrf&Bh?@TN6kZB(H|I2cma+sgn|w-&p1HbLkGV(+*Iq-P_3Iqy^bFfGgS-%cGx+n z<1+>ZR%#f;%D_MsCvi}BNP~fu(m@th1}NaCY}kh%WCq+#l)DEKP8?*1Gx~rXD>-?E pnFsD1l;J~mPEv*s1-Ouh4*`z1w%YBMLP`Jt002ovPDHLkV1j(M#^(S4 literal 0 HcmV?d00001 diff --git a/plugins/woocommerce/assets/images/payment_methods/72x72/mollie.png b/plugins/woocommerce/assets/images/payment_methods/72x72/mollie.png new file mode 100644 index 0000000000000000000000000000000000000000..3ec678962371b897a673caf825735de3b41c3e2d GIT binary patch literal 637 zcmV-@0)qXCP)fUY;1dce0zI) z$jQlEU0r;9f`R}501$LiPE!EipAe7l&tM=R5buyMFfaj$Q6T^T0jfzvK~z}7?U>ze z+b|4;8Ou(bblrF?(UE`BIBEC(uQ=IjF$-M^C*s90_`47!UVJzrU;`BW!R)ea!HX%j z+0M{s_IbhglkF+SeB%en<2W0Bn9VHIkKjo#eUI~`XEMQ)Xfl&1>T^QJ_pHEpxr|vQ zl3@*=4DDEj9c8RSMq5^4O9iX&M6ARrT1BhqpAuc#E8DhZY5OKgbVSV+z!s=gM6UBH ziI(@sof{d~v%H_1vyDU|ZY0iU7;(fM(iP6;9KAaf(D1XzRLe zar#O$TdP-e9I$|kMIS5`g+%Tzi3E7D);88I6*-UI%GINeMB&g0yRL7RinzkWAhGDp z13lnnv51=i@U`Pu#Bv01l)F9=C+Xo=)P@?hF-vdoGdfK&M7hq* zh)PzVBvOf#YdvWkXrkOCdP;idd6VRMra*e8kY|R;AMW#kV#U<1|L_zr^7t zI`!%5^V{9&Yk2LWtK>ma^V{C+m7nT)h2>CR=1*VmvAFAplJ29c=Ur^_&C%sgU-8Y+ zsJG_K0000EbW%=J0N)VrkIx_=pkSXckY5qyV)y_60y9ZOK~z}7)tK3Gqc9LfWq~m` zGunkf?3w@n$7v`+jDac%^pM;)Q|8p|))iXNw{Z5nN5omtJJdl8U+Lowga43| zir02-Cv14&+L!zhSVeaX*)cqQmcLh}=#j=`IC*(eD=)X>;^a@Ki*WJPJ+ z$knqVAFI^T2UIZ1_R7cwm{J}fbM*HDNR>ONqckV`YM{E#8PrqS9DoV{Rn8y-Jv{)G zV+@=@6xh-Cul32rGD3k&+Ns`e< zN+G+b;z49K7#c)8l zJkp^upj9Gb$s~xzp=c6pv%yJ(bWn+5t;;i@Lt*n7Fg*)-uK~w_-RWQ!4W^M0Bmrh6 zn4gF2A;|6r{wqj%4hfIp%FALzG{zQCY*Js@ zjoE-ARgg~Ljn*j2a*w=^3G+Ka`|#rN#SBi0)52dt@M~8WBGsIx0Nak4Y5SwMB)x5qs+b^#N9Xq>@Rl?j5fO2iVfR`e|}2Jf69;s!-UPX$D5|N%~i#d z9Q@Ve&zlpqgW82TVd_S_FG82vC5{dOp5` z^Uuv|5sA?aU|wDpIWH^iZAs!awfEsrCa1D>jElrtWf9!I7QyX^9I1%A2IGQ?>oXKP zp`}+SGhX7)jCb8l`N34rloUUx;A4w?x;g-vQqH$v~CkX3ETc_!NnER5xw%q4C66mIdjM$N^KR#R&6OWHQctv z_+)2N4@WE@xdo%=>+%DP7G-?r*@W!s%fnCWqt>%5cnPMSbiw;9?88sG`C|gYJl%J8 zJ3BY)X|?is!O{+8R|7RhU;I(2pf(+JjVkom_5^i5>&twPXJ+NXkY3xZesqk~o4a<# zaDW?_;`z4mLtVb3y}!l3u^Ez(TnPp9h$W$o9B8E3KNu#9@e@qZJe!L9dZu6|<8)d8 z#v>pt6HRsv>nwPs*GAVTzc?g4Tj&vt?dPMEIH&RS9S<6{+w``cl2&cmPK`Vk9;Z7* z_$eu_i`{iD^N8A{QT_gs5PF3cIj>Ta&{M)2_PfU;uZa zj2z6nLcF8CiT%s%oKa(za(2b5n#Dt#BYZh!ZrdHc4?5aQn~<>;N>pp7d3=+r@?LGN zfrX(}a>i%6{Br8;)b*)P?BDAZ_u*2-UL}m1AvFtDEKP;wm`}7dL%w#}VPlcPLDpjw Qf_zF;M;iJ1KA+_O03WO-S^xk5 literal 0 HcmV?d00001 diff --git a/plugins/woocommerce/assets/images/payment_methods/72x72/paystack.png b/plugins/woocommerce/assets/images/payment_methods/72x72/paystack.png new file mode 100644 index 0000000000000000000000000000000000000000..362446dfb00748432b989e3910bc042b69767a98 GIT binary patch literal 736 zcmV<60w4W}P))?KTy1=dm!Zhc(tC-OU2S|G%J+PUl@TaFsLx000kkQchC< z-yjf=?$4iKpfK;CUtl7?sw)5h0qsddK~z}7?U>zi+At7C9Sj)Kr29okviuWc7ds|N zllJ|et}EP3P~B!4*o$Pwhd~CJIoe&}3vKjDyh@e}`fnD?^>rMLnavmUe%7-nB%afW z%)@Xl=wvRAg1ThS8Kz5tc!HQ>_MB-Nb}y*&85N(A06w`jkw{1}N$@3(RwTlTGZP_+ zmL$S5T98bUA`)T}O`=IOi9V$$|49Kg?4P2$n}Iue3k3?WW6|xgD7!CIxhK=A%vpC& zRJpWOU2Zk_o9)WNQOZ9tEHW+&MBg{Rc{nVLEfi@U;S`KPQ^IN80$Nqr@-%7iD}zfm=B#WF>T zB~I&1B6eJtY9NTUG^Cm=Qf(ipMwC!%ERI5Er#vf*`79a}f4zEN`!HD@Me_?^m@cv+ Szn=F100007Qqc9LfLBtq4$@GnZaO8nvU1?B;Qv zXfsbYp^ohNf$1oaXO>B^=ZB`Wy?M{u8_!9AFkDF_5=oH+g~}=tq2kFzD6>6@u+It- zp~y&xhv*^tzarqbiHIDWj0k{?$lOchHRI_Uw7xvlxZly_5HJIMMFSX5Ob62UMGm92 zwgyi01tEq`L;Y)OFcj$F;*{TudTqJ|BF?TyFx?y3I#o7yx1x||iKvFQcA6~EHh7Qq zqTV7hpvW7V7R~u-ok%mBqNYd->zzp(YgdU9>?42T{f)3;%-EV=C32Q&22bw}rmH&^ zop2)Zobf7hOFN1Q+B90MnJb!zMj#f}18_@4#?V^ZH` z8(eUDsi=2=i>-4kxU@z+B4K*EFB%5ApeL?<@bOpMVLJQ6E}NFN{%6;iN7yLR;RO{C zHK8gAQ6FB=kNm<9b2Y#DL$%!7$GQ6sDfwI00000NkvXX Hu0mjf9|}_& literal 0 HcmV?d00001 diff --git a/plugins/woocommerce/assets/images/payment_methods/72x72/razorpay.png b/plugins/woocommerce/assets/images/payment_methods/72x72/razorpay.png new file mode 100644 index 0000000000000000000000000000000000000000..01dedd7bfa6d6a2e77d02a56d9430cbf0ac8d5d6 GIT binary patch literal 706 zcmV;z0zLhSP)>xyy2H+Xj;3IAk>~2|Z-JX(bdkEk&NG$& z*xll*wZvDa{zsqwmB#yIt^Kpr`rhmK(dGDilCh4Qu#KFse7gLh%=&q=@otyKjhnD? zh^BCXoDOni<^TWy5_D2dQve|E-=7f9kYL_05D<@F&ySC^5JYSM00Fv5L_t(o!|j;I zZlq8UMISVDc6;Vly+BT#oG1VPrwvFW4K4S=hT2%>NN)#6w+h~(O8qBX2Dd)mjDH*K zLiHMN;p6=Tp1LIbf`Qyz#+hL-vtFom>ltBMOXTTi3R}-eQ>Z$6eIC_u4+3<>1rT7N z)D{x>p;|(Or3xTIpym)^u6&5#D+n=(Ceb9CME`jBE+aah?j=Nz$EJBqnK}M7PIPy^ zJK@EpY0Tq9PrsXQ&>BUY?=@9iSIle^O>7ZI&bEgvo*ij@V1p{VE^6QX+1!I#wBg9) zRG5@?!kLMql*-gnmbl1LnoyollCilesvXgf<{_mzC3~O*Rg68Cumr?;=76$2To3G47l7e$XOlGR3L$+Gff);_DX%p$7I(__0B+~V!Z literal 0 HcmV?d00001 diff --git a/plugins/woocommerce/assets/images/payment_methods/72x72/square.png b/plugins/woocommerce/assets/images/payment_methods/72x72/square.png new file mode 100644 index 0000000000000000000000000000000000000000..3ffe48772223206ab451e309721f00d94b8c0bf0 GIT binary patch literal 609 zcmV-n0-pVeP)`O@9!5E7at!Vz`?<$r>ByVlJWBL+S}Xn^z_Zn&aber zZf%X~U1_3? z0000IbW%=J03Z} z>Mp0ClgR)PoH})v|Nq|>n=E7Cnzoz|y7_)JdD2D*?IGV$FBo&sj*nfBl5P&;V4mx= z7|Osh_H#J{ux`*;)JQtk(tT?^z1Kvg(@UZfV$eG#HwFkDH|GMNEQta}LrOeA$s;Zf z6c8T=@(IU*h6IrMcTl$8PEWO~%EAPCo{C?4$@QVE7K-9R8%l+;R2!P8p`|vo7qUar z>L6rWZRkN9+pMBi`BMHcF@ur_I<0J=N9Y#)DX2_kZ@r-Av$^OAJ$*s*D|9)~JJ#P1 zuXEYrd|r_=f|3k61LP)Xjt@Tp0GwtflIq);00000NkvXXu0mjfmTV}= literal 0 HcmV?d00001 diff --git a/plugins/woocommerce/assets/images/payment_methods/72x72/stripe.png b/plugins/woocommerce/assets/images/payment_methods/72x72/stripe.png new file mode 100644 index 0000000000000000000000000000000000000000..0175f2947d6d670265d5c6f0780450607e91346c GIT binary patch literal 2783 zcmV<53Ly1~P)XIJX+^(fc<*Iw z>1ag3VSMssY3OE2#sJg*V|MLiZ|h)r@o7Z9Wozdv!~Zy{`e$Y3WKPTg&i^CE|2M1q z6UqN3w*6yt?qzN20LuRW-2VX5|6+CWV{q(iL&5+5|7LFMWpeFhaO`Gm>SS~7V1V;q zg7ju*=4nR3XKLwYY3E^n@@8V+StGsryHu`)Eta0M`FtqW?^v z{7{tlQI7PXrt^Dv=F;5#*WCQx-}}tc`^Culnx^%&we**q@ppjkWlhWZ`Tz3r{pacZ z;O70n(EQZb`@PBgJ*E1$!ug-L`PkU_!^HPxkoU^U_Nub>RF3wLp!H#X^NW}Ad5Q9g zi|%oI?T(4)8NUBRt^ZV||6QB^Or-uTwEa|_{iVG6vc36oj`V1F@NjnOYiQ(hZQ=mO z|JC6BTb2ASultj*`az-ke4qGxp7?~A^QIHSE<;EtsN8T8a{mZgbCwe%}k!Td-v+q zt5>WD3kyr?nG_P@Ubk|YS_O#hDq60hzrTMc0VxL=aFTZ;15i>@Qi!{I-;NzScInc& zOhGHAcsWp%GkOOy=rAZbIT_<%9+#wGbox>UR#Rh~LYz^Ma%RGUJU%M|fN^mN36^;} z4{Y1EZAmRuYikD}@ovGvG?Ck8ordLim@RvgqLcRR_ z`t%7x2J&EC(B@#-X5DGirU@;~UxR3W_Kykm8srB~P|%FtJ^&33qj|7B%Argmb=tKn z4^V08fU^JOkkC*sFRwv^Fb?LSal)7*+avlz8GyQ1gAUO^IPsHXhV<_rDjYxV$UGt7 zi1u*DiN$Hxy?coQq$Vh%_X9En7;wDM!S+B|P#5H34)mw5Rb4C&I29izy#9?h#bwQhzgLQ2;nLIta5vx3+&pvGK@BZYR zNIex3lahAgYQ{aa4aL|YPc$;!x)Q5A$jCeouEaR%E9QLeU(La~q9G4DU5RkKq$B$L z?uw)BTIR&+5_wF}A8-&-4j(%*P}hS!j&>k5_GpZQPW$!@GHf4~=Yguq2qh}VRXPA6 zqt!KoZ^NUf9rK5rjgBWU=yV{4ak1M6uAi{$j&>kEb~!H0!yt9Mf>?j>SR8e<3-NKW z{)jvsIuJu1c`S15LSh@5urB3z5OplXErz$DKKaz^SFdKC*ux=S7ck73MxEszMBP5< z@Js%!*6JKM6}X-Aa?i&A1PrT7wg=*Pcnl!=Htd0Y#@kS9Q4Dq1Hhi6ZW|=r$-Bew` z4Dcpw8|uf@#R$ET4z}l~I$l^7I?K`VCXfdlzJ0`PcvRbs$KRlnXgKY>J)j6Dl8Eh$ zapdj%HdSkOT1Fl{UGf(Dg>htmm}4e#JEJ2mRd?flOv%i>kB%A_I;yTnw}f$oe*|$} z+D9kXdjIUny2BiR4D*UiNH{WX!Pv2+kA6Et9(JpCIcvKY&t$u9$L*|*D-sY`3l@xx zAYvPeJi+Wzy?4sdcAdCu@-$(b+iVvm967?6u@Mmj9o}Z_v%KZ+<#&z5+@g7=(Rq0Q z1kAW`-~hz8q3y>#FHHez-O{#?M*=zvs56q#Tb?@j)?jrZ1iLUJ#TMH0ir0n5B0b!$QVD$chdCQsN)Te`6fEZAjD1= z?K5w)`k2a(`)^IkMY?WS_(6L3@<`Bu%zXakPAW;eIN5!<$LC!BD{ z-T0=Q6b+I5&Bu=Tmee6Q# zEK#&BbOPh3gUqmD1naVK>_TU+nSo)>l)zZ&03_vd#`TW|V=&6ubCj_}R6iA!e=FaT9B+&=u6 z%145@4K0?aC^Ilyr4tty8=KA?3{;0y7LNM3>u*DGi$Pty(Y!wZ#ivu};=FnL_BAAB z&`C@LMt86-RH%cDnKQzHJn`}A()rYw)Y9uxZy!|`-ag)#$J#k`C>@=B&mO*?wX!)W zPyrbA6Jl+?y`Xj7Xt>1b3kW1-#)j& z5pPV3nK`~6pFG(+ckY}y`9g(@5WZSEbl0|_bg(~WZ1d8kOO_n83l(lb 'payfast', 'title' => __( 'PayFast', 'woocommerce' ), 'content' => __( 'The PayFast extension for WooCommerce enables you to accept payments by Credit Card and EFT via one of South Africa’s most popular payment gateways. No setup fees or monthly subscription costs. Selecting this extension will configure your store to use South African rands as the selected currency.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payfast.png', + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/payfast.png', 'plugins' => array( 'woocommerce-payfast-gateway' ), 'is_visible' => array( (object) array( @@ -40,7 +40,7 @@ class DefaultPaymentGateways { 'id' => 'stripe', 'title' => __( ' Stripe', 'woocommerce' ), 'content' => __( 'Accept debit and credit cards in 135+ currencies, methods such as Alipay, and one-touch checkout with Apple Pay.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/stripe.png', + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/stripe.png', 'plugins' => array( 'woocommerce-gateway-stripe' ), 'is_visible' => array( // https://stripe.com/global. @@ -96,7 +96,7 @@ class DefaultPaymentGateways { 'id' => 'paystack', 'title' => __( 'Paystack', 'woocommerce' ), 'content' => __( 'Paystack helps African merchants accept one-time and recurring payments online with a modern, safe, and secure payment gateway.', 'woocommerce' ), - 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/paystack.png', + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/paystack.png', 'plugins' => array( 'woo-paystack' ), 'is_visible' => array( self::get_rules_for_countries( array( 'ZA', 'GH', 'NG' ) ), @@ -107,7 +107,7 @@ class DefaultPaymentGateways { 'id' => 'kco', 'title' => __( 'Klarna Checkout', 'woocommerce' ), 'content' => __( 'Choose the payment that you want, pay now, pay later or slice it. No credit card numbers, no passwords, no worries.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/klarna-black.png', + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/klarna.png', 'plugins' => array( 'klarna-checkout-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( array( 'SE', 'FI', 'NO' ) ), @@ -118,7 +118,7 @@ class DefaultPaymentGateways { 'id' => 'klarna_payments', 'title' => __( 'Klarna Payments', 'woocommerce' ), 'content' => __( 'Choose the payment that you want, pay now, pay later or slice it. No credit card numbers, no passwords, no worries.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/klarna-black.png', + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/klarna.png', 'plugins' => array( 'klarna-payments-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( @@ -143,7 +143,7 @@ class DefaultPaymentGateways { 'id' => 'mollie_wc_gateway_banktransfer', 'title' => __( 'Mollie', 'woocommerce' ), 'content' => __( 'Effortless payments by Mollie: Offer global and local payment methods, get onboarded in minutes, and supported in your language.', 'woocommerce' ), - 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/mollie.svg', + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/mollie.png', 'plugins' => array( 'mollie-payments-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( @@ -167,7 +167,7 @@ class DefaultPaymentGateways { 'id' => 'woo-mercado-pago-custom', 'title' => __( 'Mercado Pago Checkout Pro & Custom', 'woocommerce' ), 'content' => __( 'Accept credit and debit cards, offline (cash or bank transfer) and logged-in payments with money in Mercado Pago. Safe and secure payments with the leading payment processor in LATAM.', 'woocommerce' ), - 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/mercadopago.png', + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/mercadopago.png', 'plugins' => array( 'woocommerce-mercadopago' ), 'is_visible' => array( self::get_rules_for_countries( array( 'AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY' ) ), @@ -179,7 +179,7 @@ class DefaultPaymentGateways { 'id' => 'ppcp-gateway', 'title' => __( 'PayPal Payments', 'woocommerce' ), 'content' => __( "Safe and secure payments using credit cards or your customer's PayPal account.", 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/paypal.png', + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/paypal.png', 'plugins' => array( 'woocommerce-paypal-payments' ), 'is_visible' => array( (object) array( @@ -194,7 +194,7 @@ class DefaultPaymentGateways { 'id' => 'cod', 'title' => __( 'Cash on delivery', 'woocommerce' ), 'content' => __( 'Take payments in cash upon delivery.', 'woocommerce' ), - 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/cod.svg', + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/cod.png', 'is_visible' => array( self::get_rules_for_cbd( false ), ), @@ -204,7 +204,7 @@ class DefaultPaymentGateways { 'id' => 'bacs', 'title' => __( 'Direct bank transfer', 'woocommerce' ), 'content' => __( 'Take payments via bank transfer.', 'woocommerce' ), - 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/bacs.svg', + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/bacs.png', 'is_visible' => array( self::get_rules_for_cbd( false ), ), @@ -324,7 +324,7 @@ class DefaultPaymentGateways { 'id' => 'razorpay', 'title' => __( 'Razorpay', 'woocommerce' ), 'content' => __( 'The official Razorpay extension for WooCommerce allows you to accept credit cards, debit cards, netbanking, wallet, and UPI payments.', 'woocommerce' ), - 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/razorpay.svg', + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/razorpay.png', 'plugins' => array( 'woo-razorpay' ), 'is_visible' => array( (object) array( @@ -339,7 +339,7 @@ class DefaultPaymentGateways { 'id' => 'payubiz', 'title' => __( 'PayU for WooCommerce', 'woocommerce' ), 'content' => __( 'Enable PayU’s exclusive plugin for WooCommerce to start accepting payments in 100+ payment methods available in India including credit cards, debit cards, UPI, & more!', 'woocommerce' ), - 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/payu.svg', + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/payu.png', 'plugins' => array( 'payu-india' ), 'is_visible' => array( (object) array( @@ -365,7 +365,7 @@ class DefaultPaymentGateways { 'id' => 'square_credit_card', 'title' => __( 'Square', 'woocommerce' ), 'content' => __( 'Securely accept credit and debit cards with one low rate, no surprise fees (custom rates available). Sell online and in store and track sales and inventory in one place.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/square-black.png', + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/square.png', 'plugins' => array( 'woocommerce-square' ), 'is_visible' => array( (object) array( From adfdeabc6c6040b59e719a661cd6a0ebb90ea12b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor=20Soriano?= Date: Mon, 11 Apr 2022 09:40:48 +0200 Subject: [PATCH 148/386] Add a couple of missing spaces (code formatting). --- .../importers/class-wc-product-csv-importer-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php b/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php index 97acf002e73..17b13f81653 100644 --- a/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php +++ b/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php @@ -97,7 +97,7 @@ class WC_Product_CSV_Importer_Controller { * @param bool $check_import_file_path If the import file path should be checked. * @param string $file Path of the file to be checked. */ - return wc_is_file_valid_csv($file, $check_path); + return wc_is_file_valid_csv( $file, $check_path ); } /** From 1f77200e7050880f54a33c9270929c7ed9686de0 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Mon, 11 Apr 2022 15:57:26 +0800 Subject: [PATCH 149/386] Add missing track prop --- .../fills/PaymentGatewaySuggestions/index.js | 22 ++++++++++--------- .../PaymentGatewaySuggestions/test/index.js | 1 + 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js index 9c552cd887d..084a4711efa 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js @@ -161,16 +161,6 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { [ paymentGateways ] ); - const trackSeeMore = () => { - recordEvent( 'tasklist_payment_see_more', {} ); - }; - - const trackToggle = ( isShow ) => { - recordEvent( 'tasklist_payment_show_toggle', { - toggle: isShow ? 'hide' : 'show', - } ); - }; - const recommendation = useMemo( () => Array.from( paymentGateways.values() ) @@ -243,6 +233,18 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { const isEligibleWCPay = !! wcPayGateway.length; + const trackSeeMore = () => { + recordEvent( 'tasklist_payment_see_more', {} ); + }; + + const trackToggle = ( isShow ) => { + recordEvent( 'tasklist_payment_show_toggle', { + toggle: isShow ? 'hide' : 'show', + payment_method_count: + offlineGateways.length + additionalGateways.length, + } ); + }; + if ( query.id && ! currentGateway ) { return ; } diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js index 7df91e7a82a..c2ca2617ff0 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js @@ -270,6 +270,7 @@ describe( 'PaymentGatewaySuggestions', () => { 'tasklist_payment_show_toggle', { toggle: 'show', + payment_method_count: paymentGatewaySuggestions.length - 1, // Minus one for WCPay since it's not counted in "other payment methods". } ); } ); From 52732f2bc1072f7d6d9ff06ea65f86198d4d6990 Mon Sep 17 00:00:00 2001 From: Sakri Koskimies Date: Mon, 11 Apr 2022 11:06:08 +0300 Subject: [PATCH 150/386] Add changelog entry --- packages/js/components/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/js/components/CHANGELOG.md b/packages/js/components/CHANGELOG.md index 1453039b5d5..c0d21596f66 100644 --- a/packages/js/components/CHANGELOG.md +++ b/packages/js/components/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- Fix documentation for `TableCard` component - Update dependency `@wordpress/hooks` to ^3.5.0 - Update dependency `@wordpress/icons` to ^8.1.0 From 812a11e31b7ea0b6437180255002de662caa4de0 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Mon, 11 Apr 2022 16:21:48 +0800 Subject: [PATCH 151/386] Add changelog --- .../changelog/dev-32131-ui-changes-additional-payment-section | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/dev-32131-ui-changes-additional-payment-section diff --git a/plugins/woocommerce/changelog/dev-32131-ui-changes-additional-payment-section b/plugins/woocommerce/changelog/dev-32131-ui-changes-additional-payment-section new file mode 100644 index 00000000000..f914a935799 --- /dev/null +++ b/plugins/woocommerce/changelog/dev-32131-ui-changes-additional-payment-section @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +UI changes for set up payments task From 21ecb88c4c167f33555cf9ad246a8def22350653 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Mon, 11 Apr 2022 10:22:13 +0200 Subject: [PATCH 152/386] Fix PHPCS issues in URLTests.php --- .../php/src/Internal/Utilities/URLTest.php | 108 +++++++++--------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php b/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php index 95ab9838164..70f89a06cc0 100644 --- a/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php +++ b/plugins/woocommerce/tests/php/src/Internal/Utilities/URLTest.php @@ -9,9 +9,13 @@ use WC_Unit_Test_Case; * A collection of tests for the filepath utility class. */ class URLTest extends WC_Unit_Test_Case { + + /** + * @testdox Test if it can be determined whether a URL is absolute or relative. + */ public function test_if_absolute_or_relative() { $this->assertTrue( - ( new URL( '/etc/foo/bar' ) )->is_absolute() , + ( new URL( '/etc/foo/bar' ) )->is_absolute(), 'Correctly determines if a Unix-style path is absolute.' ); @@ -29,8 +33,8 @@ class URLTest extends WC_Unit_Test_Case { /** * @dataProvider path_expectations * - * @param string $source_path - * @param string $expected_resolution + * @param string $source_path Source path to test. + * @param string $expected_resolution Expected result of the test. */ public function test_path_resolution( $source_path, $expected_resolution ) { $this->assertEquals( $expected_resolution, ( new URL( $source_path ) )->get_path() ); @@ -43,22 +47,22 @@ class URLTest extends WC_Unit_Test_Case { */ public function path_expectations(): array { return array( - array( '/var/foo/bar/baz/../../foobar', '/var/foo/foobar' ), - array( '/var/foo/../../../../bazbar', '/bazbar' ), - array( '././././.', './' ), - array( 'empty/segments//are/stripped', 'empty/segments/are/stripped' ), - array( '///nonempty/ /whitespace/ /is//kept', '/nonempty/ /whitespace/ /is/kept' ), - array( 'relative/../../should/remain/relative', '../should/remain/relative' ), + array( '/var/foo/bar/baz/../../foobar', '/var/foo/foobar' ), + array( '/var/foo/../../../../bazbar', '/bazbar' ), + array( '././././.', './' ), + array( 'empty/segments//are/stripped', 'empty/segments/are/stripped' ), + array( '///nonempty/ /whitespace/ /is//kept', '/nonempty/ /whitespace/ /is/kept' ), + array( 'relative/../../should/remain/relative', '../should/remain/relative' ), array( 'relative/../../../should/remain/relative', '../../should/remain/relative' ), - array( 'c:\\Windows\Server\HTTP\dump.xml', 'c:/Windows/Server/HTTP/dump.xml') + array( 'c:\\Windows\Server\HTTP\dump.xml', 'c:/Windows/Server/HTTP/dump.xml' ), ); } /** * @dataProvider url_expectations * - * @param string $source_url - * @param string $expected_resolution + * @param string $source_url URL to test. + * @param string $expected_resolution Expected result of the test. */ public function test_url_resolution( $source_url, $expected_resolution ) { $this->assertEquals( $expected_resolution, ( new URL( $source_url ) )->get_url() ); @@ -71,34 +75,34 @@ class URLTest extends WC_Unit_Test_Case { */ public function url_expectations(): array { return array( - array( '/../foo/bar/baz/bazooka/../../baz', 'file:///foo/bar/baz' ), - array( './a/b/c/./../././../b/c', 'file://a/b/c' ), - array( 'relative/path', 'file://relative/path' ), - array( '/absolute/path', 'file:///absolute/path' ), - array( '/var/www/network/%2econfig', 'file:///var/www/network/%2econfig' ), - array( '///foo', 'file:///foo' ), - array( '~/foo.txt', 'file://~/foo.txt' ), - array( 'baz///foo', 'file://baz/foo' ), - array( 'file:///etc/foo/bar', 'file:///etc/foo/bar' ), - array( 'foo://bar', 'foo://bar/' ), - array( 'foo://bar/baz-file', 'foo://bar/baz-file' ), - array( 'foo://bar/baz-dir/', 'foo://bar/baz-dir/' ), - array( 'https://foo.bar/parent/.%2e/asset.txt', 'https://foo.bar/asset.txt' ), - array( 'https://foo.bar/parent/%2E./asset.txt', 'https://foo.bar/asset.txt' ), + array( '/../foo/bar/baz/bazooka/../../baz', 'file:///foo/bar/baz' ), + array( './a/b/c/./../././../b/c', 'file://a/b/c' ), + array( 'relative/path', 'file://relative/path' ), + array( '/absolute/path', 'file:///absolute/path' ), + array( '/var/www/network/%2econfig', 'file:///var/www/network/%2econfig' ), + array( '///foo', 'file:///foo' ), + array( '~/foo.txt', 'file://~/foo.txt' ), + array( 'baz///foo', 'file://baz/foo' ), + array( 'file:///etc/foo/bar', 'file:///etc/foo/bar' ), + array( 'foo://bar', 'foo://bar/' ), + array( 'foo://bar/baz-file', 'foo://bar/baz-file' ), + array( 'foo://bar/baz-dir/', 'foo://bar/baz-dir/' ), + array( 'https://foo.bar/parent/.%2e/asset.txt', 'https://foo.bar/asset.txt' ), + array( 'https://foo.bar/parent/%2E./asset.txt', 'https://foo.bar/asset.txt' ), array( 'https://foo.bar/parent/%2E%2e/asset.txt', 'https://foo.bar/asset.txt' ), array( 'https://foo.bar/parent/%2E.%2fasset.txt', 'https://foo.bar/parent/%2E.%2fasset.txt' ), - array( 'http://localhost?../../bar', 'http://localhost/?../../bar' ), - array( '//http.or.https/', '//http.or.https/' ), - array( '//schemaless/with-path', '//schemaless/with-path' ), + array( 'http://localhost?../../bar', 'http://localhost/?../../bar' ), + array( '//http.or.https/', '//http.or.https/' ), + array( '//schemaless/with-path', '//schemaless/with-path' ), ); } /** * @dataProvider parent_url_expectations * - * @param string $source_path - * @param int $parent_level - * @param string|false $expectation + * @param string $source_path Path to test. + * @param int $parent_level Parent level to use for the test. + * @param string|false $expectation Expected result of the test. */ public function test_can_obtain_parent_url( string $source_path, int $parent_level, $expectation ) { $this->assertEquals( $expectation, ( new URL( $source_path ) )->get_parent_url( $parent_level ) ); @@ -111,32 +115,32 @@ class URLTest extends WC_Unit_Test_Case { */ public function parent_url_expectations(): array { return array( - array( '/', 1, false ), - array( '/', 2, false ), - array( './', 1, 'file://../' ), - array( '../', 1, 'file://../../' ), - array( 'relative-file.png', 1, 'file://./' ), - array( 'relative-file.png', 2, 'file://../' ), - array( '/var/dev/', 1, 'file:///var/' ), - array( '/var/../dev/./../foo/bar', 1, 'file:///foo/' ), - array( 'https://example.com', 1, false ), - array( 'https://example.com/foo', 1, 'https://example.com/' ), - array( 'https://example.com/foo/bar/baz/../cat/', 2, 'https://example.com/foo/' ), + array( '/', 1, false ), + array( '/', 2, false ), + array( './', 1, 'file://../' ), + array( '../', 1, 'file://../../' ), + array( 'relative-file.png', 1, 'file://./' ), + array( 'relative-file.png', 2, 'file://../' ), + array( '/var/dev/', 1, 'file:///var/' ), + array( '/var/../dev/./../foo/bar', 1, 'file:///foo/' ), + array( 'https://example.com', 1, false ), + array( 'https://example.com/foo', 1, 'https://example.com/' ), + array( 'https://example.com/foo/bar/baz/../cat/', 2, 'https://example.com/foo/' ), array( 'https://example.com/foo/bar/baz/%2E%2E/dog/', 2, 'https://example.com/foo/' ), - array( 'file://./', 1, 'file://../' ), - array( 'file://./', 2, 'file://../../' ), - array( 'file://../../foo', 1, 'file://../../' ), - array( 'file://../../foo', 2, 'file://../../../' ), - array( 'file://../../', 1, 'file://../../../' ), - array( 'file://./../', 2, 'file://../../../' ), + array( 'file://./', 1, 'file://../' ), + array( 'file://./', 2, 'file://../../' ), + array( 'file://../../foo', 1, 'file://../../' ), + array( 'file://../../foo', 2, 'file://../../../' ), + array( 'file://../../', 1, 'file://../../../' ), + array( 'file://./../', 2, 'file://../../../' ), ); } /** * @dataProvider all_parent_url_expectations * - * @param string $source_path - * @param array $expectation + * @param string $source_path Path to test. + * @param array $expectation Expected result of the test. */ public function test_can_obtain_all_parent_urls( string $source_path, array $expectation ) { $this->assertEquals( $expectation, ( new URL( $source_path ) )->get_all_parent_urls() ); @@ -193,7 +197,7 @@ class URLTest extends WC_Unit_Test_Case { array( '../../some.file', array( - 'file://../../' + 'file://../../', ), ), ); From 700633957cb4e3591c82ae8e1cbba06ca8ade035 Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Mon, 11 Apr 2022 08:08:28 -0400 Subject: [PATCH 153/386] Update progress header bar styles in task list (#32498) * Update progress header bar styles in task list * Remove units on margin * Add changelog entry --- .../client/task-lists/progress-header/progress-header.scss | 4 ++-- plugins/woocommerce/changelog/fix-32425 | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-32425 diff --git a/plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.scss b/plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.scss index e51ab9b22a7..4d45cfea491 100644 --- a/plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.scss +++ b/plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.scss @@ -19,9 +19,9 @@ $progress-complete-color: #007cba; appearance: none; border: 1px solid #ddd; border-radius: 16px; - height: 10px; + height: 12px; width: 100%; - margin-bottom: 20px; + margin-bottom: 0; // Firefox & { diff --git a/plugins/woocommerce/changelog/fix-32425 b/plugins/woocommerce/changelog/fix-32425 new file mode 100644 index 00000000000..ebb4792044a --- /dev/null +++ b/plugins/woocommerce/changelog/fix-32425 @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Update progress header bar styles in task list #32498 From 10e9c6c5f9534b39c7eb0082b640eedc9f78881b Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 6 Apr 2022 14:01:26 -0300 Subject: [PATCH 154/386] Fix experiment UI issues --- .../client/two-column-tasks/style.scss | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/plugins/woocommerce-admin/client/two-column-tasks/style.scss b/plugins/woocommerce-admin/client/two-column-tasks/style.scss index bf3a58ac0db..6b6caf8c4d0 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/style.scss +++ b/plugins/woocommerce-admin/client/two-column-tasks/style.scss @@ -19,10 +19,10 @@ flex: 1; } - .woocommerce-ellipsis-menu.setup { + .woocommerce-ellipsis-menu { position: absolute; - top: 20px; - right: 16px; + top: $gap; + right: $gap-large; } .woocommerce-task-card.is-loading { @@ -130,11 +130,11 @@ } .woocommerce-task-header__contents { - max-width: 380px; + max-width: calc( 60% - 2% ); } .svg-background { - right: 0.5%; + right: 2%; width: 40%; } } @@ -143,6 +143,13 @@ @include single-column; } + &.two-columns .svg-background { + top: 50%; + bottom: 50%; + margin-top: auto; + margin-bottom: auto; + } + ul { display: flex; li { @@ -229,10 +236,10 @@ position: absolute; z-index: 0; right: 6%; - top: 50%; - bottom: 50%; - margin-top: auto; - margin-bottom: auto; + // top: 50%; + // bottom: 50%; + // margin-top: auto; + // margin-bottom: auto; .admin-theme-color { fill: var(--wp-admin-theme-color); From 263bd5776ed66e5cb888f529583f2142f40b04a3 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Mon, 11 Apr 2022 06:16:52 -0700 Subject: [PATCH 155/386] Correct text-domain. --- .../Controllers/Version2/class-wc-rest-orders-v2-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php index 30b012c9096..b1eb4ee5745 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php @@ -1852,7 +1852,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller { ); $params['order_item_display_meta'] = array( 'default' => false, - 'description' => __( 'Only show meta which is meant to be displayed for an order.', 'woocomerce' ), + 'description' => __( 'Only show meta which is meant to be displayed for an order.', 'woocommerce' ), 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'validate_callback' => 'rest_validate_request_arg', From c3b1352f904ff43348c2f3a9ff00a473254f5184 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 6 Apr 2022 14:02:02 -0300 Subject: [PATCH 156/386] Make sure activity header does not show when displaying task list with progressive header --- .../client/homescreen/layout.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce-admin/client/homescreen/layout.js b/plugins/woocommerce-admin/client/homescreen/layout.js index 282572d6b7b..e996bb08b09 100644 --- a/plugins/woocommerce-admin/client/homescreen/layout.js +++ b/plugins/woocommerce-admin/client/homescreen/layout.js @@ -64,6 +64,8 @@ export const Layout = ( { query, taskListComplete, hasTaskList, + showingProgressHeader, + isLoadingTaskLists, shouldShowWelcomeModal, shouldShowWelcomeFromCalypsoModal, isTaskListHidden, @@ -141,7 +143,9 @@ export const Layout = ( { { ! isLoadingExperimentAssignment && ! isLoadingTwoColExperimentAssignment && - ! isRunningTaskListExperiment && ( + ! isRunningTaskListExperiment && + ! isLoadingTaskLists && + ! showingProgressHeader && ( 0, + showingProgressHeader: !! taskLists.find( + ( list ) => list.isVisible && list.displayProgressHeader + ), taskListComplete: getTaskList( 'setup' )?.isComplete, installTimestamp, installTimestampHasResolved, From 5251aa6e97d3940b11cd09a5eb6dc0674edfe8f3 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 6 Apr 2022 14:33:30 -0300 Subject: [PATCH 157/386] Fix lint issue --- plugins/woocommerce-admin/client/two-column-tasks/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/client/two-column-tasks/style.scss b/plugins/woocommerce-admin/client/two-column-tasks/style.scss index 6b6caf8c4d0..59bb4369e6c 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/style.scss +++ b/plugins/woocommerce-admin/client/two-column-tasks/style.scss @@ -130,7 +130,7 @@ } .woocommerce-task-header__contents { - max-width: calc( 60% - 2% ); + max-width: calc(60% - 2%); } .svg-background { From 8eb03eba4627e5d0ab05f82711e8dc48559b0dcc Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 11 Apr 2022 10:19:30 -0300 Subject: [PATCH 158/386] Remove commented out code --- plugins/woocommerce-admin/client/two-column-tasks/style.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/woocommerce-admin/client/two-column-tasks/style.scss b/plugins/woocommerce-admin/client/two-column-tasks/style.scss index 59bb4369e6c..a6f1dc686a1 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/style.scss +++ b/plugins/woocommerce-admin/client/two-column-tasks/style.scss @@ -236,10 +236,6 @@ position: absolute; z-index: 0; right: 6%; - // top: 50%; - // bottom: 50%; - // margin-top: auto; - // margin-bottom: auto; .admin-theme-color { fill: var(--wp-admin-theme-color); From 7065f678b72956106951805e6b579f70c75b49c3 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Mon, 11 Apr 2022 06:20:09 -0700 Subject: [PATCH 159/386] Add changelog. --- .../update-filter-out-product-variation-line-item-meta | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/update-filter-out-product-variation-line-item-meta diff --git a/plugins/woocommerce/changelog/update-filter-out-product-variation-line-item-meta b/plugins/woocommerce/changelog/update-filter-out-product-variation-line-item-meta new file mode 100644 index 00000000000..dac8fdd6c51 --- /dev/null +++ b/plugins/woocommerce/changelog/update-filter-out-product-variation-line-item-meta @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add `order_item_display_meta` option to orders endpoint (REST API), to osupport filtering out variation meta. From d58bbc112fbbe3a30d9c9f865806ad2990285727 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Fri, 4 Mar 2022 12:50:30 +0530 Subject: [PATCH 160/386] Add error logger class for custom table migration. --- .../Migrations/MigrationErrorLogger.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 plugins/woocommerce/src/Database/Migrations/MigrationErrorLogger.php diff --git a/plugins/woocommerce/src/Database/Migrations/MigrationErrorLogger.php b/plugins/woocommerce/src/Database/Migrations/MigrationErrorLogger.php new file mode 100644 index 00000000000..af3288beac8 --- /dev/null +++ b/plugins/woocommerce/src/Database/Migrations/MigrationErrorLogger.php @@ -0,0 +1,17 @@ + Date: Fri, 4 Mar 2022 20:24:32 +0530 Subject: [PATCH 161/386] Add generic meta to custom table migrator class, to be used in meta to custom table migrations. --- .../MetaToCustomTableMigrator.php | 374 ++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php new file mode 100644 index 00000000000..7e353203474 --- /dev/null +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php @@ -0,0 +1,374 @@ + '%d', + 'decimal' => '%f', + 'string' => '%s', + 'date' => '%s', + ); + + /** + * MetaToCustomTableMigrator constructor. + * + * @param array $schema_config This parameters provides general but essential information about tables under migrations. Must be of the form- + * array( + * 'entity_schema' => + * array ( + * 'primary_id' => 'primary_id column name of source table', + * 'table_name' => 'name of the source table'. + * ), + * 'entity_meta_schema' => + * array ( + * 'meta_key_column' => 'name of meta_key column in source meta table', + * 'meta_value_column' => 'name of meta_value column in source meta table', + * 'table_name' => 'name of source meta table', + * ), + * 'destination_table' => 'name of destination custom table', + * 'entity_meta_relation' => + * array ( + * 'entity' => 'name of column in source table which is used in source meta table', + * 'meta' => 'name of column in source meta table which contains key of records in source table', + * ) + * ) + * ). + * + * @param array $meta_column_mapping Mapping information of keys in source meta table. Must be of the form: + * array( + * '$meta_key_1' => array( // $meta_key_1 is the name of meta_key in source meta table. + * 'type' => 'type of value, could be string/int/date/float', + * 'destination' => 'name of the column in column name where this data should be inserted in.', + * ), + * '$meta_key_2' => array( + * ...... + * ), + * .... + * ). + * + * @param array $core_column_mapping Mapping of keys in source table, similar to meta_column_mapping param, must be of the form: + * array( + * '$source_column_name_1' => array( // $source_column_name_1 is column name in source table. + * 'type' => 'type of value, could be string/int/date/float.', + * 'destination' => 'name of the column in column name where this data should be inserted in.', + * ), + * '$source_column_name_2' => array( + * ...... + * ), + * .... + * ). + */ + public function __construct( $schema_config, $meta_column_mapping, $core_column_mapping ) { + // TODO: Add code to validate params. + $this->schema_config = $schema_config; + $this->meta_column_mapping = $meta_column_mapping; + $this->core_column_mapping = $core_column_mapping; + } + + /** + * Generate SQL for data insertion. + * + * @param array $batch Data to generate queries for. Will be 'data' array returned by `$this->fetch_data_for_migration()` method. + * @param string $insert_switch Insert command to use in generating queries, could be insert, insert_ignore, or replace. + * + * @return string Generated queries for insertion for this batch, would be of the form: + * INSERT/INSERT IGNORE/REPLACE INTO $table_name ($columns) values + * ($value for row 1) + * ($value for row 2) + * ... + */ + public function generate_insert_sql_for_batch( $batch, $insert_switch ) { + global $wpdb; + + // TODO: Add code to validate params. + $table = $this->schema_config['destination_table']; + + switch ( $insert_switch ) { + case 'insert_ignore': + $insert_query = 'INSERT IGNORE'; + break; + case 'replace': + $insert_query = 'REPLACE'; + break; + case 'insert': + default: + $insert_query = 'INSERT'; + } + + $columns = array(); + $placeholders = array(); + foreach ( array_merge( $this->core_column_mapping, $this->meta_column_mapping ) as $prev_column => $schema ) { + $columns[] = $schema['destination']; + $placeholders[] = $this->wpdb_placeholder_for_type[ $schema['type'] ]; + } + $placeholders = "'" . implode( "', '", $placeholders ) . "'"; + + $values = array(); + foreach ( array_values( $batch ) as $row ) { + $query_params = array(); + foreach ( $columns as $column ) { + $query_params[] = $row[ $column ] ?? null; + } + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $placeholders can only contain combination of placeholders described in $this->>wpdb_placeholder_for_type. + $value_string = '(' . $wpdb->prepare( $placeholders, $query_params ) . ')'; + $values[] = $value_string; + } + + $value_sql = implode( ',', $values ); + + $column_sql = implode( '`, `', $this->escape_backtick( $columns ) ); + + return "$insert_query INTO $table (`$column_sql`) VALUES $value_sql;"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, -- $insert_query is hardcoded, $value_sql is already escaped. + } + + /** + * Fetch data for migration. + * + * @param string $where_clause Where conditions to use while selecting data from source table. + * @param string $batch_size Batch size, will be used in LIMIT clause. + * @param string $order_by Will be used in ORDER BY clause. + * + * @return array[] Data along with errors (if any), will of the form: + * array( + * 'data' => array( + * 'id_1' => array( 'column1' => value1, 'column2' => value2, ...), + * ..., + * ), + * 'errors' => array( + * 'id_1' => array( 'column1' => error1, 'column2' => value2, ...), + * ..., + * ) + */ + public function fetch_data_for_migration( $where_clause, $batch_size, $order_by ) { + global $wpdb; + + // TODO: Add code to validate params. + $entity_table_query = $this->build_entity_table_query( $where_clause, $batch_size, $order_by ); + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Output of $this->build_entity_table_query is already prepared. + $entity_data = $wpdb->get_results( $entity_table_query ); + if ( empty( $entity_data ) ) { + return array( + 'data' => array(), + 'errors' => array(), + ); + } + $entity_ids = array_column( $entity_data, 'primary_key_id' ); + + $meta_table_query = $this->build_meta_data_query( $entity_ids ); + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Output of $this->build_meta_data_query is already prepared. + $meta_data = $wpdb->get_results( $meta_table_query ); + + return $this->process_and_sanitize_data( $entity_data, $meta_data ); + } + + /** + * Helper method to build query used to fetch data from core source table. + * + * @param string $where_clause Where conditions to use while selecting data from source table. + * @param string $batch_size Batch size, will be used in LIMIT clause. + * @param string $order_by Will be used in ORDER BY clause. + * + * @return string Query that can be used to fetch data. + */ + private function build_entity_table_query( $where_clause, $batch_size, $order_by ) { + global $wpdb; + $entity_table = $this->escape_backtick( $this->schema_config['entity_schema']['table_name'] ); + $primary_id_column = $this->escape_backtick( $this->schema_config['entity_schema']['primary_id'] ); + $entity_keys = $this->escape_backtick( array_keys( $this->core_column_mapping ) ); + $entity_column_string = '`' . implode( '`, `', $entity_keys ) . '`'; + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $entity_table, $primary_id_column and $entity_column_string is escaped for backticks. $where clause and $order_by should already be escaped. + $query = $wpdb->prepare( + " +SELECT `$primary_id_column` as primary_key_id, $entity_column_string FROM $entity_table WHERE $where_clause ORDER BY $order_by LIMIT %d; +", + array( + $batch_size, + ) + ); + // phpcs:enable + + return $query; + } + + /** + * Helper method to escape backtick in column and table names. + * WP does not provide a method to escape table/columns names yet, but hopefully soon in @link https://core.trac.wordpress.org/ticket/52506 + * + * @param string|array $identifier Column or table name. + * + * @return array|string|string[] Escaped identifier. + */ + private function escape_backtick( $identifier ) { + return str_replace( '`', '``', $identifier ); + } + + /** + * Helper method to build query that will be used to fetch data from source meta table. + * + * @param array $entity_ids List of IDs to fetch metadata for. + * + * @return string|void Query for fetching meta data. + */ + private function build_meta_data_query( $entity_ids ) { + global $wpdb; + $meta_table = $this->escape_backtick( $this->schema_config['entity_meta_schema']['table_name'] ); + $meta_keys = array_keys( $this->meta_column_mapping ); + $meta_key_column = $this->escape_backtick( $this->schema_config['entity_meta_schema']['meta_key_column'] ); + $meta_value_column = $this->escape_backtick( $this->schema_config['entity_meta_schema']['meta_value_column'] ); + $meta_table_relational_key = $this->escape_backtick( $this->schema_config['entity_meta_relation']['meta'] ); + + $meta_column_string = implode( ', ', array_fill( 0, count( $meta_keys ), '%s' ) ); + $entity_id_string = implode( ', ', array_fill( 0, count( $entity_ids ), '%d' ) ); + + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $meta_table_relational_key, $meta_key_column, $meta_value_column and $meta_table is escaped for backticks. $entity_id_string and $meta_column_string are placeholders. + $query = $wpdb->prepare( + " +SELECT `$meta_table_relational_key` as entity_id, `$meta_key_column` as meta_key, `$meta_value_column` as meta_value +FROM `$meta_table` +WHERE + `$meta_table_relational_key` IN ( $entity_id_string ) + AND `$meta_key_column` IN ( $meta_column_string ); +", + array_merge( + $entity_ids, + $meta_keys + ) + ); + // phpcs:enable + + return $query; + } + + /** + * Helper function to validate and combine data before we try to insert. + * + * @param array $entity_data Data from source table. + * @param array $meta_data Data from meta table. + * + * @return array[] Validated and combined data with errors. + */ + private function process_and_sanitize_data( $entity_data, $meta_data ) { + /** + * TODO: Add more validations for: + * 1. Column size + * 2. Value limits + */ + $sanitized_entity_data = array(); + $error_records = array(); + $this->process_and_sanitize_entity_data( $sanitized_entity_data, $error_records, $entity_data ); + $this->processs_and_sanitize_meta_data( $sanitized_entity_data, $error_records, $meta_data ); + + return array( + 'data' => $sanitized_entity_data, + 'errors' => $error_records, + ); + } + + /** + * Helper method to sanitize core source table. + * + * @param array $sanitized_entity_data Array containing sanitized data for insertion. + * @param array $error_records Error records. + * @param array $entity_data Original source data. + */ + private function process_and_sanitize_entity_data( &$sanitized_entity_data, &$error_records, $entity_data ) { + foreach ( $entity_data as $entity ) { + $row_data = array(); + foreach ( $this->core_column_mapping as $column_name => $schema ) { + $custom_table_column_name = $schema['destination'] ?? $column_name; + $value = $entity->$column_name; + $value = $this->validate_data( $value, $schema['type'] ); + if ( is_wp_error( $value ) ) { + $error_records[ $entity->primary_key_id ][ $custom_table_column_name ] = $value->get_error_message(); + } else { + $row_data[ $custom_table_column_name ] = $value; + } + } + $sanitized_entity_data[ $entity->primary_key_id ] = $row_data; + } + } + + /** + * Helper method to sanitize soure meta data. + * + * @param array $sanitized_entity_data Array containing sanitized data for insertion. + * @param array $error_records Error records. + * @param array $meta_data Original source data. + */ + private function processs_and_sanitize_meta_data( &$sanitized_entity_data, &$error_records, $meta_data ) { + foreach ( $meta_data as $datum ) { + $column_schema = $this->meta_column_mapping[ $datum->meta_key ]; + $value = $this->validate_data( $datum->meta_value, $column_schema['type'] ); + if ( is_wp_error( $value ) ) { + $error_records[ $datum->entity_id ][ $column_schema['destination'] ] = $value->get_error_message(); + } else { + $sanitized_entity_data[ $datum->entity_id ][ $column_schema['destination'] ] = $value; + } + } + } + + /** + * Validate and transform data so that we catch as many errors as possible before inserting. + * + * @param mixed $value Actual data value. + * @param string $type Type of data, could be decimal, int, date, string. + * + * @return float|int|mixed|string|\WP_Error + */ + private function validate_data( $value, $type ) { + switch ( $type ) { + case 'decimal': + $value = (float) $value; + break; + case 'int': + $value = (int) $value; + break; + case 'date': + // TODO: Test this validation in unit tests. + try { + $value = ( new \DateTime( $value ) )->format( 'Y-m-d H:i:s' ); + } catch ( \Exception $e ) { + return new \WP_Error( $e->getMessage() ); + } + break; + } + + return $value; + } +} From bdff0100185b592f11a23ac352fea25a23d40d00 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Mon, 7 Mar 2022 17:05:19 +0530 Subject: [PATCH 162/386] Add class WPPostToCOTMigrator --- .../CustomOrderTable/WPPostToCOTMigrator.php | 217 ++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php new file mode 100644 index 00000000000..c3d3b375470 --- /dev/null +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php @@ -0,0 +1,217 @@ + array( + 'primary_id' => 'ID', + 'table_name' => $wpdb->posts, + ), + 'entity_meta_schema' => array( + 'meta_key_column' => 'meta_key', + 'meta_value_column' => 'meta_value', + 'table_name' => $wpdb->postmeta, + ), + 'destination_table' => $wpdb->prefix . 'wc_orders', + 'entity_meta_relation' => array( + 'entity' => 'ID', + 'meta' => 'post_id', + ), + + ); + + $order_table_core_config = array( + 'ID' => array( + 'type' => 'int', + 'destination' => 'post_id', + ), + 'post_status' => array( + 'type' => 'string', + 'destination' => 'status', + ), + 'post_date_gmt' => array( + 'type' => 'date', + 'destination' => 'date_created_gmt', + ), + 'post_modified_gmt' => array( + 'type' => 'date', + 'destination' => 'date_updated_gmt', + ), + 'post_parent' => array( + 'type' => 'int', + 'destination' => 'parent_order_id', + ), + ); + + $order_table_meta_config = array( + '_order_currency' => array( + 'type' => 'string', + 'destination' => 'currency', + ), + '_order_tax' => array( + 'type' => 'decimal', + 'destination' => 'tax_amount', + ), + '_order_total' => array( + 'type' => 'decimal', + 'destination' => 'total_amount', + ), + '_customer_user' => array( + 'type' => 'int', + 'destination' => 'customer_id', + ), + '_billing_email' => array( + 'type' => 'string', + 'destination' => 'billing_email', + ), + '_payment_method' => array( + 'type' => 'string', + 'destination' => 'payment_method', + ), + '_payment_method_title' => array( + 'type' => 'string', + 'destination' => 'payment_method_title', + ), + '_customer_ip_address' => array( + 'type' => 'string', + 'destination' => 'ip_address', + ), + '_customer_user_agent' => array( + 'type' => 'string', + 'destination' => 'user_agent', + ), + ); + $this->order_table_migrator = new MetaToCustomTableMigrator( $order_table_schema_config, $order_table_meta_config, $order_table_core_config ); + $this->error_logger = new MigrationErrorLogger(); + } + + /** + * Process next migration batch, uses option `wc_cot_migration` to checkpoints of what have been processed so far. + * + * @param int $batch_size Batch size of records to migrate. + * + * @return bool True if migration is completed, false if there are still records to process. + */ + public function process_next_migration_batch( $batch_size = 500 ) { + global $wpdb; + $order_by = 'ID ASC'; + + $data = $this->order_table_migrator->fetch_data_for_migration( $this->get_where_clause(), $batch_size, $order_by ); + + foreach ( $data['errors'] as $post_id => $error ) { + $this->error_logger->log( 'info', "Error in importing post id $post_id: " . print_r( $error, true ) ); + } + + if ( count( $data['data'] ) === 0 ) { + return true; + } + + $queries = $this->order_table_migrator->generate_insert_sql_for_batch( $data['data'], 'insert' ); + $result = $wpdb->query( $queries ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $queries is already prepared. + if ( count( $data['data'] ) !== $result ) { + // Some rows were not inserted. + // TODO: Find and log the entity ids that were not inserted. + echo ' error '; + } + + $last_post_migrated = max( array_keys( $data['data'] ) ); + $this->update_checkpoint( $last_post_migrated ); + + return false; + } + + /** + * Method to migrate single record. + * + * @param int $post_id Post ID of record to migrate. + * + * @return bool|\WP_Error + */ + public function process_single( $post_id ) { + global $wpdb; + + $where_clause = $wpdb->prepare( 'ID = %d', $post_id ); + $data = $this->order_table_migrator->fetch_data_for_migration( $where_clause, 1, 'ID ASC' ); + if ( isset( $data['errors'][ $post_id ] ) ) { + return new \WP_Error( $data['errors'][ $post_id ] ); + } + + $queries = $this->order_table_migrator->generate_insert_sql_for_batch( $data['data'], 'replace' ); + $result = $wpdb->query( $queries ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $queries is already prepared. + if ( 1 !== $result ) { + // TODO: Fetch and return last error. + echo 'error'; + + return new \WP_Error( 'error' ); + } + + return true; + } + + /** + * Helper function to get where clause to send to MetaToCustomTableMigrator instance. + * + * @return string|void Where clause. + */ + private function get_where_clause() { + global $wpdb; + + $checkpoint = $this->get_checkpoint(); + $where_clause = $wpdb->prepare( + 'post_type = "shop_order" AND ID > %d', + $checkpoint['id'] + ); + + return $where_clause; + } + + /** + * Current checkpoint status. + * + * @return false|mixed|void + */ + private function get_checkpoint() { + return get_option( 'wc_cot_migration', array( 'id' => 0 ) ); + } + + /** + * Updates current checkpoint + * + * @param int $id Order ID. + */ + public function update_checkpoint( $id ) { + update_option( 'wc_cot_migration', array( 'id' => $id ) ); + } +} From 17b1623071308f7ce4c6fb164ae3a8e79f68368b Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 10 Mar 2022 17:19:06 +0530 Subject: [PATCH 163/386] Add support for select clause in column name. --- .../MetaToCustomTableMigrator.php | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php index 7e353203474..0f0833e6bb9 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php @@ -188,7 +188,7 @@ class MetaToCustomTableMigrator { 'errors' => array(), ); } - $entity_ids = array_column( $entity_data, 'primary_key_id' ); + $entity_ids = array_column( $entity_data, 'entity_rel_column' ); $meta_table_query = $this->build_meta_data_query( $entity_ids ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Output of $this->build_meta_data_query is already prepared. @@ -210,12 +210,21 @@ class MetaToCustomTableMigrator { global $wpdb; $entity_table = $this->escape_backtick( $this->schema_config['entity_schema']['table_name'] ); $primary_id_column = $this->escape_backtick( $this->schema_config['entity_schema']['primary_id'] ); - $entity_keys = $this->escape_backtick( array_keys( $this->core_column_mapping ) ); - $entity_column_string = '`' . implode( '`, `', $entity_keys ) . '`'; + $entity_rel_column = $this->escape_backtick( $this->schema_config['entity_meta_relation']['entity_rel_column'] ); + $entity_keys = array(); + foreach ( $this->core_column_mapping as $column_name => $column_schema ) { + if ( isset( $column_schema['select_clause'] ) ) { + $select_clause = $column_schema['select_clause']; + $entity_keys[] = "$select_clause AS $column_name"; + } else { + $entity_keys[] = '`' . $this->escape_backtick( $column_name ) . '`'; + } + } + $entity_column_string = implode( ', ', $entity_keys ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $entity_table, $primary_id_column and $entity_column_string is escaped for backticks. $where clause and $order_by should already be escaped. $query = $wpdb->prepare( " -SELECT `$primary_id_column` as primary_key_id, $entity_column_string FROM $entity_table WHERE $where_clause ORDER BY $order_by LIMIT %d; +SELECT `$primary_id_column` as primary_key_id, `$entity_rel_column` AS entity_rel_column, $entity_column_string FROM $entity_table WHERE $where_clause ORDER BY $order_by LIMIT %d; ", array( $batch_size, @@ -251,7 +260,7 @@ SELECT `$primary_id_column` as primary_key_id, $entity_column_string FROM $entit $meta_keys = array_keys( $this->meta_column_mapping ); $meta_key_column = $this->escape_backtick( $this->schema_config['entity_meta_schema']['meta_key_column'] ); $meta_value_column = $this->escape_backtick( $this->schema_config['entity_meta_schema']['meta_value_column'] ); - $meta_table_relational_key = $this->escape_backtick( $this->schema_config['entity_meta_relation']['meta'] ); + $meta_table_relational_key = $this->escape_backtick( $this->schema_config['entity_meta_relation']['meta_rel_column'] ); $meta_column_string = implode( ', ', array_fill( 0, count( $meta_keys ), '%s' ) ); $entity_id_string = implode( ', ', array_fill( 0, count( $entity_ids ), '%d' ) ); From 32d1162a017be2863e1d0211ba3b26dad5561038 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 10 Mar 2022 17:19:36 +0530 Subject: [PATCH 164/386] Add migration support for billing address. --- .../CustomOrderTable/WPPostToCOTMigrator.php | 189 ++++++++++++++++-- 1 file changed, 177 insertions(+), 12 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php index c3d3b375470..8dba1643868 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php @@ -22,18 +22,53 @@ class WPPostToCOTMigrator { private $error_logger; /** - * Migrator instance to actually perform migrations. + * Migrator instance to migrate data into wc_order table. * * @var MetaToCustomTableMigrator */ private $order_table_migrator; + /** + * Migrator instance to migrate data into address table. + * + * @var MetaToCustomTableMigrator + */ + private $address_table_migrator; + + /** + * Names of different order tables. + * + * @var array + */ + private $table_names; + /** * WPPostToCOTMigrator constructor. */ public function __construct() { global $wpdb; + // TODO: Remove hardcoding. + $this->table_names = array( + 'orders' => $wpdb->prefix . 'wc_orders', + 'addresses' => $wpdb->prefix . 'wc_order_addresses', + 'op_data' => $wpdb->prefix . 'wc_order_operational_data', + ); + + $order_config = $this->get_config_for_order_table(); + $address_config = $this->get_config_for_address_table_billing(); + $this->order_table_migrator = new MetaToCustomTableMigrator( $order_config['schema'], $order_config['meta'], $order_config['core'] ); + $this->address_table_migrator = new MetaToCustomTableMigrator( $address_config['schema'], $address_config['meta'], $address_config['core'] ); + $this->error_logger = new MigrationErrorLogger(); + } + + /** + * Returns migration configuration for order table. + * + * @return array Config for order table. + */ + public function get_config_for_order_table() { + global $wpdb; $order_table_schema_config = array( 'entity_schema' => array( 'primary_id' => 'ID', @@ -46,8 +81,8 @@ class WPPostToCOTMigrator { ), 'destination_table' => $wpdb->prefix . 'wc_orders', 'entity_meta_relation' => array( - 'entity' => 'ID', - 'meta' => 'post_id', + 'entity_rel_column' => 'ID', + 'meta_rel_column' => 'post_id', ), ); @@ -75,7 +110,7 @@ class WPPostToCOTMigrator { ), ); - $order_table_meta_config = array( + $order_table_meta_config = array( '_order_currency' => array( 'type' => 'string', 'destination' => 'currency', @@ -113,8 +148,104 @@ class WPPostToCOTMigrator { 'destination' => 'user_agent', ), ); - $this->order_table_migrator = new MetaToCustomTableMigrator( $order_table_schema_config, $order_table_meta_config, $order_table_core_config ); - $this->error_logger = new MigrationErrorLogger(); + + return array( + 'schema' => $order_table_schema_config, + 'core' => $order_table_core_config, + 'meta' => $order_table_meta_config, + ); + } + + /** + * Get configuration for billing addresses for migration. + * + * @return array Billing address migration config. + */ + public function get_config_for_address_table_billing() { + global $wpdb; + // We join order core table and post meta table to get data for address, since we need order ID. + // So order core record needs to be already present. + $schema_config = array( + 'entity_schema' => array( + 'primary_id' => 'id', + 'table_name' => $this->table_names['orders'], + ), + 'entity_meta_schema' => array( + 'meta_key_column' => 'meta_key', + 'meta_value_column' => 'meta_value', + 'table_name' => $wpdb->postmeta, + ), + 'destination_table' => $this->table_names['addresses'], + 'entity_meta_relation' => array( + 'entity_rel_column' => 'post_id', + 'meta_rel_column' => 'post_id', + ), + ); + + $core_config = array( + 'id' => array( + 'type' => 'int', + 'destination' => 'order_id', + ), + 'billing' => array( + 'type' => 'string', + 'destination' => 'address_type', + 'select_clause' => "'billing'", + ), + ); + + $meta_config = array( + '_billing_first_name' => array( + 'type' => 'string', + 'destination' => 'first_name', + ), + '_billing_last_name' => array( + 'type' => 'string', + 'destination' => 'last_name', + ), + '_billing_company' => array( + 'type' => 'string', + 'destination' => 'company', + ), + '_billing_address_1' => array( + 'type' => 'string', + 'destination' => 'address_1', + ), + '_billing_address_2' => array( + 'type' => 'string', + 'destination' => 'address_2', + ), + '_billing_city' => array( + 'type' => 'string', + 'destination' => 'city', + ), + '_billing_state' => array( + 'type' => 'string', + 'destination' => 'state', + ), + '_billing_postcode' => array( + 'type' => 'string', + 'destination' => 'postcode', + ), + '_billing_country' => array( + 'type' => 'string', + 'destination' => 'country', + ), + '_billing_email' => array( + 'type' => 'string', + 'destination' => 'email', + ), + '_billing_phone' => array( + 'type' => 'string', + 'destination' => 'phone', + ), + ); + + return array( + 'schema' => $schema_config, + 'core' => $core_config, + 'meta' => $meta_config, + ); } /** @@ -128,25 +259,39 @@ class WPPostToCOTMigrator { global $wpdb; $order_by = 'ID ASC'; - $data = $this->order_table_migrator->fetch_data_for_migration( $this->get_where_clause(), $batch_size, $order_by ); + $order_data = $this->order_table_migrator->fetch_data_for_migration( $this->get_where_clause(), $batch_size, $order_by ); - foreach ( $data['errors'] as $post_id => $error ) { + foreach ( $order_data['errors'] as $post_id => $error ) { $this->error_logger->log( 'info', "Error in importing post id $post_id: " . print_r( $error, true ) ); } - if ( count( $data['data'] ) === 0 ) { + if ( count( $order_data['data'] ) === 0 ) { return true; } - $queries = $this->order_table_migrator->generate_insert_sql_for_batch( $data['data'], 'insert' ); + $queries = $this->order_table_migrator->generate_insert_sql_for_batch( $order_data['data'], 'insert' ); $result = $wpdb->query( $queries ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $queries is already prepared. - if ( count( $data['data'] ) !== $result ) { + if ( count( $order_data['data'] ) !== $result ) { // Some rows were not inserted. // TODO: Find and log the entity ids that were not inserted. echo ' error '; } - $last_post_migrated = max( array_keys( $data['data'] ) ); + $order_post_ids = array_column( $order_data['data'], 'post_id' ); + $post_ids_where_clause = $this->get_where_id_clause( $order_post_ids, 'post_id' ); + $address_data = $this->address_table_migrator->fetch_data_for_migration( $post_ids_where_clause, $batch_size, $order_by ); + foreach ( $address_data['errors'] as $order_id => $error ) { + $this->error_logger->log( 'info', "Error in importing address data for Order ID $order_id: " . print_r( $error, true ) ); + } + $address_queries = $this->address_table_migrator->generate_insert_sql_for_batch( $address_data['data'], 'insert' ); + $result = $wpdb->query( $address_queries ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Insert statements should already be escaped. + if ( count( $address_data['data'] ) !== $result ) { + // Some rows were not inserted. + // TODO: Find and log the entity ids that were not inserted. + echo 'error'; + } + + $last_post_migrated = max( array_keys( $order_data['data'] ) ); $this->update_checkpoint( $last_post_migrated ); return false; @@ -197,6 +342,26 @@ class WPPostToCOTMigrator { return $where_clause; } + /** + * Helper method to create `ID in (.., .., ...)` clauses. + * + * @param array $ids List of IDs. + * @param string $column_name Name of the ID column. + * + * @return string Prepared clause for where. + */ + private function get_where_id_clause( $ids, $column_name = 'ID' ) { + global $wpdb; + + if ( 0 === count( $ids ) ) { + return ''; + } + + $id_placeholder_array = '(' . implode( ',', array_fill( 0, count( $ids ), '%d' ) ) . ')'; + + return $wpdb->prepare( "`$column_name` IN $id_placeholder_array", $ids ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Both $column_name and $id_placeholder_array should already be prepared. + } + /** * Current checkpoint status. * From 6cc34e8616c5cf464d72c7a0fe895304471eb4a4 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 10 Mar 2022 18:35:36 +0530 Subject: [PATCH 165/386] Add support for migrating shipping addresses. --- .../CustomOrderTable/WPPostToCOTMigrator.php | 105 ++++++++++++------ 1 file changed, 74 insertions(+), 31 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php index 8dba1643868..5eda1f77a0d 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php @@ -29,11 +29,18 @@ class WPPostToCOTMigrator { private $order_table_migrator; /** - * Migrator instance to migrate data into address table. + * Migrator instance to migrate billing data into address table. * * @var MetaToCustomTableMigrator */ - private $address_table_migrator; + private $billing_address_table_migrator; + + /** + * Migrator instance to migrate shipping data into address table. + * + * @var MetaToCustomTableMigrator + */ + private $shipping_address_table_migrator; /** * Names of different order tables. @@ -55,11 +62,13 @@ class WPPostToCOTMigrator { 'op_data' => $wpdb->prefix . 'wc_order_operational_data', ); - $order_config = $this->get_config_for_order_table(); - $address_config = $this->get_config_for_address_table_billing(); - $this->order_table_migrator = new MetaToCustomTableMigrator( $order_config['schema'], $order_config['meta'], $order_config['core'] ); - $this->address_table_migrator = new MetaToCustomTableMigrator( $address_config['schema'], $address_config['meta'], $address_config['core'] ); - $this->error_logger = new MigrationErrorLogger(); + $order_config = $this->get_config_for_order_table(); + $billing_address_config = $this->get_config_for_address_table_billing(); + $shipping_address_config = $this->get_config_for_address_table_shipping(); + $this->order_table_migrator = new MetaToCustomTableMigrator( $order_config['schema'], $order_config['meta'], $order_config['core'] ); + $this->billing_address_table_migrator = new MetaToCustomTableMigrator( $billing_address_config['schema'], $billing_address_config['meta'], $billing_address_config['core'] ); + $this->shipping_address_table_migrator = new MetaToCustomTableMigrator( $shipping_address_config['schema'], $shipping_address_config['meta'], $shipping_address_config['core'] ); + $this->error_logger = new MigrationErrorLogger(); } /** @@ -162,12 +171,32 @@ class WPPostToCOTMigrator { * @return array Billing address migration config. */ public function get_config_for_address_table_billing() { + return $this->get_config_for_address_table( 'billing' ); + } + + /** + * Get configuration for shipping addresses for migration. + * + * @return array Shipping address migration config. + */ + public function get_config_for_address_table_shipping() { + return $this->get_config_for_address_table( 'shipping' ); + } + + /** + * Generate config for address data. + * + * @param string $type Type of address, this will be using in fetching meta keys. + * + * @return array Config for address table. + */ + private function get_config_for_address_table( $type ) { global $wpdb; // We join order core table and post meta table to get data for address, since we need order ID. // So order core record needs to be already present. $schema_config = array( 'entity_schema' => array( - 'primary_id' => 'id', + 'primary_id' => 'post_id', 'table_name' => $this->table_names['orders'], ), 'entity_meta_schema' => array( @@ -183,59 +212,59 @@ class WPPostToCOTMigrator { ); $core_config = array( - 'id' => array( + 'id' => array( 'type' => 'int', 'destination' => 'order_id', ), - 'billing' => array( + 'type' => array( 'type' => 'string', 'destination' => 'address_type', - 'select_clause' => "'billing'", + 'select_clause' => "'$type'", ), ); $meta_config = array( - '_billing_first_name' => array( + "_{$type}_first_name" => array( 'type' => 'string', 'destination' => 'first_name', ), - '_billing_last_name' => array( + "_{$type}_last_name" => array( 'type' => 'string', 'destination' => 'last_name', ), - '_billing_company' => array( + "_{$type}_company" => array( 'type' => 'string', 'destination' => 'company', ), - '_billing_address_1' => array( + "_{$type}_address_1" => array( 'type' => 'string', 'destination' => 'address_1', ), - '_billing_address_2' => array( + "_{$type}_address_2" => array( 'type' => 'string', 'destination' => 'address_2', ), - '_billing_city' => array( + "_{$type}_city" => array( 'type' => 'string', 'destination' => 'city', ), - '_billing_state' => array( + "_{$type}_state" => array( 'type' => 'string', 'destination' => 'state', ), - '_billing_postcode' => array( + "_{$type}_postcode" => array( 'type' => 'string', 'destination' => 'postcode', ), - '_billing_country' => array( + "_{$type}_country" => array( 'type' => 'string', 'destination' => 'country', ), - '_billing_email' => array( + "_{$type}_email" => array( 'type' => 'string', 'destination' => 'email', ), - '_billing_phone' => array( + "_{$type}_phone" => array( 'type' => 'string', 'destination' => 'phone', ), @@ -255,7 +284,7 @@ class WPPostToCOTMigrator { * * @return bool True if migration is completed, false if there are still records to process. */ - public function process_next_migration_batch( $batch_size = 500 ) { + public function process_next_migration_batch( $batch_size = 100 ) { global $wpdb; $order_by = 'ID ASC'; @@ -277,24 +306,38 @@ class WPPostToCOTMigrator { echo ' error '; } - $order_post_ids = array_column( $order_data['data'], 'post_id' ); + $order_post_ids = array_column( $order_data['data'], 'post_id' ); + $this->process_next_address_batch( $this->billing_address_table_migrator, $order_post_ids, $order_by ); + $this->process_next_address_batch( $this->shipping_address_table_migrator, $order_post_ids, $order_by ); + + $last_post_migrated = max( array_keys( $order_data['data'] ) ); + $this->update_checkpoint( $last_post_migrated ); + + return false; + } + + /** + * Process next batch for a given address type. + * + * @param MetaToCustomTableMigrator $migrator Migrator instance for address type. + * @param array $order_post_ids Array of post IDs for orders. + * @param string $order_by Order by clause. + */ + private function process_next_address_batch( $migrator, $order_post_ids, $order_by ) { + global $wpdb; $post_ids_where_clause = $this->get_where_id_clause( $order_post_ids, 'post_id' ); - $address_data = $this->address_table_migrator->fetch_data_for_migration( $post_ids_where_clause, $batch_size, $order_by ); + $batch_size = count( $order_post_ids ); + $address_data = $migrator->fetch_data_for_migration( $post_ids_where_clause, $batch_size, $order_by ); foreach ( $address_data['errors'] as $order_id => $error ) { $this->error_logger->log( 'info', "Error in importing address data for Order ID $order_id: " . print_r( $error, true ) ); } - $address_queries = $this->address_table_migrator->generate_insert_sql_for_batch( $address_data['data'], 'insert' ); + $address_queries = $migrator->generate_insert_sql_for_batch( $address_data['data'], 'insert' ); $result = $wpdb->query( $address_queries ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Insert statements should already be escaped. if ( count( $address_data['data'] ) !== $result ) { // Some rows were not inserted. // TODO: Find and log the entity ids that were not inserted. echo 'error'; } - - $last_post_migrated = max( array_keys( $order_data['data'] ) ); - $this->update_checkpoint( $last_post_migrated ); - - return false; } /** From 786561d6fd14e63b27e1014619b640eb9aa2d19d Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 10 Mar 2022 19:50:01 +0530 Subject: [PATCH 166/386] Add migrations for operational data table. --- .../MetaToCustomTableMigrator.php | 52 +++++-- .../CustomOrderTable/WPPostToCOTMigrator.php | 147 ++++++++++++++++-- .../Orders/OrdersTableDataStore.php | 4 +- 3 files changed, 170 insertions(+), 33 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php index 0f0833e6bb9..81d4bdcf233 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php @@ -39,10 +39,12 @@ class MetaToCustomTableMigrator { * @var string[] */ private $wpdb_placeholder_for_type = array( - 'int' => '%d', - 'decimal' => '%f', - 'string' => '%s', - 'date' => '%s', + 'int' => '%d', + 'decimal' => '%f', + 'string' => '%s', + 'date' => '%s', + 'date_epoch' => '%s', + 'bool' => '%d', ); /** @@ -181,18 +183,18 @@ class MetaToCustomTableMigrator { // TODO: Add code to validate params. $entity_table_query = $this->build_entity_table_query( $where_clause, $batch_size, $order_by ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Output of $this->build_entity_table_query is already prepared. - $entity_data = $wpdb->get_results( $entity_table_query ); + $entity_data = $wpdb->get_results( $entity_table_query ); if ( empty( $entity_data ) ) { return array( - 'data' => array(), + 'data' => array(), 'errors' => array(), ); } - $entity_ids = array_column( $entity_data, 'entity_rel_column' ); + $entity_ids = array_column( $entity_data, 'entity_rel_column' ); $meta_table_query = $this->build_meta_data_query( $entity_ids ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Output of $this->build_meta_data_query is already prepared. - $meta_data = $wpdb->get_results( $meta_table_query ); + $meta_data = $wpdb->get_results( $meta_table_query ); return $this->process_and_sanitize_data( $entity_data, $meta_data ); } @@ -208,10 +210,10 @@ class MetaToCustomTableMigrator { */ private function build_entity_table_query( $where_clause, $batch_size, $order_by ) { global $wpdb; - $entity_table = $this->escape_backtick( $this->schema_config['entity_schema']['table_name'] ); - $primary_id_column = $this->escape_backtick( $this->schema_config['entity_schema']['primary_id'] ); + $entity_table = $this->escape_backtick( $this->schema_config['entity_schema']['table_name'] ); + $primary_id_column = $this->escape_backtick( $this->schema_config['entity_schema']['primary_id'] ); $entity_rel_column = $this->escape_backtick( $this->schema_config['entity_meta_relation']['entity_rel_column'] ); - $entity_keys = array(); + $entity_keys = array(); foreach ( $this->core_column_mapping as $column_name => $column_schema ) { if ( isset( $column_schema['select_clause'] ) ) { $select_clause = $column_schema['select_clause']; @@ -222,7 +224,7 @@ class MetaToCustomTableMigrator { } $entity_column_string = implode( ', ', $entity_keys ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $entity_table, $primary_id_column and $entity_column_string is escaped for backticks. $where clause and $order_by should already be escaped. - $query = $wpdb->prepare( + $query = $wpdb->prepare( " SELECT `$primary_id_column` as primary_key_id, `$entity_rel_column` AS entity_rel_column, $entity_column_string FROM $entity_table WHERE $where_clause ORDER BY $order_by LIMIT %d; ", @@ -230,6 +232,7 @@ SELECT `$primary_id_column` as primary_key_id, `$entity_rel_column` AS entity_re $batch_size, ) ); + // phpcs:enable return $query; @@ -279,6 +282,7 @@ WHERE $meta_keys ) ); + // phpcs:enable return $query; @@ -304,7 +308,7 @@ WHERE $this->processs_and_sanitize_meta_data( $sanitized_entity_data, $error_records, $meta_data ); return array( - 'data' => $sanitized_entity_data, + 'data' => $sanitized_entity_data, 'errors' => $error_records, ); } @@ -345,7 +349,7 @@ WHERE $column_schema = $this->meta_column_mapping[ $datum->meta_key ]; $value = $this->validate_data( $datum->meta_value, $column_schema['type'] ); if ( is_wp_error( $value ) ) { - $error_records[ $datum->entity_id ][ $column_schema['destination'] ] = $value->get_error_message(); + $error_records[ $datum->entity_id ][ $column_schema['destination'] ] = "{$value->get_error_code()}: {$value->get_error_message()}"; } else { $sanitized_entity_data[ $datum->entity_id ][ $column_schema['destination'] ] = $value; } @@ -368,10 +372,28 @@ WHERE case 'int': $value = (int) $value; break; + case 'bool': + $value = wc_string_to_bool( $value ); + break; case 'date': // TODO: Test this validation in unit tests. try { - $value = ( new \DateTime( $value ) )->format( 'Y-m-d H:i:s' ); + if ( '' === $value ) { + $value = null; + } else { + $value = ( new \DateTime( $value ) )->format( 'Y-m-d H:i:s' ); + } + } catch ( \Exception $e ) { + return new \WP_Error( $e->getMessage() ); + } + break; + case 'date_epoch': + try { + if ( '' === $value ) { + $value = null; + } else { + $value = ( new \DateTime( "@$value" ) )->format( 'Y-m-d H:i:s' ); + } } catch ( \Exception $e ) { return new \WP_Error( $e->getMessage() ); } diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php index 5eda1f77a0d..0a1e9fbf117 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php @@ -42,6 +42,13 @@ class WPPostToCOTMigrator { */ private $shipping_address_table_migrator; + /** + * Migrator instance to migrate operational data. + * + * @var MetaToCustomTableMigrator + */ + private $operation_data_table_migrator; + /** * Names of different order tables. * @@ -62,13 +69,17 @@ class WPPostToCOTMigrator { 'op_data' => $wpdb->prefix . 'wc_order_operational_data', ); - $order_config = $this->get_config_for_order_table(); - $billing_address_config = $this->get_config_for_address_table_billing(); - $shipping_address_config = $this->get_config_for_address_table_shipping(); + $order_config = $this->get_config_for_order_table(); + $billing_address_config = $this->get_config_for_address_table_billing(); + $shipping_address_config = $this->get_config_for_address_table_shipping(); + $operation_data_config = $this->get_config_for_operational_data_table(); + $this->order_table_migrator = new MetaToCustomTableMigrator( $order_config['schema'], $order_config['meta'], $order_config['core'] ); $this->billing_address_table_migrator = new MetaToCustomTableMigrator( $billing_address_config['schema'], $billing_address_config['meta'], $billing_address_config['core'] ); $this->shipping_address_table_migrator = new MetaToCustomTableMigrator( $shipping_address_config['schema'], $shipping_address_config['meta'], $shipping_address_config['core'] ); - $this->error_logger = new MigrationErrorLogger(); + $this->operation_data_table_migrator = new MetaToCustomTableMigrator( $operation_data_config['schema'], $operation_data_config['meta'], $operation_data_config['core'] ); + + $this->error_logger = new MigrationErrorLogger(); } /** @@ -76,7 +87,7 @@ class WPPostToCOTMigrator { * * @return array Config for order table. */ - public function get_config_for_order_table() { + private function get_config_for_order_table() { global $wpdb; $order_table_schema_config = array( 'entity_schema' => array( @@ -170,7 +181,7 @@ class WPPostToCOTMigrator { * * @return array Billing address migration config. */ - public function get_config_for_address_table_billing() { + private function get_config_for_address_table_billing() { return $this->get_config_for_address_table( 'billing' ); } @@ -179,7 +190,7 @@ class WPPostToCOTMigrator { * * @return array Shipping address migration config. */ - public function get_config_for_address_table_shipping() { + private function get_config_for_address_table_shipping() { return $this->get_config_for_address_table( 'shipping' ); } @@ -277,6 +288,108 @@ class WPPostToCOTMigrator { ); } + /** + * Generate config for operational data. + * + * @return array Config for operational data table. + */ + private function get_config_for_operational_data_table() { + global $wpdb; + + $schema_config = array( + 'entity_schema' => array( + 'primary_id' => 'post_id', + 'table_name' => $this->table_names['orders'], + ), + 'entity_meta_schema' => array( + 'meta_key_column' => 'meta_key', + 'meta_value_column' => 'meta_value', + 'table_name' => $wpdb->postmeta, + ), + 'destination_table' => $this->table_names['op_data'], + 'entity_meta_relation' => array( + 'entity_rel_column' => 'post_id', + 'meta_rel_column' => 'post_id', + ), + ); + + $core_config = array( + 'id' => array( + 'type' => 'int', + 'destination' => 'order_id', + ), + ); + + $meta_config = array( + '_created_via' => array( + 'type' => 'string', + 'destination' => 'created_via', + ), + '_order_version' => array( + 'type' => 'string', + 'destination' => 'woocommerce_version', + ), + '_prices_include_tax' => array( + 'type' => 'bool', + 'destination' => 'prices_include_tax', + ), + '_recorded_coupon_usage_counts' => array( + 'type' => 'bool', + 'destination' => 'coupon_usages_are_counted', + ), + '_download_permissions_granted' => array( + 'type' => 'bool', + 'destination' => 'download_permissions_granted', + ), + '_cart_hash' => array( + 'type' => 'string', + 'destination' => 'cart_hash', + ), + '_new_order_email_sent' => array( + 'type' => 'bool', + 'destination' => 'new_order_email_sent', + ), + '_order_key' => array( + 'type' => 'string', + 'destination' => 'order_key', + ), + '_order_stock_reduced' => array( + 'type' => 'bool', + 'destination' => 'order_stock_reduced', + ), + '_date_paid' => array( + 'type' => 'date_epoch', + 'destination' => 'date_paid_gmt', + ), + '_date_completed' => array( + 'type' => 'date_epoch', + 'destination' => 'date_completed_gmt', + ), + '_order_shipping_tax' => array( + 'type' => 'decimal', + 'destination' => 'shipping_tax_amount', + ), + '_order_shipping' => array( + 'type' => 'decimal', + 'destination' => 'shipping_total_amount', + ), + '_cart_discount_tax' => array( + 'type' => 'decimal', + 'destination' => 'discount_tax_amount', + ), + '_cart_discount' => array( + 'type' => 'decimal', + 'destination' => 'discount_total_amount', + ), + ); + + return array( + 'schema' => $schema_config, + 'core' => $core_config, + 'meta' => $meta_config, + ); + } + /** * Process next migration batch, uses option `wc_cot_migration` to checkpoints of what have been processed so far. * @@ -307,8 +420,9 @@ class WPPostToCOTMigrator { } $order_post_ids = array_column( $order_data['data'], 'post_id' ); - $this->process_next_address_batch( $this->billing_address_table_migrator, $order_post_ids, $order_by ); - $this->process_next_address_batch( $this->shipping_address_table_migrator, $order_post_ids, $order_by ); + $this->process_next_migrator_batch( $this->billing_address_table_migrator, $order_post_ids, $order_by ); + $this->process_next_migrator_batch( $this->shipping_address_table_migrator, $order_post_ids, $order_by ); + $this->process_next_migrator_batch( $this->operation_data_table_migrator, $order_post_ids, $order_by ); $last_post_migrated = max( array_keys( $order_data['data'] ) ); $this->update_checkpoint( $last_post_migrated ); @@ -323,17 +437,18 @@ class WPPostToCOTMigrator { * @param array $order_post_ids Array of post IDs for orders. * @param string $order_by Order by clause. */ - private function process_next_address_batch( $migrator, $order_post_ids, $order_by ) { + private function process_next_migrator_batch( $migrator, $order_post_ids, $order_by ) { global $wpdb; $post_ids_where_clause = $this->get_where_id_clause( $order_post_ids, 'post_id' ); $batch_size = count( $order_post_ids ); - $address_data = $migrator->fetch_data_for_migration( $post_ids_where_clause, $batch_size, $order_by ); - foreach ( $address_data['errors'] as $order_id => $error ) { - $this->error_logger->log( 'info', "Error in importing address data for Order ID $order_id: " . print_r( $error, true ) ); + $data = $migrator->fetch_data_for_migration( $post_ids_where_clause, $batch_size, $order_by ); + foreach ( $data['errors'] as $order_id => $error ) { + // TODO: Add name of the migrator in error message. + $this->error_logger->log( 'info', "Error in importing data for Order ID $order_id: " . print_r( $error, true ) ); } - $address_queries = $migrator->generate_insert_sql_for_batch( $address_data['data'], 'insert' ); - $result = $wpdb->query( $address_queries ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Insert statements should already be escaped. - if ( count( $address_data['data'] ) !== $result ) { + $queries = $migrator->generate_insert_sql_for_batch( $data['data'], 'insert' ); + $result = $wpdb->query( $queries ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Insert statements should already be escaped. + if ( count( $data['data'] ) !== $result ) { // Some rows were not inserted. // TODO: Find and log the entity ids that were not inserted. echo 'error'; diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php index db22608e63b..8647ab413ca 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php @@ -234,7 +234,7 @@ CREATE TABLE $operational_data_table_name ( woocommerce_version varchar(20) NULL, prices_include_tax tinyint(1) NULL, coupon_usages_are_counted tinyint(1) NULL, - download_permissionis_granted tinyint(1) NULL, + download_permission_granted tinyint(1) NULL, cart_hash varchar(100) NULL, new_order_email_sent tinyint(1) NULL, order_key varchar(100) NULL, @@ -242,7 +242,7 @@ CREATE TABLE $operational_data_table_name ( date_paid_gmt datetime NULL, date_completed_gmt datetime NULL, shipping_tax_amount decimal(26, 8) NULL, - shopping_total_amount decimal(26, 8) NULL, + shipping_total_amount decimal(26, 8) NULL, discount_tax_amount decimal(26, 8) NULL, discount_total_amount decimal(26, 8) NULL, KEY order_id (order_id), From cb280829cc8d91f47247d0b21c7ab1a2ac0f35e7 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 24 Mar 2022 12:52:54 +0530 Subject: [PATCH 167/386] Add a migration helper class to refactor common utility methods. --- .../Database/Migrations/MigrationHelper.php | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 plugins/woocommerce/src/Database/Migrations/MigrationHelper.php diff --git a/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php b/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php new file mode 100644 index 00000000000..8d7e7da30a9 --- /dev/null +++ b/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php @@ -0,0 +1,75 @@ + '%d', + 'decimal' => '%f', + 'string' => '%s', + 'date' => '%s', + 'date_epoch' => '%s', + 'bool' => '%d', + ); + + /** + * Get insert clause for appropriate switch. + * + * @param string $switch Name of the switch to use. + * + * @return string Insert clause. + */ + public static function get_insert_switch( $switch ) { + switch ( $switch ) { + case 'insert_ignore': + $insert_query = 'INSERT IGNORE'; + break; + case 'replace': + $insert_query = 'REPLACE'; + break; + case 'insert': + default: + $insert_query = 'INSERT'; + } + + return $insert_query; + } + + /** + * Helper method to escape backtick in column and table names. + * WP does not provide a method to escape table/columns names yet, but hopefully soon in @link https://core.trac.wordpress.org/ticket/52506 + * + * @param string|array $identifier Column or table name. + * + * @return array|string|string[] Escaped identifier. + */ + public static function escape_backtick( $identifier ) { + return str_replace( '`', '``', $identifier ); + } + + /** + * Return $wpdb->prepare placeholder for data type. + * + * @param string $type Data type. + * + * @return string $wpdb placeholder. + */ + public static function get_wpdb_placeholder_for_type( $type ) { + return self::$wpdb_placeholder_for_type[ $type ]; + } + +} From c44cc40d2143f306596a4af77dd7e7784a448ea3 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 24 Mar 2022 13:40:55 +0530 Subject: [PATCH 168/386] Add utility class for migrating metadata. --- .../MetaToMetaTableMigrator.php | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php new file mode 100644 index 00000000000..400674b7bd0 --- /dev/null +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php @@ -0,0 +1,121 @@ + + * array ( + * 'primary_id' => 'primary_id column name of source table', + * 'table_name' => 'name of the source table'. + * ), + * 'entity_meta_schema' => + * array ( + * 'meta_key_column' => 'name of meta_key column in source meta table', + * 'meta_value_column' => 'name of meta_value column in source meta table', + * 'table_name' => 'name of source meta table', + * ), + * 'destination_table' => 'name of destination custom table', + * 'entity_meta_relation' => + * array ( + * 'entity' => 'name of column in source table which is used in source meta table', + * 'meta' => 'name of column in source meta table which contains key of records in source table', + * ) + * ) + * ). + * @param array $exclude_columns List of columns to exclude. + */ + public function __construct( $schema_config ) { + $this->schema_config = $schema_config; + } + + public function generate_insert_sql_for_batch( $batch, $insert_switch ) { + global $wpdb; + + $insert_query = MigrationHelper::get_insert_switch( $insert_switch ); + + $meta_key_column = MigrationHelper::escape_backtick( $this->schema_config['destination']['meta']['meta_key_column'] ); + $meta_value_column = MigrationHelper::escape_backtick( $this->schema_config['destination']['meta']['meta_value_column'] ); + $entity_id_column = MigrationHelper::escape_backtick( $this->schema_config['destination']['meta']['entity_id_column'] ); + $column_sql = "(`$entity_id_column`, `$meta_key_column`, `$meta_value_column`)"; + $table = $this->schema_config['destination']['meta_table_name']; + + $entity_id_column_placeholder = MigrationHelper::get_wpdb_placeholder_for_type( $this->schema_config['destination']['meta']['entity_id_type'] ); + $placeholder_string = "( $entity_id_column_placeholder, %s, %s )"; + $values = array(); + foreach ( array_values( $batch ) as $row ) { + $query_params = array( + $row['destination_entity_id'], + $row['meta_key'], + $row['meta_value'] + ); + $value_sql = $wpdb->prepare( "( $placeholder_string )", $query_params ); + $values[] = $value_sql; + } + + $values_sql = implode( ',', $values ); + + return "$insert_query INTO $table $column_sql VALUES $values_sql"; + } + + public function fetch_data_for_migration( $where_clause ) { + global $wpdb; + + $meta_query = $this->build_meta_table_query( $where_clause ); + + $meta_data_rows = $wpdb->get_results( $meta_query ); + if ( empty ( $meta_data_rows ) ) { + return array( + 'data' => array(), + 'errors' => array(), + ); + } + + return $meta_data_rows; + } + + private function build_meta_table_query( $where_clause ) { + global $wpdb; + $source_meta_key_column = MigrationHelper::escape_backtick( $this->schema_config['source']['meta_key_column'] ); + $source_meta_value_column = MigrationHelper::escape_backtick( $this->schema_config['source']['meta_value_column'] ); + $source_entity_id_column = MigrationHelper::escape_backtick( $this->schema_config['source']['meta_entity_id_column'] ); + $source_meta_table_name = MigrationHelper::escape_backtick( $this->schema_config['source']['meta_table_name'] ); + $order_by = "`$source_meta_table_name`.`$source_entity_id_column` ASC"; + + $destination_entity_table = $this->schema_config['destination']['entity_table_name']; + $destination_entity_id_column = $this->schema_config['destination']['entity_id_column']; + $destination_source_id_mapping_column = $this->schema_config['destination']['source_id_column']; + + if ( $this->schema_config['source']['excluded_keys'] ) { + $key_placeholder = implode( ',', array_fill( 0, count( $this->schema_config['source']['excluded_keys'] ), '%s' ) ); + $exclude_clause = $wpdb->prepare( "$source_meta_key_column NOT IN ( $key_placeholder )", $this->schema_config['source']['excluded_keys'] ); + $where_clause = "$where_clause AND $exclude_clause"; + } + + $meta_data_sql = " +SELECT + `$source_meta_table_name`.`$source_entity_id_column` as source_entity_id, + `$destination_entity_table`.`$destination_entity_id_column` as destination_entity_id, + `$source_meta_table_name`.`$source_meta_key_column` as meta_key, + `$source_meta_table_name`.`$source_meta_value_column` as meta_value +FROM `$source_meta_table_name` +JOIN `$destination_entity_table` ON `$destination_entity_table`.`$destination_source_id_mapping_column` = `$source_meta_table_name`.`$source_entity_id_column` +WHERE $where_clause ORDER BY $order_by +"; + + return $meta_data_sql; + } + +} From ee834e2cf1a9da04affcfe245b881b3e20bb3665 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 24 Mar 2022 13:43:47 +0530 Subject: [PATCH 169/386] Applied coding standards. --- .../MetaToMetaTableMigrator.php | 87 ++++++++++++------- 1 file changed, 57 insertions(+), 30 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php index 400674b7bd0..2335997d607 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php @@ -5,42 +5,43 @@ namespace Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable; -use MigrationHelper; +use Automattic\WooCommerce\DataBase\Migrations\MigrationHelper; +/** + * Class MetaToMetaTableMigrator. + * + * Generic class for powering migrations from one meta table to another table. + * + * @package Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable + */ class MetaToMetaTableMigrator { + /** + * Schema config, see __construct for more details. + * + * @var array + */ private $schema_config; /** * MetaToMetaTableMigrator constructor. * * @param array $schema_config This parameters provides general but essential information about tables under migrations. Must be of the form- - * array( - * 'entity_schema' => - * array ( - * 'primary_id' => 'primary_id column name of source table', - * 'table_name' => 'name of the source table'. - * ), - * 'entity_meta_schema' => - * array ( - * 'meta_key_column' => 'name of meta_key column in source meta table', - * 'meta_value_column' => 'name of meta_value column in source meta table', - * 'table_name' => 'name of source meta table', - * ), - * 'destination_table' => 'name of destination custom table', - * 'entity_meta_relation' => - * array ( - * 'entity' => 'name of column in source table which is used in source meta table', - * 'meta' => 'name of column in source meta table which contains key of records in source table', - * ) - * ) - * ). - * @param array $exclude_columns List of columns to exclude. + * TODO: Add structure. */ public function __construct( $schema_config ) { + // TODO: Validate params. $this->schema_config = $schema_config; } + /** + * Generate insert sql queries for batches. + * + * @param array $batch Data to generate queries for. + * @param string $insert_switch Insert switch to use. + * + * @return string + */ public function generate_insert_sql_for_batch( $batch, $insert_switch ) { global $wpdb; @@ -59,10 +60,11 @@ class MetaToMetaTableMigrator { $query_params = array( $row['destination_entity_id'], $row['meta_key'], - $row['meta_value'] + $row['meta_value'], ); - $value_sql = $wpdb->prepare( "( $placeholder_string )", $query_params ); - $values[] = $value_sql; + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $placeholder_string is hardcoded. + $value_sql = $wpdb->prepare( "( $placeholder_string )", $query_params ); + $values[] = $value_sql; } $values_sql = implode( ',', $values ); @@ -70,13 +72,30 @@ class MetaToMetaTableMigrator { return "$insert_query INTO $table $column_sql VALUES $values_sql"; } + /** + * Fetch data for migration. + * + * @param string $where_clause Where conditions to use while selecting data from source table. + * + * @return array[] Data along with errors (if any), will of the form: + * array( + * 'data' => array( + * 'id_1' => array( 'column1' => value1, 'column2' => value2, ...), + * ..., + * ), + * 'errors' => array( + * 'id_1' => array( 'column1' => error1, 'column2' => value2, ...), + * ..., + * ) + */ public function fetch_data_for_migration( $where_clause ) { global $wpdb; $meta_query = $this->build_meta_table_query( $where_clause ); + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Meta query has interpolated variables, but they should all be escaped for backticks. $meta_data_rows = $wpdb->get_results( $meta_query ); - if ( empty ( $meta_data_rows ) ) { + if ( empty( $meta_data_rows ) ) { return array( 'data' => array(), 'errors' => array(), @@ -86,6 +105,13 @@ class MetaToMetaTableMigrator { return $meta_data_rows; } + /** + * Helper method to build query used to fetch data from source meta table. + * + * @param string $where_clause Where conditions to use while selecting data from source table. + * + * @return string Query that can be used to fetch data. + */ private function build_meta_table_query( $where_clause ) { global $wpdb; $source_meta_key_column = MigrationHelper::escape_backtick( $this->schema_config['source']['meta_key_column'] ); @@ -94,13 +120,14 @@ class MetaToMetaTableMigrator { $source_meta_table_name = MigrationHelper::escape_backtick( $this->schema_config['source']['meta_table_name'] ); $order_by = "`$source_meta_table_name`.`$source_entity_id_column` ASC"; - $destination_entity_table = $this->schema_config['destination']['entity_table_name']; - $destination_entity_id_column = $this->schema_config['destination']['entity_id_column']; - $destination_source_id_mapping_column = $this->schema_config['destination']['source_id_column']; + $destination_entity_table = MigrationHelper::escape_backtick( $this->schema_config['destination']['entity_table_name'] ); + $destination_entity_id_column = MigrationHelper::escape_backtick( $this->schema_config['destination']['entity_id_column'] ); + $destination_source_id_mapping_column = MigrationHelper::escape_backtick( $this->schema_config['destination']['source_id_column'] ); if ( $this->schema_config['source']['excluded_keys'] ) { $key_placeholder = implode( ',', array_fill( 0, count( $this->schema_config['source']['excluded_keys'] ), '%s' ) ); - $exclude_clause = $wpdb->prepare( "$source_meta_key_column NOT IN ( $key_placeholder )", $this->schema_config['source']['excluded_keys'] ); + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $source_meta_key_column is escated for backticks, $key_placeholder is hardcoded. + $exclude_clause = $wpdb->prepare( "`$source_meta_key_column` NOT IN ( $key_placeholder )", $this->schema_config['source']['excluded_keys'] ); $where_clause = "$where_clause AND $exclude_clause"; } From aa00d7af684a160917245bf96ba4102e26ba6700 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 24 Mar 2022 13:45:31 +0530 Subject: [PATCH 170/386] Add defination of order meta data table. Made table name methods static so that they can be reused. --- .../Orders/OrdersTableDataStore.php | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php index 8647ab413ca..cb0218d1f1c 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php @@ -17,7 +17,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements * * @return string The custom orders table name. */ - public function get_orders_table_name() { + public static function get_orders_table_name() { global $wpdb; return $wpdb->prefix . 'wc_orders'; } @@ -27,7 +27,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements * * @return string The order addresses table name. */ - public function get_addresses_table_name() { + public static function get_addresses_table_name() { global $wpdb; return $wpdb->prefix . 'wc_order_addresses'; } @@ -37,11 +37,21 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements * * @return string The orders operational data table name. */ - public function get_operational_data_table_name() { + public static function get_operational_data_table_name() { global $wpdb; return $wpdb->prefix . 'wc_order_operational_data'; } + /** + * Get the orders meta data table name. + * + * @return string Name of order meta data table. + */ + public static function get_meta_table_name() { + global $wpdb; + return $wpdb->prefix . 'wc_ordes_meta'; + } + /** * Get the names of all the tables involved in the custom orders table feature. * @@ -185,6 +195,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements $orders_table_name = $this->get_orders_table_name(); $addresses_table_name = $this->get_addresses_table_name(); $operational_data_table_name = $this->get_operational_data_table_name(); + $meta_table = $this->get_meta_table_name(); $sql = " CREATE TABLE $orders_table_name ( @@ -247,7 +258,16 @@ CREATE TABLE $operational_data_table_name ( discount_total_amount decimal(26, 8) NULL, KEY order_id (order_id), KEY order_key (order_key) -);"; +); +CREATE TABLE $meta_table ( + id bigint(20) unsigned auto_increment primary key, + order_id bigint(20) unsigned null, + meta_key varchar(255), + meta_value text null, + KEY meta_key_value (meta_key, meta_value(100)) +); +"; + return $sql; } } From 6b70afc7314ce58f208cbf9bb7ae60ede6079bdf Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 24 Mar 2022 13:46:38 +0530 Subject: [PATCH 171/386] Refactor to use migration helper for better re-usability. --- .../MetaToCustomTableMigrator.php | 63 +++++-------------- 1 file changed, 14 insertions(+), 49 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php index 81d4bdcf233..dbe66cc10bf 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php @@ -5,6 +5,8 @@ namespace Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable; +use MigrationHelper; + /** * Class MetaToCustomTableMigrator. * @@ -33,20 +35,6 @@ class MetaToCustomTableMigrator { */ private $core_column_mapping; - /** - * Placeholders that we will use in building $wpdb queries. - * - * @var string[] - */ - private $wpdb_placeholder_for_type = array( - 'int' => '%d', - 'decimal' => '%f', - 'string' => '%s', - 'date' => '%s', - 'date_epoch' => '%s', - 'bool' => '%d', - ); - /** * MetaToCustomTableMigrator constructor. * @@ -120,24 +108,13 @@ class MetaToCustomTableMigrator { // TODO: Add code to validate params. $table = $this->schema_config['destination_table']; - - switch ( $insert_switch ) { - case 'insert_ignore': - $insert_query = 'INSERT IGNORE'; - break; - case 'replace': - $insert_query = 'REPLACE'; - break; - case 'insert': - default: - $insert_query = 'INSERT'; - } + $insert_query = MigrationHelper::get_insert_switch( $insert_switch ); $columns = array(); $placeholders = array(); foreach ( array_merge( $this->core_column_mapping, $this->meta_column_mapping ) as $prev_column => $schema ) { $columns[] = $schema['destination']; - $placeholders[] = $this->wpdb_placeholder_for_type[ $schema['type'] ]; + $placeholders[] = MigrationHelper::get_wpdb_placeholder_for_type( $schema['type'] ); } $placeholders = "'" . implode( "', '", $placeholders ) . "'"; @@ -147,14 +124,14 @@ class MetaToCustomTableMigrator { foreach ( $columns as $column ) { $query_params[] = $row[ $column ] ?? null; } - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $placeholders can only contain combination of placeholders described in $this->>wpdb_placeholder_for_type. + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $placeholders can only contain combination of placeholders described in MigrationHelper::get_wpdb_placeholder_for_type $value_string = '(' . $wpdb->prepare( $placeholders, $query_params ) . ')'; $values[] = $value_string; } $value_sql = implode( ',', $values ); - $column_sql = implode( '`, `', $this->escape_backtick( $columns ) ); + $column_sql = implode( '`, `', MigrationHelper::escape_backtick( $columns ) ); return "$insert_query INTO $table (`$column_sql`) VALUES $value_sql;"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, -- $insert_query is hardcoded, $value_sql is already escaped. } @@ -210,16 +187,16 @@ class MetaToCustomTableMigrator { */ private function build_entity_table_query( $where_clause, $batch_size, $order_by ) { global $wpdb; - $entity_table = $this->escape_backtick( $this->schema_config['entity_schema']['table_name'] ); - $primary_id_column = $this->escape_backtick( $this->schema_config['entity_schema']['primary_id'] ); - $entity_rel_column = $this->escape_backtick( $this->schema_config['entity_meta_relation']['entity_rel_column'] ); + $entity_table = MigrationHelper::escape_backtick( $this->schema_config['entity_schema']['table_name'] ); + $primary_id_column = MigrationHelper::escape_backtick( $this->schema_config['entity_schema']['primary_id'] ); + $entity_rel_column = MigrationHelper::escape_backtick( $this->schema_config['entity_meta_relation']['entity_rel_column'] ); $entity_keys = array(); foreach ( $this->core_column_mapping as $column_name => $column_schema ) { if ( isset( $column_schema['select_clause'] ) ) { $select_clause = $column_schema['select_clause']; $entity_keys[] = "$select_clause AS $column_name"; } else { - $entity_keys[] = '`' . $this->escape_backtick( $column_name ) . '`'; + $entity_keys[] = '`' . MigrationHelper::escape_backtick( $column_name ) . '`'; } } $entity_column_string = implode( ', ', $entity_keys ); @@ -238,18 +215,6 @@ SELECT `$primary_id_column` as primary_key_id, `$entity_rel_column` AS entity_re return $query; } - /** - * Helper method to escape backtick in column and table names. - * WP does not provide a method to escape table/columns names yet, but hopefully soon in @link https://core.trac.wordpress.org/ticket/52506 - * - * @param string|array $identifier Column or table name. - * - * @return array|string|string[] Escaped identifier. - */ - private function escape_backtick( $identifier ) { - return str_replace( '`', '``', $identifier ); - } - /** * Helper method to build query that will be used to fetch data from source meta table. * @@ -259,11 +224,11 @@ SELECT `$primary_id_column` as primary_key_id, `$entity_rel_column` AS entity_re */ private function build_meta_data_query( $entity_ids ) { global $wpdb; - $meta_table = $this->escape_backtick( $this->schema_config['entity_meta_schema']['table_name'] ); + $meta_table = MigrationHelper::escape_backtick( $this->schema_config['entity_meta_schema']['table_name'] ); $meta_keys = array_keys( $this->meta_column_mapping ); - $meta_key_column = $this->escape_backtick( $this->schema_config['entity_meta_schema']['meta_key_column'] ); - $meta_value_column = $this->escape_backtick( $this->schema_config['entity_meta_schema']['meta_value_column'] ); - $meta_table_relational_key = $this->escape_backtick( $this->schema_config['entity_meta_relation']['meta_rel_column'] ); + $meta_key_column = MigrationHelper::escape_backtick( $this->schema_config['entity_meta_schema']['meta_key_column'] ); + $meta_value_column = MigrationHelper::escape_backtick( $this->schema_config['entity_meta_schema']['meta_value_column'] ); + $meta_table_relational_key = MigrationHelper::escape_backtick( $this->schema_config['entity_meta_relation']['meta_rel_column'] ); $meta_column_string = implode( ', ', array_fill( 0, count( $meta_keys ), '%s' ) ); $entity_id_string = implode( ', ', array_fill( 0, count( $entity_ids ), '%d' ) ); From 7bb25779aaae28efc7107aa5488a9efe2c2e6463 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 24 Mar 2022 13:47:15 +0530 Subject: [PATCH 172/386] Add config for migration meta data table as well. --- .../CustomOrderTable/WPPostToCOTMigrator.php | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php index 0a1e9fbf117..fc2e30b809b 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php @@ -49,6 +49,8 @@ class WPPostToCOTMigrator { */ private $operation_data_table_migrator; + private $meta_table_migrator; + /** * Names of different order tables. * @@ -67,18 +69,22 @@ class WPPostToCOTMigrator { 'orders' => $wpdb->prefix . 'wc_orders', 'addresses' => $wpdb->prefix . 'wc_order_addresses', 'op_data' => $wpdb->prefix . 'wc_order_operational_data', + 'meta' => $wpdb->prefix . 'wc_orders_meta', ); $order_config = $this->get_config_for_order_table(); $billing_address_config = $this->get_config_for_address_table_billing(); $shipping_address_config = $this->get_config_for_address_table_shipping(); $operation_data_config = $this->get_config_for_operational_data_table(); + $meta_data_config = $this->get_config_for_meta_table(); $this->order_table_migrator = new MetaToCustomTableMigrator( $order_config['schema'], $order_config['meta'], $order_config['core'] ); $this->billing_address_table_migrator = new MetaToCustomTableMigrator( $billing_address_config['schema'], $billing_address_config['meta'], $billing_address_config['core'] ); $this->shipping_address_table_migrator = new MetaToCustomTableMigrator( $shipping_address_config['schema'], $shipping_address_config['meta'], $shipping_address_config['core'] ); $this->operation_data_table_migrator = new MetaToCustomTableMigrator( $operation_data_config['schema'], $operation_data_config['meta'], $operation_data_config['core'] ); + $this->meta_table_migrator = new MetaToMetaTableMigrator( $meta_data_config ); + $this->error_logger = new MigrationErrorLogger(); } @@ -390,6 +396,41 @@ class WPPostToCOTMigrator { ); } + private function get_config_for_meta_table() { + global $wpdb; + + $excluded_columns = array_keys( $this->get_config_for_order_table()['meta'] ); + $excluded_columns = array_merge( $excluded_columns, array_keys( $this->get_config_for_operational_data_table()['meta'] ) ); + $excluded_columns = array_merge( $excluded_columns, array_keys( $this->get_config_for_address_table_billing()['meta'] ) ); + $excluded_columns = array_merge( $excluded_columns, array_keys( $this->get_config_for_address_table_shipping()['meta'] ) ); + + return array( + 'source' => array( + 'meta' => array( + 'table_name' => $wpdb->postmeta, + 'entity_id_column' => 'post_id', + 'meta_key_column' => 'meta_key', + 'meta_value_column' => 'meta_value', + ), + 'excluded_keys' => $excluded_columns, + ), + 'destination' => array( + 'meta' => array( + 'table_name' => $this->table_names['meta'], + 'entity_id_column' => 'order_id', + 'meta_key_column' => 'meta_key', + 'meta_value_column' => 'meta_value', + 'entity_id_type' => 'int', + ), + 'entity' => array( + 'table_name' => $this->table_names['orders'], + 'source_id_column' => 'post_id', + 'id_column' => 'id', + ), + ) + ); + } + /** * Process next migration batch, uses option `wc_cot_migration` to checkpoints of what have been processed so far. * @@ -423,6 +464,7 @@ class WPPostToCOTMigrator { $this->process_next_migrator_batch( $this->billing_address_table_migrator, $order_post_ids, $order_by ); $this->process_next_migrator_batch( $this->shipping_address_table_migrator, $order_post_ids, $order_by ); $this->process_next_migrator_batch( $this->operation_data_table_migrator, $order_post_ids, $order_by ); + $this->process_meta_migration( $order_post_ids ); $last_post_migrated = max( array_keys( $order_data['data'] ) ); $this->update_checkpoint( $last_post_migrated ); @@ -434,8 +476,8 @@ class WPPostToCOTMigrator { * Process next batch for a given address type. * * @param MetaToCustomTableMigrator $migrator Migrator instance for address type. - * @param array $order_post_ids Array of post IDs for orders. - * @param string $order_by Order by clause. + * @param array $order_post_ids Array of post IDs for orders. + * @param string $order_by Order by clause. */ private function process_next_migrator_batch( $migrator, $order_post_ids, $order_by ) { global $wpdb; @@ -455,6 +497,18 @@ class WPPostToCOTMigrator { } } + private function process_meta_migration( $order_post_ids ) { + global $wpdb; + $post_ids_where_clause = $this->get_where_id_clause( $order_post_ids, 'post_id' ); + $data_to_migrate = $this->meta_table_migrator->fetch_data_for_migration( $post_ids_where_clause ); + $insert_queries = $this->meta_table_migrator->generate_insert_sql_for_batch( $data_to_migrate['data'], 'insert' ); + $result = $wpdb->query( $insert_queries ); + if ( count( $data_to_migrate['data'] ) !== $result ) { + // TODO: Find and log entity ids that were not inserted. + echo 'error'; + } + } + /** * Method to migrate single record. * @@ -503,7 +557,7 @@ class WPPostToCOTMigrator { /** * Helper method to create `ID in (.., .., ...)` clauses. * - * @param array $ids List of IDs. + * @param array $ids List of IDs. * @param string $column_name Name of the ID column. * * @return string Prepared clause for where. From 5d6eaff099d8d4f30b7bad98f26d70a8378c538f Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 24 Mar 2022 13:56:46 +0530 Subject: [PATCH 173/386] Applied coding standards. --- .../CustomOrderTable/WPPostToCOTMigrator.php | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php index fc2e30b809b..4a9cf58b5c6 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php @@ -49,6 +49,11 @@ class WPPostToCOTMigrator { */ private $operation_data_table_migrator; + /** + * Migrator instance to migrate meta data. + * + * @var MetaToMetaTableMigrator + */ private $meta_table_migrator; /** @@ -396,6 +401,11 @@ class WPPostToCOTMigrator { ); } + /** + * Generate config for meta data migration. + * + * @return array Meta data migration config. + */ private function get_config_for_meta_table() { global $wpdb; @@ -427,7 +437,7 @@ class WPPostToCOTMigrator { 'source_id_column' => 'post_id', 'id_column' => 'id', ), - ) + ), ); } @@ -476,8 +486,8 @@ class WPPostToCOTMigrator { * Process next batch for a given address type. * * @param MetaToCustomTableMigrator $migrator Migrator instance for address type. - * @param array $order_post_ids Array of post IDs for orders. - * @param string $order_by Order by clause. + * @param array $order_post_ids Array of post IDs for orders. + * @param string $order_by Order by clause. */ private function process_next_migrator_batch( $migrator, $order_post_ids, $order_by ) { global $wpdb; @@ -497,13 +507,19 @@ class WPPostToCOTMigrator { } } + /** + * Process migration for metadata for given post ids.\ + * + * @param array $order_post_ids Post IDs. + */ private function process_meta_migration( $order_post_ids ) { global $wpdb; $post_ids_where_clause = $this->get_where_id_clause( $order_post_ids, 'post_id' ); $data_to_migrate = $this->meta_table_migrator->fetch_data_for_migration( $post_ids_where_clause ); $insert_queries = $this->meta_table_migrator->generate_insert_sql_for_batch( $data_to_migrate['data'], 'insert' ); + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $insert_queries should already be escaped in the generating function. $result = $wpdb->query( $insert_queries ); - if ( count( $data_to_migrate['data'] ) !== $result ) { + if ( count( $data_to_migrate['data'] ) !== $result ) { // TODO: Find and log entity ids that were not inserted. echo 'error'; } @@ -557,7 +573,7 @@ class WPPostToCOTMigrator { /** * Helper method to create `ID in (.., .., ...)` clauses. * - * @param array $ids List of IDs. + * @param array $ids List of IDs. * @param string $column_name Name of the ID column. * * @return string Prepared clause for where. From 48356c213f3e7d5bca2248345a60588a70582690 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 24 Mar 2022 15:38:16 +0530 Subject: [PATCH 174/386] Refactor config format to be more clear. --- .../MetaToCustomTableMigrator.php | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php index dbe66cc10bf..0c7126edc62 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php @@ -5,7 +5,7 @@ namespace Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable; -use MigrationHelper; +use Automattic\WooCommerce\DataBase\Migrations\MigrationHelper; /** * Class MetaToCustomTableMigrator. @@ -94,7 +94,7 @@ class MetaToCustomTableMigrator { /** * Generate SQL for data insertion. * - * @param array $batch Data to generate queries for. Will be 'data' array returned by `$this->fetch_data_for_migration()` method. + * @param array $batch Data to generate queries for. Will be 'data' array returned by `$this->fetch_data_for_migration()` method. * @param string $insert_switch Insert command to use in generating queries, could be insert, insert_ignore, or replace. * * @return string Generated queries for insertion for this batch, would be of the form: @@ -107,7 +107,7 @@ class MetaToCustomTableMigrator { global $wpdb; // TODO: Add code to validate params. - $table = $this->schema_config['destination_table']; + $table = $this->schema_config['destination']['table_name']; $insert_query = MigrationHelper::get_insert_switch( $insert_switch ); $columns = array(); @@ -187,10 +187,14 @@ class MetaToCustomTableMigrator { */ private function build_entity_table_query( $where_clause, $batch_size, $order_by ) { global $wpdb; - $entity_table = MigrationHelper::escape_backtick( $this->schema_config['entity_schema']['table_name'] ); - $primary_id_column = MigrationHelper::escape_backtick( $this->schema_config['entity_schema']['primary_id'] ); - $entity_rel_column = MigrationHelper::escape_backtick( $this->schema_config['entity_meta_relation']['entity_rel_column'] ); - $entity_keys = array(); + $source_entity_table = MigrationHelper::escape_backtick( $this->schema_config['source']['entity']['table_name'] ); + $source_meta_rel_id_column = MigrationHelper::escape_backtick( $this->schema_config['source']['entity']['meta_rel_column'] ); + $source_destination_rel_id_column = MigrationHelper::escape_backtick( $this->schema_config['source']['entity']['destination_rel_column'] ); + + $destination_table = MigrationHelper::escape_backtick( $this->schema_config['destination']['table_name'] ); + $destination_source_rel_id_column = MigrationHelper::escape_backtick( $this->schema_config['destination']['source_rel_column'] ); + + $entity_keys = array(); foreach ( $this->core_column_mapping as $column_name => $column_schema ) { if ( isset( $column_schema['select_clause'] ) ) { $select_clause = $column_schema['select_clause']; @@ -200,10 +204,20 @@ class MetaToCustomTableMigrator { } } $entity_column_string = implode( ', ', $entity_keys ); - // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $entity_table, $primary_id_column and $entity_column_string is escaped for backticks. $where clause and $order_by should already be escaped. + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $source_meta_rel_id_column, $source_destination_rel_id_column etc is escaped for backticks. $where clause and $order_by should already be escaped. $query = $wpdb->prepare( " -SELECT `$primary_id_column` as primary_key_id, `$entity_rel_column` AS entity_rel_column, $entity_column_string FROM $entity_table WHERE $where_clause ORDER BY $order_by LIMIT %d; +SELECT + `$source_entity_table`.`$source_meta_rel_id_column` as entity_meta_rel_id, + `$source_entity_table`.`$source_destination_rel_id_column` AS destination_rel_id, + CASE + WHEN EXISTS( `$destination_table`.`$destination_source_rel_id_column` ) THEN 'replace' + ELSE 'insert' + END as insert_switch, + $entity_column_string +FROM `$source_entity_table` +LEFT JOIN `$destination_table` ON `$destination_table`.`$destination_source_rel_id_column` = `$source_entity_table`.`$source_destination_rel_id_column` +WHERE $where_clause ORDER BY $order_by LIMIT %d; ", array( $batch_size, @@ -224,11 +238,11 @@ SELECT `$primary_id_column` as primary_key_id, `$entity_rel_column` AS entity_re */ private function build_meta_data_query( $entity_ids ) { global $wpdb; - $meta_table = MigrationHelper::escape_backtick( $this->schema_config['entity_meta_schema']['table_name'] ); + $meta_table = MigrationHelper::escape_backtick( $this->schema_config['source']['meta']['table_name'] ); $meta_keys = array_keys( $this->meta_column_mapping ); - $meta_key_column = MigrationHelper::escape_backtick( $this->schema_config['entity_meta_schema']['meta_key_column'] ); - $meta_value_column = MigrationHelper::escape_backtick( $this->schema_config['entity_meta_schema']['meta_value_column'] ); - $meta_table_relational_key = MigrationHelper::escape_backtick( $this->schema_config['entity_meta_relation']['meta_rel_column'] ); + $meta_key_column = MigrationHelper::escape_backtick( $this->schema_config['source']['meta']['meta_key_column'] ); + $meta_value_column = MigrationHelper::escape_backtick( $this->schema_config['source']['meta']['meta_value_column'] ); + $meta_table_relational_key = MigrationHelper::escape_backtick( $this->schema_config['source']['entity']['meta_rel_column'] ); $meta_column_string = implode( ', ', array_fill( 0, count( $meta_keys ), '%s' ) ); $entity_id_string = implode( ', ', array_fill( 0, count( $entity_ids ), '%d' ) ); @@ -324,7 +338,7 @@ WHERE /** * Validate and transform data so that we catch as many errors as possible before inserting. * - * @param mixed $value Actual data value. + * @param mixed $value Actual data value. * @param string $type Type of data, could be decimal, int, date, string. * * @return float|int|mixed|string|\WP_Error From 78360660086177fa27e866d41a371c9bc2145970 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 24 Mar 2022 15:39:07 +0530 Subject: [PATCH 175/386] Refactor config format to be more clear. --- .../Migrations/CustomOrderTable/MetaToCustomTableMigrator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php index 0c7126edc62..5f8b1438740 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php @@ -94,7 +94,7 @@ class MetaToCustomTableMigrator { /** * Generate SQL for data insertion. * - * @param array $batch Data to generate queries for. Will be 'data' array returned by `$this->fetch_data_for_migration()` method. + * @param array $batch Data to generate queries for. Will be 'data' array returned by `$this->fetch_data_for_migration()` method. * @param string $insert_switch Insert command to use in generating queries, could be insert, insert_ignore, or replace. * * @return string Generated queries for insertion for this batch, would be of the form: @@ -338,7 +338,7 @@ WHERE /** * Validate and transform data so that we catch as many errors as possible before inserting. * - * @param mixed $value Actual data value. + * @param mixed $value Actual data value. * @param string $type Type of data, could be decimal, int, date, string. * * @return float|int|mixed|string|\WP_Error From 8724429382d0331293423208c2512710d3547b0c Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 24 Mar 2022 15:39:36 +0530 Subject: [PATCH 176/386] Refactor to use new config format. --- .../CustomOrderTable/WPPostToCOTMigrator.php | 84 +++++++++++-------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php index 4a9cf58b5c6..aa92014bf6a 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php @@ -101,21 +101,25 @@ class WPPostToCOTMigrator { private function get_config_for_order_table() { global $wpdb; $order_table_schema_config = array( - 'entity_schema' => array( - 'primary_id' => 'ID', - 'table_name' => $wpdb->posts, + 'source' => array( + 'entity' => array( + 'table_name' => $wpdb->posts, + 'meta_rel_column' => 'ID', + 'destination_rel_column' => 'ID', + ), + 'meta' => array( + 'table_name' => $wpdb->postmeta, + 'meta_key_column' => 'meta_key', + 'meta_value_column' => 'meta_value', + 'entity_id_column' => 'post_id', + ), ), - 'entity_meta_schema' => array( - 'meta_key_column' => 'meta_key', - 'meta_value_column' => 'meta_value', - 'table_name' => $wpdb->postmeta, + 'destination' => array( + array( + 'table_name' => $this->table_names['orders'], + 'source_rel_column' => 'post_id', + ), ), - 'destination_table' => $wpdb->prefix . 'wc_orders', - 'entity_meta_relation' => array( - 'entity_rel_column' => 'ID', - 'meta_rel_column' => 'post_id', - ), - ); $order_table_core_config = array( @@ -217,19 +221,22 @@ class WPPostToCOTMigrator { // We join order core table and post meta table to get data for address, since we need order ID. // So order core record needs to be already present. $schema_config = array( - 'entity_schema' => array( - 'primary_id' => 'post_id', - 'table_name' => $this->table_names['orders'], + 'source' => array( + 'entity' => array( + 'table_name' => $this->table_names['orders'], + 'meta_rel_column' => 'post_id', + 'destination_rel_column' => 'id', + ), + 'meta' => array( + 'table_name' => $wpdb->postmeta, + 'meta_key_column' => 'meta_key', + 'meta_value_column' => 'meta_value', + 'entity_id_column' => 'post_id', + ), ), - 'entity_meta_schema' => array( - 'meta_key_column' => 'meta_key', - 'meta_value_column' => 'meta_value', - 'table_name' => $wpdb->postmeta, - ), - 'destination_table' => $this->table_names['addresses'], - 'entity_meta_relation' => array( - 'entity_rel_column' => 'post_id', - 'meta_rel_column' => 'post_id', + 'destination' => array( + 'table_name' => $this->table_names['addresses'], + 'source_rel_column' => 'order_id', ), ); @@ -308,19 +315,22 @@ class WPPostToCOTMigrator { global $wpdb; $schema_config = array( - 'entity_schema' => array( - 'primary_id' => 'post_id', - 'table_name' => $this->table_names['orders'], + 'source' => array( + 'entity' => array( + 'table_name' => $this->table_names['orders'], + 'meta_rel_column' => 'post_id', + 'destination_rel_column' => 'id', + ), + 'meta' => array( + 'table_name' => $wpdb->postmeta, + 'meta_key_column' => 'meta_key', + 'meta_value_column' => 'meta_value', + 'entity_id_column' => 'post_id', + ), ), - 'entity_meta_schema' => array( - 'meta_key_column' => 'meta_key', - 'meta_value_column' => 'meta_value', - 'table_name' => $wpdb->postmeta, - ), - 'destination_table' => $this->table_names['op_data'], - 'entity_meta_relation' => array( - 'entity_rel_column' => 'post_id', - 'meta_rel_column' => 'post_id', + 'destination' => array( + 'table_name' => $this->table_names['op_data'], + 'source_rel_column' => 'order_id', ), ); From ee76fbc17cf387928a63f2bfc510bfb6d4572fb3 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Fri, 25 Mar 2022 16:32:33 +0530 Subject: [PATCH 177/386] Modify to migrate by IDs to reduce responsibilities of generate migrator classes. --- .../MetaToCustomTableMigrator.php | 45 ++++---- .../MetaToMetaTableMigrator.php | 49 ++++---- .../CustomOrderTable/WPPostToCOTMigrator.php | 105 +++++++++--------- .../Database/Migrations/MigrationHelper.php | 8 ++ 4 files changed, 112 insertions(+), 95 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php index 5f8b1438740..d7b397c7727 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php @@ -94,7 +94,7 @@ class MetaToCustomTableMigrator { /** * Generate SQL for data insertion. * - * @param array $batch Data to generate queries for. Will be 'data' array returned by `$this->fetch_data_for_migration()` method. + * @param array $batch Data to generate queries for. Will be 'data' array returned by `$this->fetch_data_for_migration()` method. * @param string $insert_switch Insert command to use in generating queries, could be insert, insert_ignore, or replace. * * @return string Generated queries for insertion for this batch, would be of the form: @@ -154,11 +154,11 @@ class MetaToCustomTableMigrator { * ..., * ) */ - public function fetch_data_for_migration( $where_clause, $batch_size, $order_by ) { + public function fetch_data_for_migration_for_ids( $entity_ids ) { global $wpdb; // TODO: Add code to validate params. - $entity_table_query = $this->build_entity_table_query( $where_clause, $batch_size, $order_by ); + $entity_table_query = $this->build_entity_table_query( $entity_ids ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Output of $this->build_entity_table_query is already prepared. $entity_data = $wpdb->get_results( $entity_table_query ); if ( empty( $entity_data ) ) { @@ -167,15 +167,16 @@ class MetaToCustomTableMigrator { 'errors' => array(), ); } - $entity_ids = array_column( $entity_data, 'entity_rel_column' ); + $entity_meta_rel_ids = array_column( $entity_data, 'entity_meta_rel_id' ); - $meta_table_query = $this->build_meta_data_query( $entity_ids ); + $meta_table_query = $this->build_meta_data_query( $entity_meta_rel_ids ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Output of $this->build_meta_data_query is already prepared. $meta_data = $wpdb->get_results( $meta_table_query ); return $this->process_and_sanitize_data( $entity_data, $meta_data ); } + /** * Helper method to build query used to fetch data from core source table. * @@ -185,22 +186,24 @@ class MetaToCustomTableMigrator { * * @return string Query that can be used to fetch data. */ - private function build_entity_table_query( $where_clause, $batch_size, $order_by ) { + private function build_entity_table_query( $entity_ids ) { global $wpdb; - $source_entity_table = MigrationHelper::escape_backtick( $this->schema_config['source']['entity']['table_name'] ); - $source_meta_rel_id_column = MigrationHelper::escape_backtick( $this->schema_config['source']['entity']['meta_rel_column'] ); - $source_destination_rel_id_column = MigrationHelper::escape_backtick( $this->schema_config['source']['entity']['destination_rel_column'] ); + $source_entity_table = MigrationHelper::escape_backtick( $this->schema_config['source']['entity']['table_name'] ); + $source_meta_rel_id_column = MigrationHelper::add_table_name_to_column( $source_entity_table, $this->schema_config['source']['entity']['meta_rel_column'] ); + $source_destination_rel_id_column = MigrationHelper::add_table_name_to_column( $source_entity_table, $this->schema_config['source']['entity']['destination_rel_column'] ); + $source_primary_key_column = MigrationHelper::add_table_name_to_column( $source_entity_table, $this->schema_config['source']['entity']['primary_key'] ); $destination_table = MigrationHelper::escape_backtick( $this->schema_config['destination']['table_name'] ); - $destination_source_rel_id_column = MigrationHelper::escape_backtick( $this->schema_config['destination']['source_rel_column'] ); + $destination_source_rel_id_column = MigrationHelper::add_table_name_to_column( $destination_table, $this->schema_config['destination']['source_rel_column'] ); + $where_clause = "$source_primary_key_column IN (" . implode( ',', array_fill( 0, count( $entity_ids ), '%d' ) ) . ')'; $entity_keys = array(); foreach ( $this->core_column_mapping as $column_name => $column_schema ) { if ( isset( $column_schema['select_clause'] ) ) { $select_clause = $column_schema['select_clause']; $entity_keys[] = "$select_clause AS $column_name"; } else { - $entity_keys[] = '`' . MigrationHelper::escape_backtick( $column_name ) . '`'; + $entity_keys[] = MigrationHelper::add_table_name_to_column( $source_entity_table, $column_name ); } } $entity_column_string = implode( ', ', $entity_keys ); @@ -208,20 +211,18 @@ class MetaToCustomTableMigrator { $query = $wpdb->prepare( " SELECT - `$source_entity_table`.`$source_meta_rel_id_column` as entity_meta_rel_id, - `$source_entity_table`.`$source_destination_rel_id_column` AS destination_rel_id, + $source_meta_rel_id_column as entity_meta_rel_id, + $source_destination_rel_id_column AS destination_rel_id, CASE - WHEN EXISTS( `$destination_table`.`$destination_source_rel_id_column` ) THEN 'replace' + WHEN $destination_source_rel_id_column IS NOT NULL THEN 'replace' ELSE 'insert' END as insert_switch, $entity_column_string FROM `$source_entity_table` -LEFT JOIN `$destination_table` ON `$destination_table`.`$destination_source_rel_id_column` = `$source_entity_table`.`$source_destination_rel_id_column` -WHERE $where_clause ORDER BY $order_by LIMIT %d; +LEFT JOIN `$destination_table` ON $destination_source_rel_id_column = $source_destination_rel_id_column +WHERE $where_clause; ", - array( - $batch_size, - ) + $entity_ids ); // phpcs:enable @@ -242,7 +243,7 @@ WHERE $where_clause ORDER BY $order_by LIMIT %d; $meta_keys = array_keys( $this->meta_column_mapping ); $meta_key_column = MigrationHelper::escape_backtick( $this->schema_config['source']['meta']['meta_key_column'] ); $meta_value_column = MigrationHelper::escape_backtick( $this->schema_config['source']['meta']['meta_value_column'] ); - $meta_table_relational_key = MigrationHelper::escape_backtick( $this->schema_config['source']['entity']['meta_rel_column'] ); + $meta_table_relational_key = MigrationHelper::escape_backtick( $this->schema_config['source']['meta']['entity_id_column'] ); $meta_column_string = implode( ', ', array_fill( 0, count( $meta_keys ), '%s' ) ); $entity_id_string = implode( ', ', array_fill( 0, count( $entity_ids ), '%d' ) ); @@ -312,7 +313,7 @@ WHERE $row_data[ $custom_table_column_name ] = $value; } } - $sanitized_entity_data[ $entity->primary_key_id ] = $row_data; + $sanitized_entity_data[ $entity->destination_rel_id ] = $row_data; } } @@ -338,7 +339,7 @@ WHERE /** * Validate and transform data so that we catch as many errors as possible before inserting. * - * @param mixed $value Actual data value. + * @param mixed $value Actual data value. * @param string $type Type of data, could be decimal, int, date, string. * * @return float|int|mixed|string|\WP_Error diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php index 2335997d607..d3db92b05ec 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php @@ -51,7 +51,7 @@ class MetaToMetaTableMigrator { $meta_value_column = MigrationHelper::escape_backtick( $this->schema_config['destination']['meta']['meta_value_column'] ); $entity_id_column = MigrationHelper::escape_backtick( $this->schema_config['destination']['meta']['entity_id_column'] ); $column_sql = "(`$entity_id_column`, `$meta_key_column`, `$meta_value_column`)"; - $table = $this->schema_config['destination']['meta_table_name']; + $table = $this->schema_config['destination']['meta']['table_name']; $entity_id_column_placeholder = MigrationHelper::get_wpdb_placeholder_for_type( $this->schema_config['destination']['meta']['entity_id_type'] ); $placeholder_string = "( $entity_id_column_placeholder, %s, %s )"; @@ -88,10 +88,10 @@ class MetaToMetaTableMigrator { * ..., * ) */ - public function fetch_data_for_migration( $where_clause ) { + public function fetch_data_for_migration_for_ids( $order_post_ids ) { global $wpdb; - $meta_query = $this->build_meta_table_query( $where_clause ); + $meta_query = $this->build_meta_table_query( $order_post_ids ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Meta query has interpolated variables, but they should all be escaped for backticks. $meta_data_rows = $wpdb->get_results( $meta_query ); @@ -112,37 +112,40 @@ class MetaToMetaTableMigrator { * * @return string Query that can be used to fetch data. */ - private function build_meta_table_query( $where_clause ) { + private function build_meta_table_query( $entity_ids ) { global $wpdb; - $source_meta_key_column = MigrationHelper::escape_backtick( $this->schema_config['source']['meta_key_column'] ); - $source_meta_value_column = MigrationHelper::escape_backtick( $this->schema_config['source']['meta_value_column'] ); - $source_entity_id_column = MigrationHelper::escape_backtick( $this->schema_config['source']['meta_entity_id_column'] ); - $source_meta_table_name = MigrationHelper::escape_backtick( $this->schema_config['source']['meta_table_name'] ); - $order_by = "`$source_meta_table_name`.`$source_entity_id_column` ASC"; + $source_meta_table = MigrationHelper::escape_backtick( $this->schema_config['source']['meta']['table_name'] ); + $source_meta_key_column = MigrationHelper::add_table_name_to_column( $source_meta_table, $this->schema_config['source']['meta']['meta_key_column'] ); + $source_meta_value_column = MigrationHelper::add_table_name_to_column( $source_meta_table, $this->schema_config['source']['meta']['meta_value_column'] ); + $source_entity_id_column = MigrationHelper::add_table_name_to_column( $source_meta_table, $this->schema_config['source']['meta']['entity_id_column'] ); + $order_by = "$source_entity_id_column ASC"; - $destination_entity_table = MigrationHelper::escape_backtick( $this->schema_config['destination']['entity_table_name'] ); - $destination_entity_id_column = MigrationHelper::escape_backtick( $this->schema_config['destination']['entity_id_column'] ); - $destination_source_id_mapping_column = MigrationHelper::escape_backtick( $this->schema_config['destination']['source_id_column'] ); + $where_clause = "$source_entity_id_column IN (" . implode( array_fill( 0, count( $entity_ids ), '%d') ) . ')'; + + $destination_entity_table = MigrationHelper::escape_backtick( $this->schema_config['destination']['entity']['table_name'] ); + $destination_entity_id_column = MigrationHelper::add_table_name_to_column( $destination_entity_table, $this->schema_config['destination']['entity']['id_column'] ); + $destination_source_id_mapping_column = MigrationHelper::add_table_name_to_column( $destination_entity_table, $this->schema_config['destination']['entity']['source_id_column'] ); if ( $this->schema_config['source']['excluded_keys'] ) { $key_placeholder = implode( ',', array_fill( 0, count( $this->schema_config['source']['excluded_keys'] ), '%s' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $source_meta_key_column is escated for backticks, $key_placeholder is hardcoded. - $exclude_clause = $wpdb->prepare( "`$source_meta_key_column` NOT IN ( $key_placeholder )", $this->schema_config['source']['excluded_keys'] ); + $exclude_clause = $wpdb->prepare( "$source_meta_key_column NOT IN ( $key_placeholder )", $this->schema_config['source']['excluded_keys'] ); $where_clause = "$where_clause AND $exclude_clause"; } - $meta_data_sql = " + return $wpdb->prepare( + " SELECT - `$source_meta_table_name`.`$source_entity_id_column` as source_entity_id, - `$destination_entity_table`.`$destination_entity_id_column` as destination_entity_id, - `$source_meta_table_name`.`$source_meta_key_column` as meta_key, - `$source_meta_table_name`.`$source_meta_value_column` as meta_value -FROM `$source_meta_table_name` -JOIN `$destination_entity_table` ON `$destination_entity_table`.`$destination_source_id_mapping_column` = `$source_meta_table_name`.`$source_entity_id_column` + $source_entity_id_column as source_entity_id, + $destination_entity_id_column as destination_entity_id, + $source_meta_key_column as meta_key, + $source_meta_value_column as meta_value +FROM `$source_meta_table` +JOIN `$destination_entity_table` ON $destination_source_id_mapping_column = $source_entity_id_column WHERE $where_clause ORDER BY $order_by -"; - - return $meta_data_sql; +", + $entity_ids + ); } } diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php index aa92014bf6a..d6041f0863b 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php @@ -101,24 +101,23 @@ class WPPostToCOTMigrator { private function get_config_for_order_table() { global $wpdb; $order_table_schema_config = array( - 'source' => array( + 'source' => array( 'entity' => array( - 'table_name' => $wpdb->posts, - 'meta_rel_column' => 'ID', + 'table_name' => $wpdb->posts, + 'meta_rel_column' => 'ID', 'destination_rel_column' => 'ID', + 'primary_key' => 'ID', ), - 'meta' => array( - 'table_name' => $wpdb->postmeta, - 'meta_key_column' => 'meta_key', + 'meta' => array( + 'table_name' => $wpdb->postmeta, + 'meta_key_column' => 'meta_key', 'meta_value_column' => 'meta_value', - 'entity_id_column' => 'post_id', + 'entity_id_column' => 'post_id', ), ), 'destination' => array( - array( - 'table_name' => $this->table_names['orders'], - 'source_rel_column' => 'post_id', - ), + 'table_name' => $this->table_names['orders'], + 'source_rel_column' => 'post_id', ), ); @@ -221,21 +220,22 @@ class WPPostToCOTMigrator { // We join order core table and post meta table to get data for address, since we need order ID. // So order core record needs to be already present. $schema_config = array( - 'source' => array( + 'source' => array( 'entity' => array( - 'table_name' => $this->table_names['orders'], - 'meta_rel_column' => 'post_id', + 'table_name' => $this->table_names['orders'], + 'meta_rel_column' => 'post_id', 'destination_rel_column' => 'id', + 'primary_key' => 'id', ), - 'meta' => array( - 'table_name' => $wpdb->postmeta, - 'meta_key_column' => 'meta_key', + 'meta' => array( + 'table_name' => $wpdb->postmeta, + 'meta_key_column' => 'meta_key', 'meta_value_column' => 'meta_value', - 'entity_id_column' => 'post_id', + 'entity_id_column' => 'post_id', ), ), 'destination' => array( - 'table_name' => $this->table_names['addresses'], + 'table_name' => $this->table_names['addresses'], 'source_rel_column' => 'order_id', ), ); @@ -315,21 +315,22 @@ class WPPostToCOTMigrator { global $wpdb; $schema_config = array( - 'source' => array( + 'source' => array( 'entity' => array( - 'table_name' => $this->table_names['orders'], - 'meta_rel_column' => 'post_id', + 'table_name' => $this->table_names['orders'], + 'meta_rel_column' => 'post_id', 'destination_rel_column' => 'id', + 'primary_key' => 'id', ), - 'meta' => array( - 'table_name' => $wpdb->postmeta, - 'meta_key_column' => 'meta_key', + 'meta' => array( + 'table_name' => $wpdb->postmeta, + 'meta_key_column' => 'meta_key', 'meta_value_column' => 'meta_value', - 'entity_id_column' => 'post_id', + 'entity_id_column' => 'post_id', ), ), 'destination' => array( - 'table_name' => $this->table_names['op_data'], + 'table_name' => $this->table_names['op_data'], 'source_rel_column' => 'order_id', ), ); @@ -460,9 +461,8 @@ class WPPostToCOTMigrator { */ public function process_next_migration_batch( $batch_size = 100 ) { global $wpdb; - $order_by = 'ID ASC'; - $order_data = $this->order_table_migrator->fetch_data_for_migration( $this->get_where_clause(), $batch_size, $order_by ); + $order_data = $this->order_table_migrator->fetch_data_for_migration_for_ids( $this->get_next_batch_ids( $batch_size ) ); foreach ( $order_data['errors'] as $post_id => $error ) { $this->error_logger->log( 'info', "Error in importing post id $post_id: " . print_r( $error, true ) ); @@ -481,9 +481,9 @@ class WPPostToCOTMigrator { } $order_post_ids = array_column( $order_data['data'], 'post_id' ); - $this->process_next_migrator_batch( $this->billing_address_table_migrator, $order_post_ids, $order_by ); - $this->process_next_migrator_batch( $this->shipping_address_table_migrator, $order_post_ids, $order_by ); - $this->process_next_migrator_batch( $this->operation_data_table_migrator, $order_post_ids, $order_by ); + $this->process_next_migrator_batch( $this->billing_address_table_migrator, $order_post_ids ); + $this->process_next_migrator_batch( $this->shipping_address_table_migrator, $order_post_ids ); + $this->process_next_migrator_batch( $this->operation_data_table_migrator, $order_post_ids ); $this->process_meta_migration( $order_post_ids ); $last_post_migrated = max( array_keys( $order_data['data'] ) ); @@ -496,14 +496,12 @@ class WPPostToCOTMigrator { * Process next batch for a given address type. * * @param MetaToCustomTableMigrator $migrator Migrator instance for address type. - * @param array $order_post_ids Array of post IDs for orders. - * @param string $order_by Order by clause. + * @param array $order_post_ids Array of post IDs for orders. + * @param string $order_by Order by clause. */ - private function process_next_migrator_batch( $migrator, $order_post_ids, $order_by ) { + private function process_next_migrator_batch( $migrator, $order_post_ids ) { global $wpdb; - $post_ids_where_clause = $this->get_where_id_clause( $order_post_ids, 'post_id' ); - $batch_size = count( $order_post_ids ); - $data = $migrator->fetch_data_for_migration( $post_ids_where_clause, $batch_size, $order_by ); + $data = $migrator->fetch_data_for_migration_for_ids( $order_post_ids ); foreach ( $data['errors'] as $order_id => $error ) { // TODO: Add name of the migrator in error message. $this->error_logger->log( 'info', "Error in importing data for Order ID $order_id: " . print_r( $error, true ) ); @@ -524,9 +522,8 @@ class WPPostToCOTMigrator { */ private function process_meta_migration( $order_post_ids ) { global $wpdb; - $post_ids_where_clause = $this->get_where_id_clause( $order_post_ids, 'post_id' ); - $data_to_migrate = $this->meta_table_migrator->fetch_data_for_migration( $post_ids_where_clause ); - $insert_queries = $this->meta_table_migrator->generate_insert_sql_for_batch( $data_to_migrate['data'], 'insert' ); + $data_to_migrate = $this->meta_table_migrator->fetch_data_for_migration_for_ids( $order_post_ids ); + $insert_queries = $this->meta_table_migrator->generate_insert_sql_for_batch( $data_to_migrate['data'], 'insert' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $insert_queries should already be escaped in the generating function. $result = $wpdb->query( $insert_queries ); if ( count( $data_to_migrate['data'] ) !== $result ) { @@ -546,7 +543,7 @@ class WPPostToCOTMigrator { global $wpdb; $where_clause = $wpdb->prepare( 'ID = %d', $post_id ); - $data = $this->order_table_migrator->fetch_data_for_migration( $where_clause, 1, 'ID ASC' ); + $data = $this->order_table_migrator->fetch_data_for_migration_for_ids( $where_clause, 1, 'ID ASC' ); if ( isset( $data['errors'][ $post_id ] ) ) { return new \WP_Error( $data['errors'][ $post_id ] ); } @@ -566,24 +563,28 @@ class WPPostToCOTMigrator { /** * Helper function to get where clause to send to MetaToCustomTableMigrator instance. * - * @return string|void Where clause. + * @return array Where clause. */ - private function get_where_clause() { + private function get_next_batch_ids( $batch_size ) { global $wpdb; $checkpoint = $this->get_checkpoint(); - $where_clause = $wpdb->prepare( - 'post_type = "shop_order" AND ID > %d', - $checkpoint['id'] + $post_ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT ID FROM $wpdb->posts WHERE ID > %d AND post_type = '%s' LIMIT %d", + $checkpoint['id'], + 'shop_order', + $batch_size + ) ); - return $where_clause; + return $post_ids; } /** * Helper method to create `ID in (.., .., ...)` clauses. * - * @param array $ids List of IDs. + * @param array $ids List of IDs. * @param string $column_name Name of the ID column. * * @return string Prepared clause for where. @@ -615,6 +616,10 @@ class WPPostToCOTMigrator { * @param int $id Order ID. */ public function update_checkpoint( $id ) { - update_option( 'wc_cot_migration', array( 'id' => $id ) ); + return update_option( 'wc_cot_migration', array( 'id' => $id ) ); + } + + public function delete_checkpoint() { + return delete_option( 'wp_cot_migration' ); } } diff --git a/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php b/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php index 8d7e7da30a9..72e1c3ad4fd 100644 --- a/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php +++ b/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php @@ -5,6 +5,8 @@ namespace Automattic\WooCommerce\DataBase\Migrations; +use function DeliciousBrains\WP_Offload_Media\Aws3\JmesPath\search; + /** * Class MigrationHelper. * @@ -72,4 +74,10 @@ class MigrationHelper { return self::$wpdb_placeholder_for_type[ $type ]; } + public static function add_table_name_to_column( $table_name, $column_name ) { + $table_name = self::escape_backtick( $table_name ); + $column_name = self::escape_backtick( $column_name ); + return "`$table_name`.`$column_name`"; + } + } From 169bd4fd4f2c1edb98f3802cff8c39cbd7645f03 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Fri, 25 Mar 2022 18:21:40 +0530 Subject: [PATCH 178/386] Fix incorrect column mapping bugs. --- .../MetaToCustomTableMigrator.php | 2 +- .../MetaToMetaTableMigrator.php | 17 ++++++++--------- .../CustomOrderTable/WPPostToCOTMigrator.php | 7 ++++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php index d7b397c7727..b96fb8f74db 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php @@ -313,7 +313,7 @@ WHERE $row_data[ $custom_table_column_name ] = $value; } } - $sanitized_entity_data[ $entity->destination_rel_id ] = $row_data; + $sanitized_entity_data[ $entity->entity_meta_rel_id ] = $row_data; } } diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php index d3db92b05ec..bef0c8434cc 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php @@ -54,22 +54,22 @@ class MetaToMetaTableMigrator { $table = $this->schema_config['destination']['meta']['table_name']; $entity_id_column_placeholder = MigrationHelper::get_wpdb_placeholder_for_type( $this->schema_config['destination']['meta']['entity_id_type'] ); - $placeholder_string = "( $entity_id_column_placeholder, %s, %s )"; + $placeholder_string = "$entity_id_column_placeholder, %s, %s"; $values = array(); foreach ( array_values( $batch ) as $row ) { $query_params = array( - $row['destination_entity_id'], - $row['meta_key'], - $row['meta_value'], + $row->destination_entity_id, + $row->meta_key, + $row->meta_value, ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $placeholder_string is hardcoded. - $value_sql = $wpdb->prepare( "( $placeholder_string )", $query_params ); + $value_sql = $wpdb->prepare( "$placeholder_string", $query_params ); $values[] = $value_sql; } - $values_sql = implode( ',', $values ); + $values_sql = implode( '), (', $values ); - return "$insert_query INTO $table $column_sql VALUES $values_sql"; + return "$insert_query INTO $table $column_sql VALUES ($values_sql)"; } /** @@ -102,7 +102,7 @@ class MetaToMetaTableMigrator { ); } - return $meta_data_rows; + return array( 'data' => $meta_data_rows, 'errors' => array() ); } /** @@ -147,5 +147,4 @@ WHERE $where_clause ORDER BY $order_by $entity_ids ); } - } diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php index d6041f0863b..a8963bd381e 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php @@ -225,7 +225,7 @@ class WPPostToCOTMigrator { 'table_name' => $this->table_names['orders'], 'meta_rel_column' => 'post_id', 'destination_rel_column' => 'id', - 'primary_key' => 'id', + 'primary_key' => 'post_id', ), 'meta' => array( 'table_name' => $wpdb->postmeta, @@ -320,7 +320,7 @@ class WPPostToCOTMigrator { 'table_name' => $this->table_names['orders'], 'meta_rel_column' => 'post_id', 'destination_rel_column' => 'id', - 'primary_key' => 'id', + 'primary_key' => 'post_id', ), 'meta' => array( 'table_name' => $wpdb->postmeta, @@ -361,7 +361,7 @@ class WPPostToCOTMigrator { ), '_download_permissions_granted' => array( 'type' => 'bool', - 'destination' => 'download_permissions_granted', + 'destination' => 'download_permission_granted', ), '_cart_hash' => array( 'type' => 'string', @@ -474,6 +474,7 @@ class WPPostToCOTMigrator { $queries = $this->order_table_migrator->generate_insert_sql_for_batch( $order_data['data'], 'insert' ); $result = $wpdb->query( $queries ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $queries is already prepared. + $wpdb->query("COMMIT;"); // For some reason, this seems necessary on some hosts? Maybe a MySQL configuration? if ( count( $order_data['data'] ) !== $result ) { // Some rows were not inserted. // TODO: Find and log the entity ids that were not inserted. From 2802162be52d8ce1be2b3a85efd6463e479251ee Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Fri, 25 Mar 2022 18:22:00 +0530 Subject: [PATCH 179/386] Add meta table and typo fix. --- .../src/Internal/DataStores/Orders/OrdersTableDataStore.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php index cb0218d1f1c..ed6b2b4a957 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php @@ -49,7 +49,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements */ public static function get_meta_table_name() { global $wpdb; - return $wpdb->prefix . 'wc_ordes_meta'; + return $wpdb->prefix . 'wc_orders_meta'; } /** @@ -62,6 +62,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements $this->get_orders_table_name(), $this->get_addresses_table_name(), $this->get_operational_data_table_name(), + $this->get_meta_table_name(), ); } From a4c83de142bf74830ae3926702ddc1a34e49d0e7 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Fri, 25 Mar 2022 18:23:09 +0530 Subject: [PATCH 180/386] Add dependency injection class. --- plugins/woocommerce/src/Container.php | 2 ++ .../COTMigrationServiceProvider.php | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/COTMigrationServiceProvider.php diff --git a/plugins/woocommerce/src/Container.php b/plugins/woocommerce/src/Container.php index 711a6e16408..300e72d4aa4 100644 --- a/plugins/woocommerce/src/Container.php +++ b/plugins/woocommerce/src/Container.php @@ -6,6 +6,7 @@ namespace Automattic\WooCommerce; use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer; +use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\COTMigrationServiceProvider; use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\DownloadPermissionsAdjusterServiceProvider; use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\AssignDefaultCategoryServiceProvider; use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\OrdersDataStoreServiceProvider; @@ -47,6 +48,7 @@ final class Container implements \Psr\Container\ContainerInterface { ProxiesServiceProvider::class, RestockRefundedItemsAdjusterServiceProvider::class, UtilsClassesServiceProvider::class, + COTMigrationServiceProvider::class, ); /** diff --git a/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/COTMigrationServiceProvider.php b/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/COTMigrationServiceProvider.php new file mode 100644 index 00000000000..cbe4221f3fb --- /dev/null +++ b/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/COTMigrationServiceProvider.php @@ -0,0 +1,27 @@ +leagueContainer property or the `getLeagueContainer` method + * from the ContainerAwareTrait. + * + * @return void + */ + public function register() { + $this->share( WPPostToCOTMigrator::class ); + } +} From 7186c80c5eb4518e1ddc774bd15999ce70bd2180 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Tue, 29 Mar 2022 15:44:59 +0530 Subject: [PATCH 181/386] Reuse function instead of hardcoding for consistency. --- plugins/woocommerce/includes/abstracts/abstract-wc-data.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-data.php b/plugins/woocommerce/includes/abstracts/abstract-wc-data.php index eda6c546b7f..a9642de97c4 100644 --- a/plugins/woocommerce/includes/abstracts/abstract-wc-data.php +++ b/plugins/woocommerce/includes/abstracts/abstract-wc-data.php @@ -640,7 +640,7 @@ abstract class WC_Data { } } if ( ! empty( $this->cache_group ) ) { - $cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $this->get_id() ) . 'object_meta_' . $this->get_id(); + $cache_key = self::generate_meta_cache_key( $this->get_id(), $this->cache_group ); wp_cache_delete( $cache_key, $this->cache_group ); } } From 3ed99434742c328d19cc3325134c4738afdf672c Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Tue, 29 Mar 2022 15:45:42 +0530 Subject: [PATCH 182/386] Also add migration config for transaction_id column. --- .../Migrations/CustomOrderTable/WPPostToCOTMigrator.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php index a8963bd381e..cbc0e43da17 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php @@ -181,6 +181,10 @@ class WPPostToCOTMigrator { 'type' => 'string', 'destination' => 'user_agent', ), + '_transaction_id' => array( + 'type' => 'string', + 'destination' => 'transaction_id', + ), ); return array( @@ -509,6 +513,7 @@ class WPPostToCOTMigrator { } $queries = $migrator->generate_insert_sql_for_batch( $data['data'], 'insert' ); $result = $wpdb->query( $queries ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Insert statements should already be escaped. + $wpdb->query('COMMIT;'); // For some reason, this seems necessary on some hosts? Maybe a MySQL configuration? if ( count( $data['data'] ) !== $result ) { // Some rows were not inserted. // TODO: Find and log the entity ids that were not inserted. From 9a178b7704e91928066b678855ec703a49865225 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Tue, 29 Mar 2022 15:49:36 +0530 Subject: [PATCH 183/386] Add unit test for order data migration. --- .../WPPostToCOTMigratorTest.php | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php diff --git a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php new file mode 100644 index 00000000000..57d7868b06d --- /dev/null +++ b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php @@ -0,0 +1,250 @@ +create_order_custom_table_if_not_exist(); + $this->data_store = wc_get_container()->get( OrdersTableDataStore::class ); + $this->sut = wc_get_container()->get( WPPostToCOTMigrator::class ); + } + + public function test_process_next_migration_batch_normal_order() { + + $order = wc_get_order( $this->create_complex_wp_post_order() ); + $this->clear_all_orders_and_reset_checkpoint(); + $this->sut->process_next_migration_batch( 100 ); + $db_order = $this->get_order_from_cot( $order ); + + // Verify core data. + $this->assertEquals( $order->get_id(), $db_order->post_id ); + $this->assertEquals( 'wc-' . $order->get_status(), $db_order->status ); + $this->assertEquals( 'INR', $db_order->currency ); + $this->assertEquals( $order->get_customer_id(), $db_order->customer_id ); + $this->assertEquals( $order->get_billing_email(), $db_order->billing_email ); + $this->assertEquals( $order->get_payment_method(), $db_order->payment_method ); + $this->assertEquals( + $order->get_date_created()->date( DATE_ISO8601 ), + ( new WC_DateTime( $db_order->date_created_gmt ) )->date( DATE_ISO8601 ) + ); + $this->assertEquals( $order->get_date_modified()->date( DATE_ISO8601 ), ( new WC_DateTime( $db_order->date_updated_gmt ) )->date( DATE_ISO8601 ) ); + $this->assertEquals( $order->get_parent_id(), $db_order->parent_order_id ); + $this->assertEquals( $order->get_payment_method_title(), $db_order->payment_method_title ); + $this->assertEquals( $order->get_transaction_id(), $db_order->transaction_id ); + $this->assertEquals( $order->get_customer_ip_address(), $db_order->ip_address ); + $this->assertEquals( $order->get_customer_user_agent(), $db_order->user_agent ); + + // Verify order billing address. + $db_order_address = $this->get_address_details_from_cot( $db_order->id, 'billing' ); + $this->assertEquals( $order->get_billing_first_name(), $db_order_address->first_name ); + $this->assertEquals( $order->get_billing_last_name(), $db_order_address->last_name ); + $this->assertEquals( $order->get_billing_company(), $db_order_address->company ); + $this->assertEquals( $order->get_billing_address_1(), $db_order_address->address_1 ); + $this->assertEquals( $order->get_billing_address_2(), $db_order_address->address_2 ); + $this->assertEquals( $order->get_billing_city(), $db_order_address->city ); + $this->assertEquals( $order->get_billing_postcode(), $db_order_address->postcode ); + $this->assertEquals( $order->get_billing_country(), $db_order_address->country ); + $this->assertEquals( $order->get_billing_email(), $db_order_address->email ); + $this->assertEquals( $order->get_billing_phone(), $db_order_address->phone ); + + // Verify order shipping address. + $db_order_address = $this->get_address_details_from_cot( $db_order->id, 'shipping' ); + $this->assertEquals( $order->get_shipping_first_name(), $db_order_address->first_name ); + $this->assertEquals( $order->get_shipping_last_name(), $db_order_address->last_name ); + $this->assertEquals( $order->get_shipping_company(), $db_order_address->company ); + $this->assertEquals( $order->get_shipping_address_1(), $db_order_address->address_1 ); + $this->assertEquals( $order->get_shipping_address_2(), $db_order_address->address_2 ); + $this->assertEquals( $order->get_shipping_city(), $db_order_address->city ); + $this->assertEquals( $order->get_shipping_postcode(), $db_order_address->postcode ); + $this->assertEquals( $order->get_shipping_country(), $db_order_address->country ); + $this->assertEquals( $order->get_shipping_phone(), $db_order_address->phone ); + + // Verify order operational data. + $db_order_op_data = $this->get_order_operational_data_from_cot( $db_order->id ); + $this->assertEquals( $order->get_created_via(), $db_order_op_data->created_via ); + $this->assertEquals( $order->get_version(), $db_order_op_data->woocommerce_version ); + $this->assertEquals( $order->get_prices_include_tax(), $db_order_op_data->prices_include_tax ); + $this->assertEquals( + wc_string_to_bool( $order->get_data_store()->get_recorded_coupon_usage_counts( $order ) ), + $db_order_op_data->coupon_usages_are_counted + ); + $this->assertEquals( + wc_string_to_bool( $order->get_data_store()->get_download_permissions_granted( $order ) ), + $db_order_op_data->download_permission_granted + ); + $this->assertEquals( $order->get_cart_hash(), $db_order_op_data->cart_hash ); + $this->assertEquals( + wc_string_to_bool( $order->get_meta( '_new_order_email_sent' ) ), + $db_order_op_data->new_order_email_sent + ); + $this->assertEquals( $order->get_order_key(), $db_order_op_data->order_key ); + $this->assertEquals( $order->get_data_store()->get_stock_reduced( $order ), $db_order_op_data->order_stock_reduced ); + $this->assertEquals( + $order->get_date_paid()->date( DATE_ISO8601 ), + ( new WC_DateTime( $db_order_op_data->date_paid_gmt ) )->date( DATE_ISO8601 ) + ); + $this->assertEquals( + $order->get_date_completed()->date( DATE_ISO8601 ), + ( new WC_DateTime( $db_order_op_data->date_completed_gmt ) )->date( DATE_ISO8601 ) + ); + $this->assertEquals( $order->get_shipping_tax(), $db_order_op_data->shipping_tax_amount ); + $this->assertEquals( $order->get_shipping_total(), $db_order_op_data->shipping_total_amount ); + $this->assertEquals( $order->get_discount_tax(), $db_order_op_data->discount_tax_amount ); + $this->assertEquals( $order->get_discount_total(), $db_order_op_data->discount_total_amount ); + } + + public function test_process_next_migration_batch_interrupted_migrating_order() { + + } + + public function test_process_next_migration_batch_already_migrated_order() { + + } + + public function test_process_next_migration_batch_invalid_order_data() { + + } + + public function test_process_next_migration_batch_invalid_valid_order_combo() { + + } + + public function test_process_next_migration_batch_stale_order() { + + } + + private function get_order_from_cot( $post_order ) { + global $wpdb; + $order_table = $this->data_store::get_orders_table_name(); + $query = "SELECT * FROM $order_table WHERE post_id = {$post_order->get_id()};"; + return $wpdb->get_row( $query ); + } + + private function get_address_details_from_cot( $order_id, $address_type ) { + global $wpdb; + $address_table = $this->data_store::get_addresses_table_name(); + return $wpdb->get_row( "SELECT * FROM $address_table WHERE order_id = $order_id AND address_type = '$address_type';" ); + } + + private function get_order_operational_data_from_cot( $order_id ) { + global $wpdb; + $operational_data_table = $this->data_store::get_operational_data_table_name(); + return $wpdb->get_row( "SELECT * FROM $operational_data_table WHERE order_id = $order_id;" ); + } + + private function create_complex_wp_post_order() { + update_option( 'woocommerce_prices_include_tax', 'yes' ); + update_option( 'woocommerce_calc_taxes', 'yes' ); + $customer = CustomerHelper::create_customer(); + $tax_rate = array( + 'tax_rate_country' => '', + 'tax_rate_state' => '', + 'tax_rate' => '15.0000', + 'tax_rate_name' => 'tax', + 'tax_rate_priority' => '1', + 'tax_rate_order' => '1', + 'tax_rate_shipping' => '1', + ); + WC_Tax::_insert_tax_rate( $tax_rate ); + + ShippingHelper::create_simple_flat_rate(); + + $order = OrderHelper::create_order(); + // Make sure this is a wp_post order. + $post = get_post( $order->get_id() ); + $this->assertNotNull( $post, 'Order is not created in wp_post table.' ); + $this->assertEquals( 'shop_order', $post->post_type, 'Order is not created in wp_post table.' ); + + $order->save(); + + $order->set_status( 'completed' ); + $order->set_currency( 'INR' ); + $order->set_customer_id( $customer->get_id() ); + $order->set_billing_email( $customer->get_billing_email() ); + + $payment_gateway = new WC_Mock_Payment_Gateway(); + $order->set_payment_method( 'mock' ); + $order->set_transaction_id( 'mock1' ); + + $order->set_customer_ip_address( '1.1.1.1' ); + $order->set_customer_user_agent( 'wc_unit_tests' ); + + $order->save(); + + $order->set_shipping_first_name( 'Albert' ); + $order->set_shipping_last_name( 'Einstein' ); + $order->set_shipping_company( 'The Olympia Academy' ); + $order->set_shipping_address_1( '112 Mercer Street' ); + $order->set_shipping_address_2( 'Princeton' ); + $order->set_shipping_city( 'New Jersey' ); + $order->set_shipping_postcode( '08544' ); + $order->set_shipping_phone( '299792458' ); + $order->set_shipping_country( 'US' ); + + $order->set_created_via( 'unit_tests' ); + $order->set_version( '0.0.2' ); + $order->set_prices_include_tax( true ); + wc_update_coupon_usage_counts( $order->get_id() ); + $order->get_data_store()->set_download_permissions_granted( $order, true ); + $order->set_cart_hash( '1234' ); + $order->update_meta_data( '_new_order_email_sent', 'true' ); + $order->update_meta_data( '_order_stock_reduced', 'true' ); + $order->set_date_paid( time() ); + $order->set_date_completed( time() ); + $order->calculate_shipping(); + + $order->save(); + $order->save_meta_data(); + + return $order->get_id(); + } + + private function clear_all_orders_and_reset_checkpoint() { + global $wpdb; + $order_tables = $this->data_store->get_all_table_names(); + foreach ( $order_tables as $table ) { + $wpdb->query( "TRUNCATE table $table;" ); + } + $this->sut->delete_checkpoint(); + } + + private function create_order_custom_table_if_not_exist() { + $order_table_controller = wc_get_container() + ->get( 'Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController' ); + $order_table_controller->show_feature(); + $this->synchronizer = wc_get_container() + ->get( DataSynchronizer::class ); + if( ! $this->synchronizer->check_orders_table_exists() ) { + $this->synchronizer->create_database_tables(); + } + } +} From 56ee0b2f4a555b191b61844d0e59bbcc53974cba Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 31 Mar 2022 14:30:08 +0530 Subject: [PATCH 184/386] Add update support along with inser. --- plugins/woocommerce.php | 0 .../MetaToCustomTableMigrator.php | 88 +++++++++++++++---- .../WPPostToOrderAddressTableMigrator.php | 0 3 files changed, 70 insertions(+), 18 deletions(-) create mode 100644 plugins/woocommerce.php create mode 100644 plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php diff --git a/plugins/woocommerce.php b/plugins/woocommerce.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php index b96fb8f74db..0bc08e73136 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php @@ -12,7 +12,7 @@ use Automattic\WooCommerce\DataBase\Migrations\MigrationHelper; * * @package Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable */ -class MetaToCustomTableMigrator { +abstract class MetaToCustomTableMigrator { /** * Config for tables being migrated and migrated from. See __construct() for detailed config. @@ -103,16 +103,45 @@ class MetaToCustomTableMigrator { * ($value for row 2) * ... */ - public function generate_insert_sql_for_batch( $batch, $insert_switch ) { - global $wpdb; + public function generate_insert_sql_for_batch( $batch ) { + $table = $this->schema_config['destination']['table_name']; - // TODO: Add code to validate params. - $table = $this->schema_config['destination']['table_name']; - $insert_query = MigrationHelper::get_insert_switch( $insert_switch ); + list( $value_sql, $column_sql ) = $this->generate_column_clauses( array_merge( $this->core_column_mapping, $this->meta_column_mapping ), $batch ); + + + return "INSERT IGNORE INTO $table (`$column_sql`) VALUES $value_sql;"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, -- $insert_query is hardcoded, $value_sql is already escaped. + } + + public function generate_update_sql_for_batch( $batch ) { + $table = $this->schema_config['destination']['table_name']; + + $destination_primary_id_schema = $this->get_destination_table_primary_id_schema(); + + list( $value_sql, $column_sql, $columns ) = $this->generate_column_clauses( + array_merge( $destination_primary_id_schema, $this->core_column_mapping, $this->meta_column_mapping ), + $batch + ); + + $duplicate_update_key_statement = $this->generate_on_duplicate_statement_clause( $columns ); + + return "INSERT INTO $table (`$column_sql`) VALUES $value_sql $duplicate_update_key_statement;"; + } + + private function get_destination_table_primary_id_schema() { + return array( + 'destination_primary_key' => array( + 'destination' => $this->schema_config['destination']['primary_key'], + 'type' => $this->schema_config['destination']['primary_key_type'], + ), + ); + } + + private function generate_column_clauses( $columns_schema, $batch ) { + global $wpdb; $columns = array(); $placeholders = array(); - foreach ( array_merge( $this->core_column_mapping, $this->meta_column_mapping ) as $prev_column => $schema ) { + foreach ( $columns_schema as $prev_column => $schema ) { $columns[] = $schema['destination']; $placeholders[] = MigrationHelper::get_wpdb_placeholder_for_type( $schema['type'] ); } @@ -133,7 +162,17 @@ class MetaToCustomTableMigrator { $column_sql = implode( '`, `', MigrationHelper::escape_backtick( $columns ) ); - return "$insert_query INTO $table (`$column_sql`) VALUES $value_sql;"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, -- $insert_query is hardcoded, $value_sql is already escaped. + return array( $value_sql, $column_sql, $columns ); + } + + private function generate_on_duplicate_statement_clause( $columns ) { + $update_value_statements = []; + foreach ( $columns as $column ) { + $update_value_statements[] = "$column = VALUES( $column )"; + } + $update_value_clause = implode( ', ', $update_value_statements ); + + return "ON DUPLICATE KEY UPDATE $update_value_clause"; } /** @@ -157,7 +196,13 @@ class MetaToCustomTableMigrator { public function fetch_data_for_migration_for_ids( $entity_ids ) { global $wpdb; - // TODO: Add code to validate params. + if ( empty( $entity_ids ) ) { + return array( + 'data' => array(), + 'errors' => array(), + ); + } + $entity_table_query = $this->build_entity_table_query( $entity_ids ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Output of $this->build_entity_table_query is already prepared. $entity_data = $wpdb->get_results( $entity_table_query ); @@ -191,13 +236,14 @@ class MetaToCustomTableMigrator { $source_entity_table = MigrationHelper::escape_backtick( $this->schema_config['source']['entity']['table_name'] ); $source_meta_rel_id_column = MigrationHelper::add_table_name_to_column( $source_entity_table, $this->schema_config['source']['entity']['meta_rel_column'] ); $source_destination_rel_id_column = MigrationHelper::add_table_name_to_column( $source_entity_table, $this->schema_config['source']['entity']['destination_rel_column'] ); - $source_primary_key_column = MigrationHelper::add_table_name_to_column( $source_entity_table, $this->schema_config['source']['entity']['primary_key'] ); + $source_primary_key_column = MigrationHelper::add_table_name_to_column( $source_entity_table, $this->schema_config['source']['entity']['primary_key'] ); $destination_table = MigrationHelper::escape_backtick( $this->schema_config['destination']['table_name'] ); $destination_source_rel_id_column = MigrationHelper::add_table_name_to_column( $destination_table, $this->schema_config['destination']['source_rel_column'] ); + $destination_primary_key = MigrationHelper::add_table_name_to_column( $destination_table, $this->schema_config['destination']['primary_key'] ); $where_clause = "$source_primary_key_column IN (" . implode( ',', array_fill( 0, count( $entity_ids ), '%d' ) ) . ')'; - $entity_keys = array(); + $entity_keys = array(); foreach ( $this->core_column_mapping as $column_name => $column_schema ) { if ( isset( $column_schema['select_clause'] ) ) { $select_clause = $column_schema['select_clause']; @@ -213,8 +259,9 @@ class MetaToCustomTableMigrator { SELECT $source_meta_rel_id_column as entity_meta_rel_id, $source_destination_rel_id_column AS destination_rel_id, + $destination_primary_key AS destination_primary_key, CASE - WHEN $destination_source_rel_id_column IS NOT NULL THEN 'replace' + WHEN $destination_source_rel_id_column IS NOT NULL THEN 'update' ELSE 'insert' END as insert_switch, $entity_column_string @@ -284,8 +331,9 @@ WHERE */ $sanitized_entity_data = array(); $error_records = array(); - $this->process_and_sanitize_entity_data( $sanitized_entity_data, $error_records, $entity_data ); - $this->processs_and_sanitize_meta_data( $sanitized_entity_data, $error_records, $meta_data ); + $insert_switch = array(); + $this->process_and_sanitize_entity_data( $sanitized_entity_data, $error_records, $insert_switch, $entity_data ); + $this->processs_and_sanitize_meta_data( $sanitized_entity_data, $error_records, $insert_switch, $meta_data ); return array( 'data' => $sanitized_entity_data, @@ -300,10 +348,10 @@ WHERE * @param array $error_records Error records. * @param array $entity_data Original source data. */ - private function process_and_sanitize_entity_data( &$sanitized_entity_data, &$error_records, $entity_data ) { + private function process_and_sanitize_entity_data( &$sanitized_entity_data, &$error_records, &$insert_switch, $entity_data ) { foreach ( $entity_data as $entity ) { $row_data = array(); - foreach ( $this->core_column_mapping as $column_name => $schema ) { + foreach ( array_merge( $this->get_destination_table_primary_id_schema(), $this->core_column_mapping ) as $column_name => $schema ) { $custom_table_column_name = $schema['destination'] ?? $column_name; $value = $entity->$column_name; $value = $this->validate_data( $value, $schema['type'] ); @@ -313,6 +361,8 @@ WHERE $row_data[ $custom_table_column_name ] = $value; } } + $insert_switch[ $entity->entity_meta_rel_id ] = $entity->insert_switch; // Save the insert_switch in row data so that we can use it later. + $sanitized_entity_data[ $entity->entity_meta_rel_id ] = $row_data; } } @@ -324,14 +374,16 @@ WHERE * @param array $error_records Error records. * @param array $meta_data Original source data. */ - private function processs_and_sanitize_meta_data( &$sanitized_entity_data, &$error_records, $meta_data ) { + private function processs_and_sanitize_meta_data( &$sanitized_entity_data, &$error_records, &$insert_switch, $meta_data ) { foreach ( $meta_data as $datum ) { $column_schema = $this->meta_column_mapping[ $datum->meta_key ]; $value = $this->validate_data( $datum->meta_value, $column_schema['type'] ); if ( is_wp_error( $value ) ) { $error_records[ $datum->entity_id ][ $column_schema['destination'] ] = "{$value->get_error_code()}: {$value->get_error_message()}"; } else { - $sanitized_entity_data[ $datum->entity_id ][ $column_schema['destination'] ] = $value; + $insert_switch_string = $insert_switch[ $datum->entity_id ]; + + $sanitized_entity_data[ $insert_switch_string ][ $datum->entity_id ][ $column_schema['destination'] ] = $value; } } } diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php new file mode 100644 index 00000000000..e69de29bb2d From 78e0e6cb780fff9813a5371069975de24fec1fe2 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 31 Mar 2022 18:15:49 +0530 Subject: [PATCH 185/386] Refactor migrator config into dedicated files. --- .../MetaToCustomTableMigrator.php | 172 ++++-- .../MetaToMetaTableMigrator.php | 36 +- .../CustomOrderTable/WPPostToCOTMigrator.php | 490 ++++-------------- .../WPPostToOrderAddressTableMigrator.php | 141 +++++ .../WPPostToOrderOpTableMigrator.php | 114 ++++ .../WPPostToOrderTableMigrator.php | 117 +++++ .../Database/Migrations/MigrationHelper.php | 22 +- 7 files changed, 633 insertions(+), 459 deletions(-) create mode 100644 plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php create mode 100644 plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php index 0bc08e73136..ad8d6e2e05b 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php @@ -19,21 +19,23 @@ abstract class MetaToCustomTableMigrator { * * @var array */ - private $schema_config; + protected $schema_config; /** * Meta config, see __construct for detailed config. * * @var array */ - private $meta_column_mapping; + protected $meta_column_mapping; /** * Column mapping from source table to destination custom table. See __construct for detailed config. * * @var array */ - private $core_column_mapping; + protected $core_column_mapping; + + protected $errors; /** * MetaToCustomTableMigrator constructor. @@ -84,13 +86,21 @@ abstract class MetaToCustomTableMigrator { * .... * ). */ - public function __construct( $schema_config, $meta_column_mapping, $core_column_mapping ) { + public function __construct() { // TODO: Add code to validate params. - $this->schema_config = $schema_config; - $this->meta_column_mapping = $meta_column_mapping; - $this->core_column_mapping = $core_column_mapping; + $this->schema_config = MigrationHelper::escape_schema_for_backtick( $this->get_schema_config() ); + $this->meta_column_mapping = $this->get_meta_column_config(); + $this->core_column_mapping = $this->get_core_column_mapping(); + $this->errors = array(); } + abstract function get_schema_config(); + + abstract function get_core_column_mapping(); + + abstract function get_meta_column_config(); + + /** * Generate SQL for data insertion. * @@ -112,10 +122,13 @@ abstract class MetaToCustomTableMigrator { return "INSERT IGNORE INTO $table (`$column_sql`) VALUES $value_sql;"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, -- $insert_query is hardcoded, $value_sql is already escaped. } - public function generate_update_sql_for_batch( $batch ) { + public function generate_update_sql_for_batch( $batch, $entity_row_mapping ) { $table = $this->schema_config['destination']['table_name']; $destination_primary_id_schema = $this->get_destination_table_primary_id_schema(); + foreach ( $batch as $entity_id => $row ) { + $batch[ $entity_id ][ $destination_primary_id_schema['destination_primary_key']['destination'] ] = $entity_row_mapping[ $entity_id ]->destination_id; + } list( $value_sql, $column_sql, $columns ) = $this->generate_column_clauses( array_merge( $destination_primary_id_schema, $this->core_column_mapping, $this->meta_column_mapping ), @@ -127,7 +140,7 @@ abstract class MetaToCustomTableMigrator { return "INSERT INTO $table (`$column_sql`) VALUES $value_sql $duplicate_update_key_statement;"; } - private function get_destination_table_primary_id_schema() { + protected function get_destination_table_primary_id_schema() { return array( 'destination_primary_key' => array( 'destination' => $this->schema_config['destination']['primary_key'], @@ -136,7 +149,7 @@ abstract class MetaToCustomTableMigrator { ); } - private function generate_column_clauses( $columns_schema, $batch ) { + protected function generate_column_clauses( $columns_schema, $batch ) { global $wpdb; $columns = array(); @@ -160,7 +173,7 @@ abstract class MetaToCustomTableMigrator { $value_sql = implode( ',', $values ); - $column_sql = implode( '`, `', MigrationHelper::escape_backtick( $columns ) ); + $column_sql = implode( '`, `', $columns ); return array( $value_sql, $column_sql, $columns ); } @@ -175,6 +188,67 @@ abstract class MetaToCustomTableMigrator { return "ON DUPLICATE KEY UPDATE $update_value_clause"; } + /** + * Process next migration batch, uses option `wc_cot_migration` to checkpoints of what have been processed so far. + * + * @param int $batch_size Batch size of records to migrate. + * + * @return array True if migration is completed, false if there are still records to process. + */ + public function process_migration_batch_for_ids( $entity_ids ) { + $data = $this->fetch_data_for_migration_for_ids( $entity_ids ); + + foreach ( $data['errors'] as $entity_id => $error ) { + $this->errors[ $entity_id ] = "Error in importing post id $entity_id: " . print_r( $error, true ); + } + + if ( count( $data['data'] ) === 0 ) { + return array(); + } + + $entity_ids = array_keys( $data['data'] ); + $already_migrated = $this->get_already_migrated_records( $entity_ids ); + + $to_insert = array_diff_key( $data['data'], $already_migrated ); + $this->process_insert_batch( $to_insert ); + + $to_update = array_intersect_key( $data['data'], $already_migrated ); + $this->process_update_batch( $to_update, $already_migrated ); + + return array( + 'errors' => $this->errors + ); + } + + protected function process_insert_batch( $batch ) { + global $wpdb; + if ( 0 === count( $batch ) ) { + return; + } + $queries = $this->generate_insert_sql_for_batch( $batch ); + $result = $wpdb->query( $queries ); + $wpdb->query( "COMMIT;" ); // For some reason, this seems necessary on some hosts? Maybe a MySQL configuration? + if ( count( $batch ) !== $result ) { + // Some rows were not inserted. + // TODO: Find and log the entity ids that were not inserted. + } + } + + protected function process_update_batch( $batch, $already_migrated ) { + global $wpdb; + if ( 0 === count( $batch ) ) { + return; + } + $queries = $this->generate_update_sql_for_batch( $batch, $already_migrated ); + $result = $wpdb->query( $queries ); + $wpdb->query( "COMMIT;" ); // For some reason, this seems necessary on some hosts? Maybe a MySQL configuration? + if ( count( $batch ) !== $result ) { + // Some rows were not inserted. + // TODO: Find and log the entity ids that were not updateed. + } + } + + /** * Fetch data for migration. * @@ -221,6 +295,33 @@ abstract class MetaToCustomTableMigrator { return $this->process_and_sanitize_data( $entity_data, $meta_data ); } + public function get_already_migrated_records( $entity_ids ) { + global $wpdb; + $source_table = $this->schema_config['source']['entity']['table_name']; + $source_destination_join_column = $this->schema_config['source']['entity']['destination_rel_column']; + $source_primary_key_column = $this->schema_config['source']['entity']['primary_key']; + + $destination_table = $this->schema_config['destination']['table_name']; + $destination_source_join_column = $this->schema_config['destination']['source_rel_column']; + $destination_primary_key_column = $this->schema_config['destination']['primary_key']; + + $entity_id_placeholder = implode( ',', array_fill( 0, count( $entity_ids ), '%d' ) ); + + $already_migrated_entity_ids = $wpdb->get_results( + $wpdb->prepare( + " +SELECT source.`$source_primary_key_column` as source_id, destination.`$destination_primary_key_column` as destination_id +FROM `$destination_table` destination +JOIN `$source_table` source ON source.`$source_destination_join_column` = destination.`$destination_source_join_column` +WHERE source.`$source_primary_key_column` IN ( $entity_id_placeholder ) + ", + $entity_ids + ) + ); + + return array_column( $already_migrated_entity_ids, null, 'source_id' ); + } + /** * Helper method to build query used to fetch data from core source table. @@ -231,16 +332,11 @@ abstract class MetaToCustomTableMigrator { * * @return string Query that can be used to fetch data. */ - private function build_entity_table_query( $entity_ids ) { + protected function build_entity_table_query( $entity_ids ) { global $wpdb; - $source_entity_table = MigrationHelper::escape_backtick( $this->schema_config['source']['entity']['table_name'] ); - $source_meta_rel_id_column = MigrationHelper::add_table_name_to_column( $source_entity_table, $this->schema_config['source']['entity']['meta_rel_column'] ); - $source_destination_rel_id_column = MigrationHelper::add_table_name_to_column( $source_entity_table, $this->schema_config['source']['entity']['destination_rel_column'] ); - $source_primary_key_column = MigrationHelper::add_table_name_to_column( $source_entity_table, $this->schema_config['source']['entity']['primary_key'] ); - - $destination_table = MigrationHelper::escape_backtick( $this->schema_config['destination']['table_name'] ); - $destination_source_rel_id_column = MigrationHelper::add_table_name_to_column( $destination_table, $this->schema_config['destination']['source_rel_column'] ); - $destination_primary_key = MigrationHelper::add_table_name_to_column( $destination_table, $this->schema_config['destination']['primary_key'] ); + $source_entity_table = $this->schema_config['source']['entity']['table_name']; + $source_meta_rel_id_column = "`$source_entity_table`.`{$this->schema_config['source']['entity']['meta_rel_column']}`"; + $source_primary_key_column = "`$source_entity_table`.`{$this->schema_config['source']['entity']['primary_key']}`"; $where_clause = "$source_primary_key_column IN (" . implode( ',', array_fill( 0, count( $entity_ids ), '%d' ) ) . ')'; $entity_keys = array(); @@ -249,7 +345,7 @@ abstract class MetaToCustomTableMigrator { $select_clause = $column_schema['select_clause']; $entity_keys[] = "$select_clause AS $column_name"; } else { - $entity_keys[] = MigrationHelper::add_table_name_to_column( $source_entity_table, $column_name ); + $entity_keys[] = "$source_entity_table.$column_name"; } } $entity_column_string = implode( ', ', $entity_keys ); @@ -258,15 +354,8 @@ abstract class MetaToCustomTableMigrator { " SELECT $source_meta_rel_id_column as entity_meta_rel_id, - $source_destination_rel_id_column AS destination_rel_id, - $destination_primary_key AS destination_primary_key, - CASE - WHEN $destination_source_rel_id_column IS NOT NULL THEN 'update' - ELSE 'insert' - END as insert_switch, $entity_column_string FROM `$source_entity_table` -LEFT JOIN `$destination_table` ON $destination_source_rel_id_column = $source_destination_rel_id_column WHERE $where_clause; ", $entity_ids @@ -284,13 +373,13 @@ WHERE $where_clause; * * @return string|void Query for fetching meta data. */ - private function build_meta_data_query( $entity_ids ) { + protected function build_meta_data_query( $entity_ids ) { global $wpdb; - $meta_table = MigrationHelper::escape_backtick( $this->schema_config['source']['meta']['table_name'] ); + $meta_table = $this->schema_config['source']['meta']['table_name']; $meta_keys = array_keys( $this->meta_column_mapping ); - $meta_key_column = MigrationHelper::escape_backtick( $this->schema_config['source']['meta']['meta_key_column'] ); - $meta_value_column = MigrationHelper::escape_backtick( $this->schema_config['source']['meta']['meta_value_column'] ); - $meta_table_relational_key = MigrationHelper::escape_backtick( $this->schema_config['source']['meta']['entity_id_column'] ); + $meta_key_column = $this->schema_config['source']['meta']['meta_key_column']; + $meta_value_column = $this->schema_config['source']['meta']['meta_value_column']; + $meta_table_relational_key = $this->schema_config['source']['meta']['entity_id_column']; $meta_column_string = implode( ', ', array_fill( 0, count( $meta_keys ), '%s' ) ); $entity_id_string = implode( ', ', array_fill( 0, count( $entity_ids ), '%d' ) ); @@ -331,9 +420,8 @@ WHERE */ $sanitized_entity_data = array(); $error_records = array(); - $insert_switch = array(); - $this->process_and_sanitize_entity_data( $sanitized_entity_data, $error_records, $insert_switch, $entity_data ); - $this->processs_and_sanitize_meta_data( $sanitized_entity_data, $error_records, $insert_switch, $meta_data ); + $this->process_and_sanitize_entity_data( $sanitized_entity_data, $error_records, $entity_data ); + $this->processs_and_sanitize_meta_data( $sanitized_entity_data, $error_records, $meta_data ); return array( 'data' => $sanitized_entity_data, @@ -348,10 +436,10 @@ WHERE * @param array $error_records Error records. * @param array $entity_data Original source data. */ - private function process_and_sanitize_entity_data( &$sanitized_entity_data, &$error_records, &$insert_switch, $entity_data ) { + private function process_and_sanitize_entity_data( &$sanitized_entity_data, &$error_records, $entity_data ) { foreach ( $entity_data as $entity ) { $row_data = array(); - foreach ( array_merge( $this->get_destination_table_primary_id_schema(), $this->core_column_mapping ) as $column_name => $schema ) { + foreach ( $this->core_column_mapping as $column_name => $schema ) { $custom_table_column_name = $schema['destination'] ?? $column_name; $value = $entity->$column_name; $value = $this->validate_data( $value, $schema['type'] ); @@ -361,8 +449,6 @@ WHERE $row_data[ $custom_table_column_name ] = $value; } } - $insert_switch[ $entity->entity_meta_rel_id ] = $entity->insert_switch; // Save the insert_switch in row data so that we can use it later. - $sanitized_entity_data[ $entity->entity_meta_rel_id ] = $row_data; } } @@ -374,16 +460,14 @@ WHERE * @param array $error_records Error records. * @param array $meta_data Original source data. */ - private function processs_and_sanitize_meta_data( &$sanitized_entity_data, &$error_records, &$insert_switch, $meta_data ) { + private function processs_and_sanitize_meta_data( &$sanitized_entity_data, &$error_records, $meta_data ) { foreach ( $meta_data as $datum ) { $column_schema = $this->meta_column_mapping[ $datum->meta_key ]; $value = $this->validate_data( $datum->meta_value, $column_schema['type'] ); if ( is_wp_error( $value ) ) { $error_records[ $datum->entity_id ][ $column_schema['destination'] ] = "{$value->get_error_code()}: {$value->get_error_message()}"; } else { - $insert_switch_string = $insert_switch[ $datum->entity_id ]; - - $sanitized_entity_data[ $insert_switch_string ][ $datum->entity_id ][ $column_schema['destination'] ] = $value; + $sanitized_entity_data[ $datum->entity_id ][ $column_schema['destination'] ] = $value; } } } diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php index bef0c8434cc..cbe9b78d7b1 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php @@ -90,6 +90,12 @@ class MetaToMetaTableMigrator { */ public function fetch_data_for_migration_for_ids( $order_post_ids ) { global $wpdb; + if ( empty( $order_post_ids ) ) { + return array( + 'data' => array(), + 'errors' => array(), + ); + } $meta_query = $this->build_meta_table_query( $order_post_ids ); @@ -114,34 +120,34 @@ class MetaToMetaTableMigrator { */ private function build_meta_table_query( $entity_ids ) { global $wpdb; - $source_meta_table = MigrationHelper::escape_backtick( $this->schema_config['source']['meta']['table_name'] ); - $source_meta_key_column = MigrationHelper::add_table_name_to_column( $source_meta_table, $this->schema_config['source']['meta']['meta_key_column'] ); - $source_meta_value_column = MigrationHelper::add_table_name_to_column( $source_meta_table, $this->schema_config['source']['meta']['meta_value_column'] ); - $source_entity_id_column = MigrationHelper::add_table_name_to_column( $source_meta_table, $this->schema_config['source']['meta']['entity_id_column'] ); + $source_meta_table = $this->schema_config['source']['meta']['table_name']; + $source_meta_key_column = $this->schema_config['source']['meta']['meta_key_column']; + $source_meta_value_column = $this->schema_config['source']['meta']['meta_value_column']; + $source_entity_id_column = $this->schema_config['source']['meta']['entity_id_column']; $order_by = "$source_entity_id_column ASC"; - $where_clause = "$source_entity_id_column IN (" . implode( array_fill( 0, count( $entity_ids ), '%d') ) . ')'; + $where_clause = "$source_entity_id_column IN (" . implode( ', ', array_fill( 0, count( $entity_ids ), '%d') ) . ')'; - $destination_entity_table = MigrationHelper::escape_backtick( $this->schema_config['destination']['entity']['table_name'] ); - $destination_entity_id_column = MigrationHelper::add_table_name_to_column( $destination_entity_table, $this->schema_config['destination']['entity']['id_column'] ); - $destination_source_id_mapping_column = MigrationHelper::add_table_name_to_column( $destination_entity_table, $this->schema_config['destination']['entity']['source_id_column'] ); + $destination_entity_table = $this->schema_config['destination']['entity']['table_name']; + $destination_entity_id_column = $this->schema_config['destination']['entity']['id_column']; + $destination_source_id_mapping_column = $this->schema_config['destination']['entity']['source_id_column']; if ( $this->schema_config['source']['excluded_keys'] ) { $key_placeholder = implode( ',', array_fill( 0, count( $this->schema_config['source']['excluded_keys'] ), '%s' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $source_meta_key_column is escated for backticks, $key_placeholder is hardcoded. - $exclude_clause = $wpdb->prepare( "$source_meta_key_column NOT IN ( $key_placeholder )", $this->schema_config['source']['excluded_keys'] ); + $exclude_clause = $wpdb->prepare( "source.$source_meta_key_column NOT IN ( $key_placeholder )", $this->schema_config['source']['excluded_keys'] ); $where_clause = "$where_clause AND $exclude_clause"; } return $wpdb->prepare( " SELECT - $source_entity_id_column as source_entity_id, - $destination_entity_id_column as destination_entity_id, - $source_meta_key_column as meta_key, - $source_meta_value_column as meta_value -FROM `$source_meta_table` -JOIN `$destination_entity_table` ON $destination_source_id_mapping_column = $source_entity_id_column + source.`$source_entity_id_column` as source_entity_id, + destination.`$destination_entity_id_column` as destination_entity_id, + source.`$source_meta_key_column` as meta_key, + source.`$source_meta_value_column` as meta_value +FROM `$source_meta_table` source +JOIN `$destination_entity_table` destination ON destination.`$destination_source_id_mapping_column` = source.`$source_entity_id_column` WHERE $where_clause ORDER BY $order_by ", $entity_ids diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php index cbc0e43da17..d03b5e5cdfa 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php @@ -6,6 +6,9 @@ namespace Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable; use Automattic\WooCommerce\DataBase\Migrations\MigrationErrorLogger; +use Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable\WPPostToOrderAddressTableMigrator; +use Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable\WPPostToOrderOpTableMigrator; +use Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable\WPPostToOrderTableMigrator; /** * Class WPPostToCOTMigrator @@ -56,366 +59,23 @@ class WPPostToCOTMigrator { */ private $meta_table_migrator; - /** - * Names of different order tables. - * - * @var array - */ - private $table_names; - /** * WPPostToCOTMigrator constructor. */ public function __construct() { - global $wpdb; - // TODO: Remove hardcoding. - $this->table_names = array( - 'orders' => $wpdb->prefix . 'wc_orders', - 'addresses' => $wpdb->prefix . 'wc_order_addresses', - 'op_data' => $wpdb->prefix . 'wc_order_operational_data', - 'meta' => $wpdb->prefix . 'wc_orders_meta', - ); + $this->order_table_migrator = new WPPostToOrderTableMigrator(); + $this->billing_address_table_migrator = new WPPostToOrderAddressTableMigrator( 'billing' ); + $this->shipping_address_table_migrator = new WPPostToOrderAddressTableMigrator( 'shipping' ); + $this->operation_data_table_migrator = new WPPostToOrderOpTableMigrator(); - $order_config = $this->get_config_for_order_table(); - $billing_address_config = $this->get_config_for_address_table_billing(); - $shipping_address_config = $this->get_config_for_address_table_shipping(); - $operation_data_config = $this->get_config_for_operational_data_table(); - $meta_data_config = $this->get_config_for_meta_table(); - - $this->order_table_migrator = new MetaToCustomTableMigrator( $order_config['schema'], $order_config['meta'], $order_config['core'] ); - $this->billing_address_table_migrator = new MetaToCustomTableMigrator( $billing_address_config['schema'], $billing_address_config['meta'], $billing_address_config['core'] ); - $this->shipping_address_table_migrator = new MetaToCustomTableMigrator( $shipping_address_config['schema'], $shipping_address_config['meta'], $shipping_address_config['core'] ); - $this->operation_data_table_migrator = new MetaToCustomTableMigrator( $operation_data_config['schema'], $operation_data_config['meta'], $operation_data_config['core'] ); + $meta_data_config = $this->get_config_for_meta_table(); $this->meta_table_migrator = new MetaToMetaTableMigrator( $meta_data_config ); $this->error_logger = new MigrationErrorLogger(); } - /** - * Returns migration configuration for order table. - * - * @return array Config for order table. - */ - private function get_config_for_order_table() { - global $wpdb; - $order_table_schema_config = array( - 'source' => array( - 'entity' => array( - 'table_name' => $wpdb->posts, - 'meta_rel_column' => 'ID', - 'destination_rel_column' => 'ID', - 'primary_key' => 'ID', - ), - 'meta' => array( - 'table_name' => $wpdb->postmeta, - 'meta_key_column' => 'meta_key', - 'meta_value_column' => 'meta_value', - 'entity_id_column' => 'post_id', - ), - ), - 'destination' => array( - 'table_name' => $this->table_names['orders'], - 'source_rel_column' => 'post_id', - ), - ); - - $order_table_core_config = array( - 'ID' => array( - 'type' => 'int', - 'destination' => 'post_id', - ), - 'post_status' => array( - 'type' => 'string', - 'destination' => 'status', - ), - 'post_date_gmt' => array( - 'type' => 'date', - 'destination' => 'date_created_gmt', - ), - 'post_modified_gmt' => array( - 'type' => 'date', - 'destination' => 'date_updated_gmt', - ), - 'post_parent' => array( - 'type' => 'int', - 'destination' => 'parent_order_id', - ), - ); - - $order_table_meta_config = array( - '_order_currency' => array( - 'type' => 'string', - 'destination' => 'currency', - ), - '_order_tax' => array( - 'type' => 'decimal', - 'destination' => 'tax_amount', - ), - '_order_total' => array( - 'type' => 'decimal', - 'destination' => 'total_amount', - ), - '_customer_user' => array( - 'type' => 'int', - 'destination' => 'customer_id', - ), - '_billing_email' => array( - 'type' => 'string', - 'destination' => 'billing_email', - ), - '_payment_method' => array( - 'type' => 'string', - 'destination' => 'payment_method', - ), - '_payment_method_title' => array( - 'type' => 'string', - 'destination' => 'payment_method_title', - ), - '_customer_ip_address' => array( - 'type' => 'string', - 'destination' => 'ip_address', - ), - '_customer_user_agent' => array( - 'type' => 'string', - 'destination' => 'user_agent', - ), - '_transaction_id' => array( - 'type' => 'string', - 'destination' => 'transaction_id', - ), - ); - - return array( - 'schema' => $order_table_schema_config, - 'core' => $order_table_core_config, - 'meta' => $order_table_meta_config, - ); - } - - /** - * Get configuration for billing addresses for migration. - * - * @return array Billing address migration config. - */ - private function get_config_for_address_table_billing() { - return $this->get_config_for_address_table( 'billing' ); - } - - /** - * Get configuration for shipping addresses for migration. - * - * @return array Shipping address migration config. - */ - private function get_config_for_address_table_shipping() { - return $this->get_config_for_address_table( 'shipping' ); - } - - /** - * Generate config for address data. - * - * @param string $type Type of address, this will be using in fetching meta keys. - * - * @return array Config for address table. - */ - private function get_config_for_address_table( $type ) { - global $wpdb; - // We join order core table and post meta table to get data for address, since we need order ID. - // So order core record needs to be already present. - $schema_config = array( - 'source' => array( - 'entity' => array( - 'table_name' => $this->table_names['orders'], - 'meta_rel_column' => 'post_id', - 'destination_rel_column' => 'id', - 'primary_key' => 'post_id', - ), - 'meta' => array( - 'table_name' => $wpdb->postmeta, - 'meta_key_column' => 'meta_key', - 'meta_value_column' => 'meta_value', - 'entity_id_column' => 'post_id', - ), - ), - 'destination' => array( - 'table_name' => $this->table_names['addresses'], - 'source_rel_column' => 'order_id', - ), - ); - - $core_config = array( - 'id' => array( - 'type' => 'int', - 'destination' => 'order_id', - ), - 'type' => array( - 'type' => 'string', - 'destination' => 'address_type', - 'select_clause' => "'$type'", - ), - ); - - $meta_config = array( - "_{$type}_first_name" => array( - 'type' => 'string', - 'destination' => 'first_name', - ), - "_{$type}_last_name" => array( - 'type' => 'string', - 'destination' => 'last_name', - ), - "_{$type}_company" => array( - 'type' => 'string', - 'destination' => 'company', - ), - "_{$type}_address_1" => array( - 'type' => 'string', - 'destination' => 'address_1', - ), - "_{$type}_address_2" => array( - 'type' => 'string', - 'destination' => 'address_2', - ), - "_{$type}_city" => array( - 'type' => 'string', - 'destination' => 'city', - ), - "_{$type}_state" => array( - 'type' => 'string', - 'destination' => 'state', - ), - "_{$type}_postcode" => array( - 'type' => 'string', - 'destination' => 'postcode', - ), - "_{$type}_country" => array( - 'type' => 'string', - 'destination' => 'country', - ), - "_{$type}_email" => array( - 'type' => 'string', - 'destination' => 'email', - ), - "_{$type}_phone" => array( - 'type' => 'string', - 'destination' => 'phone', - ), - ); - - return array( - 'schema' => $schema_config, - 'core' => $core_config, - 'meta' => $meta_config, - ); - } - - /** - * Generate config for operational data. - * - * @return array Config for operational data table. - */ - private function get_config_for_operational_data_table() { - global $wpdb; - - $schema_config = array( - 'source' => array( - 'entity' => array( - 'table_name' => $this->table_names['orders'], - 'meta_rel_column' => 'post_id', - 'destination_rel_column' => 'id', - 'primary_key' => 'post_id', - ), - 'meta' => array( - 'table_name' => $wpdb->postmeta, - 'meta_key_column' => 'meta_key', - 'meta_value_column' => 'meta_value', - 'entity_id_column' => 'post_id', - ), - ), - 'destination' => array( - 'table_name' => $this->table_names['op_data'], - 'source_rel_column' => 'order_id', - ), - ); - - $core_config = array( - 'id' => array( - 'type' => 'int', - 'destination' => 'order_id', - ), - ); - - $meta_config = array( - '_created_via' => array( - 'type' => 'string', - 'destination' => 'created_via', - ), - '_order_version' => array( - 'type' => 'string', - 'destination' => 'woocommerce_version', - ), - '_prices_include_tax' => array( - 'type' => 'bool', - 'destination' => 'prices_include_tax', - ), - '_recorded_coupon_usage_counts' => array( - 'type' => 'bool', - 'destination' => 'coupon_usages_are_counted', - ), - '_download_permissions_granted' => array( - 'type' => 'bool', - 'destination' => 'download_permission_granted', - ), - '_cart_hash' => array( - 'type' => 'string', - 'destination' => 'cart_hash', - ), - '_new_order_email_sent' => array( - 'type' => 'bool', - 'destination' => 'new_order_email_sent', - ), - '_order_key' => array( - 'type' => 'string', - 'destination' => 'order_key', - ), - '_order_stock_reduced' => array( - 'type' => 'bool', - 'destination' => 'order_stock_reduced', - ), - '_date_paid' => array( - 'type' => 'date_epoch', - 'destination' => 'date_paid_gmt', - ), - '_date_completed' => array( - 'type' => 'date_epoch', - 'destination' => 'date_completed_gmt', - ), - '_order_shipping_tax' => array( - 'type' => 'decimal', - 'destination' => 'shipping_tax_amount', - ), - '_order_shipping' => array( - 'type' => 'decimal', - 'destination' => 'shipping_total_amount', - ), - '_cart_discount_tax' => array( - 'type' => 'decimal', - 'destination' => 'discount_tax_amount', - ), - '_cart_discount' => array( - 'type' => 'decimal', - 'destination' => 'discount_total_amount', - ), - ); - - return array( - 'schema' => $schema_config, - 'core' => $core_config, - 'meta' => $meta_config, - ); - } - /** * Generate config for meta data migration. * @@ -423,11 +83,18 @@ class WPPostToCOTMigrator { */ private function get_config_for_meta_table() { global $wpdb; + // TODO: Remove hardcoding. + $this->table_names = array( + 'orders' => $wpdb->prefix . 'wc_orders', + 'addresses' => $wpdb->prefix . 'wc_order_addresses', + 'op_data' => $wpdb->prefix . 'wc_order_operational_data', + 'meta' => $wpdb->prefix . 'wc_orders_meta', + ); - $excluded_columns = array_keys( $this->get_config_for_order_table()['meta'] ); - $excluded_columns = array_merge( $excluded_columns, array_keys( $this->get_config_for_operational_data_table()['meta'] ) ); - $excluded_columns = array_merge( $excluded_columns, array_keys( $this->get_config_for_address_table_billing()['meta'] ) ); - $excluded_columns = array_merge( $excluded_columns, array_keys( $this->get_config_for_address_table_shipping()['meta'] ) ); + $excluded_columns = array_keys( $this->order_table_migrator->get_meta_column_config() ); + $excluded_columns = array_merge( $excluded_columns, array_keys( $this->billing_address_table_migrator->get_meta_column_config() ) ); + $excluded_columns = array_merge( $excluded_columns, array_keys( $this->shipping_address_table_migrator->get_meta_column_config() ) ); + $excluded_columns = array_merge( $excluded_columns, array_keys( $this->operation_data_table_migrator->get_meta_column_config() ) ); return array( 'source' => array( @@ -456,6 +123,66 @@ class WPPostToCOTMigrator { ); } + /** + * @param MetaToCustomTableMigrator $migrator + * @param $type + * @param $order_post_ids + */ + private function process_next_address_batch( $migrator, $type, $order_post_ids ) { + global $wpdb; + + if ( 0 === count( $order_post_ids ) ) { + return; + } + + $data = $migrator->fetch_data_for_migration_for_ids( $order_post_ids ); + + foreach ( $data['errors'] as $order_id => $error ) { + // TODO: Add name of the migrator in error message. + $this->error_logger->log( 'info', "Error in importing data for Order ID $order_id: " . print_r( $error, true ) ); + } + + $to_insert = isset( $data['data']['insert'] ) ? $data['data']['insert'] : array(); + $to_update = isset( $data['data']['update'] ) ? $data['data']['update'] : array(); + + if ( 0 === count( $to_insert ) && 0 === count( $to_update ) ) { + return; + } + + $order_post_ids_placeholders = implode( ',', array_fill( 0, count( $order_post_ids ), '%d' ) ); + + $already_migrated = $wpdb->get_results( + $wpdb->prepare( + " +SELECT addresses.id, addresses.order_id, orders.post_id FROM {$this->table_names['addresses']} as addresses +JOIN {$this->table_names['orders']} orders ON addresses.order_id = orders.id +WHERE + address_type = %s AND + orders.post_id IN ( $order_post_ids_placeholders ) + ", + $type, + $order_post_ids + ) + ); + + $order_post_ids_to_migrate = array_diff( $order_post_ids, array_column( $already_migrated, 'post_id' ) ); + $to_insert = array_intersect_key( $to_insert, array_flip( $order_post_ids_to_migrate ) ); + $this->execute_action_for_batch( $migrator, $to_insert, 'insert' ); + + if ( 0 < count( $to_update ) ) { + echo 'comes here'; + } + $to_update_intersect = array(); + $ids_to_update = array_flip( array_column( $already_migrated, 'id' ) ); + foreach ( $to_update as $post_id => $address_record ) { + if ( isset( $ids_to_update[ $address_record['id'] ] ) ) { + $to_update_intersect[ $post_id ] = $address_record; + } + } + + $this->execute_action_for_batch( $migrator, $to_update_intersect, 'update' ); + } + /** * Process next migration batch, uses option `wc_cot_migration` to checkpoints of what have been processed so far. * @@ -464,34 +191,17 @@ class WPPostToCOTMigrator { * @return bool True if migration is completed, false if there are still records to process. */ public function process_next_migration_batch( $batch_size = 100 ) { - global $wpdb; - - $order_data = $this->order_table_migrator->fetch_data_for_migration_for_ids( $this->get_next_batch_ids( $batch_size ) ); - - foreach ( $order_data['errors'] as $post_id => $error ) { - $this->error_logger->log( 'info', "Error in importing post id $post_id: " . print_r( $error, true ) ); - } - - if ( count( $order_data['data'] ) === 0 ) { + $order_post_ids = $this->get_next_batch_ids( $batch_size ); + if ( 0 === count( $order_post_ids ) ) { return true; } - $queries = $this->order_table_migrator->generate_insert_sql_for_batch( $order_data['data'], 'insert' ); - $result = $wpdb->query( $queries ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $queries is already prepared. - $wpdb->query("COMMIT;"); // For some reason, this seems necessary on some hosts? Maybe a MySQL configuration? - if ( count( $order_data['data'] ) !== $result ) { - // Some rows were not inserted. - // TODO: Find and log the entity ids that were not inserted. - echo ' error '; - } + $this->order_table_migrator->process_migration_batch_for_ids( $order_post_ids ); + $this->billing_address_table_migrator->process_migration_batch_for_ids( $order_post_ids ); + $this->shipping_address_table_migrator->process_migration_batch_for_ids( $order_post_ids ); + $this->operation_data_table_migrator->process_migration_batch_for_ids( $order_post_ids ); - $order_post_ids = array_column( $order_data['data'], 'post_id' ); - $this->process_next_migrator_batch( $this->billing_address_table_migrator, $order_post_ids ); - $this->process_next_migrator_batch( $this->shipping_address_table_migrator, $order_post_ids ); - $this->process_next_migrator_batch( $this->operation_data_table_migrator, $order_post_ids ); - $this->process_meta_migration( $order_post_ids ); - - $last_post_migrated = max( array_keys( $order_data['data'] ) ); + $last_post_migrated = max( $order_post_ids ); $this->update_checkpoint( $last_post_migrated ); return false; @@ -505,20 +215,18 @@ class WPPostToCOTMigrator { * @param string $order_by Order by clause. */ private function process_next_migrator_batch( $migrator, $order_post_ids ) { - global $wpdb; - $data = $migrator->fetch_data_for_migration_for_ids( $order_post_ids ); + $data = $migrator->fetch_data_for_migration_for_ids( $order_post_ids ); + foreach ( $data['errors'] as $order_id => $error ) { // TODO: Add name of the migrator in error message. $this->error_logger->log( 'info', "Error in importing data for Order ID $order_id: " . print_r( $error, true ) ); } - $queries = $migrator->generate_insert_sql_for_batch( $data['data'], 'insert' ); - $result = $wpdb->query( $queries ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Insert statements should already be escaped. - $wpdb->query('COMMIT;'); // For some reason, this seems necessary on some hosts? Maybe a MySQL configuration? - if ( count( $data['data'] ) !== $result ) { - // Some rows were not inserted. - // TODO: Find and log the entity ids that were not inserted. - echo 'error'; - } + + $to_replace = isset( $data['data']['update'] ) ? $data['data']['update'] : array(); + $this->execute_action_for_batch( $migrator, $to_replace, 'update' ); + + $to_insert = isset( $data['data']['insert'] ) ? $data['data']['insert'] : array(); + $this->execute_action_for_batch( $migrator, $to_insert, 'insert' ); } /** @@ -528,8 +236,8 @@ class WPPostToCOTMigrator { */ private function process_meta_migration( $order_post_ids ) { global $wpdb; - $data_to_migrate = $this->meta_table_migrator->fetch_data_for_migration_for_ids( $order_post_ids ); - $insert_queries = $this->meta_table_migrator->generate_insert_sql_for_batch( $data_to_migrate['data'], 'insert' ); + $data_to_migrate = $this->meta_table_migrator->fetch_data_for_migration_for_ids( $order_post_ids ); + $insert_queries = $this->meta_table_migrator->generate_insert_sql_for_batch( $data_to_migrate['data'], 'insert' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $insert_queries should already be escaped in the generating function. $result = $wpdb->query( $insert_queries ); if ( count( $data_to_migrate['data'] ) !== $result ) { @@ -574,8 +282,8 @@ class WPPostToCOTMigrator { private function get_next_batch_ids( $batch_size ) { global $wpdb; - $checkpoint = $this->get_checkpoint(); - $post_ids = $wpdb->get_col( + $checkpoint = $this->get_checkpoint(); + $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE ID > %d AND post_type = '%s' LIMIT %d", $checkpoint['id'], @@ -622,7 +330,7 @@ class WPPostToCOTMigrator { * @param int $id Order ID. */ public function update_checkpoint( $id ) { - return update_option( 'wc_cot_migration', array( 'id' => $id ) ); + return update_option( 'wc_cot_migration', array( 'id' => $id ), false ); } public function delete_checkpoint() { diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php index e69de29bb2d..55c7fb438bc 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php @@ -0,0 +1,141 @@ +type = $type; + parent::__construct(); + } + + function get_schema_config() { + global $wpdb; + // TODO: Remove hardcoding. + $this->table_names = array( + 'orders' => $wpdb->prefix . 'wc_orders', + 'addresses' => $wpdb->prefix . 'wc_order_addresses', + 'op_data' => $wpdb->prefix . 'wc_order_operational_data', + 'meta' => $wpdb->prefix . 'wc_orders_meta', + ); + + return array( + 'source' => array( + 'entity' => array( + 'table_name' => $this->table_names['orders'], + 'meta_rel_column' => 'post_id', + 'destination_rel_column' => 'id', + 'primary_key' => 'post_id', + ), + 'meta' => array( + 'table_name' => $wpdb->postmeta, + 'meta_key_column' => 'meta_key', + 'meta_value_column' => 'meta_value', + 'entity_id_column' => 'post_id', + ), + ), + 'destination' => array( + 'table_name' => $this->table_names['addresses'], + 'source_rel_column' => 'order_id', + 'primary_key' => 'id', + 'primary_key_type' => 'int', + ), + ); + } + + function get_core_column_mapping() { + $type = $this->type; + return array( + 'id' => array( + 'type' => 'int', + 'destination' => 'order_id', + ), + 'type' => array( + 'type' => 'string', + 'destination' => 'address_type', + 'select_clause' => "'$type'", + ), + ); + } + + function get_meta_column_config() { + $type = $this->type; + return array( + "_{$type}_first_name" => array( + 'type' => 'string', + 'destination' => 'first_name', + ), + "_{$type}_last_name" => array( + 'type' => 'string', + 'destination' => 'last_name', + ), + "_{$type}_company" => array( + 'type' => 'string', + 'destination' => 'company', + ), + "_{$type}_address_1" => array( + 'type' => 'string', + 'destination' => 'address_1', + ), + "_{$type}_address_2" => array( + 'type' => 'string', + 'destination' => 'address_2', + ), + "_{$type}_city" => array( + 'type' => 'string', + 'destination' => 'city', + ), + "_{$type}_state" => array( + 'type' => 'string', + 'destination' => 'state', + ), + "_{$type}_postcode" => array( + 'type' => 'string', + 'destination' => 'postcode', + ), + "_{$type}_country" => array( + 'type' => 'string', + 'destination' => 'country', + ), + "_{$type}_email" => array( + 'type' => 'string', + 'destination' => 'email', + ), + "_{$type}_phone" => array( + 'type' => 'string', + 'destination' => 'phone', + ), + ); + } + + public function get_already_migrated_records( $entity_ids ) { + global $wpdb; + $source_table = $this->schema_config['source']['entity']['table_name']; + $source_destination_join_column = $this->schema_config['source']['entity']['destination_rel_column']; + $source_primary_key_column = $this->schema_config['source']['entity']['primary_key']; + + $destination_table = $this->schema_config['destination']['table_name']; + $destination_source_join_column = $this->schema_config['destination']['source_rel_column']; + $destination_primary_key_column = $this->schema_config['destination']['primary_key']; + + $address_type = $this->type; + + $entity_id_placeholder = implode( ',', array_fill( 0, count( $entity_ids ), '%d' ) ); + + $already_migrated_entity_ids = $wpdb->get_results( + $wpdb->prepare( + " +SELECT source.`$source_primary_key_column` as source_id, destination.`$destination_primary_key_column` as destination_id +FROM `$destination_table` destination +JOIN `$source_table` source ON source.`$source_destination_join_column` = destination.`$destination_source_join_column` +WHERE source.`$source_primary_key_column` IN ( $entity_id_placeholder ) AND destination.`address_type` = '$address_type' + ", + $entity_ids + ) + ); + + return array_column( $already_migrated_entity_ids, null, 'source_id' ); + } +} diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php new file mode 100644 index 00000000000..6c7645a6b31 --- /dev/null +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php @@ -0,0 +1,114 @@ +table_names = array( + 'orders' => $wpdb->prefix . 'wc_orders', + 'addresses' => $wpdb->prefix . 'wc_order_addresses', + 'op_data' => $wpdb->prefix . 'wc_order_operational_data', + 'meta' => $wpdb->prefix . 'wc_orders_meta', + ); + + return array( + 'source' => array( + 'entity' => array( + 'table_name' => $this->table_names['orders'], + 'meta_rel_column' => 'post_id', + 'destination_rel_column' => 'id', + 'primary_key' => 'post_id', + ), + 'meta' => array( + 'table_name' => $wpdb->postmeta, + 'meta_key_column' => 'meta_key', + 'meta_value_column' => 'meta_value', + 'entity_id_column' => 'post_id', + ), + ), + 'destination' => array( + 'table_name' => $this->table_names['op_data'], + 'source_rel_column' => 'order_id', + 'primary_key' => 'id', + 'primary_key_type' => 'int', + ), + ); + } + + public function get_core_column_mapping() { + return array( + 'id' => array( + 'type' => 'int', + 'destination' => 'order_id', + ), + ); + } + + public function get_meta_column_config() { + return array( + '_created_via' => array( + 'type' => 'string', + 'destination' => 'created_via', + ), + '_order_version' => array( + 'type' => 'string', + 'destination' => 'woocommerce_version', + ), + '_prices_include_tax' => array( + 'type' => 'bool', + 'destination' => 'prices_include_tax', + ), + '_recorded_coupon_usage_counts' => array( + 'type' => 'bool', + 'destination' => 'coupon_usages_are_counted', + ), + '_download_permissions_granted' => array( + 'type' => 'bool', + 'destination' => 'download_permission_granted', + ), + '_cart_hash' => array( + 'type' => 'string', + 'destination' => 'cart_hash', + ), + '_new_order_email_sent' => array( + 'type' => 'bool', + 'destination' => 'new_order_email_sent', + ), + '_order_key' => array( + 'type' => 'string', + 'destination' => 'order_key', + ), + '_order_stock_reduced' => array( + 'type' => 'bool', + 'destination' => 'order_stock_reduced', + ), + '_date_paid' => array( + 'type' => 'date_epoch', + 'destination' => 'date_paid_gmt', + ), + '_date_completed' => array( + 'type' => 'date_epoch', + 'destination' => 'date_completed_gmt', + ), + '_order_shipping_tax' => array( + 'type' => 'decimal', + 'destination' => 'shipping_tax_amount', + ), + '_order_shipping' => array( + 'type' => 'decimal', + 'destination' => 'shipping_total_amount', + ), + '_cart_discount_tax' => array( + 'type' => 'decimal', + 'destination' => 'discount_tax_amount', + ), + '_cart_discount' => array( + 'type' => 'decimal', + 'destination' => 'discount_total_amount', + ), + ); + } +} diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php new file mode 100644 index 00000000000..b0b7a7ec840 --- /dev/null +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php @@ -0,0 +1,117 @@ +table_names = array( + 'orders' => $wpdb->prefix . 'wc_orders', + 'addresses' => $wpdb->prefix . 'wc_order_addresses', + 'op_data' => $wpdb->prefix . 'wc_order_operational_data', + 'meta' => $wpdb->prefix . 'wc_orders_meta', + ); + + return array( + 'source' => array( + 'entity' => array( + 'table_name' => $wpdb->posts, + 'meta_rel_column' => 'ID', + 'destination_rel_column' => 'ID', + 'primary_key' => 'ID', + ), + 'meta' => array( + 'table_name' => $wpdb->postmeta, + 'meta_key_column' => 'meta_key', + 'meta_value_column' => 'meta_value', + 'entity_id_column' => 'post_id', + ), + ), + 'destination' => array( + 'table_name' => $this->table_names['orders'], + 'source_rel_column' => 'post_id', + 'primary_key' => 'id', + 'primary_key_type' => 'int', + ), + ); + } + + public function get_core_column_mapping() { + return array( + 'ID' => array( + 'type' => 'int', + 'destination' => 'post_id', + ), + 'post_status' => array( + 'type' => 'string', + 'destination' => 'status', + ), + 'post_date_gmt' => array( + 'type' => 'date', + 'destination' => 'date_created_gmt', + ), + 'post_modified_gmt' => array( + 'type' => 'date', + 'destination' => 'date_updated_gmt', + ), + 'post_parent' => array( + 'type' => 'int', + 'destination' => 'parent_order_id', + ), + ); + } + + public function get_meta_column_config() { + return array( + '_order_currency' => array( + 'type' => 'string', + 'destination' => 'currency', + ), + '_order_tax' => array( + 'type' => 'decimal', + 'destination' => 'tax_amount', + ), + '_order_total' => array( + 'type' => 'decimal', + 'destination' => 'total_amount', + ), + '_customer_user' => array( + 'type' => 'int', + 'destination' => 'customer_id', + ), + '_billing_email' => array( + 'type' => 'string', + 'destination' => 'billing_email', + ), + '_payment_method' => array( + 'type' => 'string', + 'destination' => 'payment_method', + ), + '_payment_method_title' => array( + 'type' => 'string', + 'destination' => 'payment_method_title', + ), + '_customer_ip_address' => array( + 'type' => 'string', + 'destination' => 'ip_address', + ), + '_customer_user_agent' => array( + 'type' => 'string', + 'destination' => 'user_agent', + ), + '_transaction_id' => array( + 'type' => 'string', + 'destination' => 'transaction_id', + ), + ); + } +} diff --git a/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php b/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php index 72e1c3ad4fd..d0af0007a4f 100644 --- a/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php +++ b/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php @@ -40,9 +40,12 @@ class MigrationHelper { case 'insert_ignore': $insert_query = 'INSERT IGNORE'; break; - case 'replace': + case 'replace': // delete and then insert. $insert_query = 'REPLACE'; break; + case 'update': + $insert_query = 'UPDATE'; + break; case 'insert': default: $insert_query = 'INSERT'; @@ -51,6 +54,13 @@ class MigrationHelper { return $insert_query; } + public static function escape_schema_for_backtick( $schema_config ) { + array_walk( $schema_config['source']['entity'], array( self::class, 'escape_and_add_backtick' ) ); + array_walk( $schema_config['source']['meta'], array( self::class, 'escape_and_add_backtick' ) ); + array_walk( $schema_config['destination'], array( self::class, 'escape_and_add_backtick' ) ); + return $schema_config; + } + /** * Helper method to escape backtick in column and table names. * WP does not provide a method to escape table/columns names yet, but hopefully soon in @link https://core.trac.wordpress.org/ticket/52506 @@ -59,8 +69,8 @@ class MigrationHelper { * * @return array|string|string[] Escaped identifier. */ - public static function escape_backtick( $identifier ) { - return str_replace( '`', '``', $identifier ); + public static function escape_and_add_backtick( $identifier ) { + return '`' . str_replace( '`', '``', $identifier ) . '`'; } /** @@ -74,10 +84,4 @@ class MigrationHelper { return self::$wpdb_placeholder_for_type[ $type ]; } - public static function add_table_name_to_column( $table_name, $column_name ) { - $table_name = self::escape_backtick( $table_name ); - $column_name = self::escape_backtick( $column_name ); - return "`$table_name`.`$column_name`"; - } - } From eff7569dfc7fd40501989ff9b9869064ad153582 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 31 Mar 2022 18:16:42 +0530 Subject: [PATCH 186/386] Add unit tests for handling duplicates. --- .../WPPostToCOTMigratorTest.php | 240 +++++++++++------- 1 file changed, 152 insertions(+), 88 deletions(-) diff --git a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php index 57d7868b06d..4de5d1cfce6 100644 --- a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php +++ b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php @@ -35,136 +35,113 @@ class WPPostToCOTMigratorTest extends \WC_Unit_Test_Case { parent::setUp(); $this->create_order_custom_table_if_not_exist(); $this->data_store = wc_get_container()->get( OrdersTableDataStore::class ); - $this->sut = wc_get_container()->get( WPPostToCOTMigrator::class ); + $this->sut = wc_get_container()->get( WPPostToCOTMigrator::class ); } public function test_process_next_migration_batch_normal_order() { - $order = wc_get_order( $this->create_complex_wp_post_order() ); $this->clear_all_orders_and_reset_checkpoint(); $this->sut->process_next_migration_batch( 100 ); - $db_order = $this->get_order_from_cot( $order ); - - // Verify core data. - $this->assertEquals( $order->get_id(), $db_order->post_id ); - $this->assertEquals( 'wc-' . $order->get_status(), $db_order->status ); - $this->assertEquals( 'INR', $db_order->currency ); - $this->assertEquals( $order->get_customer_id(), $db_order->customer_id ); - $this->assertEquals( $order->get_billing_email(), $db_order->billing_email ); - $this->assertEquals( $order->get_payment_method(), $db_order->payment_method ); - $this->assertEquals( - $order->get_date_created()->date( DATE_ISO8601 ), - ( new WC_DateTime( $db_order->date_created_gmt ) )->date( DATE_ISO8601 ) - ); - $this->assertEquals( $order->get_date_modified()->date( DATE_ISO8601 ), ( new WC_DateTime( $db_order->date_updated_gmt ) )->date( DATE_ISO8601 ) ); - $this->assertEquals( $order->get_parent_id(), $db_order->parent_order_id ); - $this->assertEquals( $order->get_payment_method_title(), $db_order->payment_method_title ); - $this->assertEquals( $order->get_transaction_id(), $db_order->transaction_id ); - $this->assertEquals( $order->get_customer_ip_address(), $db_order->ip_address ); - $this->assertEquals( $order->get_customer_user_agent(), $db_order->user_agent ); - - // Verify order billing address. - $db_order_address = $this->get_address_details_from_cot( $db_order->id, 'billing' ); - $this->assertEquals( $order->get_billing_first_name(), $db_order_address->first_name ); - $this->assertEquals( $order->get_billing_last_name(), $db_order_address->last_name ); - $this->assertEquals( $order->get_billing_company(), $db_order_address->company ); - $this->assertEquals( $order->get_billing_address_1(), $db_order_address->address_1 ); - $this->assertEquals( $order->get_billing_address_2(), $db_order_address->address_2 ); - $this->assertEquals( $order->get_billing_city(), $db_order_address->city ); - $this->assertEquals( $order->get_billing_postcode(), $db_order_address->postcode ); - $this->assertEquals( $order->get_billing_country(), $db_order_address->country ); - $this->assertEquals( $order->get_billing_email(), $db_order_address->email ); - $this->assertEquals( $order->get_billing_phone(), $db_order_address->phone ); - - // Verify order shipping address. - $db_order_address = $this->get_address_details_from_cot( $db_order->id, 'shipping' ); - $this->assertEquals( $order->get_shipping_first_name(), $db_order_address->first_name ); - $this->assertEquals( $order->get_shipping_last_name(), $db_order_address->last_name ); - $this->assertEquals( $order->get_shipping_company(), $db_order_address->company ); - $this->assertEquals( $order->get_shipping_address_1(), $db_order_address->address_1 ); - $this->assertEquals( $order->get_shipping_address_2(), $db_order_address->address_2 ); - $this->assertEquals( $order->get_shipping_city(), $db_order_address->city ); - $this->assertEquals( $order->get_shipping_postcode(), $db_order_address->postcode ); - $this->assertEquals( $order->get_shipping_country(), $db_order_address->country ); - $this->assertEquals( $order->get_shipping_phone(), $db_order_address->phone ); - - // Verify order operational data. - $db_order_op_data = $this->get_order_operational_data_from_cot( $db_order->id ); - $this->assertEquals( $order->get_created_via(), $db_order_op_data->created_via ); - $this->assertEquals( $order->get_version(), $db_order_op_data->woocommerce_version ); - $this->assertEquals( $order->get_prices_include_tax(), $db_order_op_data->prices_include_tax ); - $this->assertEquals( - wc_string_to_bool( $order->get_data_store()->get_recorded_coupon_usage_counts( $order ) ), - $db_order_op_data->coupon_usages_are_counted - ); - $this->assertEquals( - wc_string_to_bool( $order->get_data_store()->get_download_permissions_granted( $order ) ), - $db_order_op_data->download_permission_granted - ); - $this->assertEquals( $order->get_cart_hash(), $db_order_op_data->cart_hash ); - $this->assertEquals( - wc_string_to_bool( $order->get_meta( '_new_order_email_sent' ) ), - $db_order_op_data->new_order_email_sent - ); - $this->assertEquals( $order->get_order_key(), $db_order_op_data->order_key ); - $this->assertEquals( $order->get_data_store()->get_stock_reduced( $order ), $db_order_op_data->order_stock_reduced ); - $this->assertEquals( - $order->get_date_paid()->date( DATE_ISO8601 ), - ( new WC_DateTime( $db_order_op_data->date_paid_gmt ) )->date( DATE_ISO8601 ) - ); - $this->assertEquals( - $order->get_date_completed()->date( DATE_ISO8601 ), - ( new WC_DateTime( $db_order_op_data->date_completed_gmt ) )->date( DATE_ISO8601 ) - ); - $this->assertEquals( $order->get_shipping_tax(), $db_order_op_data->shipping_tax_amount ); - $this->assertEquals( $order->get_shipping_total(), $db_order_op_data->shipping_total_amount ); - $this->assertEquals( $order->get_discount_tax(), $db_order_op_data->discount_tax_amount ); - $this->assertEquals( $order->get_discount_total(), $db_order_op_data->discount_total_amount ); - } - - public function test_process_next_migration_batch_interrupted_migrating_order() { + $this->assert_core_data_is_migrated( $order ); + $this->assert_order_addresses_are_migrated( $order ); + $this->assert_order_op_data_is_migrated( $order ); } public function test_process_next_migration_batch_already_migrated_order() { + global $wpdb; + $order = wc_get_order( $this->create_complex_wp_post_order() ); + $this->clear_all_orders_and_reset_checkpoint(); + // Run the migration once. + $this->sut->process_next_migration_batch( 100 ); + + // Delete checkpoint and run migration again, assert there are still no duplicates. + $this->sut->update_checkpoint( 0 ); + $this->sut->process_next_migration_batch( 100 ); + + $this->assertEquals( + 1, + $wpdb->get_var( + " +SELECT COUNT(*) FROM {$this->data_store::get_orders_table_name()} +WHERE post_id = {$order->get_id()}" + ), + 'Order record is duplicated.' + ); + $order_id = $wpdb->get_var( "SELECT id FROM {$this->data_store::get_orders_table_name()} WHERE post_id = {$order->get_id()}" ); + $this->assertEquals( + 1, + $wpdb->get_var( + " +SELECT COUNT(*) FROM {$this->data_store::get_addresses_table_name()} +WHERE order_id = {$order_id} AND address_type = 'billing' +" + ) + ); + $this->assertEquals( + 1, + $wpdb->get_var( + " +SELECT COUNT(*) FROM {$this->data_store::get_addresses_table_name()} +WHERE order_id = {$order_id} AND address_type = 'shipping' +" + ) + ); + $this->assertEquals( + 1, + $wpdb->get_var( + " +SELECT COUNT(*) FROM {$this->data_store::get_operational_data_table_name()} +WHERE order_id = {$order_id} +" + ) + ); + } + + public function test_process_next_migration_batch_interrupted_migrating_order() { + $this->markTestSkipped(); } public function test_process_next_migration_batch_invalid_order_data() { - + $this->markTestSkipped(); } public function test_process_next_migration_batch_invalid_valid_order_combo() { - + $this->markTestSkipped(); } public function test_process_next_migration_batch_stale_order() { - + $this->markTestSkipped(); } private function get_order_from_cot( $post_order ) { global $wpdb; $order_table = $this->data_store::get_orders_table_name(); - $query = "SELECT * FROM $order_table WHERE post_id = {$post_order->get_id()};"; + $query = "SELECT * FROM $order_table WHERE post_id = {$post_order->get_id()};"; + return $wpdb->get_row( $query ); } private function get_address_details_from_cot( $order_id, $address_type ) { global $wpdb; $address_table = $this->data_store::get_addresses_table_name(); + return $wpdb->get_row( "SELECT * FROM $address_table WHERE order_id = $order_id AND address_type = '$address_type';" ); } private function get_order_operational_data_from_cot( $order_id ) { global $wpdb; $operational_data_table = $this->data_store::get_operational_data_table_name(); + return $wpdb->get_row( "SELECT * FROM $operational_data_table WHERE order_id = $order_id;" ); } private function create_complex_wp_post_order() { update_option( 'woocommerce_prices_include_tax', 'yes' ); update_option( 'woocommerce_calc_taxes', 'yes' ); - $customer = CustomerHelper::create_customer(); + $uniq_cust_id = wp_generate_password( 10, false ); + $customer = CustomerHelper::create_customer( "user$uniq_cust_id", $uniq_cust_id, "user$uniq_cust_id@example.com" ); $tax_rate = array( 'tax_rate_country' => '', 'tax_rate_state' => '', @@ -228,6 +205,93 @@ class WPPostToCOTMigratorTest extends \WC_Unit_Test_Case { return $order->get_id(); } + private function assert_core_data_is_migrated( $order ) { + $db_order = $this->get_order_from_cot( $order ); + + // Verify core data. + $this->assertEquals( $order->get_id(), $db_order->post_id ); + $this->assertEquals( 'wc-' . $order->get_status(), $db_order->status ); + $this->assertEquals( 'INR', $db_order->currency ); + $this->assertEquals( $order->get_customer_id(), $db_order->customer_id ); + $this->assertEquals( $order->get_billing_email(), $db_order->billing_email ); + $this->assertEquals( $order->get_payment_method(), $db_order->payment_method ); + $this->assertEquals( + $order->get_date_created()->date( DATE_ISO8601 ), + ( new WC_DateTime( $db_order->date_created_gmt ) )->date( DATE_ISO8601 ) + ); + $this->assertEquals( $order->get_date_modified()->date( DATE_ISO8601 ), ( new WC_DateTime( $db_order->date_updated_gmt ) )->date( DATE_ISO8601 ) ); + $this->assertEquals( $order->get_parent_id(), $db_order->parent_order_id ); + $this->assertEquals( $order->get_payment_method_title(), $db_order->payment_method_title ); + $this->assertEquals( $order->get_transaction_id(), $db_order->transaction_id ); + $this->assertEquals( $order->get_customer_ip_address(), $db_order->ip_address ); + $this->assertEquals( $order->get_customer_user_agent(), $db_order->user_agent ); + } + + private function assert_order_addresses_are_migrated( $order ) { + $db_order = $this->get_order_from_cot( $order ); + + // Verify order billing address. + $db_order_address = $this->get_address_details_from_cot( $db_order->id, 'billing' ); + $this->assertEquals( $order->get_billing_first_name(), $db_order_address->first_name ); + $this->assertEquals( $order->get_billing_last_name(), $db_order_address->last_name ); + $this->assertEquals( $order->get_billing_company(), $db_order_address->company ); + $this->assertEquals( $order->get_billing_address_1(), $db_order_address->address_1 ); + $this->assertEquals( $order->get_billing_address_2(), $db_order_address->address_2 ); + $this->assertEquals( $order->get_billing_city(), $db_order_address->city ); + $this->assertEquals( $order->get_billing_postcode(), $db_order_address->postcode ); + $this->assertEquals( $order->get_billing_country(), $db_order_address->country ); + $this->assertEquals( $order->get_billing_email(), $db_order_address->email ); + $this->assertEquals( $order->get_billing_phone(), $db_order_address->phone ); + + // Verify order shipping address. + $db_order_address = $this->get_address_details_from_cot( $db_order->id, 'shipping' ); + $this->assertEquals( $order->get_shipping_first_name(), $db_order_address->first_name ); + $this->assertEquals( $order->get_shipping_last_name(), $db_order_address->last_name ); + $this->assertEquals( $order->get_shipping_company(), $db_order_address->company ); + $this->assertEquals( $order->get_shipping_address_1(), $db_order_address->address_1 ); + $this->assertEquals( $order->get_shipping_address_2(), $db_order_address->address_2 ); + $this->assertEquals( $order->get_shipping_city(), $db_order_address->city ); + $this->assertEquals( $order->get_shipping_postcode(), $db_order_address->postcode ); + $this->assertEquals( $order->get_shipping_country(), $db_order_address->country ); + $this->assertEquals( $order->get_shipping_phone(), $db_order_address->phone ); + } + + private function assert_order_op_data_is_migrated( $order ) { + $db_order = $this->get_order_from_cot( $order ); + // Verify order operational data. + $db_order_op_data = $this->get_order_operational_data_from_cot( $db_order->id ); + $this->assertEquals( $order->get_created_via(), $db_order_op_data->created_via ); + $this->assertEquals( $order->get_version(), $db_order_op_data->woocommerce_version ); + $this->assertEquals( $order->get_prices_include_tax(), $db_order_op_data->prices_include_tax ); + $this->assertEquals( + wc_string_to_bool( $order->get_data_store()->get_recorded_coupon_usage_counts( $order ) ), + $db_order_op_data->coupon_usages_are_counted + ); + $this->assertEquals( + wc_string_to_bool( $order->get_data_store()->get_download_permissions_granted( $order ) ), + $db_order_op_data->download_permission_granted + ); + $this->assertEquals( $order->get_cart_hash(), $db_order_op_data->cart_hash ); + $this->assertEquals( + wc_string_to_bool( $order->get_meta( '_new_order_email_sent' ) ), + $db_order_op_data->new_order_email_sent + ); + $this->assertEquals( $order->get_order_key(), $db_order_op_data->order_key ); + $this->assertEquals( $order->get_data_store()->get_stock_reduced( $order ), $db_order_op_data->order_stock_reduced ); + $this->assertEquals( + $order->get_date_paid()->date( DATE_ISO8601 ), + ( new WC_DateTime( $db_order_op_data->date_paid_gmt ) )->date( DATE_ISO8601 ) + ); + $this->assertEquals( + $order->get_date_completed()->date( DATE_ISO8601 ), + ( new WC_DateTime( $db_order_op_data->date_completed_gmt ) )->date( DATE_ISO8601 ) + ); + $this->assertEquals( $order->get_shipping_tax(), $db_order_op_data->shipping_tax_amount ); + $this->assertEquals( $order->get_shipping_total(), $db_order_op_data->shipping_total_amount ); + $this->assertEquals( $order->get_discount_tax(), $db_order_op_data->discount_tax_amount ); + $this->assertEquals( $order->get_discount_total(), $db_order_op_data->discount_total_amount ); + } + private function clear_all_orders_and_reset_checkpoint() { global $wpdb; $order_tables = $this->data_store->get_all_table_names(); @@ -243,7 +307,7 @@ class WPPostToCOTMigratorTest extends \WC_Unit_Test_Case { $order_table_controller->show_feature(); $this->synchronizer = wc_get_container() ->get( DataSynchronizer::class ); - if( ! $this->synchronizer->check_orders_table_exists() ) { + if ( ! $this->synchronizer->check_orders_table_exists() ) { $this->synchronizer->create_database_tables(); } } From 02376e6ec5b73417a33d2f31b65f3fa3653961e0 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 31 Mar 2022 19:19:17 +0530 Subject: [PATCH 187/386] Add return statement to try to fix CI. --- .../Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php index 4de5d1cfce6..c2ae02a084e 100644 --- a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php +++ b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php @@ -31,7 +31,7 @@ class WPPostToCOTMigratorTest extends \WC_Unit_Test_Case { */ private $data_store; - public function setUp() { + public function setUp(): void { parent::setUp(); $this->create_order_custom_table_if_not_exist(); $this->data_store = wc_get_container()->get( OrdersTableDataStore::class ); From d5b164622f1e1695beaa4518796330cc9013b477 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 31 Mar 2022 19:44:29 +0530 Subject: [PATCH 188/386] Applied coding standards. --- .../MetaToCustomTableMigrator.php | 213 ++++++++++++------ 1 file changed, 143 insertions(+), 70 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php index ad8d6e2e05b..7347781a3fe 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php @@ -35,34 +35,72 @@ abstract class MetaToCustomTableMigrator { */ protected $core_column_mapping; + /** + * Store errors along with entity IDs from migrations. + * + * @var array + */ protected $errors; /** * MetaToCustomTableMigrator constructor. + */ + public function __construct() { + $this->schema_config = MigrationHelper::escape_schema_for_backtick( $this->get_schema_config() ); + $this->meta_column_mapping = $this->get_meta_column_config(); + $this->core_column_mapping = $this->get_core_column_mapping(); + $this->errors = array(); + } + + /** + * Specify schema config the source and destination table. * - * @param array $schema_config This parameters provides general but essential information about tables under migrations. Must be of the form- + * @return array Schema, must of the form: * array( - * 'entity_schema' => - * array ( - * 'primary_id' => 'primary_id column name of source table', - * 'table_name' => 'name of the source table'. - * ), - * 'entity_meta_schema' => - * array ( - * 'meta_key_column' => 'name of meta_key column in source meta table', - * 'meta_value_column' => 'name of meta_value column in source meta table', - * 'table_name' => 'name of source meta table', - * ), - * 'destination_table' => 'name of destination custom table', - * 'entity_meta_relation' => - * array ( - * 'entity' => 'name of column in source table which is used in source meta table', - * 'meta' => 'name of column in source meta table which contains key of records in source table', - * ) - * ) - * ). + 'source' => array( + 'entity' => array( + 'table_name' => $source_table_name, + 'meta_rel_column' => $column_meta, Name of column in source table which is referenced by meta table. + 'destination_rel_column' => $column_dest, Name of column in source table which is refenced by destination table, + 'primary_key' => $primary_key, Primary key of the source table + ), + 'meta' => array( + 'table' => $meta_table_name, + 'meta_key_column' => $meta_key_column_name, + 'meta_value_column' => $meta_value_column_name, + 'entity_id_column' => $entity_id_column, Name of the column having entity IDs. + ), + ), + 'destination' => array( + 'table_name' => $table_name, Name of destination table, + 'source_rel_column' => $column_source_id, Name of the column in destination table which is referenced by source table. + 'primary_key' => $table_primary_key, + 'primary_key_type' => $type bool|int|string|decimal + ) + */ + abstract public function get_schema_config(); + + /** + * Specify column config from the source table. * - * @param array $meta_column_mapping Mapping information of keys in source meta table. Must be of the form: + * @return array Config, must be of the form: + * array( + * '$source_column_name_1' => array( // $source_column_name_1 is column name in source table, or a select statement. + * 'type' => 'type of value, could be string/int/date/float.', + * 'destination' => 'name of the column in column name where this data should be inserted in.', + * ), + * '$source_column_name_2' => array( + * ...... + * ), + * .... + * ). + */ + abstract public function get_core_column_mapping(); + + /** + * Specify meta keys config from source meta table. + * + * @return array Config, must be of the form. * array( * '$meta_key_1' => array( // $meta_key_1 is the name of meta_key in source meta table. * 'type' => 'type of value, could be string/int/date/float', @@ -73,42 +111,16 @@ abstract class MetaToCustomTableMigrator { * ), * .... * ). - * - * @param array $core_column_mapping Mapping of keys in source table, similar to meta_column_mapping param, must be of the form: - * array( - * '$source_column_name_1' => array( // $source_column_name_1 is column name in source table. - * 'type' => 'type of value, could be string/int/date/float.', - * 'destination' => 'name of the column in column name where this data should be inserted in.', - * ), - * '$source_column_name_2' => array( - * ...... - * ), - * .... - * ). */ - public function __construct() { - // TODO: Add code to validate params. - $this->schema_config = MigrationHelper::escape_schema_for_backtick( $this->get_schema_config() ); - $this->meta_column_mapping = $this->get_meta_column_config(); - $this->core_column_mapping = $this->get_core_column_mapping(); - $this->errors = array(); - } - - abstract function get_schema_config(); - - abstract function get_core_column_mapping(); - - abstract function get_meta_column_config(); - + abstract public function get_meta_column_config(); /** * Generate SQL for data insertion. * - * @param array $batch Data to generate queries for. Will be 'data' array returned by `$this->fetch_data_for_migration()` method. - * @param string $insert_switch Insert command to use in generating queries, could be insert, insert_ignore, or replace. + * @param array $batch Data to generate queries for. Will be 'data' array returned by `$this->fetch_data_for_migration_for_ids()` method. * * @return string Generated queries for insertion for this batch, would be of the form: - * INSERT/INSERT IGNORE/REPLACE INTO $table_name ($columns) values + * INSERT IGNORE INTO $table_name ($columns) values * ($value for row 1) * ($value for row 2) * ... @@ -118,10 +130,26 @@ abstract class MetaToCustomTableMigrator { list( $value_sql, $column_sql ) = $this->generate_column_clauses( array_merge( $this->core_column_mapping, $this->meta_column_mapping ), $batch ); - return "INSERT IGNORE INTO $table (`$column_sql`) VALUES $value_sql;"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, -- $insert_query is hardcoded, $value_sql is already escaped. } + /** + * Generate SQL for data updating. + * + * @param array $batch Data to generate queries for. Will be `data` array returned by fetch_data_for_migration_for_ids() method. + * + * @param array $entity_row_mapping Maps rows to update data with their original IDs. Will be returned by `generate_update_sql_for_batch`. + * + * @return string Generated queries for batch update. Would be of the form: + * INSERT INTO $table ( $columns ) VALUES + * ($value for row 1) + * ($valye for row 2) + * ... + * ON DUPLICATE KEY UPDATE + * $column1 = VALUES($column1) + * $column2 = VALUES($column2) + * ... + */ public function generate_update_sql_for_batch( $batch, $entity_row_mapping ) { $table = $this->schema_config['destination']['table_name']; @@ -140,6 +168,11 @@ abstract class MetaToCustomTableMigrator { return "INSERT INTO $table (`$column_sql`) VALUES $value_sql $duplicate_update_key_statement;"; } + /** + * Generate schema for primary ID column of destination table. + * + * @return array[] Schema for primary ID column. + */ protected function get_destination_table_primary_id_schema() { return array( 'destination_primary_key' => array( @@ -149,6 +182,14 @@ abstract class MetaToCustomTableMigrator { ); } + /** + * Generate values and columns clauses to be used in INSERT and INSERT..ON DUPLICATE KEY UPDATE statements. + * + * @param array $columns_schema Columns config for destination table. + * @param array $batch Actual data to migrate as returned by `data` in `fetch_data_for_migration_for_ids` method. + * + * @return array SQL clause for values, columns placeholders, and columns. + */ protected function generate_column_clauses( $columns_schema, $batch ) { global $wpdb; @@ -178,8 +219,15 @@ abstract class MetaToCustomTableMigrator { return array( $value_sql, $column_sql, $columns ); } + /** + * Generates ON DUPLICATE KEY UPDATE clause to be used in migration. + * + * @param array $columns List of column names. + * + * @return string SQL clause for INSERT...ON DUPLICATE KEY UPDATE + */ private function generate_on_duplicate_statement_clause( $columns ) { - $update_value_statements = []; + $update_value_statements = array(); foreach ( $columns as $column ) { $update_value_statements[] = "$column = VALUES( $column )"; } @@ -191,15 +239,15 @@ abstract class MetaToCustomTableMigrator { /** * Process next migration batch, uses option `wc_cot_migration` to checkpoints of what have been processed so far. * - * @param int $batch_size Batch size of records to migrate. + * @param array $entity_ids List of entity IDs to perform migrations for. * - * @return array True if migration is completed, false if there are still records to process. + * @return array List of errors happened during migration. */ public function process_migration_batch_for_ids( $entity_ids ) { $data = $this->fetch_data_for_migration_for_ids( $entity_ids ); foreach ( $data['errors'] as $entity_id => $error ) { - $this->errors[ $entity_id ] = "Error in importing post id $entity_id: " . print_r( $error, true ); + $this->errors[ $entity_id ] = "Error in importing post id $entity_id: " . $error->get_message(); } if ( count( $data['data'] ) === 0 ) { @@ -216,32 +264,45 @@ abstract class MetaToCustomTableMigrator { $this->process_update_batch( $to_update, $already_migrated ); return array( - 'errors' => $this->errors + 'errors' => $this->errors, ); } + /** + * Process batch for insertion into destination table. + * + * @param array $batch Data to insert, will be of the form as returned by `data` in `fetch_data_for_migration_for_ids`. + */ protected function process_insert_batch( $batch ) { global $wpdb; if ( 0 === count( $batch ) ) { return; } $queries = $this->generate_insert_sql_for_batch( $batch ); - $result = $wpdb->query( $queries ); - $wpdb->query( "COMMIT;" ); // For some reason, this seems necessary on some hosts? Maybe a MySQL configuration? + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Queries should already be prepared. + $result = $wpdb->query( $queries ); + $wpdb->query( 'COMMIT;' ); // For some reason, this seems necessary on some hosts? Maybe a MySQL configuration? if ( count( $batch ) !== $result ) { // Some rows were not inserted. // TODO: Find and log the entity ids that were not inserted. } } + /** + * Process batch for update into destination table. + * + * @param array $batch Data to insert, will be of the form as returned by `data` in `fetch_data_for_migration_for_ids`. + * @param array $already_migrated Maps rows to update data with their original IDs. + */ protected function process_update_batch( $batch, $already_migrated ) { global $wpdb; if ( 0 === count( $batch ) ) { return; } $queries = $this->generate_update_sql_for_batch( $batch, $already_migrated ); - $result = $wpdb->query( $queries ); - $wpdb->query( "COMMIT;" ); // For some reason, this seems necessary on some hosts? Maybe a MySQL configuration? + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Queries should already be prepared. + $result = $wpdb->query( $queries ); + $wpdb->query( 'COMMIT;' ); // For some reason, this seems necessary on some hosts? Maybe a MySQL configuration? if ( count( $batch ) !== $result ) { // Some rows were not inserted. // TODO: Find and log the entity ids that were not updateed. @@ -252,9 +313,7 @@ abstract class MetaToCustomTableMigrator { /** * Fetch data for migration. * - * @param string $where_clause Where conditions to use while selecting data from source table. - * @param string $batch_size Batch size, will be used in LIMIT clause. - * @param string $order_by Will be used in ORDER BY clause. + * @param array $entity_ids Entity IDs to fetch data for. * * @return array[] Data along with errors (if any), will of the form: * array( @@ -295,6 +354,20 @@ abstract class MetaToCustomTableMigrator { return $this->process_and_sanitize_data( $entity_data, $meta_data ); } + /** + * Fetch id mappings for records that are already inserted, or can be considered duplicates. + * + * @param array $entity_ids List of entity IDs to verify. + * + * @return array Already migrated entities, would be of the form + * array( + * '$source_id1' => array( + * 'source_id' => $source_id1, + * 'destination_id' => $destination_id1, + * ), + * ... + * ) + */ public function get_already_migrated_records( $entity_ids ) { global $wpdb; $source_table = $this->schema_config['source']['entity']['table_name']; @@ -309,6 +382,7 @@ abstract class MetaToCustomTableMigrator { $already_migrated_entity_ids = $wpdb->get_results( $wpdb->prepare( + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- All columns and table names are hardcoded. " SELECT source.`$source_primary_key_column` as source_id, destination.`$destination_primary_key_column` as destination_id FROM `$destination_table` destination @@ -317,6 +391,7 @@ WHERE source.`$source_primary_key_column` IN ( $entity_id_placeholder ) ", $entity_ids ) + // phpcs:enable ); return array_column( $already_migrated_entity_ids, null, 'source_id' ); @@ -326,9 +401,7 @@ WHERE source.`$source_primary_key_column` IN ( $entity_id_placeholder ) /** * Helper method to build query used to fetch data from core source table. * - * @param string $where_clause Where conditions to use while selecting data from source table. - * @param string $batch_size Batch size, will be used in LIMIT clause. - * @param string $order_by Will be used in ORDER BY clause. + * @param array $entity_ids List of entity IDs to fetch. * * @return string Query that can be used to fetch data. */ @@ -349,7 +422,7 @@ WHERE source.`$source_primary_key_column` IN ( $entity_id_placeholder ) } } $entity_column_string = implode( ', ', $entity_keys ); - // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $source_meta_rel_id_column, $source_destination_rel_id_column etc is escaped for backticks. $where clause and $order_by should already be escaped. + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $source_meta_rel_id_column, $source_destination_rel_id_column etc is escaped for backticks. $where clause and $order_by should already be escaped. $query = $wpdb->prepare( " SELECT @@ -371,7 +444,7 @@ WHERE $where_clause; * * @param array $entity_ids List of IDs to fetch metadata for. * - * @return string|void Query for fetching meta data. + * @return string Query for fetching meta data. */ protected function build_meta_data_query( $entity_ids ) { global $wpdb; @@ -384,7 +457,7 @@ WHERE $where_clause; $meta_column_string = implode( ', ', array_fill( 0, count( $meta_keys ), '%s' ) ); $entity_id_string = implode( ', ', array_fill( 0, count( $entity_ids ), '%d' ) ); - // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $meta_table_relational_key, $meta_key_column, $meta_value_column and $meta_table is escaped for backticks. $entity_id_string and $meta_column_string are placeholders. + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $meta_table_relational_key, $meta_key_column, $meta_value_column and $meta_table is escaped for backticks. $entity_id_string and $meta_column_string are placeholders. $query = $wpdb->prepare( " SELECT `$meta_table_relational_key` as entity_id, `$meta_key_column` as meta_key, `$meta_value_column` as meta_value @@ -475,7 +548,7 @@ WHERE /** * Validate and transform data so that we catch as many errors as possible before inserting. * - * @param mixed $value Actual data value. + * @param mixed $value Actual data value. * @param string $type Type of data, could be decimal, int, date, string. * * @return float|int|mixed|string|\WP_Error From a220e4f2ea77a217992189a4ba78112a405eebce Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 31 Mar 2022 19:46:33 +0530 Subject: [PATCH 189/386] Change parent class reference to try to fix CI. --- .../Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php index c2ae02a084e..1832cb464fd 100644 --- a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php +++ b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php @@ -14,7 +14,7 @@ use Automattic\WooCommerce\RestApi\UnitTests\Helpers\ShippingHelper; /** * Class WPPostToCOTMigratorTest. */ -class WPPostToCOTMigratorTest extends \WC_Unit_Test_Case { +class WPPostToCOTMigratorTest extends WC_Unit_Test_Case { /** * @var DataSynchronizer From 2270bfbe9d219ffe981cab8df201b1b391107b8c Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 31 Mar 2022 20:04:24 +0530 Subject: [PATCH 190/386] Applied coding standards + minor fixups. --- .../CustomOrderTable/WPPostToCOTMigrator.php | 164 +++--------------- 1 file changed, 28 insertions(+), 136 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php index d03b5e5cdfa..a21e0126eae 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php @@ -6,9 +6,6 @@ namespace Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable; use Automattic\WooCommerce\DataBase\Migrations\MigrationErrorLogger; -use Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable\WPPostToOrderAddressTableMigrator; -use Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable\WPPostToOrderOpTableMigrator; -use Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable\WPPostToOrderTableMigrator; /** * Class WPPostToCOTMigrator @@ -27,28 +24,28 @@ class WPPostToCOTMigrator { /** * Migrator instance to migrate data into wc_order table. * - * @var MetaToCustomTableMigrator + * @var WPPostToOrderTableMigrator */ private $order_table_migrator; /** * Migrator instance to migrate billing data into address table. * - * @var MetaToCustomTableMigrator + * @var WPPostToOrderAddressTableMigrator */ private $billing_address_table_migrator; /** * Migrator instance to migrate shipping data into address table. * - * @var MetaToCustomTableMigrator + * @var WPPostToOrderAddressTableMigrator */ private $shipping_address_table_migrator; /** * Migrator instance to migrate operational data. * - * @var MetaToCustomTableMigrator + * @var WPPostToOrderOpTableMigrator */ private $operation_data_table_migrator; @@ -123,66 +120,6 @@ class WPPostToCOTMigrator { ); } - /** - * @param MetaToCustomTableMigrator $migrator - * @param $type - * @param $order_post_ids - */ - private function process_next_address_batch( $migrator, $type, $order_post_ids ) { - global $wpdb; - - if ( 0 === count( $order_post_ids ) ) { - return; - } - - $data = $migrator->fetch_data_for_migration_for_ids( $order_post_ids ); - - foreach ( $data['errors'] as $order_id => $error ) { - // TODO: Add name of the migrator in error message. - $this->error_logger->log( 'info', "Error in importing data for Order ID $order_id: " . print_r( $error, true ) ); - } - - $to_insert = isset( $data['data']['insert'] ) ? $data['data']['insert'] : array(); - $to_update = isset( $data['data']['update'] ) ? $data['data']['update'] : array(); - - if ( 0 === count( $to_insert ) && 0 === count( $to_update ) ) { - return; - } - - $order_post_ids_placeholders = implode( ',', array_fill( 0, count( $order_post_ids ), '%d' ) ); - - $already_migrated = $wpdb->get_results( - $wpdb->prepare( - " -SELECT addresses.id, addresses.order_id, orders.post_id FROM {$this->table_names['addresses']} as addresses -JOIN {$this->table_names['orders']} orders ON addresses.order_id = orders.id -WHERE - address_type = %s AND - orders.post_id IN ( $order_post_ids_placeholders ) - ", - $type, - $order_post_ids - ) - ); - - $order_post_ids_to_migrate = array_diff( $order_post_ids, array_column( $already_migrated, 'post_id' ) ); - $to_insert = array_intersect_key( $to_insert, array_flip( $order_post_ids_to_migrate ) ); - $this->execute_action_for_batch( $migrator, $to_insert, 'insert' ); - - if ( 0 < count( $to_update ) ) { - echo 'comes here'; - } - $to_update_intersect = array(); - $ids_to_update = array_flip( array_column( $already_migrated, 'id' ) ); - foreach ( $to_update as $post_id => $address_record ) { - if ( isset( $ids_to_update[ $address_record['id'] ] ) ) { - $to_update_intersect[ $post_id ] = $address_record; - } - } - - $this->execute_action_for_batch( $migrator, $to_update_intersect, 'update' ); - } - /** * Process next migration batch, uses option `wc_cot_migration` to checkpoints of what have been processed so far. * @@ -195,38 +132,24 @@ WHERE if ( 0 === count( $order_post_ids ) ) { return true; } + $this->process_migration_for_ids( $order_post_ids ); + $last_post_migrated = max( $order_post_ids ); + $this->update_checkpoint( $last_post_migrated ); + } + /** + * Process migration for specific order post IDs. + * + * @param array $order_post_ids List of post IDs to migrate. + */ + public function process_migration_for_ids( $order_post_ids ) { $this->order_table_migrator->process_migration_batch_for_ids( $order_post_ids ); $this->billing_address_table_migrator->process_migration_batch_for_ids( $order_post_ids ); $this->shipping_address_table_migrator->process_migration_batch_for_ids( $order_post_ids ); $this->operation_data_table_migrator->process_migration_batch_for_ids( $order_post_ids ); - - $last_post_migrated = max( $order_post_ids ); - $this->update_checkpoint( $last_post_migrated ); - - return false; - } - - /** - * Process next batch for a given address type. - * - * @param MetaToCustomTableMigrator $migrator Migrator instance for address type. - * @param array $order_post_ids Array of post IDs for orders. - * @param string $order_by Order by clause. - */ - private function process_next_migrator_batch( $migrator, $order_post_ids ) { - $data = $migrator->fetch_data_for_migration_for_ids( $order_post_ids ); - - foreach ( $data['errors'] as $order_id => $error ) { - // TODO: Add name of the migrator in error message. - $this->error_logger->log( 'info', "Error in importing data for Order ID $order_id: " . print_r( $error, true ) ); - } - - $to_replace = isset( $data['data']['update'] ) ? $data['data']['update'] : array(); - $this->execute_action_for_batch( $migrator, $to_replace, 'update' ); - - $to_insert = isset( $data['data']['insert'] ) ? $data['data']['insert'] : array(); - $this->execute_action_for_batch( $migrator, $to_insert, 'insert' ); + // TODO: Add resilience for meta migrations. + // $this->process_meta_migration( $order_post_ids ); + // TODO: Return merged error array. } /** @@ -250,34 +173,18 @@ WHERE * Method to migrate single record. * * @param int $post_id Post ID of record to migrate. - * - * @return bool|\WP_Error */ public function process_single( $post_id ) { - global $wpdb; - - $where_clause = $wpdb->prepare( 'ID = %d', $post_id ); - $data = $this->order_table_migrator->fetch_data_for_migration_for_ids( $where_clause, 1, 'ID ASC' ); - if ( isset( $data['errors'][ $post_id ] ) ) { - return new \WP_Error( $data['errors'][ $post_id ] ); - } - - $queries = $this->order_table_migrator->generate_insert_sql_for_batch( $data['data'], 'replace' ); - $result = $wpdb->query( $queries ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $queries is already prepared. - if ( 1 !== $result ) { - // TODO: Fetch and return last error. - echo 'error'; - - return new \WP_Error( 'error' ); - } - - return true; + $this->process_migration_for_ids( array( $post_id ) ); + // TODO: Return error. } /** * Helper function to get where clause to send to MetaToCustomTableMigrator instance. * - * @return array Where clause. + * @param int $batch_size Number of orders in batch. + * + * @return array List of IDs in the current patch. */ private function get_next_batch_ids( $batch_size ) { global $wpdb; @@ -285,7 +192,7 @@ WHERE $checkpoint = $this->get_checkpoint(); $post_ids = $wpdb->get_col( $wpdb->prepare( - "SELECT ID FROM $wpdb->posts WHERE ID > %d AND post_type = '%s' LIMIT %d", + "SELECT ID FROM $wpdb->posts WHERE ID > %d AND post_type = %s LIMIT %d", $checkpoint['id'], 'shop_order', $batch_size @@ -295,26 +202,6 @@ WHERE return $post_ids; } - /** - * Helper method to create `ID in (.., .., ...)` clauses. - * - * @param array $ids List of IDs. - * @param string $column_name Name of the ID column. - * - * @return string Prepared clause for where. - */ - private function get_where_id_clause( $ids, $column_name = 'ID' ) { - global $wpdb; - - if ( 0 === count( $ids ) ) { - return ''; - } - - $id_placeholder_array = '(' . implode( ',', array_fill( 0, count( $ids ), '%d' ) ) . ')'; - - return $wpdb->prepare( "`$column_name` IN $id_placeholder_array", $ids ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Both $column_name and $id_placeholder_array should already be prepared. - } - /** * Current checkpoint status. * @@ -333,6 +220,11 @@ WHERE return update_option( 'wc_cot_migration', array( 'id' => $id ), false ); } + /** + * Remove checkpoint. + * + * @return bool Whether checkpoint was removed. + */ public function delete_checkpoint() { return delete_option( 'wp_cot_migration' ); } From 5b3e9f78d24dd97bc28bd720942e1919d56fcb2e Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 31 Mar 2022 20:10:15 +0530 Subject: [PATCH 191/386] Applied coding standards. --- .../WPPostToOrderTableMigrator.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php index b0b7a7ec840..e611ede6b2c 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php @@ -10,6 +10,11 @@ namespace Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable; */ class WPPostToOrderTableMigrator extends MetaToCustomTableMigrator { + /** + * Get schema config for wp_posts and wc_order table. + * + * @return array Config. + */ public function get_schema_config() { global $wpdb; @@ -45,6 +50,11 @@ class WPPostToOrderTableMigrator extends MetaToCustomTableMigrator { ); } + /** + * Get columns config. + * + * @return \string[][] Config. + */ public function get_core_column_mapping() { return array( 'ID' => array( @@ -70,6 +80,11 @@ class WPPostToOrderTableMigrator extends MetaToCustomTableMigrator { ); } + /** + * Get meta data config. + * + * @return \string[][] Config. + */ public function get_meta_column_config() { return array( '_order_currency' => array( From f8d5746a2d6bb2687440a296e8847392372e1be8 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 31 Mar 2022 20:16:18 +0530 Subject: [PATCH 192/386] Applied coding standards --- .../WPPostToOrderAddressTableMigrator.php | 62 +++++++++++++++++-- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php index 55c7fb438bc..83d41d9db6c 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php @@ -1,17 +1,39 @@ type = $type; parent::__construct(); } - function get_schema_config() { + /** + * Get schema config for wp_posts and wc_order_address table. + * + * @return array Config. + */ + public function get_schema_config() { global $wpdb; // TODO: Remove hardcoding. $this->table_names = array( @@ -45,8 +67,14 @@ class WPPostToOrderAddressTableMigrator extends MetaToCustomTableMigrator { ); } - function get_core_column_mapping() { + /** + * Get columns config. + * + * @return \string[][] Config. + */ + public function get_core_column_mapping() { $type = $this->type; + return array( 'id' => array( 'type' => 'int', @@ -60,8 +88,14 @@ class WPPostToOrderAddressTableMigrator extends MetaToCustomTableMigrator { ); } - function get_meta_column_config() { + /** + * Get meta data config. + * + * @return \string[][] Config. + */ + public function get_meta_column_config() { $type = $this->type; + return array( "_{$type}_first_name" => array( 'type' => 'string', @@ -110,6 +144,20 @@ class WPPostToOrderAddressTableMigrator extends MetaToCustomTableMigrator { ); } + /** + * We overwrite this method to add a subclause to only fetch address of current type. + * + * @param array $entity_ids List of entity IDs to verify. + * + * @return array Already migrated entities, would be of the form + * array( + * '$source_id1' => array( + * 'source_id' => $source_id1, + * 'destination_id' => $destination_id1, + * ), + * ... + * ) + */ public function get_already_migrated_records( $entity_ids ) { global $wpdb; $source_table = $this->schema_config['source']['entity']['table_name']; @@ -126,6 +174,7 @@ class WPPostToOrderAddressTableMigrator extends MetaToCustomTableMigrator { $already_migrated_entity_ids = $wpdb->get_results( $wpdb->prepare( + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- All columns and table names are hardcoded. " SELECT source.`$source_primary_key_column` as source_id, destination.`$destination_primary_key_column` as destination_id FROM `$destination_table` destination @@ -134,6 +183,7 @@ WHERE source.`$source_primary_key_column` IN ( $entity_id_placeholder ) AND dest ", $entity_ids ) + // phpcs:enable ); return array_column( $already_migrated_entity_ids, null, 'source_id' ); From 8e4287d19bbccd97520e5fb45d77c263966086b3 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 31 Mar 2022 20:19:26 +0530 Subject: [PATCH 193/386] Applied coding standards. --- .../MetaToMetaTableMigrator.php | 6 ++--- .../WPPostToOrderOpTableMigrator.php | 27 ++++++++++++++++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php index cbe9b78d7b1..aa3273c2ceb 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php @@ -47,9 +47,9 @@ class MetaToMetaTableMigrator { $insert_query = MigrationHelper::get_insert_switch( $insert_switch ); - $meta_key_column = MigrationHelper::escape_backtick( $this->schema_config['destination']['meta']['meta_key_column'] ); - $meta_value_column = MigrationHelper::escape_backtick( $this->schema_config['destination']['meta']['meta_value_column'] ); - $entity_id_column = MigrationHelper::escape_backtick( $this->schema_config['destination']['meta']['entity_id_column'] ); + $meta_key_column = $this->schema_config['destination']['meta']['meta_key_column']; + $meta_value_column = $this->schema_config['destination']['meta']['meta_value_column']; + $entity_id_column = $this->schema_config['destination']['meta']['entity_id_column']; $column_sql = "(`$entity_id_column`, `$meta_key_column`, `$meta_value_column`)"; $table = $this->schema_config['destination']['meta']['table_name']; diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php index 6c7645a6b31..8147542fc5d 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php @@ -1,9 +1,22 @@ array( @@ -47,8 +66,14 @@ class WPPostToOrderOpTableMigrator extends MetaToCustomTableMigrator { ); } + + /** + * Get meta data config. + * + * @return \string[][] Config. + */ public function get_meta_column_config() { - return array( + return array( '_created_via' => array( 'type' => 'string', 'destination' => 'created_via', From d7434ce3a747957080793970c70ae6308fe304e5 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Fri, 1 Apr 2022 17:16:15 +0530 Subject: [PATCH 194/386] Force type to try to fix CI. --- .../CustomOrderTable/WPPostToCOTMigratorTest.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php index 1832cb464fd..45a074a7789 100644 --- a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php +++ b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php @@ -6,7 +6,6 @@ use Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable\WPPostToCOTMigrator; use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer; use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore; -use Automattic\WooCommerce\RestApi\UnitTests\Helpers\CouponHelper; use Automattic\WooCommerce\RestApi\UnitTests\Helpers\CustomerHelper; use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper; use Automattic\WooCommerce\RestApi\UnitTests\Helpers\ShippingHelper; @@ -287,9 +286,9 @@ WHERE order_id = {$order_id} ( new WC_DateTime( $db_order_op_data->date_completed_gmt ) )->date( DATE_ISO8601 ) ); $this->assertEquals( $order->get_shipping_tax(), $db_order_op_data->shipping_tax_amount ); - $this->assertEquals( $order->get_shipping_total(), $db_order_op_data->shipping_total_amount ); - $this->assertEquals( $order->get_discount_tax(), $db_order_op_data->discount_tax_amount ); - $this->assertEquals( $order->get_discount_total(), $db_order_op_data->discount_total_amount ); + $this->assertEquals( (float) $order->get_shipping_total(), (float) $db_order_op_data->shipping_total_amount ); + $this->assertEquals( (float) $order->get_discount_tax(), (float) $db_order_op_data->discount_tax_amount ); + $this->assertEquals( (float) $order->get_discount_total(), (float) $db_order_op_data->discount_total_amount ); } private function clear_all_orders_and_reset_checkpoint() { From b403fa0cf088b9b39a6e54051aa157ece8e9604c Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Fri, 1 Apr 2022 17:16:39 +0530 Subject: [PATCH 195/386] Remove incorrectly added file. --- plugins/woocommerce.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 plugins/woocommerce.php diff --git a/plugins/woocommerce.php b/plugins/woocommerce.php deleted file mode 100644 index e69de29bb2d..00000000000 From 81f918af877741054d400924cb7f3ba09e91d47e Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Fri, 1 Apr 2022 17:46:27 +0530 Subject: [PATCH 196/386] Force type to try to fix CI. --- .../Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php index 45a074a7789..777929b0a62 100644 --- a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php +++ b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php @@ -285,7 +285,7 @@ WHERE order_id = {$order_id} $order->get_date_completed()->date( DATE_ISO8601 ), ( new WC_DateTime( $db_order_op_data->date_completed_gmt ) )->date( DATE_ISO8601 ) ); - $this->assertEquals( $order->get_shipping_tax(), $db_order_op_data->shipping_tax_amount ); + $this->assertEquals( (float) $order->get_shipping_tax(), (float) $db_order_op_data->shipping_tax_amount ); $this->assertEquals( (float) $order->get_shipping_total(), (float) $db_order_op_data->shipping_total_amount ); $this->assertEquals( (float) $order->get_discount_tax(), (float) $db_order_op_data->discount_tax_amount ); $this->assertEquals( (float) $order->get_discount_total(), (float) $db_order_op_data->discount_total_amount ); From bb10c88b8f874f9dfde8194882b98a87b4f02532 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Fri, 1 Apr 2022 18:00:49 +0530 Subject: [PATCH 197/386] Minor fixes in bulk migration logic. --- .../Migrations/CustomOrderTable/WPPostToCOTMigrator.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php index a21e0126eae..a9ddb9b313b 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php @@ -135,6 +135,7 @@ class WPPostToCOTMigrator { $this->process_migration_for_ids( $order_post_ids ); $last_post_migrated = max( $order_post_ids ); $this->update_checkpoint( $last_post_migrated ); + return false; } /** @@ -192,7 +193,7 @@ class WPPostToCOTMigrator { $checkpoint = $this->get_checkpoint(); $post_ids = $wpdb->get_col( $wpdb->prepare( - "SELECT ID FROM $wpdb->posts WHERE ID > %d AND post_type = %s LIMIT %d", + "SELECT ID FROM $wpdb->posts WHERE ID > %d AND post_type = %s ORDER BY ID ASC LIMIT %d ", $checkpoint['id'], 'shop_order', $batch_size From c5df64ca573e58d5936dc4cc7fd8cc2b6b5bf3ca Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Mon, 4 Apr 2022 13:43:19 +0530 Subject: [PATCH 198/386] Add errors to log. --- .../CustomOrderTable/MetaToCustomTableMigrator.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php index 7347781a3fe..d7b0b4a20f4 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php @@ -283,8 +283,7 @@ abstract class MetaToCustomTableMigrator { $result = $wpdb->query( $queries ); $wpdb->query( 'COMMIT;' ); // For some reason, this seems necessary on some hosts? Maybe a MySQL configuration? if ( count( $batch ) !== $result ) { - // Some rows were not inserted. - // TODO: Find and log the entity ids that were not inserted. + $this->errors[] = 'Error with batch: ' . $wpdb->last_error; } } @@ -304,8 +303,7 @@ abstract class MetaToCustomTableMigrator { $result = $wpdb->query( $queries ); $wpdb->query( 'COMMIT;' ); // For some reason, this seems necessary on some hosts? Maybe a MySQL configuration? if ( count( $batch ) !== $result ) { - // Some rows were not inserted. - // TODO: Find and log the entity ids that were not updateed. + $this->errors[] = 'Error with batch: ' . $wpdb->last_error; } } @@ -486,11 +484,6 @@ WHERE * @return array[] Validated and combined data with errors. */ private function process_and_sanitize_data( $entity_data, $meta_data ) { - /** - * TODO: Add more validations for: - * 1. Column size - * 2. Value limits - */ $sanitized_entity_data = array(); $error_records = array(); $this->process_and_sanitize_entity_data( $sanitized_entity_data, $error_records, $entity_data ); @@ -565,7 +558,6 @@ WHERE $value = wc_string_to_bool( $value ); break; case 'date': - // TODO: Test this validation in unit tests. try { if ( '' === $value ) { $value = null; From 909a57e78476b2299007f24a60c01f3364c92761 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Mon, 4 Apr 2022 13:47:48 +0530 Subject: [PATCH 199/386] Applied coding standards. --- .../src/Database/Migrations/MigrationHelper.php | 9 ++++++++- .../COTMigrationServiceProvider.php | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php b/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php index d0af0007a4f..40ce05cc678 100644 --- a/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php +++ b/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php @@ -45,7 +45,7 @@ class MigrationHelper { break; case 'update': $insert_query = 'UPDATE'; - break; + break; case 'insert': default: $insert_query = 'INSERT'; @@ -54,6 +54,13 @@ class MigrationHelper { return $insert_query; } + /** + * Helper method to escape backtick in various schema fields. + * + * @param array $schema_config Schema config. + * + * @return array Schema config escaped for backtick. + */ public static function escape_schema_for_backtick( $schema_config ) { array_walk( $schema_config['source']['entity'], array( self::class, 'escape_and_add_backtick' ) ); array_walk( $schema_config['source']['meta'], array( self::class, 'escape_and_add_backtick' ) ); diff --git a/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/COTMigrationServiceProvider.php b/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/COTMigrationServiceProvider.php index cbe4221f3fb..d084b8f4ef0 100644 --- a/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/COTMigrationServiceProvider.php +++ b/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/COTMigrationServiceProvider.php @@ -1,6 +1,6 @@ Date: Mon, 4 Apr 2022 13:58:09 +0530 Subject: [PATCH 200/386] Applied coding standards. --- .../WPPostToCOTMigratorTest.php | 80 +++++++++++++++++-- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php index 777929b0a62..113f7f25354 100644 --- a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php +++ b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php @@ -30,6 +30,9 @@ class WPPostToCOTMigratorTest extends WC_Unit_Test_Case { */ private $data_store; + /** + * Setup data_store and sut. + */ public function setUp(): void { parent::setUp(); $this->create_order_custom_table_if_not_exist(); @@ -37,6 +40,9 @@ class WPPostToCOTMigratorTest extends WC_Unit_Test_Case { $this->sut = wc_get_container()->get( WPPostToCOTMigrator::class ); } + /** + * Test that migration for a normal order happens as expected. + */ public function test_process_next_migration_batch_normal_order() { $order = wc_get_order( $this->create_complex_wp_post_order() ); $this->clear_all_orders_and_reset_checkpoint(); @@ -47,6 +53,9 @@ class WPPostToCOTMigratorTest extends WC_Unit_Test_Case { $this->assert_order_op_data_is_migrated( $order ); } + /** + * Test that already migrated order isn't migrated twice. + */ public function test_process_next_migration_batch_already_migrated_order() { global $wpdb; $order = wc_get_order( $this->create_complex_wp_post_order() ); @@ -59,6 +68,7 @@ class WPPostToCOTMigratorTest extends WC_Unit_Test_Case { $this->sut->update_checkpoint( 0 ); $this->sut->process_next_migration_batch( 100 ); + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $this->assertEquals( 1, $wpdb->get_var( @@ -96,52 +106,88 @@ WHERE order_id = {$order_id} " ) ); + // phpcs:enable } + /** + * Test that when an order is partially migrated, it can still be resumed as expected. + */ public function test_process_next_migration_batch_interrupted_migrating_order() { $this->markTestSkipped(); } + /** + * Test that invalid order data is not migrated but logged. + */ public function test_process_next_migration_batch_invalid_order_data() { $this->markTestSkipped(); } + /** + * Test when one order is invalid but other one is valid in a migration batch. + */ public function test_process_next_migration_batch_invalid_valid_order_combo() { $this->markTestSkipped(); } - public function test_process_next_migration_batch_stale_order() { - $this->markTestSkipped(); - } - + /** + * Helper method to get order object from COT. + * + * @param WP_Post $post_order Post object for order. + * + * @return array|object|void|null DB object from COT. + */ private function get_order_from_cot( $post_order ) { global $wpdb; $order_table = $this->data_store::get_orders_table_name(); $query = "SELECT * FROM $order_table WHERE post_id = {$post_order->get_id()};"; + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared return $wpdb->get_row( $query ); } + /** + * Helper method to get address details from DB. + * + * @param int $order_id Order ID. + * @param string $address_type Address Type. + * + * @return array|object|void|null DB object. + */ private function get_address_details_from_cot( $order_id, $address_type ) { global $wpdb; $address_table = $this->data_store::get_addresses_table_name(); + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared return $wpdb->get_row( "SELECT * FROM $address_table WHERE order_id = $order_id AND address_type = '$address_type';" ); } + /** + * Helper method to get operational details from COT. + * + * @param int $order_id Order ID. + * + * @return array|object|void|null DB Object. + */ private function get_order_operational_data_from_cot( $order_id ) { global $wpdb; $operational_data_table = $this->data_store::get_operational_data_table_name(); + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared return $wpdb->get_row( "SELECT * FROM $operational_data_table WHERE order_id = $order_id;" ); } + /** + * Helper method to create complex wp_post based order. + * + * @return int Order ID + */ private function create_complex_wp_post_order() { update_option( 'woocommerce_prices_include_tax', 'yes' ); update_option( 'woocommerce_calc_taxes', 'yes' ); $uniq_cust_id = wp_generate_password( 10, false ); - $customer = CustomerHelper::create_customer( "user$uniq_cust_id", $uniq_cust_id, "user$uniq_cust_id@example.com" ); - $tax_rate = array( + $customer = CustomerHelper::create_customer( "user$uniq_cust_id", $uniq_cust_id, "user$uniq_cust_id@example.com" ); + $tax_rate = array( 'tax_rate_country' => '', 'tax_rate_state' => '', 'tax_rate' => '15.0000', @@ -204,6 +250,11 @@ WHERE order_id = {$order_id} return $order->get_id(); } + /** + * Helper method to assert core data is migrated. + * + * @param WC_Order $order Order object. + */ private function assert_core_data_is_migrated( $order ) { $db_order = $this->get_order_from_cot( $order ); @@ -226,6 +277,11 @@ WHERE order_id = {$order_id} $this->assertEquals( $order->get_customer_user_agent(), $db_order->user_agent ); } + /** + * Helper method to assert addresses are migrated. + * + * @param WC_Order $order Order object. + */ private function assert_order_addresses_are_migrated( $order ) { $db_order = $this->get_order_from_cot( $order ); @@ -255,6 +311,11 @@ WHERE order_id = {$order_id} $this->assertEquals( $order->get_shipping_phone(), $db_order_address->phone ); } + /** + * Helper method to assert operational data is migrated. + * + * @param WC_Order $order Order object. + */ private function assert_order_op_data_is_migrated( $order ) { $db_order = $this->get_order_from_cot( $order ); // Verify order operational data. @@ -291,15 +352,22 @@ WHERE order_id = {$order_id} $this->assertEquals( (float) $order->get_discount_total(), (float) $db_order_op_data->discount_total_amount ); } + /** + * Helper method to clear checkout and truncate order tables. + */ private function clear_all_orders_and_reset_checkpoint() { global $wpdb; $order_tables = $this->data_store->get_all_table_names(); foreach ( $order_tables as $table ) { + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( "TRUNCATE table $table;" ); } $this->sut->delete_checkpoint(); } + /** + * Helper method to create custom tables if not present. + */ private function create_order_custom_table_if_not_exist() { $order_table_controller = wc_get_container() ->get( 'Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController' ); From 891a9439a1c77d4d7879180f3ac93fece19eb439 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Mon, 4 Apr 2022 15:43:31 +0530 Subject: [PATCH 201/386] Applied coding standards. --- .../MetaToMetaTableMigrator.php | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php index aa3273c2ceb..c4cb68f6bc2 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php @@ -75,7 +75,7 @@ class MetaToMetaTableMigrator { /** * Fetch data for migration. * - * @param string $where_clause Where conditions to use while selecting data from source table. + * @param array $order_post_ids Array of IDs to fetch data for. * * @return array[] Data along with errors (if any), will of the form: * array( @@ -92,7 +92,7 @@ class MetaToMetaTableMigrator { global $wpdb; if ( empty( $order_post_ids ) ) { return array( - 'data' => array(), + 'data' => array(), 'errors' => array(), ); } @@ -108,25 +108,28 @@ class MetaToMetaTableMigrator { ); } - return array( 'data' => $meta_data_rows, 'errors' => array() ); + return array( + 'data' => $meta_data_rows, + 'errors' => array(), + ); } /** * Helper method to build query used to fetch data from source meta table. * - * @param string $where_clause Where conditions to use while selecting data from source table. + * @param string $entity_ids List of entity IDs to build meta query for. * * @return string Query that can be used to fetch data. */ private function build_meta_table_query( $entity_ids ) { global $wpdb; - $source_meta_table = $this->schema_config['source']['meta']['table_name']; + $source_meta_table = $this->schema_config['source']['meta']['table_name']; $source_meta_key_column = $this->schema_config['source']['meta']['meta_key_column']; $source_meta_value_column = $this->schema_config['source']['meta']['meta_value_column']; $source_entity_id_column = $this->schema_config['source']['meta']['entity_id_column']; $order_by = "$source_entity_id_column ASC"; - $where_clause = "$source_entity_id_column IN (" . implode( ', ', array_fill( 0, count( $entity_ids ), '%d') ) . ')'; + $where_clause = "$source_entity_id_column IN (" . implode( ', ', array_fill( 0, count( $entity_ids ), '%d' ) ) . ')'; $destination_entity_table = $this->schema_config['destination']['entity']['table_name']; $destination_entity_id_column = $this->schema_config['destination']['entity']['id_column']; @@ -134,11 +137,12 @@ class MetaToMetaTableMigrator { if ( $this->schema_config['source']['excluded_keys'] ) { $key_placeholder = implode( ',', array_fill( 0, count( $this->schema_config['source']['excluded_keys'] ), '%s' ) ); - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $source_meta_key_column is escated for backticks, $key_placeholder is hardcoded. - $exclude_clause = $wpdb->prepare( "source.$source_meta_key_column NOT IN ( $key_placeholder )", $this->schema_config['source']['excluded_keys'] ); - $where_clause = "$where_clause AND $exclude_clause"; + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $source_meta_key_column is escated for backticks, $key_placeholder is hardcoded. + $exclude_clause = $wpdb->prepare( "source.$source_meta_key_column NOT IN ( $key_placeholder )", $this->schema_config['source']['excluded_keys'] ); + $where_clause = "$where_clause AND $exclude_clause"; } + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared return $wpdb->prepare( " SELECT @@ -152,5 +156,6 @@ WHERE $where_clause ORDER BY $order_by ", $entity_ids ); + // phpcs:enable } } From b14b34abf829996609093d7e6b35b44bc8beffed Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Mon, 11 Apr 2022 12:18:55 +0530 Subject: [PATCH 202/386] Typos and DataBase -> Database. --- .../CustomOrderTable/MetaToCustomTableMigrator.php | 6 +++--- .../CustomOrderTable/MetaToMetaTableMigrator.php | 6 +++--- .../Migrations/CustomOrderTable/WPPostToCOTMigrator.php | 8 ++++---- .../WPPostToOrderAddressTableMigrator.php | 4 ++-- .../CustomOrderTable/WPPostToOrderOpTableMigrator.php | 4 ++-- .../CustomOrderTable/WPPostToOrderTableMigrator.php | 2 +- .../src/Database/Migrations/MigrationErrorLogger.php | 4 ++-- .../src/Database/Migrations/MigrationHelper.php | 6 ++---- .../ServiceProviders/COTMigrationServiceProvider.php | 2 +- .../CustomOrderTable/WPPostToCOTMigratorTest.php | 2 +- 10 files changed, 21 insertions(+), 23 deletions(-) diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php index d7b0b4a20f4..f69d9dd3e32 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php @@ -3,14 +3,14 @@ * Generic migration class to move any entity, entity_meta table combination to custom table. */ -namespace Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable; +namespace Automattic\WooCommerce\Database\Migrations\CustomOrderTable; -use Automattic\WooCommerce\DataBase\Migrations\MigrationHelper; +use Automattic\WooCommerce\Database\Migrations\MigrationHelper; /** * Class MetaToCustomTableMigrator. * - * @package Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable + * @package Automattic\WooCommerce\Database\Migrations\CustomOrderTable */ abstract class MetaToCustomTableMigrator { diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php index c4cb68f6bc2..f043b70ffe2 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php @@ -3,16 +3,16 @@ * Generic Migration class to move any meta data associated to an entity, to a different meta table associated with a custom entity table. */ -namespace Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable; +namespace Automattic\WooCommerce\Database\Migrations\CustomOrderTable; -use Automattic\WooCommerce\DataBase\Migrations\MigrationHelper; +use Automattic\WooCommerce\Database\Migrations\MigrationHelper; /** * Class MetaToMetaTableMigrator. * * Generic class for powering migrations from one meta table to another table. * - * @package Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable + * @package Automattic\WooCommerce\Database\Migrations\CustomOrderTable */ class MetaToMetaTableMigrator { diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php index a9ddb9b313b..6e94d9106e4 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php @@ -3,14 +3,14 @@ * Class for implementing migration from wp_posts and wp_postmeta to custom order tables. */ -namespace Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable; +namespace Automattic\WooCommerce\Database\Migrations\CustomOrderTable; -use Automattic\WooCommerce\DataBase\Migrations\MigrationErrorLogger; +use Automattic\WooCommerce\Database\Migrations\MigrationErrorLogger; /** * Class WPPostToCOTMigrator * - * @package Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable + * @package Automattic\WooCommerce\Database\Migrations\CustomOrderTable */ class WPPostToCOTMigrator { @@ -154,7 +154,7 @@ class WPPostToCOTMigrator { } /** - * Process migration for metadata for given post ids.\ + * Process migration for metadata for given post ids. * * @param array $order_post_ids Post IDs. */ diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php index 83d41d9db6c..bbb2be8dda2 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php @@ -3,12 +3,12 @@ * Class for WPPost to wc_order_address table migrator. */ -namespace Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable; +namespace Automattic\WooCommerce\Database\Migrations\CustomOrderTable; /** * Class WPPostToOrderAddressTableMigrator * - * @package Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable + * @package Automattic\WooCommerce\Database\Migrations\CustomOrderTable */ class WPPostToOrderAddressTableMigrator extends MetaToCustomTableMigrator { /** diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php index 8147542fc5d..54217a421c4 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php @@ -3,12 +3,12 @@ * Class for WPPost to wc_order_operational_details migrator. */ -namespace Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable; +namespace Automattic\WooCommerce\Database\Migrations\CustomOrderTable; /** * Class WPPostToOrderOpTableMigrator * - * @package Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable + * @package Automattic\WooCommerce\Database\Migrations\CustomOrderTable */ class WPPostToOrderOpTableMigrator extends MetaToCustomTableMigrator { diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php index e611ede6b2c..e18b3876dfe 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php @@ -3,7 +3,7 @@ * Class for WPPost To order table migrator. */ -namespace Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable; +namespace Automattic\WooCommerce\Database\Migrations\CustomOrderTable; /** * Class WPPostToOrderTableMigrator. diff --git a/plugins/woocommerce/src/Database/Migrations/MigrationErrorLogger.php b/plugins/woocommerce/src/Database/Migrations/MigrationErrorLogger.php index af3288beac8..191dce8963d 100644 --- a/plugins/woocommerce/src/Database/Migrations/MigrationErrorLogger.php +++ b/plugins/woocommerce/src/Database/Migrations/MigrationErrorLogger.php @@ -3,14 +3,14 @@ * Error logger for custom table migrations. */ -namespace Automattic\WooCommerce\DataBase\Migrations; +namespace Automattic\WooCommerce\Database\Migrations; /** * Class MigrationErrorLogger. * * Error logging for custom table migrations. * - * @package Automattic\WooCommerce\DataBase\Migrations + * @package Automattic\WooCommerce\Database\Migrations */ class MigrationErrorLogger extends \WC_Logger { diff --git a/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php b/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php index 40ce05cc678..4aba17b86e5 100644 --- a/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php +++ b/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php @@ -3,14 +3,12 @@ * Helper class with utility functions for migrations. */ -namespace Automattic\WooCommerce\DataBase\Migrations; - -use function DeliciousBrains\WP_Offload_Media\Aws3\JmesPath\search; +namespace Automattic\WooCommerce\Database\Migrations; /** * Class MigrationHelper. * - * Helper class to asist with migration related operations. + * Helper class to assist with migration related operations. */ class MigrationHelper { diff --git a/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/COTMigrationServiceProvider.php b/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/COTMigrationServiceProvider.php index d084b8f4ef0..2312b589eb6 100644 --- a/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/COTMigrationServiceProvider.php +++ b/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/COTMigrationServiceProvider.php @@ -5,7 +5,7 @@ namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders; -use Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable\WPPostToCOTMigrator; +use Automattic\WooCommerce\Database\Migrations\CustomOrderTable\WPPostToCOTMigrator; use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider; /** diff --git a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php index 113f7f25354..e9e9c2e1034 100644 --- a/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php +++ b/plugins/woocommerce/tests/php/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigratorTest.php @@ -3,7 +3,7 @@ * Tests for WPPostToCOTMigrator class. */ -use Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable\WPPostToCOTMigrator; +use Automattic\WooCommerce\Database\Migrations\CustomOrderTable\WPPostToCOTMigrator; use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer; use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore; use Automattic\WooCommerce\RestApi\UnitTests\Helpers\CustomerHelper; From 63ae96a2b68472c1a7bbbb677304ff65187948e3 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Mon, 11 Apr 2022 19:06:33 +0530 Subject: [PATCH 203/386] Add changelog. --- plugins/woocommerce/changelog/COTMigrations | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/COTMigrations diff --git a/plugins/woocommerce/changelog/COTMigrations b/plugins/woocommerce/changelog/COTMigrations new file mode 100644 index 00000000000..1dabd6c2ca5 --- /dev/null +++ b/plugins/woocommerce/changelog/COTMigrations @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Generic migration support for migration from posts + postsmeta table to any custom table. Additionaly, implement migrations to various COT tables using this generic support. From 464bcb7783598b79f6699a8ba97add774a47ddf8 Mon Sep 17 00:00:00 2001 From: RJChow Date: Mon, 11 Apr 2022 21:31:19 +0800 Subject: [PATCH 204/386] Changed unminify plugin to hook into step before optimization --- plugins/woocommerce-admin/unminify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/unminify.js b/plugins/woocommerce-admin/unminify.js index 93b5ee9088d..760c3cae391 100644 --- a/plugins/woocommerce-admin/unminify.js +++ b/plugins/woocommerce-admin/unminify.js @@ -47,7 +47,7 @@ class UnminifyWebpackPlugin { compilation.hooks.processAssets.tap( { name: 'UnminifyWebpackPlugin', - stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE, + stage: webpack.Compilation.PROCESS_ASSETS_STAGE_DERIVED, }, ( assets ) => { Object.entries( assets ).forEach( From 5b31f5acf433c70c4482d1251b33ae38c36e9419 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 30 Mar 2022 15:46:43 -0700 Subject: [PATCH 205/386] Do not show payment recommendations if store owner is in a country where WC Payment is supported --- .../client/payments/payment-recommendations.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/woocommerce-admin/client/payments/payment-recommendations.tsx b/plugins/woocommerce-admin/client/payments/payment-recommendations.tsx index a1bd79bb71f..1bc484fcd9c 100644 --- a/plugins/woocommerce-admin/client/payments/payment-recommendations.tsx +++ b/plugins/woocommerce-admin/client/payments/payment-recommendations.tsx @@ -79,10 +79,20 @@ const PaymentRecommendations: React.FC = () => { [ isInstalled ] ); + const supportsWCPayments = + paymentGatewaySuggestions.filter( ( paymentGatewaySuggestion ) => { + return ( + paymentGatewaySuggestion.id.indexOf( + 'woocommerce_payments' + ) === 0 + ); + } ).length === 1; + const triggeredPageViewRef = useRef( false ); const shouldShowRecommendations = paymentGatewaySuggestions && paymentGatewaySuggestions.length > 0 && + ! supportsWCPayments && ! isDismissed; useEffect( () => { From 721ff370952e4b7a3638764c803b434045820d6a Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 30 Mar 2022 21:02:52 -0700 Subject: [PATCH 206/386] Support woocommerce_payment_gateways_setting_additional_rows filter to allow adding additional table rows --- .../admin/settings/class-wc-settings-payment-gateways.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/woocommerce/includes/admin/settings/class-wc-settings-payment-gateways.php b/plugins/woocommerce/includes/admin/settings/class-wc-settings-payment-gateways.php index 6d4362ecb83..e7eca9216e6 100644 --- a/plugins/woocommerce/includes/admin/settings/class-wc-settings-payment-gateways.php +++ b/plugins/woocommerce/includes/admin/settings/class-wc-settings-payment-gateways.php @@ -124,6 +124,7 @@ class WC_Settings_Payment_Gateways extends WC_Settings_Page { ); $columns = apply_filters( 'woocommerce_payment_gateways_setting_columns', $default_columns ); + $additional_rows = apply_filters( 'woocommerce_payment_gateways_setting_additional_rows', array(), count( $columns ) ); foreach ( $columns as $key => $column ) { echo '' . esc_html( $column ) . ''; @@ -201,6 +202,9 @@ class WC_Settings_Payment_Gateways extends WC_Settings_Page { echo ''; } + foreach ( $additional_rows as $additional_row ) { + echo $additional_row; + } ?> From 08e5883246e532932d38254a09522c72ea5a8de6 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 30 Mar 2022 21:03:20 -0700 Subject: [PATCH 207/386] Add other payment methods link when the store is in WC Payments eligible country --- .../PaymentGatewaysController.php | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/PaymentGatewaysController.php b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/PaymentGatewaysController.php index 6dedede0469..664ba538706 100644 --- a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/PaymentGatewaysController.php +++ b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/PaymentGatewaysController.php @@ -5,6 +5,7 @@ namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions; +use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\WooCommercePayments; use Automattic\WooCommerce\Admin\Features\TransientNotices; defined( 'ABSPATH' ) || exit; @@ -21,6 +22,7 @@ class PaymentGatewaysController { add_filter( 'woocommerce_rest_prepare_payment_gateway', array( __CLASS__, 'extend_response' ), 10, 3 ); add_filter( 'admin_init', array( __CLASS__, 'possibly_do_connection_return_action' ) ); add_action( 'woocommerce_admin_payment_gateway_connection_return', array( __CLASS__, 'handle_successfull_connection' ) ); + add_filter( 'woocommerce_payment_gateways_setting_additional_rows', array( __CLASS__, 'add_other_payment_methods_link' ), 10, 2 ); } /** @@ -145,4 +147,22 @@ class PaymentGatewaysController { wp_safe_redirect( wc_admin_url() ); } + + /** + * Add "Other payment methods" link in WooCommerce -> Settings -> Payments + * When the store is in WC Payments eligible country. + * See https://github.com/woocommerce/woocommerce/issues/32130 for more details. + * + * @return void + */ + public static function add_other_payment_methods_link( $rows, $no_of_cols ) { + if ( WooCommercePayments::is_supported() ) { + $link = 'https://woocommerce.com/product-category/woocommerce-extensions/payment-gateways/?utm_source=payments_recommendations'; + $link_text = __( 'Other payment methods', 'woocommerce' ); + $external_link_icon = ''; + $row = "{$link_text} {$external_link_icon}"; + $rows[] = $row; + } + return $rows; + } } From 06f66ba20c80108c52bfd1debbdb9fb015e41552 Mon Sep 17 00:00:00 2001 From: moon Date: Thu, 31 Mar 2022 14:04:05 -0700 Subject: [PATCH 208/386] Removed the filter and moved the link logic due to security reason --- .../class-wc-settings-payment-gateways.php | 25 ++++++++++++++++--- .../PaymentGatewaysController.php | 19 -------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/plugins/woocommerce/includes/admin/settings/class-wc-settings-payment-gateways.php b/plugins/woocommerce/includes/admin/settings/class-wc-settings-payment-gateways.php index e7eca9216e6..cb87b1a4500 100644 --- a/plugins/woocommerce/includes/admin/settings/class-wc-settings-payment-gateways.php +++ b/plugins/woocommerce/includes/admin/settings/class-wc-settings-payment-gateways.php @@ -5,6 +5,8 @@ * @package WooCommerce\Admin */ +use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\WooCommercePayments; + defined( 'ABSPATH' ) || exit; if ( class_exists( 'WC_Settings_Payment_Gateways', false ) ) { @@ -124,7 +126,6 @@ class WC_Settings_Payment_Gateways extends WC_Settings_Page { ); $columns = apply_filters( 'woocommerce_payment_gateways_setting_columns', $default_columns ); - $additional_rows = apply_filters( 'woocommerce_payment_gateways_setting_additional_rows', array(), count( $columns ) ); foreach ( $columns as $key => $column ) { echo '' . esc_html( $column ) . ''; @@ -202,8 +203,26 @@ class WC_Settings_Payment_Gateways extends WC_Settings_Page { echo ''; } - foreach ( $additional_rows as $additional_row ) { - echo $additional_row; + /** + * Add "Other payment methods" link in WooCommerce -> Settings -> Payments + * When the store is in WC Payments eligible country. + * See https://github.com/woocommerce/woocommerce/issues/32130 for more details. + */ + if ( WooCommercePayments::is_supported() ) { + $columns_count = count( $columns ); + $link_text = __( 'Other payment methods', 'woocommerce' ); + $external_link_icon = ''; + echo ''; + // phpcs:ignore -- ignoring the error since the value is harded. + echo ""; + echo ""; + // phpcs:ignore + echo $link_text; + // phpcs:ignore + echo $external_link_icon; + echo ''; + echo ''; + echo ''; } ?> diff --git a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/PaymentGatewaysController.php b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/PaymentGatewaysController.php index 664ba538706..147140e8042 100644 --- a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/PaymentGatewaysController.php +++ b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/PaymentGatewaysController.php @@ -22,7 +22,6 @@ class PaymentGatewaysController { add_filter( 'woocommerce_rest_prepare_payment_gateway', array( __CLASS__, 'extend_response' ), 10, 3 ); add_filter( 'admin_init', array( __CLASS__, 'possibly_do_connection_return_action' ) ); add_action( 'woocommerce_admin_payment_gateway_connection_return', array( __CLASS__, 'handle_successfull_connection' ) ); - add_filter( 'woocommerce_payment_gateways_setting_additional_rows', array( __CLASS__, 'add_other_payment_methods_link' ), 10, 2 ); } /** @@ -147,22 +146,4 @@ class PaymentGatewaysController { wp_safe_redirect( wc_admin_url() ); } - - /** - * Add "Other payment methods" link in WooCommerce -> Settings -> Payments - * When the store is in WC Payments eligible country. - * See https://github.com/woocommerce/woocommerce/issues/32130 for more details. - * - * @return void - */ - public static function add_other_payment_methods_link( $rows, $no_of_cols ) { - if ( WooCommercePayments::is_supported() ) { - $link = 'https://woocommerce.com/product-category/woocommerce-extensions/payment-gateways/?utm_source=payments_recommendations'; - $link_text = __( 'Other payment methods', 'woocommerce' ); - $external_link_icon = ''; - $row = "{$link_text} {$external_link_icon}"; - $rows[] = $row; - } - return $rows; - } } From 51782d822847847f56fdd23229277d4493e415af Mon Sep 17 00:00:00 2001 From: moon Date: Thu, 31 Mar 2022 14:05:50 -0700 Subject: [PATCH 209/386] Remove unused namespace import --- .../PaymentGatewaySuggestions/PaymentGatewaysController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/PaymentGatewaysController.php b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/PaymentGatewaysController.php index 147140e8042..6dedede0469 100644 --- a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/PaymentGatewaysController.php +++ b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/PaymentGatewaysController.php @@ -5,7 +5,6 @@ namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions; -use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\WooCommercePayments; use Automattic\WooCommerce\Admin\Features\TransientNotices; defined( 'ABSPATH' ) || exit; From 4fe8cf2f71949e88c2eb90e986b2528305cc46ea Mon Sep 17 00:00:00 2001 From: moon Date: Fri, 8 Apr 2022 09:32:42 -0700 Subject: [PATCH 210/386] Add id to the link --- .../admin/settings/class-wc-settings-payment-gateways.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/admin/settings/class-wc-settings-payment-gateways.php b/plugins/woocommerce/includes/admin/settings/class-wc-settings-payment-gateways.php index cb87b1a4500..9b9e97abe89 100644 --- a/plugins/woocommerce/includes/admin/settings/class-wc-settings-payment-gateways.php +++ b/plugins/woocommerce/includes/admin/settings/class-wc-settings-payment-gateways.php @@ -215,7 +215,7 @@ class WC_Settings_Payment_Gateways extends WC_Settings_Page { echo ''; // phpcs:ignore -- ignoring the error since the value is harded. echo ""; - echo ""; + echo ""; // phpcs:ignore echo $link_text; // phpcs:ignore From ff2eb6f98d636f93bceb21be1995824e484831f0 Mon Sep 17 00:00:00 2001 From: moon Date: Fri, 8 Apr 2022 09:58:58 -0700 Subject: [PATCH 211/386] Record wcadmin_settings_payments_recommendations_other_options event with available payment methods --- plugins/woocommerce/legacy/js/admin/settings.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/woocommerce/legacy/js/admin/settings.js b/plugins/woocommerce/legacy/js/admin/settings.js index 29276907af6..cfe14b123d0 100644 --- a/plugins/woocommerce/legacy/js/admin/settings.js +++ b/plugins/woocommerce/legacy/js/admin/settings.js @@ -185,5 +185,20 @@ } } ); + $('#settings-other-payment-methods').on('click', function( e ) { + if ( typeof window.wcTracks.recordEvent === 'undefined' ) { + return; + } + + var payment_methods = $.map( $( "td.wc_payment_gateways_wrapper tbody tr[data-gateway_id] "), function( tr ) { + return $( tr ).attr( 'data-gateway_id' ); + }); + + window.wcTracks.recordEvent( 'wcadmin_settings_payments_recommendations_other_options' , { + available_payment_methods: payment_methods + }); + + }); + }); })( jQuery, woocommerce_settings_params, wp ); From 869a3baf3cdac998674fffa7382e2933c48d33c0 Mon Sep 17 00:00:00 2001 From: moon Date: Fri, 8 Apr 2022 10:19:14 -0700 Subject: [PATCH 212/386] Remove wcadmin prefix --- plugins/woocommerce/legacy/js/admin/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/legacy/js/admin/settings.js b/plugins/woocommerce/legacy/js/admin/settings.js index cfe14b123d0..421debd3d19 100644 --- a/plugins/woocommerce/legacy/js/admin/settings.js +++ b/plugins/woocommerce/legacy/js/admin/settings.js @@ -194,7 +194,7 @@ return $( tr ).attr( 'data-gateway_id' ); }); - window.wcTracks.recordEvent( 'wcadmin_settings_payments_recommendations_other_options' , { + window.wcTracks.recordEvent( 'settings_payments_recommendations_other_options' , { available_payment_methods: payment_methods }); From edb8b69dc4d75f73043474a9eac145c880c7f503 Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 11 Apr 2022 07:22:36 -0700 Subject: [PATCH 213/386] Use wc.tracks.recordEvent if available --- .../woocommerce/legacy/js/admin/settings.js | 291 +++++++++++------- 1 file changed, 176 insertions(+), 115 deletions(-) diff --git a/plugins/woocommerce/legacy/js/admin/settings.js b/plugins/woocommerce/legacy/js/admin/settings.js index 421debd3d19..8464191f0e3 100644 --- a/plugins/woocommerce/legacy/js/admin/settings.js +++ b/plugins/woocommerce/legacy/js/admin/settings.js @@ -1,204 +1,265 @@ /* global woocommerce_settings_params, wp */ -( function( $, params, wp ) { - $( function() { +( function ( $, params, wp ) { + $( function () { // Sell Countries - $( 'select#woocommerce_allowed_countries' ).on( 'change', function() { - if ( 'specific' === $( this ).val() ) { - $( this ).closest('tr').next( 'tr' ).hide(); - $( this ).closest('tr').next().next( 'tr' ).show(); - } else if ( 'all_except' === $( this ).val() ) { - $( this ).closest('tr').next( 'tr' ).show(); - $( this ).closest('tr').next().next( 'tr' ).hide(); - } else { - $( this ).closest('tr').next( 'tr' ).hide(); - $( this ).closest('tr').next().next( 'tr' ).hide(); - } - }).trigger( 'change' ); + $( 'select#woocommerce_allowed_countries' ) + .on( 'change', function () { + if ( 'specific' === $( this ).val() ) { + $( this ).closest( 'tr' ).next( 'tr' ).hide(); + $( this ).closest( 'tr' ).next().next( 'tr' ).show(); + } else if ( 'all_except' === $( this ).val() ) { + $( this ).closest( 'tr' ).next( 'tr' ).show(); + $( this ).closest( 'tr' ).next().next( 'tr' ).hide(); + } else { + $( this ).closest( 'tr' ).next( 'tr' ).hide(); + $( this ).closest( 'tr' ).next().next( 'tr' ).hide(); + } + } ) + .trigger( 'change' ); // Ship Countries - $( 'select#woocommerce_ship_to_countries' ).on( 'change', function() { - if ( 'specific' === $( this ).val() ) { - $( this ).closest('tr').next( 'tr' ).show(); - } else { - $( this ).closest('tr').next( 'tr' ).hide(); - } - }).trigger( 'change' ); + $( 'select#woocommerce_ship_to_countries' ) + .on( 'change', function () { + if ( 'specific' === $( this ).val() ) { + $( this ).closest( 'tr' ).next( 'tr' ).show(); + } else { + $( this ).closest( 'tr' ).next( 'tr' ).hide(); + } + } ) + .trigger( 'change' ); // Stock management - $( 'input#woocommerce_manage_stock' ).on( 'change', function() { - if ( $( this ).is(':checked') ) { - $( this ).closest('tbody').find( '.manage_stock_field' ).closest( 'tr' ).show(); - } else { - $( this ).closest('tbody').find( '.manage_stock_field' ).closest( 'tr' ).hide(); - } - }).trigger( 'change' ); + $( 'input#woocommerce_manage_stock' ) + .on( 'change', function () { + if ( $( this ).is( ':checked' ) ) { + $( this ) + .closest( 'tbody' ) + .find( '.manage_stock_field' ) + .closest( 'tr' ) + .show(); + } else { + $( this ) + .closest( 'tbody' ) + .find( '.manage_stock_field' ) + .closest( 'tr' ) + .hide(); + } + } ) + .trigger( 'change' ); // Color picker $( '.colorpick' ) - - .iris({ - change: function( event, ui ) { - $( this ).parent().find( '.colorpickpreview' ).css({ backgroundColor: ui.color.toString() }); + .iris( { + change: function ( event, ui ) { + $( this ) + .parent() + .find( '.colorpickpreview' ) + .css( { backgroundColor: ui.color.toString() } ); }, hide: true, - border: true - }) + border: true, + } ) - .on( 'click focus', function( event ) { + .on( 'click focus', function ( event ) { event.stopPropagation(); $( '.iris-picker' ).hide(); $( this ).closest( 'td' ).find( '.iris-picker' ).show(); $( this ).data( 'originalValue', $( this ).val() ); - }) + } ) - .on( 'change', function() { + .on( 'change', function () { if ( $( this ).is( '.iris-error' ) ) { var original_value = $( this ).data( 'originalValue' ); - if ( original_value.match( /^\#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/ ) ) { - $( this ).val( $( this ).data( 'originalValue' ) ).trigger( 'change' ); + if ( + original_value.match( + /^\#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/ + ) + ) { + $( this ) + .val( $( this ).data( 'originalValue' ) ) + .trigger( 'change' ); } else { $( this ).val( '' ).trigger( 'change' ); } } - }); + } ); - $( 'body' ).on( 'click', function() { + $( 'body' ).on( 'click', function () { $( '.iris-picker' ).hide(); - }); + } ); // Edit prompt - $( function() { + $( function () { var changed = false; let $check_column = $( '.wp-list-table .check-column' ); - $( 'input, textarea, select, checkbox' ).on( 'change', function( event ) { + $( 'input, textarea, select, checkbox' ).on( 'change', function ( + event + ) { // Toggling WP List Table checkboxes should not trigger navigation warnings. - if ( $check_column.length && $check_column.has( event.target ) ) { + if ( + $check_column.length && + $check_column.has( event.target ) + ) { return; } if ( ! changed ) { - window.onbeforeunload = function() { + window.onbeforeunload = function () { return params.i18n_nav_warning; }; changed = true; } - }); + } ); - $( '.submit :input, input#search-submit' ).on( 'click', function() { - window.onbeforeunload = ''; - }); - }); + $( '.submit :input, input#search-submit' ).on( + 'click', + function () { + window.onbeforeunload = ''; + } + ); + } ); // Sorting - $( 'table.wc_gateways tbody, table.wc_shipping tbody' ).sortable({ + $( 'table.wc_gateways tbody, table.wc_shipping tbody' ).sortable( { items: 'tr', cursor: 'move', axis: 'y', handle: 'td.sort', scrollSensitivity: 40, - helper: function( event, ui ) { - ui.children().each( function() { + helper: function ( event, ui ) { + ui.children().each( function () { $( this ).width( $( this ).width() ); - }); + } ); ui.css( 'left', '0' ); return ui; }, - start: function( event, ui ) { + start: function ( event, ui ) { ui.item.css( 'background-color', '#f6f6f6' ); }, - stop: function( event, ui ) { + stop: function ( event, ui ) { ui.item.removeAttr( 'style' ); ui.item.trigger( 'updateMoveButtons' ); - } - }); + }, + } ); // Select all/none - $( '.woocommerce' ).on( 'click', '.select_all', function() { - $( this ).closest( 'td' ).find( 'select option' ).prop( 'selected', true ); + $( '.woocommerce' ).on( 'click', '.select_all', function () { + $( this ) + .closest( 'td' ) + .find( 'select option' ) + .prop( 'selected', true ); $( this ).closest( 'td' ).find( 'select' ).trigger( 'change' ); return false; - }); + } ); - $( '.woocommerce' ).on( 'click', '.select_none', function() { - $( this ).closest( 'td' ).find( 'select option' ).prop( 'selected', false ); + $( '.woocommerce' ).on( 'click', '.select_none', function () { + $( this ) + .closest( 'td' ) + .find( 'select option' ) + .prop( 'selected', false ); $( this ).closest( 'td' ).find( 'select' ).trigger( 'change' ); return false; - }); + } ); // Re-order buttons. - $( '.wc-item-reorder-nav').find( '.wc-move-up, .wc-move-down' ).on( 'click', function() { - var moveBtn = $( this ), - $row = moveBtn.closest( 'tr' ); + $( '.wc-item-reorder-nav' ) + .find( '.wc-move-up, .wc-move-down' ) + .on( 'click', function () { + var moveBtn = $( this ), + $row = moveBtn.closest( 'tr' ); - moveBtn.trigger( 'focus' ); + moveBtn.trigger( 'focus' ); - var isMoveUp = moveBtn.is( '.wc-move-up' ), - isMoveDown = moveBtn.is( '.wc-move-down' ); + var isMoveUp = moveBtn.is( '.wc-move-up' ), + isMoveDown = moveBtn.is( '.wc-move-down' ); - if ( isMoveUp ) { - var $previewRow = $row.prev( 'tr' ); + if ( isMoveUp ) { + var $previewRow = $row.prev( 'tr' ); - if ( $previewRow && $previewRow.length ) { - $previewRow.before( $row ); - wp.a11y.speak( params.i18n_moved_up ); + if ( $previewRow && $previewRow.length ) { + $previewRow.before( $row ); + wp.a11y.speak( params.i18n_moved_up ); + } + } else if ( isMoveDown ) { + var $nextRow = $row.next( 'tr' ); + + if ( $nextRow && $nextRow.length ) { + $nextRow.after( $row ); + wp.a11y.speak( params.i18n_moved_down ); + } } - } else if ( isMoveDown ) { - var $nextRow = $row.next( 'tr' ); - if ( $nextRow && $nextRow.length ) { - $nextRow.after( $row ); - wp.a11y.speak( params.i18n_moved_down ); - } - } + moveBtn.trigger( 'focus' ); // Re-focus after the container was moved. + moveBtn.closest( 'table' ).trigger( 'updateMoveButtons' ); + } ); - moveBtn.trigger( 'focus' ); // Re-focus after the container was moved. - moveBtn.closest( 'table' ).trigger( 'updateMoveButtons' ); - } ); + $( '.wc-item-reorder-nav' ) + .closest( 'table' ) + .on( 'updateMoveButtons', function () { + var table = $( this ), + lastRow = $( this ).find( 'tbody tr:last' ), + firstRow = $( this ).find( 'tbody tr:first' ); - $( '.wc-item-reorder-nav').closest( 'table' ).on( 'updateMoveButtons', function() { - var table = $( this ), - lastRow = $( this ).find( 'tbody tr:last' ), - firstRow = $( this ).find( 'tbody tr:first' ); + table + .find( '.wc-item-reorder-nav .wc-move-disabled' ) + .removeClass( 'wc-move-disabled' ) + .attr( { tabindex: '0', 'aria-hidden': 'false' } ); + firstRow + .find( '.wc-item-reorder-nav .wc-move-up' ) + .addClass( 'wc-move-disabled' ) + .attr( { tabindex: '-1', 'aria-hidden': 'true' } ); + lastRow + .find( '.wc-item-reorder-nav .wc-move-down' ) + .addClass( 'wc-move-disabled' ) + .attr( { tabindex: '-1', 'aria-hidden': 'true' } ); + } ); - table.find( '.wc-item-reorder-nav .wc-move-disabled' ).removeClass( 'wc-move-disabled' ) - .attr( { 'tabindex': '0', 'aria-hidden': 'false' } ); - firstRow.find( '.wc-item-reorder-nav .wc-move-up' ).addClass( 'wc-move-disabled' ) - .attr( { 'tabindex': '-1', 'aria-hidden': 'true' } ); - lastRow.find( '.wc-item-reorder-nav .wc-move-down' ).addClass( 'wc-move-disabled' ) - .attr( { 'tabindex': '-1', 'aria-hidden': 'true' } ); - } ); + $( '.wc-item-reorder-nav' ) + .closest( 'table' ) + .trigger( 'updateMoveButtons' ); - $( '.wc-item-reorder-nav').closest( 'table' ).trigger( 'updateMoveButtons' ); - - - $( '.submit button' ).on( 'click', function() { + $( '.submit button' ).on( 'click', function () { if ( - $( 'select#woocommerce_allowed_countries' ).val() === 'specific' && + $( 'select#woocommerce_allowed_countries' ).val() === + 'specific' && ! $( '[name="woocommerce_specific_allowed_countries[]"]' ).val() ) { - if ( window.confirm( woocommerce_settings_params.i18n_no_specific_countries_selected ) ) { + if ( + window.confirm( + woocommerce_settings_params.i18n_no_specific_countries_selected + ) + ) { return true; } return false; } } ); - $('#settings-other-payment-methods').on('click', function( e ) { - if ( typeof window.wcTracks.recordEvent === 'undefined' ) { + $( '#settings-other-payment-methods' ).on( 'click', function ( e ) { + if ( + typeof window.wcTracks.recordEvent === 'undefined' && + typeof window.wc.tracks.recordEvent === 'undefined' + ) { return; } - var payment_methods = $.map( $( "td.wc_payment_gateways_wrapper tbody tr[data-gateway_id] "), function( tr ) { - return $( tr ).attr( 'data-gateway_id' ); - }); + var recordEvent = + window.wc.tracks.recordEvent || window.wcTracks.recordEvent; - window.wcTracks.recordEvent( 'settings_payments_recommendations_other_options' , { - available_payment_methods: payment_methods - }); + var payment_methods = $.map( + $( + 'td.wc_payment_gateways_wrapper tbody tr[data-gateway_id] ' + ), + function ( tr ) { + return $( tr ).attr( 'data-gateway_id' ); + } + ); - }); - - }); -})( jQuery, woocommerce_settings_params, wp ); + recordEvent( 'settings_payments_recommendations_other_options', { + available_payment_methods: payment_methods, + } ); + } ); + } ); +} )( jQuery, woocommerce_settings_params, wp ); From 33981358b69fd0d4d88f6234ce49f3dc25ba640d Mon Sep 17 00:00:00 2001 From: Jonathan Sadowski Date: Mon, 11 Apr 2022 10:49:45 -0500 Subject: [PATCH 214/386] Fetch more git history for changelogger comparison --- .github/workflows/pr-lint-monorepo.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-lint-monorepo.yml b/.github/workflows/pr-lint-monorepo.yml index f4a40ce8f31..7c952b3a641 100644 --- a/.github/workflows/pr-lint-monorepo.yml +++ b/.github/workflows/pr-lint-monorepo.yml @@ -1,4 +1,4 @@ -name: Run lint checks potentially affected projects across the monorepo +name: Run lint checks potentially affecting projects across the monorepo on: pull_request concurrency: group: changelogger-${{ github.event_name }}-${{ github.ref }} @@ -9,10 +9,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 10 + fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 From becc53d645c1c4c6331c7c41be40e68264d8ad16 Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 11 Apr 2022 09:55:18 -0700 Subject: [PATCH 215/386] Make sure paymentGatewaySuggestions is defined --- .../client/payments/payment-recommendations.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/woocommerce-admin/client/payments/payment-recommendations.tsx b/plugins/woocommerce-admin/client/payments/payment-recommendations.tsx index 1bc484fcd9c..93d25671df2 100644 --- a/plugins/woocommerce-admin/client/payments/payment-recommendations.tsx +++ b/plugins/woocommerce-admin/client/payments/payment-recommendations.tsx @@ -80,6 +80,7 @@ const PaymentRecommendations: React.FC = () => { ); const supportsWCPayments = + paymentGatewaySuggestions && paymentGatewaySuggestions.filter( ( paymentGatewaySuggestion ) => { return ( paymentGatewaySuggestion.id.indexOf( From fc7adc283ddcfeb4cf19d0d557bbde5f783e7862 Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 11 Apr 2022 10:08:14 -0700 Subject: [PATCH 216/386] Add changelog --- ...update-32130-ui-changes-to-the-recommended-payment-options | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/update-32130-ui-changes-to-the-recommended-payment-options diff --git a/plugins/woocommerce/changelog/update-32130-ui-changes-to-the-recommended-payment-options b/plugins/woocommerce/changelog/update-32130-ui-changes-to-the-recommended-payment-options new file mode 100644 index 00000000000..ed8117ebe3c --- /dev/null +++ b/plugins/woocommerce/changelog/update-32130-ui-changes-to-the-recommended-payment-options @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Adds Other payment methods link to the payment setting page when the store is located in WC Payments eligible country. From f4a191031c048973c7679b228d144b6966471c52 Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 4 Apr 2022 17:00:32 -0700 Subject: [PATCH 217/386] Migrate wpmu_drop_tables logic to core --- .../woocommerce/includes/class-wc-install.php | 13 +++++- .../src/Internal/Admin/Install.php | 45 ------------------- plugins/woocommerce/uninstall.php | 2 - 3 files changed, 12 insertions(+), 48 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 2bac9ea3334..261a9cdf1a2 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -1136,6 +1136,17 @@ CREATE TABLE {$wpdb->prefix}wc_product_download_directories ( wc_get_container()->get( DataRegenerator::class )->get_lookup_table_name(), ); + $wca_tables = array( + "{$wpdb->prefix}wc_order_stats", + "{$wpdb->prefix}wc_order_product_lookup", + "{$wpdb->prefix}wc_order_tax_lookup", + "{$wpdb->prefix}wc_order_coupon_lookup", + "{$wpdb->prefix}wc_admin_notes", + "{$wpdb->prefix}wc_admin_note_actions", + "{$wpdb->prefix}wc_customer_lookup", + "{$wpdb->prefix}wc_category_lookup", + ); + /** * Filter the list of known WooCommerce tables. * @@ -1143,7 +1154,7 @@ CREATE TABLE {$wpdb->prefix}wc_product_download_directories ( * * @param array $tables An array of WooCommerce-specific database table names. */ - $tables = apply_filters( 'woocommerce_install_get_tables', $tables ); + $tables = apply_filters( 'woocommerce_install_get_tables', array_merge( $tables, $wca_tables ) ); return $tables; } diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php index dc401bc3461..9992d5c0099 100644 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -110,10 +110,6 @@ class Install { if ( ( is_admin() && ! wp_doing_ajax() ) || wp_doing_cron() || defined( 'WP_CLI' ) ) { add_action( 'init', array( __CLASS__, 'check_version' ), 5 ); } - add_filter( 'wpmu_drop_tables', array( __CLASS__, 'wpmu_drop_tables' ) ); - - // Add wc-admin report tables to list of WooCommerce tables. - add_filter( 'woocommerce_install_get_tables', array( __CLASS__, 'add_tables' ) ); } /** @@ -394,30 +390,6 @@ class Install { ); } - /** - * Adds new tables. - * - * @param array $wc_tables List of WooCommerce tables. - * @return array - */ - public static function add_tables( $wc_tables ) { - return array_merge( - $wc_tables, - self::get_tables() - ); - } - - /** - * Uninstall tables when MU blog is deleted. - * - * @param array $tables List of tables that will be deleted by WP. - * - * @return string[] - */ - public static function wpmu_drop_tables( $tables ) { - return array_merge( $tables, self::get_tables() ); - } - /** * Get list of DB update callbacks. * @@ -571,21 +543,4 @@ class Install { Notes::delete_notes_with_name( $obsolete_notes_names ); } - - /** - * Drop WooCommerce Admin tables. - * - * @return void - */ - public static function drop_tables() { - global $wpdb; - - $tables = self::get_tables(); - - foreach ( $tables as $table ) { - /* phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared */ - $wpdb->query( "DROP TABLE IF EXISTS {$table}" ); - /* phpcs:enable */ - } - } } diff --git a/plugins/woocommerce/uninstall.php b/plugins/woocommerce/uninstall.php index 425683e0f2f..f77c37dc69b 100644 --- a/plugins/woocommerce/uninstall.php +++ b/plugins/woocommerce/uninstall.php @@ -29,8 +29,6 @@ wp_clear_scheduled_hook( 'woocommerce_cleanup_rate_limits' ); if ( defined( 'WC_REMOVE_ALL_DATA' ) && true === WC_REMOVE_ALL_DATA ) { // Drop WC Admin tables. include_once dirname( __FILE__ ) . '/src/Internal/Admin/Install.php'; - \Automattic\WooCommerce\Internal\Admin\Install::drop_tables(); - include_once dirname( __FILE__ ) . '/includes/class-wc-install.php'; // Roles + caps. From 1a86750d060aa25dcde64c6d604f1d3310e047b9 Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 4 Apr 2022 17:03:51 -0700 Subject: [PATCH 218/386] Call migrate_options from the core install --- plugins/woocommerce/includes/class-wc-install.php | 2 ++ plugins/woocommerce/src/Internal/Admin/Install.php | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 261a9cdf1a2..2522b29e246 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -12,6 +12,7 @@ use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Registe use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Synchronize as Download_Directories_Sync; use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil; use Automattic\WooCommerce\Internal\WCCom\ConnectionHelper as WCConnectionHelper; +use Automattic\WooCommerce\Internal\Admin\Install as WCA_Install; defined( 'ABSPATH' ) || exit; @@ -334,6 +335,7 @@ class WC_Install { self::create_tables(); self::verify_base_tables(); self::create_options(); + WCA_Install::migrate_options(); self::create_roles(); self::setup_environment(); self::create_terms(); diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php index 9992d5c0099..d184d126f40 100644 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -205,7 +205,6 @@ class Install { // If we made it till here nothing is running yet, lets set the transient now. set_transient( 'wc_admin_installing', 'yes', MINUTE_IN_SECONDS * 10 ); - self::migrate_options(); self::create_tables(); self::create_events(); self::delete_obsolete_notes(); From eb5e56e3742689e85acb34466b31434c14f7e546 Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 4 Apr 2022 17:04:32 -0700 Subject: [PATCH 219/386] Call WCA create_tables from the core install --- plugins/woocommerce/includes/class-wc-install.php | 1 + plugins/woocommerce/src/Internal/Admin/Install.php | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 2522b29e246..6ecfe34153e 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -334,6 +334,7 @@ class WC_Install { self::remove_admin_notices(); self::create_tables(); self::verify_base_tables(); + WCA_Install::create_tables(); self::create_options(); WCA_Install::migrate_options(); self::create_roles(); diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php index d184d126f40..48533816864 100644 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -205,7 +205,6 @@ class Install { // If we made it till here nothing is running yet, lets set the transient now. set_transient( 'wc_admin_installing', 'yes', MINUTE_IN_SECONDS * 10 ); - self::create_tables(); self::create_events(); self::delete_obsolete_notes(); self::maybe_update_db_version(); From 80d5d7fc432984d8eee39f4ef58056a75163e99c Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 4 Apr 2022 17:09:55 -0700 Subject: [PATCH 220/386] Call create_cron_jobs from the core install. I also renamed create_events to create_cron_jobs to match the function name used in the core --- plugins/woocommerce/includes/class-wc-install.php | 1 + plugins/woocommerce/src/Internal/Admin/Install.php | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 6ecfe34153e..1279d3728b4 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -341,6 +341,7 @@ class WC_Install { self::setup_environment(); self::create_terms(); self::create_cron_jobs(); + WCA_Install::create_cron_jobs(); self::create_files(); self::maybe_create_pages(); self::maybe_set_activation_transients(); diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php index 48533816864..d8b828090ca 100644 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -205,7 +205,6 @@ class Install { // If we made it till here nothing is running yet, lets set the transient now. set_transient( 'wc_admin_installing', 'yes', MINUTE_IN_SECONDS * 10 ); - self::create_events(); self::delete_obsolete_notes(); self::maybe_update_db_version(); @@ -492,7 +491,7 @@ class Install { /** * Schedule cron events. */ - public static function create_events() { + public static function create_cron_jobs() { if ( ! wp_next_scheduled( 'wc_admin_daily' ) ) { wp_schedule_event( time(), 'daily', 'wc_admin_daily' ); } From 60ce353c0de89486cf5dce3441aa41eda0bf882a Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 4 Apr 2022 17:12:03 -0700 Subject: [PATCH 221/386] Call delete_obsolete_notes from the core install --- plugins/woocommerce/includes/class-wc-install.php | 1 + plugins/woocommerce/src/Internal/Admin/Install.php | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 1279d3728b4..f67644a7661 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -342,6 +342,7 @@ class WC_Install { self::create_terms(); self::create_cron_jobs(); WCA_Install::create_cron_jobs(); + WCA_Install::delete_obsolete_notes(); self::create_files(); self::maybe_create_pages(); self::maybe_set_activation_transients(); diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php index d8b828090ca..35c73d18014 100644 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -205,7 +205,6 @@ class Install { // If we made it till here nothing is running yet, lets set the transient now. set_transient( 'wc_admin_installing', 'yes', MINUTE_IN_SECONDS * 10 ); - self::delete_obsolete_notes(); self::maybe_update_db_version(); delete_transient( 'wc_admin_installing' ); @@ -502,7 +501,7 @@ class Install { /** * Delete obsolete notes. */ - protected static function delete_obsolete_notes() { + public static function delete_obsolete_notes() { $obsolete_notes_names = array( 'wc-admin-welcome-note', 'wc-admin-store-notice-setting-moved', From eabbab9832d21ae902f17c88a1fc4fbf197e26c3 Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 4 Apr 2022 17:22:49 -0700 Subject: [PATCH 222/386] Stop calling maybe_update_db_version --- .../src/Internal/Admin/Install.php | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php index 35c73d18014..b40400fb5c5 100644 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -205,8 +205,6 @@ class Install { // If we made it till here nothing is running yet, lets set the transient now. set_transient( 'wc_admin_installing', 'yes', MINUTE_IN_SECONDS * 10 ); - self::maybe_update_db_version(); - delete_transient( 'wc_admin_installing' ); // Use add_option() here to avoid overwriting this value with each @@ -365,27 +363,6 @@ class Install { dbDelta( self::get_schema() ); } - /** - * Return a list of tables. Used to make sure all WC Admin tables are dropped - * when uninstalling the plugin in a single site or multi site environment. - * - * @return array WC tables. - */ - public static function get_tables() { - global $wpdb; - - return array( - "{$wpdb->prefix}wc_order_stats", - "{$wpdb->prefix}wc_order_product_lookup", - "{$wpdb->prefix}wc_order_tax_lookup", - "{$wpdb->prefix}wc_order_coupon_lookup", - "{$wpdb->prefix}wc_admin_notes", - "{$wpdb->prefix}wc_admin_note_actions", - "{$wpdb->prefix}wc_customer_lookup", - "{$wpdb->prefix}wc_category_lookup", - ); - } - /** * Get list of DB update callbacks. * @@ -412,7 +389,7 @@ class Install { /** * See if we need to show or run database updates during install. */ - private static function maybe_update_db_version() { + public static function maybe_update_db_version() { if ( self::needs_db_update() ) { self::update(); } else { From f4af74aa613aba7b2c29092eb6750a5ef2480222 Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 4 Apr 2022 17:23:07 -0700 Subject: [PATCH 223/386] Call maybe_update_db_version from the core install --- plugins/woocommerce/includes/class-wc-install.php | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index f67644a7661..3c2db62f90b 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -349,6 +349,7 @@ class WC_Install { self::set_paypal_standard_load_eligibility(); self::update_wc_version(); self::maybe_update_db_version(); + WCA_Install::maybe_update_db_version(); delete_transient( 'wc_installing' ); From f8944a8f540537eb4e4be6468be037e3e7b6cee9 Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 4 Apr 2022 17:26:46 -0700 Subject: [PATCH 224/386] Remove install from the WCA install * Moved woocommerce_admin_install_timestamp option and woocommerce_admin_installed action to the core --- .../woocommerce/includes/class-wc-install.php | 5 +++ .../src/Internal/Admin/Install.php | 33 ------------------- 2 files changed, 5 insertions(+), 33 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 3c2db62f90b..890bc496c1a 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -353,8 +353,13 @@ class WC_Install { delete_transient( 'wc_installing' ); + // Use add_option() here to avoid overwriting this value with each + // plugin version update. We base plugin age off of this value. + add_option( 'woocommerce_admin_install_timestamp', time() ); + do_action( 'woocommerce_flush_rewrite_rules' ); do_action( 'woocommerce_installed' ); + do_action( 'woocommerce_admin_installed' ); } /** diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php index b40400fb5c5..f72378b8f1f 100644 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -189,39 +189,6 @@ class Install { } } - /** - * Install WC Admin. - */ - public static function install() { - if ( ! is_blog_installed() ) { - return; - } - - // Check if we are not already running this routine. - if ( self::is_installing() ) { - return; - } - - // If we made it till here nothing is running yet, lets set the transient now. - set_transient( 'wc_admin_installing', 'yes', MINUTE_IN_SECONDS * 10 ); - - delete_transient( 'wc_admin_installing' ); - - // Use add_option() here to avoid overwriting this value with each - // plugin version update. We base plugin age off of this value. - add_option( 'woocommerce_admin_install_timestamp', time() ); - do_action( 'woocommerce_admin_installed' ); - } - - /** - * Check if the installer is installing. - * - * @return bool - */ - public static function is_installing() { - return 'yes' === get_transient( 'wc_admin_installing' ); - } - /** * Get database schema. * From abbe8556682e9a990915c1b2913bc9379c07012e Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 4 Apr 2022 21:18:49 -0700 Subject: [PATCH 225/386] Migrate install method to the core * Moved woocommerce_admin_updated, woocommerce_admin_newly_installed, woocommerce_admin_updated_existing actions to the core -- they are deprecated now * Added woocommerce_updated, woocommerce_newly_installed, and woocommerce_updated_existing actions --- .../woocommerce/includes/class-wc-install.php | 15 ++++- .../src/Internal/Admin/FeaturePlugin.php | 1 - .../src/Internal/Admin/Install.php | 59 ------------------- 3 files changed, 14 insertions(+), 61 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 890bc496c1a..fa126c23f72 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -216,9 +216,22 @@ class WC_Install { * This check is done on all requests and runs if the versions do not match. */ public static function check_version() { - if ( ! Constants::is_defined( 'IFRAME_REQUEST' ) && version_compare( get_option( 'woocommerce_version' ), WC()->version, '<' ) ) { + $wc_db_version = get_option( 'woocommerce_version' ); + $wc_current_version = WC()->version; + $requires_update = version_compare( $wc_db_version, $wc_current_version, '<' ); + if ( ! Constants::is_defined( 'IFRAME_REQUEST' ) && $requires_update ) { self::install(); do_action( 'woocommerce_updated' ); + do_action_deprecated( 'woocommerce_admin_updated' , array(), $wc_current_version); + // If there is no woocommerce_version option, consider it as a new install. + if ( ! $wc_db_version ) { + do_action( 'woocommerce_newly_installed' ); + do_action_deprecated( 'woocommerce_admin_newly_installed', array(), $wc_current_version ); + } else { + // if there is already a version and we're install, we're updating an existing install. + do_action( 'woocommerce_updated_existing' ); + do_action_deprecated( 'woocommerce_admin_updated_existing', array(), $wc_current_version ); + } } } diff --git a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php index f914a0bfc32..6985e0f4cd7 100644 --- a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php +++ b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php @@ -155,7 +155,6 @@ class FeaturePlugin { */ public function includes() { // Initialize Database updates, option migrations, and Notes. - Install::init(); Events::instance()->init(); Notes::init(); diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php index f72378b8f1f..fa193ac8a51 100644 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -103,15 +103,6 @@ class Install { 'woocommerce_extended_task_list_hidden' => 'woocommerce_extended_task_list_hidden', ); - /** - * Hook in tabs. - */ - public static function init() { - if ( ( is_admin() && ! wp_doing_ajax() ) || wp_doing_cron() || defined( 'WP_CLI' ) ) { - add_action( 'init', array( __CLASS__, 'check_version' ), 5 ); - } - } - /** * Migrate option values to their new keys/names. */ @@ -139,56 +130,6 @@ class Install { } } - /** - * Check WC Admin version and run the updater is required. - * - * This check is done on all requests and runs if the versions do not match. - */ - public static function check_version() { - if ( defined( 'IFRAME_REQUEST' ) ) { - return; - } - - $version_option = get_option( self::VERSION_OPTION ); - $requires_update = version_compare( get_option( self::VERSION_OPTION ), WC_ADMIN_VERSION_NUMBER, '<' ); - - /* - * When included as part of Core, no `on_activation` hook as been called - * so there is no version in options. Make sure install gets called in this - * case as well as a regular version update - */ - if ( ! $version_option || $requires_update ) { - self::install(); - /** - * WooCommerce Admin has been installed or updated. - */ - do_action( 'woocommerce_admin_updated' ); - - if ( ! $version_option ) { - /** - * WooCommerce Admin has been installed. - */ - do_action( 'woocommerce_admin_newly_installed' ); - } - - if ( $requires_update ) { - /** - * An existing installation of WooCommerce Admin has been - * updated. - */ - do_action( 'woocommerce_admin_updated_existing' ); - } - } - - /* - * Add the version option if none is found, as would be the case when - * initialized via Core for the first time. - */ - if ( ! $version_option ) { - add_option( self::VERSION_OPTION, WC_ADMIN_VERSION_NUMBER ); - } - } - /** * Get database schema. * From f3396df6c0a76bb97c4e91a9146f6ecad5d62eff Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 4 Apr 2022 21:25:48 -0700 Subject: [PATCH 226/386] Add woocommerce_admin_version for backward compatibility --- plugins/woocommerce/includes/class-wc-install.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index fa126c23f72..45634023cef 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -493,6 +493,8 @@ class WC_Install { */ private static function update_wc_version() { update_option( 'woocommerce_version', WC()->version ); + // last version of the WCA is 3.3.1. We'll no longer release WCA separately. + update_option( 'woocommerce_admin_version', '3.3.1'); } /** From 663aa3cc7232c4f7229d445e1be4bf401e563171 Mon Sep 17 00:00:00 2001 From: moon Date: Tue, 5 Apr 2022 01:10:18 -0700 Subject: [PATCH 227/386] Add is_installing for backward compatibility --- plugins/woocommerce/includes/class-wc-install.php | 6 +++++- .../Admin/Features/OnboardingTasks/DeprecatedOptions.php | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 45634023cef..19ef1e5e1d5 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -335,7 +335,7 @@ class WC_Install { } // Check if we are not already running this routine. - if ( 'yes' === get_transient( 'wc_installing' ) ) { + if ( self::is_installing() ) { return; } @@ -375,6 +375,10 @@ class WC_Install { do_action( 'woocommerce_admin_installed' ); } + public static function is_installing() { + return 'yes' === get_transient( 'wc_installing' ); + } + /** * Check if all the base tables are present. * diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php index bd5670edff2..17e7e40eacd 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php @@ -7,6 +7,7 @@ namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks; use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\TaskList; use Automattic\WooCommerce\Internal\Admin\Install; +use WC_Install; /** * DeprecatedOptions class. @@ -30,7 +31,7 @@ class DeprecatedOptions { * @return string */ public static function get_deprecated_options( $pre_option, $option ) { - if ( Install::is_installing() ) { + if ( WC_Install::is_installing() ) { return $pre_option; }; From 224985adfbceba7a781468e70b61edcd73c0266f Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 Apr 2022 14:48:00 -0700 Subject: [PATCH 228/386] Update WCA db update logic -- uses the same logic as the core --- .../woocommerce/includes/class-wc-install.php | 14 ++--- .../src/Internal/Admin/FeaturePlugin.php | 1 + .../src/Internal/Admin/Install.php | 63 +++++++------------ 3 files changed, 30 insertions(+), 48 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 19ef1e5e1d5..566ad42218a 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -216,21 +216,21 @@ class WC_Install { * This check is done on all requests and runs if the versions do not match. */ public static function check_version() { - $wc_db_version = get_option( 'woocommerce_version' ); - $wc_current_version = WC()->version; - $requires_update = version_compare( $wc_db_version, $wc_current_version, '<' ); + $wc_version = get_option( 'woocommerce_version' ); + $wc_code_version = WC()->version; + $requires_update = version_compare( $wc_version, $wc_code_version, '<' ); if ( ! Constants::is_defined( 'IFRAME_REQUEST' ) && $requires_update ) { self::install(); do_action( 'woocommerce_updated' ); - do_action_deprecated( 'woocommerce_admin_updated' , array(), $wc_current_version); + do_action_deprecated( 'woocommerce_admin_updated' , array(), $wc_code_version); // If there is no woocommerce_version option, consider it as a new install. - if ( ! $wc_db_version ) { + if ( ! $wc_version ) { do_action( 'woocommerce_newly_installed' ); - do_action_deprecated( 'woocommerce_admin_newly_installed', array(), $wc_current_version ); + do_action_deprecated( 'woocommerce_admin_newly_installed', array(), $wc_code_version ); } else { // if there is already a version and we're install, we're updating an existing install. do_action( 'woocommerce_updated_existing' ); - do_action_deprecated( 'woocommerce_admin_updated_existing', array(), $wc_current_version ); + do_action_deprecated( 'woocommerce_admin_updated_existing', array(), $wc_code_version ); } } } diff --git a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php index 6985e0f4cd7..f914a0bfc32 100644 --- a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php +++ b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php @@ -155,6 +155,7 @@ class FeaturePlugin { */ public function includes() { // Initialize Database updates, option migrations, and Notes. + Install::init(); Events::instance()->init(); Notes::init(); diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php index fa193ac8a51..8534d22e732 100644 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -262,6 +262,10 @@ class Install { return $tables; } + public static function init() { + add_action( 'woocommerce_adminupdate_db_to_current_version', array( __CLASS__, 'update_db_version' ) ); + } + /** * Create database tables. */ @@ -314,53 +318,30 @@ class Install { foreach ( self::get_db_update_callbacks() as $version => $update_callbacks ) { if ( version_compare( $current_db_version, $version, '<' ) ) { - $completed_version_updates = 0; foreach ( $update_callbacks as $update_callback ) { - $pending_jobs = WC()->queue()->search( - array( - 'per_page' => 1, - 'hook' => 'woocommerce_run_update_callback', - 'search' => wp_json_encode( array( $update_callback ) ), - 'group' => 'woocommerce-db-updates', - 'status' => 'pending', - ) + WC()->queue()->schedule_single( + time() + $loop, + 'woocommerce_run_update_callback', + array( $update_callback ), + 'woocommerce-db-updates' ); - - $complete_jobs = WC()->queue()->search( - array( - 'per_page' => 1, - 'hook' => 'woocommerce_run_update_callback', - 'search' => wp_json_encode( array( $update_callback ) ), - 'group' => 'woocommerce-db-updates', - 'status' => 'complete', - ) - ); - - $completed_version_updates += count( $complete_jobs ); - - if ( empty( $pending_jobs ) && empty( $complete_jobs ) ) { - WC()->queue()->schedule_single( - time() + $loop, - 'woocommerce_run_update_callback', - array( $update_callback ), - 'woocommerce-db-updates' - ); - Cache::invalidate(); - } - + Cache::invalidate(); $loop++; - - } - - // Users have experienced concurrency issues where all update callbacks - // have run but the version option hasn't been updated. If all the updates - // for a version are complete, update the version option to reflect that. - // See: https:// github.com/woocommerce/woocommerce-admin/issues/5058. - if ( count( $update_callbacks ) === $completed_version_updates ) { - self::update_db_version( $version ); } } } + + if ( version_compare( $current_db_version, WC_ADMIN_PLUGIN_FILE, '<' ) && + ! WC()->queue()->get_next( 'woocommerce_adminupdate_db_to_current_version' ) ) { + WC()->queue()->schedule_single( + time() + $loop, + 'woocommerce_adminupdate_db_to_current_version', + array( + 'version' => WC_ADMIN_PLUGIN_FILE, + ), + 'woocommerce-db-updates' + ); + } } /** From 940fcd31579db8f14f4ab410004d5f4dd5e94eb9 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 Apr 2022 15:36:31 -0700 Subject: [PATCH 229/386] Remove unnecessary calls --- plugins/woocommerce/includes/class-wc-install.php | 2 -- plugins/woocommerce/tests/legacy/bootstrap.php | 1 - 2 files changed, 3 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 566ad42218a..07e47ad498c 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -497,8 +497,6 @@ class WC_Install { */ private static function update_wc_version() { update_option( 'woocommerce_version', WC()->version ); - // last version of the WCA is 3.3.1. We'll no longer release WCA separately. - update_option( 'woocommerce_admin_version', '3.3.1'); } /** diff --git a/plugins/woocommerce/tests/legacy/bootstrap.php b/plugins/woocommerce/tests/legacy/bootstrap.php index cd6e2ca4958..483356ae236 100644 --- a/plugins/woocommerce/tests/legacy/bootstrap.php +++ b/plugins/woocommerce/tests/legacy/bootstrap.php @@ -196,7 +196,6 @@ class WC_Unit_Tests_Bootstrap { // Initialize the WC API extensions. \Automattic\WooCommerce\Internal\Admin\Install::create_tables(); - \Automattic\WooCommerce\Internal\Admin\Install::create_events(); WC_Install::install(); From f974b15f289eaad75ef167c64dad7c6c18b53f10 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 Apr 2022 15:39:34 -0700 Subject: [PATCH 230/386] Use the new actions --- .../RemoteInboxNotifications/RemoteInboxNotificationsEngine.php | 2 +- .../Internal/Admin/Notes/ManageStoreActivityFromHomeScreen.php | 2 +- .../src/Internal/Admin/RemoteFreeExtensions/Init.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsEngine.php b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsEngine.php index cecefeb26f9..04493617406 100644 --- a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsEngine.php +++ b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsEngine.php @@ -40,7 +40,7 @@ class RemoteInboxNotificationsEngine { // Hook into WCA updated. This is hooked up here rather than in // on_admin_init because that runs too late to hook into the action. add_action( - 'woocommerce_admin_updated', + 'woocommerce_updated', function() { $next_hook = WC()->queue()->get_next( 'woocommerce_run_on_woocommerce_admin_updated', diff --git a/plugins/woocommerce/src/Internal/Admin/Notes/ManageStoreActivityFromHomeScreen.php b/plugins/woocommerce/src/Internal/Admin/Notes/ManageStoreActivityFromHomeScreen.php index 434190c781e..88260ab9ec4 100644 --- a/plugins/woocommerce/src/Internal/Admin/Notes/ManageStoreActivityFromHomeScreen.php +++ b/plugins/woocommerce/src/Internal/Admin/Notes/ManageStoreActivityFromHomeScreen.php @@ -29,7 +29,7 @@ class ManageStoreActivityFromHomeScreen { */ public function __construct() { add_action( - 'woocommerce_admin_updated_existing', + 'woocommerce_updated_existing', array( $this, 'possibly_add_note' ) ); } diff --git a/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/Init.php b/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/Init.php index 01813f31d2e..12a28bdec7b 100644 --- a/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/Init.php +++ b/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/Init.php @@ -20,7 +20,7 @@ class Init { */ public function __construct() { add_action( 'change_locale', array( __CLASS__, 'delete_specs_transient' ) ); - add_action( 'woocommerce_admin_updated', array( __CLASS__, 'delete_specs_transient' ) ); + add_action( 'woocommerce_updated', array( __CLASS__, 'delete_specs_transient' ) ); } /** From 05200a99fb2d62b4765288b031c37838def6ecef Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 Apr 2022 18:41:56 -0700 Subject: [PATCH 231/386] Move $db_updates to the core --- .../woocommerce/includes/class-wc-install.php | 35 ++++- .../src/Internal/Admin/FeaturePlugin.php | 1 - .../src/Internal/Admin/Install.php | 138 ------------------ 3 files changed, 34 insertions(+), 140 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 07e47ad498c..70185c94eaf 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -160,10 +160,24 @@ class WC_Install { 'wc_update_400_increase_size_of_column', 'wc_update_400_reset_action_scheduler_migration_status', 'wc_update_400_db_version', + + 'wc_admin_update_0201_order_status_index', + 'wc_admin_update_0201_db_version', + 'wc_admin_update_0230_rename_gross_total', + 'wc_admin_update_0230_db_version', + 'wc_admin_update_0251_remove_unsnooze_action', + 'wc_admin_update_0251_db_version', ), '4.4.0' => array( 'wc_update_440_insert_attribute_terms_for_variable_products', 'wc_update_440_db_version', + + 'wc_admin_update_110_remove_facebook_note', + 'wc_admin_update_110_db_version', + 'wc_admin_update_130_remove_dismiss_action_from_tracking_opt_in_note', + 'wc_admin_update_130_db_version', + 'wc_admin_update_140_change_deactivate_plugin_note_type', + 'wc_admin_update_140_db_version', ), '4.5.0' => array( 'wc_update_450_sanitize_coupons_code', @@ -172,6 +186,11 @@ class WC_Install { '5.0.0' => array( 'wc_update_500_fix_product_review_count', 'wc_update_500_db_version', + + 'wc_admin_update_160_remove_facebook_note', + 'wc_admin_update_160_db_version', + 'wc_admin_update_170_homescreen_layout', + 'wc_admin_update_170_db_version', ), '5.6.0' => array( 'wc_update_560_create_refund_returns_page', @@ -180,15 +199,30 @@ class WC_Install { '6.0.0' => array( 'wc_update_600_migrate_rate_limit_options', 'wc_update_600_db_version', + + 'wc_admin_update_270_delete_report_downloads', + 'wc_admin_update_270_db_version', + 'wc_admin_update_271_update_task_list_options', + 'wc_admin_update_271_db_version', + 'wc_admin_update_280_order_status', + 'wc_admin_update_280_db_version', + 'wc_admin_update_290_update_apperance_task_option', + 'wc_admin_update_290_delete_default_homepage_layout_option', + 'wc_admin_update_290_db_version', ), '6.3.0' => array( 'wc_update_630_create_product_attributes_lookup_table', 'wc_update_630_db_version', + 'wc_admin_update_300_update_is_read_from_last_read', + 'wc_admin_update_300_db_version', ), '6.4.0' => array( 'wc_update_640_add_primary_key_to_product_attributes_lookup_table', 'wc_update_640_approved_download_directories', 'wc_update_640_db_version', + + 'wc_admin_update_340_remove_is_primary_from_note_action', + 'wc_admin_update_340_db_version', ), ); @@ -362,7 +396,6 @@ class WC_Install { self::set_paypal_standard_load_eligibility(); self::update_wc_version(); self::maybe_update_db_version(); - WCA_Install::maybe_update_db_version(); delete_transient( 'wc_installing' ); diff --git a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php index f914a0bfc32..6985e0f4cd7 100644 --- a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php +++ b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php @@ -155,7 +155,6 @@ class FeaturePlugin { */ public function includes() { // Initialize Database updates, option migrations, and Notes. - Install::init(); Events::instance()->init(); Notes::init(); diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php index 8534d22e732..093809a1fdf 100644 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -18,71 +18,6 @@ class Install { */ const VERSION_OPTION = 'woocommerce_admin_version'; - /** - * DB updates and callbacks that need to be run per version. - * - * @var array - */ - protected static $db_updates = array( - '0.20.1' => array( - 'wc_admin_update_0201_order_status_index', - 'wc_admin_update_0201_db_version', - ), - '0.23.0' => array( - 'wc_admin_update_0230_rename_gross_total', - 'wc_admin_update_0230_db_version', - ), - '0.25.1' => array( - 'wc_admin_update_0251_remove_unsnooze_action', - 'wc_admin_update_0251_db_version', - ), - '1.1.0' => array( - 'wc_admin_update_110_remove_facebook_note', - 'wc_admin_update_110_db_version', - ), - '1.3.0' => array( - 'wc_admin_update_130_remove_dismiss_action_from_tracking_opt_in_note', - 'wc_admin_update_130_db_version', - ), - '1.4.0' => array( - 'wc_admin_update_140_change_deactivate_plugin_note_type', - 'wc_admin_update_140_db_version', - ), - '1.6.0' => array( - 'wc_admin_update_160_remove_facebook_note', - 'wc_admin_update_160_db_version', - ), - '1.7.0' => array( - 'wc_admin_update_170_homescreen_layout', - 'wc_admin_update_170_db_version', - ), - '2.7.0' => array( - 'wc_admin_update_270_delete_report_downloads', - 'wc_admin_update_270_db_version', - ), - '2.7.1' => array( - 'wc_admin_update_271_update_task_list_options', - 'wc_admin_update_271_db_version', - ), - '2.8.0' => array( - 'wc_admin_update_280_order_status', - 'wc_admin_update_280_db_version', - ), - '2.9.0' => array( - 'wc_admin_update_290_update_apperance_task_option', - 'wc_admin_update_290_delete_default_homepage_layout_option', - 'wc_admin_update_290_db_version', - ), - '3.0.0' => array( - 'wc_admin_update_300_update_is_read_from_last_read', - 'wc_admin_update_300_db_version', - ), - '3.4.0' => array( - 'wc_admin_update_340_remove_is_primary_from_note_action', - 'wc_admin_update_340_db_version', - ), - ); - /** * Migrated option names mapping. New => old. * @@ -262,10 +197,6 @@ class Install { return $tables; } - public static function init() { - add_action( 'woocommerce_adminupdate_db_to_current_version', array( __CLASS__, 'update_db_version' ) ); - } - /** * Create database tables. */ @@ -284,75 +215,6 @@ class Install { return self::$db_updates; } - /** - * Is a DB update needed? - * - * @return boolean - */ - public static function needs_db_update() { - $current_db_version = get_option( self::VERSION_OPTION, null ); - $updates = self::get_db_update_callbacks(); - $update_versions = array_keys( $updates ); - usort( $update_versions, 'version_compare' ); - - return ! is_null( $current_db_version ) && version_compare( $current_db_version, end( $update_versions ), '<' ); - } - - /** - * See if we need to show or run database updates during install. - */ - public static function maybe_update_db_version() { - if ( self::needs_db_update() ) { - self::update(); - } else { - self::update_db_version(); - } - } - - /** - * Push all needed DB updates to the queue for processing. - */ - private static function update() { - $current_db_version = get_option( self::VERSION_OPTION ); - $loop = 0; - - foreach ( self::get_db_update_callbacks() as $version => $update_callbacks ) { - if ( version_compare( $current_db_version, $version, '<' ) ) { - foreach ( $update_callbacks as $update_callback ) { - WC()->queue()->schedule_single( - time() + $loop, - 'woocommerce_run_update_callback', - array( $update_callback ), - 'woocommerce-db-updates' - ); - Cache::invalidate(); - $loop++; - } - } - } - - if ( version_compare( $current_db_version, WC_ADMIN_PLUGIN_FILE, '<' ) && - ! WC()->queue()->get_next( 'woocommerce_adminupdate_db_to_current_version' ) ) { - WC()->queue()->schedule_single( - time() + $loop, - 'woocommerce_adminupdate_db_to_current_version', - array( - 'version' => WC_ADMIN_PLUGIN_FILE, - ), - 'woocommerce-db-updates' - ); - } - } - - /** - * Update WC Admin version to current. - * - * @param string|null $version New WooCommerce Admin DB version or null. - */ - public static function update_db_version( $version = null ) { - update_option( self::VERSION_OPTION, is_null( $version ) ? WC_ADMIN_VERSION_NUMBER : $version ); - } - /** * Schedule cron events. */ From dbe913ea0e4a47e6ea0bb0435c5b1995e9b08cd5 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 Apr 2022 18:46:52 -0700 Subject: [PATCH 232/386] Move create_cron_jobs to the core --- .../woocommerce/includes/class-wc-install.php | 7 +- .../src/Internal/Admin/Install.php | 134 +----------------- 2 files changed, 7 insertions(+), 134 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 70185c94eaf..b07a231fd17 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -388,7 +388,6 @@ class WC_Install { self::setup_environment(); self::create_terms(); self::create_cron_jobs(); - WCA_Install::create_cron_jobs(); WCA_Install::delete_obsolete_notes(); self::create_files(); self::maybe_create_pages(); @@ -642,6 +641,12 @@ class WC_Install { wp_schedule_event( time() + MINUTE_IN_SECONDS, 'fifteendays', 'woocommerce_geoip_updater' ); wp_schedule_event( time() + 10, apply_filters( 'woocommerce_tracker_event_recurrence', 'daily' ), 'woocommerce_tracker_send_event' ); wp_schedule_event( time() + ( 3 * HOUR_IN_SECONDS ), 'daily', 'woocommerce_cleanup_rate_limits' ); + + if ( ! wp_next_scheduled( 'wc_admin_daily' ) ) { + wp_schedule_event( time(), 'daily', 'wc_admin_daily' ); + } + // Note: this is potentially redundant when the core package exists. + wp_schedule_single_event( time() + 10, 'generate_category_lookup_table' ); } /** diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php index 093809a1fdf..c9fef005af1 100644 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -79,119 +79,7 @@ class Install { $max_index_length = 191; $tables = " - CREATE TABLE {$wpdb->prefix}wc_order_stats ( - order_id bigint(20) unsigned NOT NULL, - parent_id bigint(20) unsigned DEFAULT 0 NOT NULL, - date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, - date_created_gmt datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, - num_items_sold int(11) DEFAULT 0 NOT NULL, - total_sales double DEFAULT 0 NOT NULL, - tax_total double DEFAULT 0 NOT NULL, - shipping_total double DEFAULT 0 NOT NULL, - net_total double DEFAULT 0 NOT NULL, - returning_customer boolean DEFAULT NULL, - status varchar(200) NOT NULL, - customer_id BIGINT UNSIGNED NOT NULL, - PRIMARY KEY (order_id), - KEY date_created (date_created), - KEY customer_id (customer_id), - KEY status (status({$max_index_length})) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_order_product_lookup ( - order_item_id BIGINT UNSIGNED NOT NULL, - order_id BIGINT UNSIGNED NOT NULL, - product_id BIGINT UNSIGNED NOT NULL, - variation_id BIGINT UNSIGNED NOT NULL, - customer_id BIGINT UNSIGNED NULL, - date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, - product_qty INT NOT NULL, - product_net_revenue double DEFAULT 0 NOT NULL, - product_gross_revenue double DEFAULT 0 NOT NULL, - coupon_amount double DEFAULT 0 NOT NULL, - tax_amount double DEFAULT 0 NOT NULL, - shipping_amount double DEFAULT 0 NOT NULL, - shipping_tax_amount double DEFAULT 0 NOT NULL, - PRIMARY KEY (order_item_id), - KEY order_id (order_id), - KEY product_id (product_id), - KEY customer_id (customer_id), - KEY date_created (date_created) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_order_tax_lookup ( - order_id BIGINT UNSIGNED NOT NULL, - tax_rate_id BIGINT UNSIGNED NOT NULL, - date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, - shipping_tax double DEFAULT 0 NOT NULL, - order_tax double DEFAULT 0 NOT NULL, - total_tax double DEFAULT 0 NOT NULL, - PRIMARY KEY (order_id, tax_rate_id), - KEY tax_rate_id (tax_rate_id), - KEY date_created (date_created) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_order_coupon_lookup ( - order_id BIGINT UNSIGNED NOT NULL, - coupon_id BIGINT NOT NULL, - date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, - discount_amount double DEFAULT 0 NOT NULL, - PRIMARY KEY (order_id, coupon_id), - KEY coupon_id (coupon_id), - KEY date_created (date_created) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_admin_notes ( - note_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - name varchar(255) NOT NULL, - type varchar(20) NOT NULL, - locale varchar(20) NOT NULL, - title longtext NOT NULL, - content longtext NOT NULL, - content_data longtext NULL default null, - status varchar(200) NOT NULL, - source varchar(200) NOT NULL, - date_created datetime NOT NULL default '0000-00-00 00:00:00', - date_reminder datetime NULL default null, - is_snoozable boolean DEFAULT 0 NOT NULL, - layout varchar(20) DEFAULT '' NOT NULL, - image varchar(200) NULL DEFAULT NULL, - is_deleted boolean DEFAULT 0 NOT NULL, - is_read boolean DEFAULT 0 NOT NULL, - icon varchar(200) NOT NULL default 'info', - PRIMARY KEY (note_id) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_admin_note_actions ( - action_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - note_id BIGINT UNSIGNED NOT NULL, - name varchar(255) NOT NULL, - label varchar(255) NOT NULL, - query longtext NOT NULL, - status varchar(255) NOT NULL, - actioned_text varchar(255) NOT NULL, - nonce_action varchar(255) NULL DEFAULT NULL, - nonce_name varchar(255) NULL DEFAULT NULL, - PRIMARY KEY (action_id), - KEY note_id (note_id) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_customer_lookup ( - customer_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - user_id BIGINT UNSIGNED DEFAULT NULL, - username varchar(60) DEFAULT '' NOT NULL, - first_name varchar(255) NOT NULL, - last_name varchar(255) NOT NULL, - email varchar(100) NULL default NULL, - date_last_active timestamp NULL default null, - date_registered timestamp NULL default null, - country char(2) DEFAULT '' NOT NULL, - postcode varchar(20) DEFAULT '' NOT NULL, - city varchar(100) DEFAULT '' NOT NULL, - state varchar(100) DEFAULT '' NOT NULL, - PRIMARY KEY (customer_id), - UNIQUE KEY user_id (user_id), - KEY email (email) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_category_lookup ( - category_tree_id BIGINT UNSIGNED NOT NULL, - category_id BIGINT UNSIGNED NOT NULL, - PRIMARY KEY (category_tree_id,category_id) - ) $collate; + "; return $tables; @@ -206,26 +94,6 @@ class Install { dbDelta( self::get_schema() ); } - /** - * Get list of DB update callbacks. - * - * @return array - */ - public static function get_db_update_callbacks() { - return self::$db_updates; - } - - /** - * Schedule cron events. - */ - public static function create_cron_jobs() { - if ( ! wp_next_scheduled( 'wc_admin_daily' ) ) { - wp_schedule_event( time(), 'daily', 'wc_admin_daily' ); - } - // Note: this is potentially redundant when the core package exists. - wp_schedule_single_event( time() + 10, 'generate_category_lookup_table' ); - } - /** * Delete obsolete notes. */ From 9a038a4bd29699354203fec3748d37f851c04b66 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 Apr 2022 18:49:13 -0700 Subject: [PATCH 233/386] Move create_tables to the core --- .../woocommerce/includes/class-wc-install.php | 114 +++++++++++++++++- .../src/Internal/Admin/Install.php | 29 ----- 2 files changed, 113 insertions(+), 30 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index b07a231fd17..6ab61c3fa49 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -381,7 +381,6 @@ class WC_Install { self::remove_admin_notices(); self::create_tables(); self::verify_base_tables(); - WCA_Install::create_tables(); self::create_options(); WCA_Install::migrate_options(); self::create_roles(); @@ -1162,6 +1161,119 @@ CREATE TABLE {$wpdb->prefix}wc_product_download_directories ( enabled TINYINT(1) NOT NULL DEFAULT 0, PRIMARY KEY (url_id), KEY `url` (`url`) +) $collate; +CREATE TABLE {$wpdb->prefix}wc_order_stats ( + order_id bigint(20) unsigned NOT NULL, + parent_id bigint(20) unsigned DEFAULT 0 NOT NULL, + date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, + date_created_gmt datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, + num_items_sold int(11) DEFAULT 0 NOT NULL, + total_sales double DEFAULT 0 NOT NULL, + tax_total double DEFAULT 0 NOT NULL, + shipping_total double DEFAULT 0 NOT NULL, + net_total double DEFAULT 0 NOT NULL, + returning_customer boolean DEFAULT NULL, + status varchar(200) NOT NULL, + customer_id BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (order_id), + KEY date_created (date_created), + KEY customer_id (customer_id), + KEY status (status({$max_index_length})) +) $collate; +CREATE TABLE {$wpdb->prefix}wc_order_product_lookup ( + order_item_id BIGINT UNSIGNED NOT NULL, + order_id BIGINT UNSIGNED NOT NULL, + product_id BIGINT UNSIGNED NOT NULL, + variation_id BIGINT UNSIGNED NOT NULL, + customer_id BIGINT UNSIGNED NULL, + date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, + product_qty INT NOT NULL, + product_net_revenue double DEFAULT 0 NOT NULL, + product_gross_revenue double DEFAULT 0 NOT NULL, + coupon_amount double DEFAULT 0 NOT NULL, + tax_amount double DEFAULT 0 NOT NULL, + shipping_amount double DEFAULT 0 NOT NULL, + shipping_tax_amount double DEFAULT 0 NOT NULL, + PRIMARY KEY (order_item_id), + KEY order_id (order_id), + KEY product_id (product_id), + KEY customer_id (customer_id), + KEY date_created (date_created) +) $collate; +CREATE TABLE {$wpdb->prefix}wc_order_tax_lookup ( + order_id BIGINT UNSIGNED NOT NULL, + tax_rate_id BIGINT UNSIGNED NOT NULL, + date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, + shipping_tax double DEFAULT 0 NOT NULL, + order_tax double DEFAULT 0 NOT NULL, + total_tax double DEFAULT 0 NOT NULL, + PRIMARY KEY (order_id, tax_rate_id), + KEY tax_rate_id (tax_rate_id), + KEY date_created (date_created) +) $collate; +CREATE TABLE {$wpdb->prefix}wc_order_coupon_lookup ( + order_id BIGINT UNSIGNED NOT NULL, + coupon_id BIGINT NOT NULL, + date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, + discount_amount double DEFAULT 0 NOT NULL, + PRIMARY KEY (order_id, coupon_id), + KEY coupon_id (coupon_id), + KEY date_created (date_created) +) $collate; +CREATE TABLE {$wpdb->prefix}wc_admin_notes ( + note_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + name varchar(255) NOT NULL, + type varchar(20) NOT NULL, + locale varchar(20) NOT NULL, + title longtext NOT NULL, + content longtext NOT NULL, + content_data longtext NULL default null, + status varchar(200) NOT NULL, + source varchar(200) NOT NULL, + date_created datetime NOT NULL default '0000-00-00 00:00:00', + date_reminder datetime NULL default null, + is_snoozable boolean DEFAULT 0 NOT NULL, + layout varchar(20) DEFAULT '' NOT NULL, + image varchar(200) NULL DEFAULT NULL, + is_deleted boolean DEFAULT 0 NOT NULL, + is_read boolean DEFAULT 0 NOT NULL, + icon varchar(200) NOT NULL default 'info', + PRIMARY KEY (note_id) +) $collate; +CREATE TABLE {$wpdb->prefix}wc_admin_note_actions ( + action_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + note_id BIGINT UNSIGNED NOT NULL, + name varchar(255) NOT NULL, + label varchar(255) NOT NULL, + query longtext NOT NULL, + status varchar(255) NOT NULL, + actioned_text varchar(255) NOT NULL, + nonce_action varchar(255) NULL DEFAULT NULL, + nonce_name varchar(255) NULL DEFAULT NULL, + PRIMARY KEY (action_id), + KEY note_id (note_id) +) $collate; +CREATE TABLE {$wpdb->prefix}wc_customer_lookup ( + customer_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + user_id BIGINT UNSIGNED DEFAULT NULL, + username varchar(60) DEFAULT '' NOT NULL, + first_name varchar(255) NOT NULL, + last_name varchar(255) NOT NULL, + email varchar(100) NULL default NULL, + date_last_active timestamp NULL default null, + date_registered timestamp NULL default null, + country char(2) DEFAULT '' NOT NULL, + postcode varchar(20) DEFAULT '' NOT NULL, + city varchar(100) DEFAULT '' NOT NULL, + state varchar(100) DEFAULT '' NOT NULL, + PRIMARY KEY (customer_id), + UNIQUE KEY user_id (user_id), + KEY email (email) +) $collate; +CREATE TABLE {$wpdb->prefix}wc_category_lookup ( + category_tree_id BIGINT UNSIGNED NOT NULL, + category_id BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (category_tree_id,category_id) ) $collate; "; diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php index c9fef005af1..be7dd39af1f 100644 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -65,35 +65,6 @@ class Install { } } - /** - * Get database schema. - * - * @return string - */ - protected static function get_schema() { - global $wpdb; - - $collate = $wpdb->has_cap( 'collation' ) ? $wpdb->get_charset_collate() : ''; - - // Max DB index length. See wp_get_db_schema(). - $max_index_length = 191; - - $tables = " - - "; - - return $tables; - } - - /** - * Create database tables. - */ - public static function create_tables() { - require_once ABSPATH . 'wp-admin/includes/upgrade.php'; - - dbDelta( self::get_schema() ); - } - /** * Delete obsolete notes. */ From 0dae9d450e107b2f66d69553fd1ed333bf0048a0 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 Apr 2022 18:50:45 -0700 Subject: [PATCH 234/386] Move migrate_options to core --- .../woocommerce/includes/class-wc-install.php | 44 ++++++++++++++++- .../src/Internal/Admin/Install.php | 47 ------------------- 2 files changed, 43 insertions(+), 48 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 6ab61c3fa49..0ff6779b0fb 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -382,7 +382,7 @@ class WC_Install { self::create_tables(); self::verify_base_tables(); self::create_options(); - WCA_Install::migrate_options(); + self::migrate_options(); self::create_roles(); self::setup_environment(); self::create_terms(); @@ -755,6 +755,48 @@ class WC_Install { } } + /** + * Migrate option values to their new keys/names. + */ + public static function migrate_options() { + + $migrated_options = array( + 'woocommerce_onboarding_profile' => 'wc_onboarding_profile', + 'woocommerce_admin_install_timestamp' => 'wc_admin_install_timestamp', + 'woocommerce_onboarding_opt_in' => 'wc_onboarding_opt_in', + 'woocommerce_admin_import_stats' => 'wc_admin_import_stats', + 'woocommerce_admin_version' => 'wc_admin_version', + 'woocommerce_admin_last_orders_milestone' => 'wc_admin_last_orders_milestone', + 'woocommerce_admin-wc-helper-last-refresh' => 'wc-admin-wc-helper-last-refresh', + 'woocommerce_admin_report_export_status' => 'wc_admin_report_export_status', + 'woocommerce_task_list_complete' => 'woocommerce_task_list_complete', + 'woocommerce_task_list_hidden' => 'woocommerce_task_list_hidden', + 'woocommerce_extended_task_list_complete' => 'woocommerce_extended_task_list_complete', + 'woocommerce_extended_task_list_hidden' => 'woocommerce_extended_task_list_hidden', + ); + + wc_maybe_define_constant( 'WC_ADMIN_MIGRATING_OPTIONS', true ); + + foreach ( $migrated_options as $new_option => $old_option ) { + $old_option_value = get_option( $old_option, false ); + + // Continue if no option value was previously set. + if ( false === $old_option_value ) { + continue; + } + + if ( '1' === $old_option_value ) { + $old_option_value = 'yes'; + } elseif ( '0' === $old_option_value ) { + $old_option_value = 'no'; + } + + update_option( $new_option, $old_option_value ); + if ( $new_option !== $old_option ) { + delete_option( $old_option ); + } + } + } /** * Add the default terms for WC taxonomies - product types and order statuses. Modify this at your own risk. */ diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php index be7dd39af1f..2b566202d60 100644 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -18,53 +18,6 @@ class Install { */ const VERSION_OPTION = 'woocommerce_admin_version'; - /** - * Migrated option names mapping. New => old. - * - * @var array - */ - protected static $migrated_options = array( - 'woocommerce_onboarding_profile' => 'wc_onboarding_profile', - 'woocommerce_admin_install_timestamp' => 'wc_admin_install_timestamp', - 'woocommerce_onboarding_opt_in' => 'wc_onboarding_opt_in', - 'woocommerce_admin_import_stats' => 'wc_admin_import_stats', - 'woocommerce_admin_version' => 'wc_admin_version', - 'woocommerce_admin_last_orders_milestone' => 'wc_admin_last_orders_milestone', - 'woocommerce_admin-wc-helper-last-refresh' => 'wc-admin-wc-helper-last-refresh', - 'woocommerce_admin_report_export_status' => 'wc_admin_report_export_status', - 'woocommerce_task_list_complete' => 'woocommerce_task_list_complete', - 'woocommerce_task_list_hidden' => 'woocommerce_task_list_hidden', - 'woocommerce_extended_task_list_complete' => 'woocommerce_extended_task_list_complete', - 'woocommerce_extended_task_list_hidden' => 'woocommerce_extended_task_list_hidden', - ); - - /** - * Migrate option values to their new keys/names. - */ - public static function migrate_options() { - wc_maybe_define_constant( 'WC_ADMIN_MIGRATING_OPTIONS', true ); - - foreach ( self::$migrated_options as $new_option => $old_option ) { - $old_option_value = get_option( $old_option, false ); - - // Continue if no option value was previously set. - if ( false === $old_option_value ) { - continue; - } - - if ( '1' === $old_option_value ) { - $old_option_value = 'yes'; - } elseif ( '0' === $old_option_value ) { - $old_option_value = 'no'; - } - - update_option( $new_option, $old_option_value ); - if ( $new_option !== $old_option ) { - delete_option( $old_option ); - } - } - } - /** * Delete obsolete notes. */ From 70ff5ad3ae6b2f8a9915cd300883c6cc7611720a Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 Apr 2022 18:51:31 -0700 Subject: [PATCH 235/386] Move delete_obsolete_notes to the core --- .../woocommerce/includes/class-wc-install.php | 42 ++++++++++++++++++- .../src/Internal/Admin/Install.php | 37 ---------------- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 0ff6779b0fb..a28feca66a5 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -7,6 +7,7 @@ */ use Automattic\Jetpack\Constants; +use Automattic\WooCommerce\Admin\Notes\Notes; use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator; use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Register as Download_Directories; use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Synchronize as Download_Directories_Sync; @@ -387,7 +388,7 @@ class WC_Install { self::setup_environment(); self::create_terms(); self::create_cron_jobs(); - WCA_Install::delete_obsolete_notes(); + self::delete_obsolete_notes(); self::create_files(); self::maybe_create_pages(); self::maybe_set_activation_transients(); @@ -755,6 +756,45 @@ class WC_Install { } } + public static function delete_obsolete_notes() { + $obsolete_notes_names = array( + 'wc-admin-welcome-note', + 'wc-admin-store-notice-setting-moved', + 'wc-admin-store-notice-giving-feedback', + 'wc-admin-learn-more-about-product-settings', + 'wc-admin-onboarding-profiler-reminder', + 'wc-admin-historical-data', + 'wc-admin-review-shipping-settings', + 'wc-admin-home-screen-feedback', + 'wc-admin-effortless-payments-by-mollie', + 'wc-admin-google-ads-and-marketing', + 'wc-admin-marketing-intro', + 'wc-admin-draw-attention', + 'wc-admin-need-some-inspiration', + 'wc-admin-choose-niche', + 'wc-admin-start-dropshipping-business', + 'wc-admin-filter-by-product-variations-in-reports', + 'wc-admin-learn-more-about-variable-products', + 'wc-admin-getting-started-ecommerce-webinar', + 'wc-admin-navigation-feedback', + 'wc-admin-navigation-feedback-follow-up', + ); + + $additional_obsolete_notes_names = apply_filters( + 'woocommerce_admin_obsolete_notes_names', + array() + ); + + if ( is_array( $additional_obsolete_notes_names ) ) { + $obsolete_notes_names = array_merge( + $obsolete_notes_names, + $additional_obsolete_notes_names + ); + } + + Notes::delete_notes_with_name( $obsolete_notes_names ); + } + /** * Migrate option values to their new keys/names. */ diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php index 2b566202d60..a526b22ce73 100644 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -21,42 +21,5 @@ class Install { /** * Delete obsolete notes. */ - public static function delete_obsolete_notes() { - $obsolete_notes_names = array( - 'wc-admin-welcome-note', - 'wc-admin-store-notice-setting-moved', - 'wc-admin-store-notice-giving-feedback', - 'wc-admin-learn-more-about-product-settings', - 'wc-admin-onboarding-profiler-reminder', - 'wc-admin-historical-data', - 'wc-admin-review-shipping-settings', - 'wc-admin-home-screen-feedback', - 'wc-admin-effortless-payments-by-mollie', - 'wc-admin-google-ads-and-marketing', - 'wc-admin-marketing-intro', - 'wc-admin-draw-attention', - 'wc-admin-need-some-inspiration', - 'wc-admin-choose-niche', - 'wc-admin-start-dropshipping-business', - 'wc-admin-filter-by-product-variations-in-reports', - 'wc-admin-learn-more-about-variable-products', - 'wc-admin-getting-started-ecommerce-webinar', - 'wc-admin-navigation-feedback', - 'wc-admin-navigation-feedback-follow-up', - ); - $additional_obsolete_notes_names = apply_filters( - 'woocommerce_admin_obsolete_notes_names', - array() - ); - - if ( is_array( $additional_obsolete_notes_names ) ) { - $obsolete_notes_names = array_merge( - $obsolete_notes_names, - $additional_obsolete_notes_names - ); - } - - Notes::delete_notes_with_name( $obsolete_notes_names ); - } } From 3c1ce9c6139186a1f175d81609dd7583a113f447 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 Apr 2022 18:57:41 -0700 Subject: [PATCH 236/386] Make create_tables public -- this is needed from WCA classes --- plugins/woocommerce/includes/class-wc-install.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index a28feca66a5..2fc22152870 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -909,7 +909,7 @@ class WC_Install { * woocommerce_tax_rates - Tax Rates are stored inside 2 tables making tax queries simple and efficient. * woocommerce_tax_rate_locations - Each rate can be applied to more than one postcode/city hence the second table. */ - private static function create_tables() { + public static function create_tables() { global $wpdb; $wpdb->hide_errors(); From c17cbb5dbdb098aefe05219f4ea0a01b13d73783 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 Apr 2022 18:58:39 -0700 Subject: [PATCH 237/386] Remove WCA Install class references --- .../src/Internal/Admin/CategoryLookup.php | 2 +- .../woocommerce/src/Internal/Admin/FeaturePlugin.php | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php b/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php index 9bc8fbf153d..d60b1bd8ca6 100644 --- a/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php +++ b/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php @@ -65,7 +65,7 @@ class CategoryLookup { global $wpdb; // Delete existing data and ensure schema is current. - Install::create_tables(); + \WC_Install::create_tables(); $wpdb->query( "TRUNCATE TABLE $wpdb->wc_category_lookup" ); $terms = get_terms( diff --git a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php index 6985e0f4cd7..d6091a2b2b6 100644 --- a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php +++ b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php @@ -78,8 +78,6 @@ class FeaturePlugin { require_once WC_ADMIN_ABSPATH . '/includes/react-admin/wc-admin-update-functions.php'; require_once WC_ADMIN_ABSPATH . '/includes/react-admin/class-experimental-abtest.php'; - register_activation_hook( WC_ADMIN_PLUGIN_FILE, array( $this, 'on_activation' ) ); - register_deactivation_hook( WC_ADMIN_PLUGIN_FILE, array( $this, 'on_deactivation' ) ); if ( did_action( 'plugins_loaded' ) ) { self::on_plugins_loaded(); } else { @@ -91,16 +89,6 @@ class FeaturePlugin { } } - /** - * Install DB and create cron events when activated. - * - * @return void - */ - public function on_activation() { - Install::create_tables(); - Install::create_events(); - } - /** * Remove WooCommerce Admin scheduled actions on deactivate. * From 03d3ffe1f90ed1de49325cc706fde343595c9901 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 Apr 2022 19:08:12 -0700 Subject: [PATCH 238/386] Delete WCA install completely --- .../woocommerce/includes/class-wc-install.php | 32 ++---- .../react-admin/wc-admin-update-functions.php | 101 ------------------ .../OnboardingTasks/DeprecatedOptions.php | 1 - .../src/Internal/Admin/CategoryLookup.php | 2 - .../src/Internal/Admin/FeaturePlugin.php | 1 - .../src/Internal/Admin/Install.php | 25 ----- .../woocommerce/tests/legacy/bootstrap.php | 3 - .../unit-tests/woocommerce-admin/install.php | 17 ++- .../woocommerce-admin/plugin-version.php | 35 ------ plugins/woocommerce/uninstall.php | 1 - 10 files changed, 12 insertions(+), 206 deletions(-) delete mode 100644 plugins/woocommerce/src/Internal/Admin/Install.php delete mode 100644 plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 2fc22152870..68a044c2df4 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -13,7 +13,6 @@ use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Registe use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Synchronize as Download_Directories_Sync; use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil; use Automattic\WooCommerce\Internal\WCCom\ConnectionHelper as WCConnectionHelper; -use Automattic\WooCommerce\Internal\Admin\Install as WCA_Install; defined( 'ABSPATH' ) || exit; @@ -160,25 +159,17 @@ class WC_Install { 'wc_update_product_lookup_tables', 'wc_update_400_increase_size_of_column', 'wc_update_400_reset_action_scheduler_migration_status', - 'wc_update_400_db_version', - 'wc_admin_update_0201_order_status_index', - 'wc_admin_update_0201_db_version', 'wc_admin_update_0230_rename_gross_total', - 'wc_admin_update_0230_db_version', 'wc_admin_update_0251_remove_unsnooze_action', - 'wc_admin_update_0251_db_version', + 'wc_update_400_db_version', ), '4.4.0' => array( 'wc_update_440_insert_attribute_terms_for_variable_products', - 'wc_update_440_db_version', - 'wc_admin_update_110_remove_facebook_note', - 'wc_admin_update_110_db_version', 'wc_admin_update_130_remove_dismiss_action_from_tracking_opt_in_note', - 'wc_admin_update_130_db_version', 'wc_admin_update_140_change_deactivate_plugin_note_type', - 'wc_admin_update_140_db_version', + 'wc_update_440_db_version', ), '4.5.0' => array( 'wc_update_450_sanitize_coupons_code', @@ -186,12 +177,9 @@ class WC_Install { ), '5.0.0' => array( 'wc_update_500_fix_product_review_count', - 'wc_update_500_db_version', - 'wc_admin_update_160_remove_facebook_note', - 'wc_admin_update_160_db_version', 'wc_admin_update_170_homescreen_layout', - 'wc_admin_update_170_db_version', + 'wc_update_500_db_version', ), '5.6.0' => array( 'wc_update_560_create_refund_returns_page', @@ -199,31 +187,23 @@ class WC_Install { ), '6.0.0' => array( 'wc_update_600_migrate_rate_limit_options', - 'wc_update_600_db_version', - 'wc_admin_update_270_delete_report_downloads', - 'wc_admin_update_270_db_version', 'wc_admin_update_271_update_task_list_options', - 'wc_admin_update_271_db_version', 'wc_admin_update_280_order_status', - 'wc_admin_update_280_db_version', 'wc_admin_update_290_update_apperance_task_option', 'wc_admin_update_290_delete_default_homepage_layout_option', - 'wc_admin_update_290_db_version', + 'wc_update_600_db_version', ), '6.3.0' => array( 'wc_update_630_create_product_attributes_lookup_table', - 'wc_update_630_db_version', 'wc_admin_update_300_update_is_read_from_last_read', - 'wc_admin_update_300_db_version', + 'wc_update_630_db_version', ), '6.4.0' => array( 'wc_update_640_add_primary_key_to_product_attributes_lookup_table', 'wc_update_640_approved_download_directories', - 'wc_update_640_db_version', - 'wc_admin_update_340_remove_is_primary_from_note_action', - 'wc_admin_update_340_db_version', + 'wc_update_640_db_version', ), ); diff --git a/plugins/woocommerce/includes/react-admin/wc-admin-update-functions.php b/plugins/woocommerce/includes/react-admin/wc-admin-update-functions.php index 368d284d3bf..c4d8bf11cd7 100644 --- a/plugins/woocommerce/includes/react-admin/wc-admin-update-functions.php +++ b/plugins/woocommerce/includes/react-admin/wc-admin-update-functions.php @@ -7,7 +7,6 @@ * @package WooCommerce\Admin */ -use Automattic\WooCommerce\Internal\Admin\Install as Installer; use \Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskLists; use \Automattic\WooCommerce\Admin\Notes\Notes; use \Automattic\WooCommerce\Internal\Admin\Notes\UnsecuredReportFiles; @@ -40,13 +39,6 @@ function wc_admin_update_0201_order_status_index() { $wpdb->query( $wpdb->prepare( "ALTER TABLE {$wpdb->prefix}wc_order_stats ADD INDEX status (status(%d))", $max_index_length ) ); } -/** - * Update DB Version. - */ -function wc_admin_update_0201_db_version() { - Installer::update_db_version( '0.20.1' ); -} - /** * Rename "gross_total" to "total_sales". * See: https://github.com/woocommerce/woocommerce-admin/issues/3175 @@ -60,13 +52,6 @@ function wc_admin_update_0230_rename_gross_total() { $wpdb->query( "ALTER TABLE {$wpdb->prefix}wc_order_stats CHANGE COLUMN `gross_total` `total_sales` double DEFAULT 0 NOT NULL" ); } -/** - * Update DB Version. - */ -function wc_admin_update_0230_db_version() { - Installer::update_db_version( '0.23.0' ); -} - /** * Remove the note unsnoozing scheduled action. */ @@ -75,13 +60,6 @@ function wc_admin_update_0251_remove_unsnooze_action() { as_unschedule_action( Notes::UNSNOOZE_HOOK, null, 'wc-admin-notes' ); } -/** - * Update DB Version. - */ -function wc_admin_update_0251_db_version() { - Installer::update_db_version( '0.25.1' ); -} - /** * Remove Facebook Extension note. */ @@ -89,13 +67,6 @@ function wc_admin_update_110_remove_facebook_note() { Notes::delete_notes_with_name( 'wc-admin-facebook-extension' ); } -/** - * Update DB Version. - */ -function wc_admin_update_110_db_version() { - Installer::update_db_version( '1.1.0' ); -} - /** * Remove Dismiss action from tracking opt-in admin note. */ @@ -105,13 +76,6 @@ function wc_admin_update_130_remove_dismiss_action_from_tracking_opt_in_note() { $wpdb->query( "DELETE actions FROM {$wpdb->prefix}wc_admin_note_actions actions INNER JOIN {$wpdb->prefix}wc_admin_notes notes USING (note_id) WHERE actions.name = 'tracking-dismiss' AND notes.name = 'wc-admin-usage-tracking-opt-in'" ); } -/** - * Update DB Version. - */ -function wc_admin_update_130_db_version() { - Installer::update_db_version( '1.3.0' ); -} - /** * Change the deactivate plugin note type to 'info'. */ @@ -121,13 +85,6 @@ function wc_admin_update_140_change_deactivate_plugin_note_type() { $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}wc_admin_notes SET type = 'info' WHERE name = %s", DeactivatePlugin::NOTE_NAME ) ); } -/** - * Update DB Version. - */ -function wc_admin_update_140_db_version() { - Installer::update_db_version( '1.4.0' ); -} - /** * Remove Facebook Experts note. */ @@ -135,13 +92,6 @@ function wc_admin_update_160_remove_facebook_note() { Notes::delete_notes_with_name( 'wc-admin-facebook-marketing-expert' ); } -/** - * Update DB Version. - */ -function wc_admin_update_160_db_version() { - Installer::update_db_version( '1.6.0' ); -} - /** * Set "two column" homescreen layout as default for existing stores. */ @@ -149,13 +99,6 @@ function wc_admin_update_170_homescreen_layout() { add_option( 'woocommerce_default_homepage_layout', 'two_columns', '', 'no' ); } -/** - * Update DB Version. - */ -function wc_admin_update_170_db_version() { - Installer::update_db_version( '1.7.0' ); -} - /** * Delete the preexisting export files. */ @@ -241,13 +184,6 @@ function wc_admin_update_270_delete_report_downloads() { } } -/** - * Update DB Version. - */ -function wc_admin_update_270_db_version() { - Installer::update_db_version( '2.7.0' ); -} - /** * Update the old task list options. */ @@ -267,13 +203,6 @@ function wc_admin_update_271_update_task_list_options() { delete_option( 'woocommerce_extended_task_list_hidden' ); } -/** - * Update DB Version. - */ -function wc_admin_update_271_db_version() { - Installer::update_db_version( '2.7.1' ); -} - /** * Update order stats `status`. */ @@ -289,13 +218,6 @@ function wc_admin_update_280_order_status() { ); } -/** - * Update DB Version. - */ -function wc_admin_update_280_db_version() { - Installer::update_db_version( '2.8.0' ); -} - /** * Update the old task list options. */ @@ -317,13 +239,6 @@ function wc_admin_update_290_delete_default_homepage_layout_option() { delete_option( 'woocommerce_default_homepage_layout' ); } -/** - * Update DB Version. - */ -function wc_admin_update_290_db_version() { - Installer::update_db_version( '2.9.0' ); -} - /** * Use woocommerce_admin_activity_panel_inbox_last_read from the user meta to set wc_admin_notes.is_read col. */ @@ -349,15 +264,6 @@ function wc_admin_update_300_update_is_read_from_last_read() { } } -/** - * Update DB Version. - */ -function wc_admin_update_300_db_version() { - Installer::update_db_version( '3.0.0' ); -} - - - /** * Delete "is_primary" column from the wc_admin_notes table. */ @@ -365,10 +271,3 @@ function wc_admin_update_340_remove_is_primary_from_note_action() { global $wpdb; $wpdb->query( "ALTER TABLE {$wpdb->prefix}wc_admin_note_actions DROP COLUMN `is_primary`" ); } - -/** - * Update DB Version. - */ -function wc_admin_update_340_db_version() { - Installer::update_db_version( '3.4.0' ); -} diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php index 17e7e40eacd..cfa45232665 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php @@ -6,7 +6,6 @@ namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks; use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\TaskList; -use Automattic\WooCommerce\Internal\Admin\Install; use WC_Install; /** diff --git a/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php b/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php index d60b1bd8ca6..a3b97d911f3 100644 --- a/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php +++ b/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php @@ -5,8 +5,6 @@ namespace Automattic\WooCommerce\Internal\Admin; -use Automattic\WooCommerce\Internal\Admin\Install; - defined( 'ABSPATH' ) || exit; /** diff --git a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php index d6091a2b2b6..fd1b862a7e3 100644 --- a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php +++ b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php @@ -8,7 +8,6 @@ namespace Automattic\WooCommerce\Internal\Admin; defined( 'ABSPATH' ) || exit; use Automattic\WooCommerce\Admin\API; -use Automattic\WooCommerce\Internal\Admin\Install; use \Automattic\WooCommerce\Admin\Notes\Notes; use \Automattic\WooCommerce\Internal\Admin\Notes\OrderMilestones; use \Automattic\WooCommerce\Internal\Admin\Notes\WooSubscriptionsNotes; diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php deleted file mode 100644 index a526b22ce73..00000000000 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ /dev/null @@ -1,25 +0,0 @@ -plugin_dir . '/uninstall.php'; - // Initialize the WC API extensions. - \Automattic\WooCommerce\Internal\Admin\Install::create_tables(); - WC_Install::install(); // Reload capabilities after install, see https://core.trac.wordpress.org/ticket/28374. diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php index 810fee1a8ef..26ac2f0244e 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php @@ -5,13 +5,13 @@ * @package WooCommerce\Admin\Tests */ -use Automattic\WooCommerce\Internal\Admin\Install; - /** * Tests for \Automattic\WooCommerce\Internal\Admin\Install class. */ class WC_Admin_Tests_Install extends WP_UnitTestCase { + const VERSION_OPTION = 'woocommerce_admin_version'; + /** * Integration test for database table creation. * @@ -40,8 +40,6 @@ class WC_Admin_Tests_Install extends WP_UnitTestCase { $query = 'DROP TABLE IF EXISTS ' . implode( ',', $tables ); $wpdb->query( $query ); // phpcs:ignore. - // Try to create the tables. - Install::create_tables(); $result = $wpdb->get_col( "SHOW TABLES LIKE '{$wpdb->prefix}%'" ); // Check all the tables exist. @@ -58,19 +56,16 @@ class WC_Admin_Tests_Install extends WP_UnitTestCase { $old_version = '1.6.0'; // This should get updated to later versions as we add more migrations. // Simulate an upgrade from an older version. - update_option( Install::VERSION_OPTION, '1.6.0' ); - Install::install(); + update_option( self::VERSION_OPTION, '1.6.0' ); WC_Helper_Queue::run_all_pending(); // Simulate a collision/failure in version updating. - update_option( Install::VERSION_OPTION, '1.6.0' ); + update_option( self::VERSION_OPTION, '1.6.0' ); // The next update check should force update the skipped version number. - Install::install(); - $this->assertTrue( version_compare( $old_version, get_option( Install::VERSION_OPTION ), '<' ) ); + $this->assertTrue( version_compare( $old_version, get_option( self::VERSION_OPTION ), '<' ) ); // The following update check should bump the version to the current (no migrations left). - Install::install(); - $this->assertEquals( get_option( Install::VERSION_OPTION ), WC_ADMIN_VERSION_NUMBER ); + $this->assertEquals( get_option( self::VERSION_OPTION ), WC_ADMIN_VERSION_NUMBER ); } } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php deleted file mode 100644 index 2130805079e..00000000000 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php +++ /dev/null @@ -1,35 +0,0 @@ - $version_callbacks ) { - // Verify all callbacks have been defined. - foreach ( $version_callbacks as $version_callback ) { - $this->assertTrue( function_exists( $version_callback ), "Callback {$version_callback}() is not defined." ); - } - - // Verify there is a version update callback for each version. - $version_string = str_replace( '.', '', $version ); - $expected_callback = "wc_admin_update_{$version_string}_db_version"; - - $this->assertContains( $expected_callback, $version_callbacks, "Expected DB update callback {$expected_callback}() was not found." ); - } - } -} diff --git a/plugins/woocommerce/uninstall.php b/plugins/woocommerce/uninstall.php index f77c37dc69b..406c659fdbb 100644 --- a/plugins/woocommerce/uninstall.php +++ b/plugins/woocommerce/uninstall.php @@ -28,7 +28,6 @@ wp_clear_scheduled_hook( 'woocommerce_cleanup_rate_limits' ); */ if ( defined( 'WC_REMOVE_ALL_DATA' ) && true === WC_REMOVE_ALL_DATA ) { // Drop WC Admin tables. - include_once dirname( __FILE__ ) . '/src/Internal/Admin/Install.php'; include_once dirname( __FILE__ ) . '/includes/class-wc-install.php'; // Roles + caps. From 5af9e1f0662862b366b8484f702cb0d7f310c266 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 Apr 2022 19:25:03 -0700 Subject: [PATCH 239/386] Fix failing test --- .../tests/legacy/unit-tests/woocommerce-admin/install.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php index 26ac2f0244e..021efdca9b1 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php @@ -40,6 +40,7 @@ class WC_Admin_Tests_Install extends WP_UnitTestCase { $query = 'DROP TABLE IF EXISTS ' . implode( ',', $tables ); $wpdb->query( $query ); // phpcs:ignore. + WC_Install::create_tables(); $result = $wpdb->get_col( "SHOW TABLES LIKE '{$wpdb->prefix}%'" ); // Check all the tables exist. @@ -53,19 +54,23 @@ class WC_Admin_Tests_Install extends WP_UnitTestCase { * See: https:// github.com/woocommerce/woocommerce-admin/issues/5058 */ public function test_missed_version_number_update() { + $this->markTestSkipped('We no longer update WooCommerce Admin versions'); $old_version = '1.6.0'; // This should get updated to later versions as we add more migrations. // Simulate an upgrade from an older version. update_option( self::VERSION_OPTION, '1.6.0' ); + WC_Install::install(); WC_Helper_Queue::run_all_pending(); // Simulate a collision/failure in version updating. update_option( self::VERSION_OPTION, '1.6.0' ); // The next update check should force update the skipped version number. + WC_Install::install(); $this->assertTrue( version_compare( $old_version, get_option( self::VERSION_OPTION ), '<' ) ); // The following update check should bump the version to the current (no migrations left). + WC_Install::install(); $this->assertEquals( get_option( self::VERSION_OPTION ), WC_ADMIN_VERSION_NUMBER ); } } From 52de6e872b5ab74d11f5d02a33e7e5bab290079c Mon Sep 17 00:00:00 2001 From: moon Date: Thu, 7 Apr 2022 09:18:40 -0700 Subject: [PATCH 240/386] Add tests for the install class --- .../unit-tests/woocommerce-admin/install.php | 84 ++++++++++++++++++- .../woocommerce-admin/plugin-version.php | 34 ++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php index 021efdca9b1..8add29261b6 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php @@ -49,12 +49,94 @@ class WC_Admin_Tests_Install extends WP_UnitTestCase { } } + /** + * Run maybe_update_db_version and confirm the expected jobs are pushed to the queue. + * + * @dataProvider db_update_version_provider + */ + public function test_running_db_updates( $db_update_version, $expected_jobs_count ) { + update_option( 'woocommerce_db_version', $db_update_version ); + add_filter( + 'woocommerce_enable_auto_update_db', + function() { + return true; + } + ); + + $class = new ReflectionClass( WC_Install::class ); + $method = $class->getMethod( 'maybe_update_db_version' ); + $method->setAccessible( true ); + $method->invoke( $class ); + + $pending_jobs = WC_Helper_Queue::get_all_pending(); + $pending_jobs = array_filter( + $pending_jobs, + function( $pending_job ) { + return $pending_job->get_hook() === 'woocommerce_run_update_callback'; + } + ); + + $this->assertCount( $expected_jobs_count, $pending_jobs ); + } + + + /** + * Ensure that a DB version callback is defined when there are updates. + */ + public function test_db_update_callbacks_exist() { + $all_callbacks = \WC_Install::get_db_update_callbacks(); + + foreach ( $all_callbacks as $version => $version_callbacks ) { + // Verify all callbacks have been defined. + foreach ( $version_callbacks as $version_callback ) { + if ( strpos( $version_callback, 'wc_admin_update' ) === 0 ) { + $this->assertTrue( + function_exists( $version_callback ), + "Callback {$version_callback}() is not defined." + ); + } + } + } + } + + /** + * By the time we hit this test method, we should have the following cron jobs. + * - wc_admin_daily + * - generate_category_lookup_table + * + * @return void + */ + public function test_cron_job_creation() { + $this->assertNotFalse( wp_next_scheduled( 'wc_admin_daily' ) ); + $this->assertNotFalse( wp_next_scheduled( 'generate_category_lookup_table' ) ); + } + + /** + * Data provider that returns DB Update version string and # of expected pending jobs. + * + * @return array[] + */ + public function db_update_version_provider() { + return array( + // [DB Update version string, # of expected pending jobs] + array( '3.9.0', 34 ), + array( '4.0.0', 27 ), + array( '4.4.0', 22 ), + array( '4.5.0', 20 ), + array( '5.0.0', 16 ), + array( '5.6.0', 14 ), + array( '6.0.0', 7 ), + array( '6.3.0', 4 ), + array( '6.4.0', 0 ), + ); + } + /** * Test missed DB version number update. * See: https:// github.com/woocommerce/woocommerce-admin/issues/5058 */ public function test_missed_version_number_update() { - $this->markTestSkipped('We no longer update WooCommerce Admin versions'); + $this->markTestSkipped( 'We no longer update WooCommerce Admin versions' ); $old_version = '1.6.0'; // This should get updated to later versions as we add more migrations. // Simulate an upgrade from an older version. diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php new file mode 100644 index 00000000000..fddec306a64 --- /dev/null +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php @@ -0,0 +1,34 @@ + 'Version' ) ); + + // Get plugin DB version. + $db_version = defined( 'WC_ADMIN_VERSION_NUMBER' ) ? constant( 'WC_ADMIN_VERSION_NUMBER' ) : false; + + // Compare all versions to the package.json value. + $this->assertEquals( $package->version, $plugin['Version'], 'Plugin header version does not match package.json' ); + $this->assertEquals( $package->version, $db_version, 'DB version constant does not match package.json' ); + } +} From 474cff5db6af96382b13d53673c6dc31e1490fe8 Mon Sep 17 00:00:00 2001 From: moon Date: Thu, 7 Apr 2022 09:22:27 -0700 Subject: [PATCH 241/386] Remove unnecessary variable --- plugins/woocommerce/includes/class-wc-install.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 68a044c2df4..9209b441f4d 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -1374,9 +1374,8 @@ CREATE TABLE {$wpdb->prefix}wc_category_lookup ( "{$wpdb->prefix}wc_reserved_stock", "{$wpdb->prefix}wc_rate_limits", wc_get_container()->get( DataRegenerator::class )->get_lookup_table_name(), - ); - $wca_tables = array( + // WCA Tables "{$wpdb->prefix}wc_order_stats", "{$wpdb->prefix}wc_order_product_lookup", "{$wpdb->prefix}wc_order_tax_lookup", @@ -1394,7 +1393,7 @@ CREATE TABLE {$wpdb->prefix}wc_category_lookup ( * * @param array $tables An array of WooCommerce-specific database table names. */ - $tables = apply_filters( 'woocommerce_install_get_tables', array_merge( $tables, $wca_tables ) ); + $tables = apply_filters( 'woocommerce_install_get_tables', $tables ); return $tables; } From 5376bd03ce3bb8742f657726326a0aeac010cb16 Mon Sep 17 00:00:00 2001 From: moon Date: Thu, 7 Apr 2022 09:38:58 -0700 Subject: [PATCH 242/386] Add WCA uninstall routines --- .../src/Internal/Admin/FeaturePlugin.php | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php index fd1b862a7e3..49dac42a47b 100644 --- a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php +++ b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php @@ -88,25 +88,6 @@ class FeaturePlugin { } } - /** - * Remove WooCommerce Admin scheduled actions on deactivate. - * - * @return void - */ - public function on_deactivation() { - // Don't clean up if the WooCommerce Admin package is in core. - // NOTE: Any future divergence from the core package will need to be accounted for here. - if ( defined( 'WC_ADMIN_PACKAGE_EXISTS' ) && WC_ADMIN_PACKAGE_EXISTS ) { - return; - } - - $this->includes(); - ReportsSync::clear_queued_actions(); - Notes::clear_queued_actions(); - wp_clear_scheduled_hook( 'wc_admin_daily' ); - wp_clear_scheduled_hook( 'generate_category_lookup_table' ); - } - /** * Setup plugin once all other plugins are loaded. * From 9e90b3be8965650f76118deb3081d001f5d19d93 Mon Sep 17 00:00:00 2001 From: moon Date: Thu, 7 Apr 2022 10:01:21 -0700 Subject: [PATCH 243/386] Fix styling --- .../woocommerce/includes/class-wc-install.php | 18 +++++++++++++----- .../unit-tests/woocommerce-admin/install.php | 6 ++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 9209b441f4d..c9696672316 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -231,13 +231,13 @@ class WC_Install { * This check is done on all requests and runs if the versions do not match. */ public static function check_version() { - $wc_version = get_option( 'woocommerce_version' ); + $wc_version = get_option( 'woocommerce_version' ); $wc_code_version = WC()->version; $requires_update = version_compare( $wc_version, $wc_code_version, '<' ); - if ( ! Constants::is_defined( 'IFRAME_REQUEST' ) && $requires_update ) { + if ( ! Constants::is_defined( 'IFRAME_REQUEST' ) && $requires_update ) { self::install(); do_action( 'woocommerce_updated' ); - do_action_deprecated( 'woocommerce_admin_updated' , array(), $wc_code_version); + do_action_deprecated( 'woocommerce_admin_updated', array(), $wc_code_version ); // If there is no woocommerce_version option, consider it as a new install. if ( ! $wc_version ) { do_action( 'woocommerce_newly_installed' ); @@ -387,8 +387,13 @@ class WC_Install { do_action( 'woocommerce_admin_installed' ); } + /** + * Returns true if we're installing. + * + * @return bool + */ public static function is_installing() { - return 'yes' === get_transient( 'wc_installing' ); + return 'yes' === get_transient( 'wc_installing' ); } /** @@ -736,6 +741,9 @@ class WC_Install { } } + /** + * Delete obsolete notes. + */ public static function delete_obsolete_notes() { $obsolete_notes_names = array( 'wc-admin-welcome-note', @@ -1375,7 +1383,7 @@ CREATE TABLE {$wpdb->prefix}wc_category_lookup ( "{$wpdb->prefix}wc_rate_limits", wc_get_container()->get( DataRegenerator::class )->get_lookup_table_name(), - // WCA Tables + // WCA Tables. "{$wpdb->prefix}wc_order_stats", "{$wpdb->prefix}wc_order_product_lookup", "{$wpdb->prefix}wc_order_tax_lookup", diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php index 8add29261b6..f27b3353994 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php @@ -49,10 +49,16 @@ class WC_Admin_Tests_Install extends WP_UnitTestCase { } } + /** * Run maybe_update_db_version and confirm the expected jobs are pushed to the queue. * * @dataProvider db_update_version_provider + * + * @param string $db_update_version WC version to test. + * @param int $expected_jobs_count # of expected jobs. + * + * @return void */ public function test_running_db_updates( $db_update_version, $expected_jobs_count ) { update_option( 'woocommerce_db_version', $db_update_version ); From 2d56be4966d575cf29edd12b4d7f9bef6655ed29 Mon Sep 17 00:00:00 2001 From: moon Date: Thu, 7 Apr 2022 10:14:19 -0700 Subject: [PATCH 244/386] Remove plugin-version test -- we no longer have woocommerce-admin.php --- .../woocommerce-admin/plugin-version.php | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php deleted file mode 100644 index fddec306a64..00000000000 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/plugin-version.php +++ /dev/null @@ -1,34 +0,0 @@ - 'Version' ) ); - - // Get plugin DB version. - $db_version = defined( 'WC_ADMIN_VERSION_NUMBER' ) ? constant( 'WC_ADMIN_VERSION_NUMBER' ) : false; - - // Compare all versions to the package.json value. - $this->assertEquals( $package->version, $plugin['Version'], 'Plugin header version does not match package.json' ); - $this->assertEquals( $package->version, $db_version, 'DB version constant does not match package.json' ); - } -} From ae6a65857828529d13e8a113f6a9b754355db0c6 Mon Sep 17 00:00:00 2001 From: moon Date: Thu, 7 Apr 2022 12:03:43 -0700 Subject: [PATCH 245/386] Add replacements for the deprecated actions --- plugins/woocommerce/includes/class-wc-install.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index c9696672316..0457dc78e39 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -237,15 +237,15 @@ class WC_Install { if ( ! Constants::is_defined( 'IFRAME_REQUEST' ) && $requires_update ) { self::install(); do_action( 'woocommerce_updated' ); - do_action_deprecated( 'woocommerce_admin_updated', array(), $wc_code_version ); + do_action_deprecated( 'woocommerce_admin_updated', array(), $wc_code_version, 'woocommerce_updated' ); // If there is no woocommerce_version option, consider it as a new install. if ( ! $wc_version ) { do_action( 'woocommerce_newly_installed' ); - do_action_deprecated( 'woocommerce_admin_newly_installed', array(), $wc_code_version ); + do_action_deprecated( 'woocommerce_admin_newly_installed', array(), $wc_code_version, 'woocommerce_newly_installed' ); } else { // if there is already a version and we're install, we're updating an existing install. do_action( 'woocommerce_updated_existing' ); - do_action_deprecated( 'woocommerce_admin_updated_existing', array(), $wc_code_version ); + do_action_deprecated( 'woocommerce_admin_updated_existing', array(), $wc_code_version, 'woocommerce_updated_existing' ); } } } From 1ca12a87843424e888e4151a612a11742cb53562 Mon Sep 17 00:00:00 2001 From: moon Date: Thu, 7 Apr 2022 12:55:20 -0700 Subject: [PATCH 246/386] Update delete_obsolete_notes to use $wpdb directly to delete the notes -- note store is not available when the plugin is activated via CLI --- plugins/woocommerce/includes/class-wc-install.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 0457dc78e39..4da97c87435 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -745,6 +745,7 @@ class WC_Install { * Delete obsolete notes. */ public static function delete_obsolete_notes() { + global $wpdb; $obsolete_notes_names = array( 'wc-admin-welcome-note', 'wc-admin-store-notice-setting-moved', @@ -780,7 +781,10 @@ class WC_Install { ); } - Notes::delete_notes_with_name( $obsolete_notes_names ); + foreach ( $obsolete_notes_names as $obsolete_notes_name ) { + $wpdb->delete( $wpdb->prefix . 'wc_admin_notes', array( 'name' => $obsolete_notes_name ) ); + $wpdb->delete( $wpdb->prefix . 'wc_admin_note_actions', array( 'name' => $obsolete_notes_name ) ); + } } /** From 27bf0e5c961194eb5521d146df3426f4fd28ed7e Mon Sep 17 00:00:00 2001 From: moon Date: Thu, 7 Apr 2022 17:42:33 -0700 Subject: [PATCH 247/386] Add WCA clean up code to uninstall.php --- plugins/woocommerce/uninstall.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/woocommerce/uninstall.php b/plugins/woocommerce/uninstall.php index 406c659fdbb..5424f2d30ca 100644 --- a/plugins/woocommerce/uninstall.php +++ b/plugins/woocommerce/uninstall.php @@ -20,6 +20,10 @@ wp_clear_scheduled_hook( 'woocommerce_cleanup_logs' ); wp_clear_scheduled_hook( 'woocommerce_geoip_updater' ); wp_clear_scheduled_hook( 'woocommerce_tracker_send_event' ); wp_clear_scheduled_hook( 'woocommerce_cleanup_rate_limits' ); +wp_clear_scheduled_hook( 'wc_admin_daily' ); +wp_clear_scheduled_hook( 'generate_category_lookup_table' ); +ReportsSync::clear_queued_actions(); +Notes::clear_queued_actions(); /* * Only remove ALL product and page data if WC_REMOVE_ALL_DATA constant is set to true in user's From 75c7359b27830936f4cbc98832e35c8fa93bbe0c Mon Sep 17 00:00:00 2001 From: moon Date: Thu, 7 Apr 2022 17:46:23 -0700 Subject: [PATCH 248/386] Import missing namespace --- plugins/woocommerce/uninstall.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/woocommerce/uninstall.php b/plugins/woocommerce/uninstall.php index 5424f2d30ca..b42ee63eaf6 100644 --- a/plugins/woocommerce/uninstall.php +++ b/plugins/woocommerce/uninstall.php @@ -8,6 +8,9 @@ * @version 2.3.0 */ +use Automattic\WooCommerce\Admin\Notes\Notes; +use Automattic\WooCommerce\Admin\ReportsSync; + defined( 'WP_UNINSTALL_PLUGIN' ) || exit; global $wpdb, $wp_version; From f2159c4bc2d41d175d28dc2e2f4a08a707849ad5 Mon Sep 17 00:00:00 2001 From: moon Date: Thu, 7 Apr 2022 18:13:33 -0700 Subject: [PATCH 249/386] Fix broken tests --- plugins/woocommerce/uninstall.php | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/woocommerce/uninstall.php b/plugins/woocommerce/uninstall.php index b42ee63eaf6..6000d5344df 100644 --- a/plugins/woocommerce/uninstall.php +++ b/plugins/woocommerce/uninstall.php @@ -25,7 +25,6 @@ wp_clear_scheduled_hook( 'woocommerce_tracker_send_event' ); wp_clear_scheduled_hook( 'woocommerce_cleanup_rate_limits' ); wp_clear_scheduled_hook( 'wc_admin_daily' ); wp_clear_scheduled_hook( 'generate_category_lookup_table' ); -ReportsSync::clear_queued_actions(); Notes::clear_queued_actions(); /* From 2bc077e0c9c3833d2fc0229e3ce061512f60cd31 Mon Sep 17 00:00:00 2001 From: moon Date: Fri, 8 Apr 2022 06:31:58 -0700 Subject: [PATCH 250/386] Clear the note hook directly --- plugins/woocommerce/uninstall.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/uninstall.php b/plugins/woocommerce/uninstall.php index 6000d5344df..623877fecec 100644 --- a/plugins/woocommerce/uninstall.php +++ b/plugins/woocommerce/uninstall.php @@ -25,7 +25,7 @@ wp_clear_scheduled_hook( 'woocommerce_tracker_send_event' ); wp_clear_scheduled_hook( 'woocommerce_cleanup_rate_limits' ); wp_clear_scheduled_hook( 'wc_admin_daily' ); wp_clear_scheduled_hook( 'generate_category_lookup_table' ); -Notes::clear_queued_actions(); +wp_clear_scheduled_hook( 'wc_admin_unsnooze_admin_notes' ); /* * Only remove ALL product and page data if WC_REMOVE_ALL_DATA constant is set to true in user's From 3c8043885d4f7e5275bcb7eb4f511a03f31cf2b8 Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 11 Apr 2022 10:33:25 -0700 Subject: [PATCH 251/386] Add changelog --- .../changelog/update-32176-merge-wca-install-logic-to-core | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/update-32176-merge-wca-install-logic-to-core diff --git a/plugins/woocommerce/changelog/update-32176-merge-wca-install-logic-to-core b/plugins/woocommerce/changelog/update-32176-merge-wca-install-logic-to-core new file mode 100644 index 00000000000..761a33e3bf5 --- /dev/null +++ b/plugins/woocommerce/changelog/update-32176-merge-wca-install-logic-to-core @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Merge WCA install routines to the core From 4729ae1f01b1a3140b8b2ec6c86ad58eb64c2fc4 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Thu, 7 Apr 2022 12:54:19 -0700 Subject: [PATCH 252/386] =?UTF-8?q?Product=20downloads=20=E2=86=92=20suppo?= =?UTF-8?q?rt=20a=20concept=20of=20active/inactive=20files.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../abstracts/abstract-wc-product.php | 94 ++++++++++++++----- .../views/html-product-data-general.php | 22 ++++- .../views/html-product-download.php | 17 +++- .../views/html-product-variation-download.php | 18 +++- .../meta-boxes/views/html-variation-admin.php | 25 ++++- .../includes/class-wc-product-download.php | 62 ++++++++---- .../includes/wc-user-functions.php | 5 + plugins/woocommerce/legacy/css/admin.scss | 13 +++ .../legacy/unit-tests/importer/product.php | 15 +-- .../class-wc-abstract-product-test.php | 36 +++---- .../class-wc-product-downloads-test.php | 44 --------- 11 files changed, 234 insertions(+), 117 deletions(-) diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-product.php b/plugins/woocommerce/includes/abstracts/abstract-wc-product.php index 68a85d7cbe4..3dd5352b356 100644 --- a/plugins/woocommerce/includes/abstracts/abstract-wc-product.php +++ b/plugins/woocommerce/includes/abstracts/abstract-wc-product.php @@ -1202,44 +1202,90 @@ class WC_Product extends WC_Abstract_Legacy_Product { /** * Set downloads. * - * @since 3.0.0 + * @throws WC_Data_Exception + * * @param array $downloads_array Array of WC_Product_Download objects or arrays. + * + * @since 3.0.0 */ public function set_downloads( $downloads_array ) { - $downloads = array(); - $errors = array(); + // When the object is first hydrated, only the previously persisted downloads will be passed in. + $existing_downloads = $this->get_object_read() ? (array) $this->get_prop( 'downloads' ) : $downloads_array; + $downloads = array(); + $errors = array(); + + $downloads_array = $this->build_downloads_map( $downloads_array ); + $existing_downloads = $this->build_downloads_map( $existing_downloads ); foreach ( $downloads_array as $download ) { - if ( is_a( $download, 'WC_Product_Download' ) ) { - $download_object = $download; - } else { - $download_object = new WC_Product_Download(); - - // If we don't have a previous hash, generate UUID for download. - if ( empty( $download['download_id'] ) ) { - $download['download_id'] = wp_generate_uuid4(); - } - - $download_object->set_id( $download['download_id'] ); - $download_object->set_name( $download['name'] ); - $download_object->set_file( $download['file'] ); - } + $download_id = $download->get_id(); + $is_new = ! isset( $existing_downloads[ $download_id ] ); try { - $download_object->check_is_valid(); - $downloads[ $download_object->get_id() ] = $download_object; + $download->check_is_valid( $this->get_object_read() ); + $downloads[ $download_id ] = $download; } catch ( Exception $e ) { - if ( $this->get_object_read() ) { + // We only add error messages for newly added downloads (let's not overwhelm the user if there are + // multiple existing files which are problematic). + if ( $is_new ) { $errors[] = $e->getMessage(); } - } - } - if ( $errors ) { - $this->error( 'product_invalid_download', $errors[0] ); + // If the problem is with an existing download, disable it. + if ( ! $is_new ) { + $download->set_enabled( false ); + $downloads[ $download_id ] = $download; + } + } } $this->set_prop( 'downloads', $downloads ); + + if ( $errors && $this->get_object_read() ) { + $this->error( 'product_invalid_download', $errors[0] ); + } + } + + /** + * Takes an array of downloadable file representations and converts it into an array of + * WC_Product_Download objects, indexed by download ID. + * + * @param array[]|WC_Product_Download[] $downloads Download data to be re-mapped. + * + * @return WC_Product_Download[] + */ + private function build_downloads_map( array $downloads ): array { + $downloads_map = array(); + + foreach ( $downloads as $download_data ) { + // If the item is already a WC_Product_Download we can add it to the map and move on. + if ( is_a( $download_data, 'WC_Product_Download' ) ) { + $downloads_map[ $download_data->get_id() ] = $download_data; + continue; + } + + // If the item is not an array, there is nothing else we can do (bad data). + if ( ! is_array( $download_data ) ) { + continue; + } + + // Otherwise, transform the array to a WC_Product_Download and add to the map. + $download_object = new WC_Product_Download(); + + // If we don't have a previous hash, generate UUID for download. + if ( empty( $download_data['download_id'] ) ) { + $download_data['download_id'] = wp_generate_uuid4(); + } + + $download_object->set_id( $download_data['download_id'] ); + $download_object->set_name( $download_data['name'] ); + $download_object->set_file( $download_data['file'] ); + $download_object->set_enabled( isset( $download_data['enabled'] ) ? $download_data['enabled'] : true ); + + $downloads_map[ $download_object->get_id() ] = $download_object; + } + + return $downloads_map; } /** diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-general.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-general.php index db243413896..cff71f755d8 100644 --- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-general.php +++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-general.php @@ -88,9 +88,13 @@ defined( 'ABSPATH' ) || exit; get_downloads( 'edit' ); + $downloadable_files = $product_object->get_downloads( 'edit' ); + $disabled_downloads_count = 0; + if ( $downloadable_files ) { foreach ( $downloadable_files as $key => $file ) { + $disabled_download = isset( $file['enabled'] ) && false === $file['enabled']; + $disabled_downloads_count += (int) $disabled_download; include __DIR__ . '/html-product-download.php'; } } @@ -98,7 +102,7 @@ defined( 'ABSPATH' ) || exit; - + + + + * + ', + '' + ); + ?> + + diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-download.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-download.php index 621b785c4cd..437af94dc91 100644 --- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-download.php +++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-download.php @@ -1,4 +1,14 @@ " name="_wc_file_names[]" value="" /> - + + + + * + + diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-variation-download.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-variation-download.php index 6c39752cc0f..f8eba6dd29d 100644 --- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-variation-download.php +++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-variation-download.php @@ -1,4 +1,15 @@ " name="_wc_variation_file_names[][]" value="" /> - + + + + * + + diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-variation-admin.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-variation-admin.php index 2bfdc85b8f3..5402c3576d1 100644 --- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-variation-admin.php +++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-variation-admin.php @@ -404,10 +404,13 @@ defined( 'ABSPATH' ) || exit; get_downloads( 'edit' ); + $downloadable_files = $variation_object->get_downloads( 'edit' ); + $disabled_downloads_count = 0; - if ( $downloads ) { - foreach ( $downloads as $key => $file ) { + if ( $downloadable_files ) { + foreach ( $downloadable_files as $key => $file ) { + $disabled_download = isset( $file['enabled'] ) && false === $file['enabled']; + $disabled_downloads_count += (int) $disabled_download; include __DIR__ . '/html-product-variation-download.php'; } } @@ -415,7 +418,7 @@ defined( 'ABSPATH' ) || exit;
- + + + + * + ', + '' + ); + ?> + +
diff --git a/plugins/woocommerce/includes/class-wc-product-download.php b/plugins/woocommerce/includes/class-wc-product-download.php index a3176b7d5ca..6f4f234e594 100644 --- a/plugins/woocommerce/includes/class-wc-product-download.php +++ b/plugins/woocommerce/includes/class-wc-product-download.php @@ -25,9 +25,10 @@ class WC_Product_Download implements ArrayAccess { * @var array */ protected $data = array( - 'id' => '', - 'name' => '', - 'file' => '', + 'id' => '', + 'name' => '', + 'file' => '', + 'enabled' => true, ); /** @@ -106,10 +107,20 @@ class WC_Product_Download implements ArrayAccess { public function check_is_valid( bool $auto_add_to_approved_directory_list = true ) { $download_file = $this->get_file(); + if ( ! $this->data['enabled'] ) { + throw new Exception( + sprintf( + /* translators: %s: Downloadable file. */ + __( 'The downloadable file %s cannot be used as it has been disabled.', 'woocommerce' ), + '' . basename( $download_file ) . '' + ) + ); + } + if ( ! $this->is_allowed_filetype() ) { throw new Exception( sprintf( - /* translators: %1$s: Downloadable file */ + /* translators: 1: Downloadable file, 2: List of allowed filetypes. */ __( 'The downloadable file %1$s cannot be used as it does not have an allowed file type. Allowed types include: %2$s', 'woocommerce' ), '' . basename( $download_file ) . '', '' . implode( ', ', array_keys( $this->get_allowed_mime_types() ) ) . '' @@ -121,7 +132,7 @@ class WC_Product_Download implements ArrayAccess { if ( ! $this->file_exists() ) { throw new Exception( sprintf( - /* translators: %s: Downloadable file */ + /* translators: %s: Downloadable file */ __( 'The downloadable file %s cannot be used as it does not exist on the server.', 'woocommerce' ), '' . $download_file . '' ) @@ -215,25 +226,22 @@ class WC_Product_Download implements ArrayAccess { $is_site_administrator = is_multisite() ? current_user_can( 'manage_sites' ) : current_user_can( 'manage_options' ); $valid_storage_directory = $download_directories->is_valid_path( $download_file ); - if ( ! $valid_storage_directory && $auto_add_to_approved_directory_list ) { + if ( $valid_storage_directory ) { + return; + } + + if ( $auto_add_to_approved_directory_list ) { try { // Add the parent URL to the approved directories list, but *do not enable it* unless the current user is a site admin. $download_directories->add_approved_directory( ( new URL( $download_file ) )->get_parent_url(), $is_site_administrator ); - } catch ( Exception $e ) { - /* translators: %s: Downloadable file */ - throw new Exception( - sprintf( - /* translators: %1$s is the downloadable file path, %2$s is an opening link tag, %3%s is a closing link tag. */ - __( 'The downloadable file %1$s cannot be used: it is not located in an approved directory. Please contact a site administrator and request their approval. %2$sLearn more.%3$s', 'woocommerce' ), - '' . $download_file . '', - '', - '' - ) - ); + $valid_storage_directory = $download_directories->is_valid_path( $download_file ); + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // At this point, $valid_storage_directory will be false. Fall-through so the appropriate exception is + // triggered (same as if the storage directory was invalid and $auto_add_to_approved_directory_list was false. } } - if ( ! $valid_storage_directory && ! $is_site_administrator ) { + if ( ! $valid_storage_directory ) { throw new Exception( sprintf( /* translators: %1$s is the downloadable file path, %2$s is an opening link tag, %3%s is a closing link tag. */ @@ -302,6 +310,15 @@ class WC_Product_Download implements ArrayAccess { } } + /** + * Sets the status of the download to enabled (true) or disabled (false). + * + * @param bool $enabled True indicates the downloadable file is enabled, false indicates it is disabled. + */ + public function set_enabled( bool $enabled = true ) { + $this->data['enabled'] = $enabled; + } + /* |-------------------------------------------------------------------------- | Getters @@ -346,6 +363,15 @@ class WC_Product_Download implements ArrayAccess { return $this->data['file']; } + /** + * Get status of the download. + * + * @return bool + */ + public function get_enabled(): bool { + return $this->data['enabled']; + } + /* |-------------------------------------------------------------------------- | ArrayAccess/Backwards compatibility. diff --git a/plugins/woocommerce/includes/wc-user-functions.php b/plugins/woocommerce/includes/wc-user-functions.php index 0c30c4f1aeb..a874c92a052 100644 --- a/plugins/woocommerce/includes/wc-user-functions.php +++ b/plugins/woocommerce/includes/wc-user-functions.php @@ -614,6 +614,11 @@ function wc_get_customer_available_downloads( $customer_id ) { $download_file = $_product->get_file( $result->download_id ); + // If the downloadable file has been disabled (it may be located in an untrusted location) then do not return it. + if ( ! $download_file->get_enabled() ) { + continue; + } + // Download name will be 'Product Name' for products with a single downloadable file, and 'Product Name - File X' for products with multiple files. $download_name = apply_filters( 'woocommerce_downloadable_product_name', diff --git a/plugins/woocommerce/legacy/css/admin.scss b/plugins/woocommerce/legacy/css/admin.scss index be221579281..b5fd23b2ae4 100644 --- a/plugins/woocommerce/legacy/css/admin.scss +++ b/plugins/woocommerce/legacy/css/admin.scss @@ -5110,6 +5110,14 @@ img.help_tip { margin: 1px 0; } + &.file_url { + /* Reduce the size of this field to make space for a warning asterisk. */ + input { + display: inline-block; + width: 96%; + } + } + .upload_file_button { width: auto; float: right; @@ -5160,6 +5168,11 @@ img.help_tip { color: #333; } } + + /* Warning asterisk (indicates if there is a problem with a downloadable file). */ + span.disabled { + color: var( --wc-red ); + } } } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/importer/product.php b/plugins/woocommerce/tests/legacy/unit-tests/importer/product.php index 637506ea631..3fbab5add05 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/importer/product.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/importer/product.php @@ -35,11 +35,14 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case { // Callback used by WP_HTTP_TestCase to decide whether to perform HTTP requests or to provide a mocked response. $this->http_responder = array( $this, 'mock_http_responses' ); $this->csv_file = dirname( __FILE__ ) . '/sample.csv'; - $this->sut = new WC_Product_CSV_Importer( $this->csv_file, array( - 'mapping' => $this->get_csv_mapped_items(), - 'parse' => true, - 'prevent_timeouts' => false, - ) ); + $this->sut = new WC_Product_CSV_Importer( + $this->csv_file, + array( + 'mapping' => $this->get_csv_mapped_items(), + 'parse' => true, + 'prevent_timeouts' => false, + ) + ); } /** @@ -112,7 +115,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case { $this->assertEquals( 0, count( $results['skipped'] ) ); $this->assertEquals( 7, - count( $results['imported'] ) , + count( $results['imported'] ), 'One import item references a downloadable file stored in an unapproved location: if the import is triggered by an admin user, that location will be automatically approved.' ); } diff --git a/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-product-test.php b/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-product-test.php index aaf81b31e7c..98f97525e9e 100644 --- a/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-product-test.php +++ b/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-product-test.php @@ -18,16 +18,18 @@ class WC_Abstract_Product_Test extends WC_Unit_Test_Case { $download_directories->add_approved_directory( 'https://always.trusted/' ); $problematic_file_source_id = $download_directories->add_approved_directory( 'https://new.supplier/' ); - $product = WC_Helper_Product::create_downloadable_product( array( + $product = WC_Helper_Product::create_downloadable_product( array( - 'name' => 'Book 1', - 'file' => 'https://always.trusted/123.pdf' - ), - array( - 'name' => 'Book 2', - 'file' => 'https://new.supplier/456.pdf' - ), - ) ); + array( + 'name' => 'Book 1', + 'file' => 'https://always.trusted/123.pdf', + ), + array( + 'name' => 'Book 2', + 'file' => 'https://new.supplier/456.pdf', + ), + ) + ); $this->assertCount( 2, @@ -36,17 +38,17 @@ class WC_Abstract_Product_Test extends WC_Unit_Test_Case { ); $download_directories->disable_by_id( $problematic_file_source_id ); + $product_downloads = wc_get_product( $product->get_id() )->get_downloads(); $this->assertCount( - 1, - wc_get_product( $product->get_id() )->get_downloads(), - 'If a trusted download directory is disabled, we expect any individual download files from that location will not be listed.' + 2, + $product_downloads, + 'If a trusted download directory rule is disabled, we still expect it to be fetched.' ); - $this->assertEquals( - 'Book 1', - current( wc_get_product( $product->get_id() )->get_downloads() )->get_name(), - 'Only individual download files that are stored in trusted locations will be fetched.' + $this->assertFalse( + next( $product_downloads )->get_enabled(), + 'If a trusted download directory rule is disabled, corresponding product downloads will also be marked as disabled.' ); $download_directories->set_mode( Download_Directories::MODE_DISABLED ); @@ -54,7 +56,7 @@ class WC_Abstract_Product_Test extends WC_Unit_Test_Case { $this->assertCount( 2, wc_get_product( $product->get_id() )->get_downloads(), - 'If the Approved Download Directories system is completely disabled, we expect all product downloads to be fetched irrespective of where they are stored.' + 'Disabling the Approved Download Directories system entirely does not impact our ability to fetch product downloads.' ); } } diff --git a/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php b/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php index ba3d9eb484e..0cb0ad4dc15 100644 --- a/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php +++ b/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php @@ -63,48 +63,4 @@ class WC_Product_Download_Test extends WC_Unit_Test_Case { $this->expectExceptionMessage( 'cannot be used: it is not located in an approved directory' ); $download->check_is_valid(); } - - /** - * Test handling of filepaths described via shortcodes in relation to the Approved Download Directory - * feature. This is to simulate scenarios such as encountered when using the S3 Downloads extension. - */ - public function test_shortcode_resolution_for_approved_directory_rules() { - /** @var Download_Directories $download_directories */ - $download_directories = wc_get_container()->get( Download_Directories::class ); - $download_directories->set_mode( Download_Directories::MODE_ENABLED ); - $dynamic_filepath = 'https://fast.reliable.external.fileserver.com/bucket-123/textbook.pdf'; - - // We select an admin user because we wish to automatically add Approved Directory rules. - $admin_user = wp_insert_user( array( 'user_login' => uniqid(), 'role' => 'administrator', 'user_pass' => 'x' ) ); - wp_set_current_user( $admin_user ); - - add_shortcode( 'dynamic-download', function () { - return 'https://fast.reliable.external.fileserver.com/bucket-123/textbook.pdf'; - } ); - - $this->assertFalse( - $download_directories->is_valid_path( $dynamic_filepath ), - 'Confirm the filepath returned by the test URL is not yet valid.' - ); - - $download = new WC_Product_Download(); - $download->set_file( '[dynamic-download]' ); - - $this->assertNull( - $download->check_is_valid(), - 'The downloadable file successfully validates (if it did not, an exception would be thrown).' - ); - - $this->assertTrue( - $download_directories->is_valid_path( $dynamic_filepath ), - 'Confirm the filepath returned by the test URL is now considered valid.' - ); - - remove_shortcode( 'dynamic-download' ); - - // Now the shortcode is removed (perhaps the parent plugin has been removed/disabled) it will not resolve - // and so the filepath will not validate. - $this->expectException( 'Error' ); - $download_directories->check_is_valid(); - } } From 10b8eef1f233b8106bffaaf6d916a57ca4700bc1 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Mon, 11 Apr 2022 15:44:10 -0700 Subject: [PATCH 253/386] Add changelog. --- plugins/woocommerce/changelog/fix-downloadable-file-data | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-downloadable-file-data diff --git a/plugins/woocommerce/changelog/fix-downloadable-file-data b/plugins/woocommerce/changelog/fix-downloadable-file-data new file mode 100644 index 00000000000..f99ba74d55b --- /dev/null +++ b/plugins/woocommerce/changelog/fix-downloadable-file-data @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Make it possible for downloadable files to be in an enabled or disabled state. From 49b3a328ce587fa5a40a2b8f55d34c80cc64a0c2 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Mon, 11 Apr 2022 15:51:55 -0700 Subject: [PATCH 254/386] PHPCS/formatting fixes; restore inadvertently removed test. --- .../abstracts/abstract-wc-product.php | 2 +- .../class-wc-product-downloads-test.php | 73 ++++++++++++++++++- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-product.php b/plugins/woocommerce/includes/abstracts/abstract-wc-product.php index 3dd5352b356..71ffba8a229 100644 --- a/plugins/woocommerce/includes/abstracts/abstract-wc-product.php +++ b/plugins/woocommerce/includes/abstracts/abstract-wc-product.php @@ -1202,7 +1202,7 @@ class WC_Product extends WC_Abstract_Legacy_Product { /** * Set downloads. * - * @throws WC_Data_Exception + * @throws WC_Data_Exception If an error relating to one of the downloads is encountered. * * @param array $downloads_array Array of WC_Product_Download objects or arrays. * diff --git a/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php b/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php index 0cb0ad4dc15..8192a44a069 100644 --- a/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php +++ b/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php @@ -9,7 +9,7 @@ class WC_Product_Download_Test extends WC_Unit_Test_Case { * Test for file without extension. */ public function test_is_allowed_filetype_with_no_extension() { - $upload_dir = trailingslashit( wp_upload_dir()['basedir'] ); + $upload_dir = trailingslashit( wp_upload_dir()['basedir'] ); $file_path_with_no_extension = $upload_dir . 'upload_file'; if ( ! file_exists( $file_path_with_no_extension ) ) { // Copy an existing file without extension. @@ -24,7 +24,7 @@ class WC_Product_Download_Test extends WC_Unit_Test_Case { * Simulates test condition for windows when filename ends with a period. */ public function test_is_allowed_filetype_on_windows_with_period_at_end() { - $upload_dir = trailingslashit( wp_upload_dir()['basedir'] ); + $upload_dir = trailingslashit( wp_upload_dir()['basedir'] ); $file_path_with_period_at_end = $upload_dir . 'upload_file.'; if ( ! file_exists( $file_path_with_period_at_end ) ) { // Copy an existing file without extension. @@ -45,8 +45,20 @@ class WC_Product_Download_Test extends WC_Unit_Test_Case { $download_directories = wc_get_container()->get( Download_Directories::class ); $download_directories->set_mode( Download_Directories::MODE_ENABLED ); - $non_admin_user = wp_insert_user( array( 'user_login' => uniqid(), 'role' => 'editor', 'user_pass' => 'x' ) ); - $admin_user = wp_insert_user( array( 'user_login' => uniqid(), 'role' => 'administrator', 'user_pass' => 'x' ) ); + $non_admin_user = wp_insert_user( + array( + 'user_login' => uniqid(), + 'role' => 'editor', + 'user_pass' => 'x', + ) + ); + $admin_user = wp_insert_user( + array( + 'user_login' => uniqid(), + 'role' => 'administrator', + 'user_pass' => 'x', + ) + ); $ebook_url = 'https://external.site/books/ultimate-guide-to-stuff.pdf'; $podcast_url = 'https://external.site/podcasts/ultimate-guide-to-stuff.mp3'; @@ -63,4 +75,57 @@ class WC_Product_Download_Test extends WC_Unit_Test_Case { $this->expectExceptionMessage( 'cannot be used: it is not located in an approved directory' ); $download->check_is_valid(); } + + /** + * Test handling of filepaths described via shortcodes in relation to the Approved Download Directory + * feature. This is to simulate scenarios such as encountered when using the S3 Downloads extension. + */ + public function test_shortcode_resolution_for_approved_directory_rules() { + /** @var Download_Directories $download_directories */ + $download_directories = wc_get_container()->get( Download_Directories::class ); + $download_directories->set_mode( Download_Directories::MODE_ENABLED ); + $dynamic_filepath = 'https://fast.reliable.external.fileserver.com/bucket-123/textbook.pdf'; + + // We select an admin user because we wish to automatically add Approved Directory rules. + $admin_user = wp_insert_user( + array( + 'user_login' => uniqid(), + 'role' => 'administrator', + 'user_pass' => 'x', + ) + ); + wp_set_current_user( $admin_user ); + + add_shortcode( + 'dynamic-download', + function () { + return 'https://fast.reliable.external.fileserver.com/bucket-123/textbook.pdf'; + } + ); + + $this->assertFalse( + $download_directories->is_valid_path( $dynamic_filepath ), + 'Confirm the filepath returned by the test URL is not yet valid.' + ); + + $download = new WC_Product_Download(); + $download->set_file( '[dynamic-download]' ); + + $this->assertNull( + $download->check_is_valid(), + 'The downloadable file successfully validates (if it did not, an exception would be thrown).' + ); + + $this->assertTrue( + $download_directories->is_valid_path( $dynamic_filepath ), + 'Confirm the filepath returned by the test URL is now considered valid.' + ); + + remove_shortcode( 'dynamic-download' ); + + // Now the shortcode is removed (perhaps the parent plugin has been removed/disabled) it will not resolve + // and so the filepath will not validate. + $this->expectException( 'Error' ); + $download_directories->check_is_valid(); + } } From ac1ad607b4bd780942e4f79518c75b2c4ed15548 Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Mon, 11 Apr 2022 16:29:43 -0700 Subject: [PATCH 255/386] Normalize auto-draft as a status in the abstract order class Ensures that an existing order with auto-draft status won't be interpreted as pending when determining if the status has changed. This also fixes an unrelated phpcs formatting error in order to pass the precommit checks. Fixes #32539 --- .../woocommerce/includes/abstracts/abstract-wc-order.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php index abb2e63fbe9..83d59992a42 100644 --- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php +++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php @@ -201,7 +201,8 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { } catch ( Exception $e ) { $message_id = $this->get_id() ? $this->get_id() : __( '(no ID)', 'woocommerce' ); - $this->handle_exception( $e, + $this->handle_exception( + $e, wp_kses_post( sprintf( /* translators: 1: Order ID or "(no ID)" if not known. */ @@ -551,15 +552,17 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { $old_status = $this->get_status(); $new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status; + $status_exceptions = array( 'auto-draft', 'trash' ); + // If setting the status, ensure it's set to a valid status. if ( true === $this->object_read ) { // Only allow valid new status. - if ( ! in_array( 'wc-' . $new_status, $this->get_valid_statuses(), true ) && 'trash' !== $new_status && 'auto-draft' !== $new_status ) { + if ( ! in_array( 'wc-' . $new_status, $this->get_valid_statuses(), true ) && ! in_array( $new_status, $status_exceptions, true ) ) { $new_status = 'pending'; } // If the old status is set but unknown (e.g. draft) assume its pending for action usage. - if ( $old_status && ! in_array( 'wc-' . $old_status, $this->get_valid_statuses(), true ) && 'trash' !== $old_status ) { + if ( $old_status && ! in_array( 'wc-' . $old_status, $this->get_valid_statuses(), true ) && ! in_array( $old_status, $status_exceptions, true ) ) { $old_status = 'pending'; } } From e2da686c31eb3609c862515c596a9d10e4f42cd8 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Tue, 12 Apr 2022 09:49:23 +0800 Subject: [PATCH 256/386] Add eway --- .../assets/images/payment_methods/72x72/eway.png | Bin 0 -> 790 bytes .../DefaultPaymentGateways.php | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 plugins/woocommerce/assets/images/payment_methods/72x72/eway.png diff --git a/plugins/woocommerce/assets/images/payment_methods/72x72/eway.png b/plugins/woocommerce/assets/images/payment_methods/72x72/eway.png new file mode 100644 index 0000000000000000000000000000000000000000..74884fa1b03f35a5e95b9869e28477812d2975f3 GIT binary patch literal 790 zcmV+x1L^#UP)tcqc9MLX&{8Q zyE8r?Y9J(e|94z_Kx#^mScq>b<-f6JVm{A5f>?I(t+F{CEBJ3!$J3v(c+{b;V7pF- z8K(Szo%;A$&KvC3+g(wg#Mw>yoYnJmO(k)jB9#Q1^?7&450F90+o zS=5lT3_2CZJfLGyTI`b}HF{KvI@sDa&XTJj*@59nJ(@V*B}e4(9AjV_qjXwj z4TK#89CeNl`{al`_Q+D_I_?Kgi51XS0LMlF+@vN)H`EL}029F^j_n#q#3|pp;DClY z=hG;tffNHs4J5S;vN-+jIYo$U92IfxG)W{FEopEW1RRwGXEpZY(j8?b8DXeQ3yez{ z>DL&5^KuzPB+|uEU1+AHrR5bSscQ<(6sSdOB*umCZVIkHM`i{wSeOEzCWesd>&JdO zNb$#NB_DGF*>(^|Gf&Zrx*2qnlFi*B+W~?z1~)Ut_x)#c40jbcfdck`h@?21 zFh&t)ABbLVJ$S=~MCqT}j!Z8xspIbHeT`Xrb-!P&}$^-Zx$!Hh0zh1Iox5 Uq9MrH&;S4c07*qoM6N<$f@U{VqW}N^ literal 0 HcmV?d00001 diff --git a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php index e648fa9722f..4b64d63b5ac 100644 --- a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php +++ b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php @@ -354,7 +354,7 @@ class DefaultPaymentGateways { 'id' => 'eway', 'title' => __( 'Eway', 'woocommerce' ), 'content' => __( 'The Eway extension for WooCommerce allows you to take credit card payments directly on your store without redirecting your customers to a third party site to make payment.', 'woocommerce' ), - 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/eway.png', + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/eway.png', 'plugins' => array( 'woocommerce-gateway-eway' ), 'is_visible' => array( self::get_rules_for_countries( array( 'AU', 'NZ' ) ), From 0ae4f8e484662a848c2c766d0ab487d08f771a4d Mon Sep 17 00:00:00 2001 From: Jacob Sewell Date: Mon, 11 Apr 2022 21:08:26 -0500 Subject: [PATCH 257/386] Add context param to Notes\DataStore::get_notes() and associated methods/filters. --- .../woocommerce/src/Admin/Notes/DataStore.php | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/plugins/woocommerce/src/Admin/Notes/DataStore.php b/plugins/woocommerce/src/Admin/Notes/DataStore.php index 0a7abdf954b..ea1949979b8 100644 --- a/plugins/woocommerce/src/Admin/Notes/DataStore.php +++ b/plugins/woocommerce/src/Admin/Notes/DataStore.php @@ -11,6 +11,9 @@ defined( 'ABSPATH' ) || exit; * WC Admin Note Data Store (Custom Tables) */ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Interface { + // Extensions should define their own contexts and use them to avoid applying woocommerce_note_where_clauses when not needed. + const WC_ADMIN_NOTE_OPER_GLOBAL = 'global'; + /** * Method to create a new note in the database. * @@ -324,9 +327,10 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter * Return an ordered list of notes. * * @param array $args Query arguments. + * @param string $context Optional argument that the woocommerce_note_where_clauses filter can use to determine whether to apply extra conditions. Extensions should define their own contexts and use them to avoid adding to notes where clauses when not needed. * @return array An array of objects containing a note id. */ - public function get_notes( $args = array() ) { + public function get_notes( $args = array(), $context = self::WC_ADMIN_NOTE_OPER_GLOBAL ) { global $wpdb; $defaults = array( @@ -338,7 +342,7 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter $args = wp_parse_args( $args, $defaults ); $offset = $args['per_page'] * ( $args['page'] - 1 ); - $where_clauses = $this->get_notes_where_clauses( $args ); + $where_clauses = $this->get_notes_where_clauses( $args, $context ); $query = $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared @@ -378,16 +382,18 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter * * @param string $type Comma separated list of note types. * @param string $status Comma separated list of statuses. + * @param string $context Optional argument that the woocommerce_note_where_clauses filter can use to determine whether to apply extra conditions. Extensions should define their own contexts and use them to avoid adding to notes where clauses when not needed. * @return array An array of objects containing a note id. */ - public function get_notes_count( $type = array(), $status = array() ) { + public function get_notes_count( $type = array(), $status = array(), $context = self::WC_ADMIN_NOTE_OPER_GLOBAL ) { global $wpdb; $where_clauses = $this->get_notes_where_clauses( array( 'type' => $type, 'status' => $status, - ) + ), + $context ); if ( ! empty( $where_clauses ) ) { @@ -426,9 +432,10 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter * * @uses args_to_where_clauses * @param array $args Array of args to pass. + * @param string $context Optional argument that the woocommerce_note_where_clauses filter can use to determine whether to apply extra conditions. Extensions should define their own contexts and use them to avoid adding to notes where clauses when not needed. * @return string Where clauses for the query. */ - public function get_notes_where_clauses( $args = array() ) { + public function get_notes_where_clauses( $args = array(), $context = self::WC_ADMIN_NOTE_OPER_GLOBAL ) { $where_clauses = $this->args_to_where_clauses( $args ); /** @@ -438,8 +445,9 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter * * @param string $where_clauses The generated WHERE clause. * @param array $args The original arguments for the request. + * @param string $context Optional argument that the woocommerce_note_where_clauses filter can use to determine whether to apply extra conditions. Extensions should define their own contexts and use them to avoid adding to notes where clauses when not needed. */ - return apply_filters( 'woocommerce_note_where_clauses', $where_clauses, $args ); + return apply_filters( 'woocommerce_note_where_clauses', $where_clauses, $args, $context ); } /** From e83631983a6339f0ae28036b680a20d33f4ad027 Mon Sep 17 00:00:00 2001 From: Jacob Sewell Date: Mon, 11 Apr 2022 21:09:04 -0500 Subject: [PATCH 258/386] Add unit tests for new context param in Notes\DataStore::get_notes(). --- .../notes/class-wc-tests-notes-data-store.php | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/notes/class-wc-tests-notes-data-store.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/notes/class-wc-tests-notes-data-store.php index c36be43720d..b4445dab708 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/notes/class-wc-tests-notes-data-store.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/notes/class-wc-tests-notes-data-store.php @@ -7,6 +7,7 @@ use \Automattic\WooCommerce\Admin\Notes\Notes; use \Automattic\WooCommerce\Admin\Notes\Note; +use \Automattic\WooCommerce\Admin\Notes\DataStore; /** * Class WC_Admin_Tests_Notes_Data_Store @@ -344,6 +345,92 @@ class WC_Admin_Tests_Notes_Data_Store extends WC_Unit_Test_Case { $this->assertEquals( $lookup_note_zero->get_id(), $get_note_zero->get_id() ); } + /** + * Test that get_notes properly handles the $context parameter + */ + public function test_get_notes_context_param() { + $data_store = WC_Data_Store::load( 'admin-note' ); + + // Create two Notes: one in context and one out of it. + $test_context = self::class; + $global_context = DataStore::WC_ADMIN_NOTE_OPER_GLOBAL; + $context_name = 'PHP_UNIT_IN_CONTEXT_TEST_NOTE'; + $out_of_context_name = 'PHP_UNIT_OUT_OF_CONTEXT_TEST_NOTE'; + + foreach ( array( $context_name, $out_of_context_name ) as $note_name ) { + $note = new Note(); + $note->set_name( $note_name ); + $note->set_title( 'PHPUNIT_CONTEXT_TEST_NOTE' ); + $note->set_content( 'PHPUNIT_CONTEXT_TEST_NOTE_CONTENT' ); + $note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); + $note->set_source( 'PHPUNIT_TEST' ); + $note->set_is_snoozable( false ); + $note->set_layout( 'plain' ); + $note->set_image( '' ); + $note->add_action( + 'PHPUNIT_TEST_ACTION_SLUG', + 'PHPUNIT_TEST_ACTION_LABEL', + '?s=PHPUNIT_TEST_ACTION_URL' + ); + $note->set_is_deleted( false ); + $note->save(); + } + + // Add filter for 'woocommerce_note_where_clauses' that applies only in context. + $context_filter_hit_count = 0; + $context_filter_callback = function( $where_clauses, $args, $context ) use ( $test_context, $global_context, $context_name, &$context_filter_hit_count ) { + if ( $context === $test_context ) { + $context_filter_hit_count++; + $where_clauses .= ' AND name = "' . $context_name . '"'; + } + return $where_clauses; + }; + add_filter( 'woocommerce_note_where_clauses', $context_filter_callback, 10, 3 ); + + // Add filter for 'woocommerce_note_where_clauses' that applies in any context. + $no_context_filter_hit_count = 0; + $global_context_received = null; + $no_context_filter_callback = function( $where_clauses, $args, $context ) use ( $test_context, &$global_context_received, &$no_context_filter_hit_count ) { + // Record the context we get passed in. + if ( $test_context !== $context ) { + $global_context_received = $context; + } + + // Record that we're here. + $no_context_filter_hit_count++; + $where_clauses .= ' AND source = "PHPUNIT_TEST"'; + return $where_clauses; + }; + add_filter( 'woocommerce_note_where_clauses', $no_context_filter_callback, 10, 3 ); + + // Get note counts. + $no_context_note_count = $data_store->get_notes_count( array( Note::E_WC_ADMIN_NOTE_INFORMATIONAL ), array() ); + $test_context_note_count = $data_store->get_notes_count( array( Note::E_WC_ADMIN_NOTE_INFORMATIONAL ), array(), $test_context ); + + // The no context filter should have been hit twice, the context filter once. + $this->assertEquals( 2, $no_context_filter_hit_count ); + $this->assertEquals( 1, $context_filter_hit_count ); + + // There should be only one note that satisfies the context filter. + $this->assertEquals( 1, $test_context_note_count ); + + // There should be more than one note that satisfies the no-context filter. + $this->assertGreaterThan( 1, $no_context_note_count ); + + // Same tests with get_notes(). + $no_context_get_notes = $data_store->get_notes( array( 'type' => array( Note::E_WC_ADMIN_NOTE_INFORMATIONAL ) ) ); + $test_context_get_notes = $data_store->get_notes( array( 'type' => array( Note::E_WC_ADMIN_NOTE_INFORMATIONAL ) ), $test_context ); + + // There should only be one note in the context result set. + $this->assertEquals( 1, count( $test_context_get_notes ) ); + + // There should be more than one note in the no-context result set. + $this->assertGreaterThan( 1, count( $no_context_get_notes ) ); + + // When not explicitly passed, context should default to WC_ADMIN_NOTE_OPER_GLOBAL. + $this->assertEquals( $global_context, $global_context_received ); + } + /** * Delete notes created by this class's tests. */ From 7cca9642f45d4453b46d1d541d78d7afddbe0fac Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Tue, 12 Apr 2022 10:09:07 +0800 Subject: [PATCH 259/386] Remove enabled section --- .../fills/PaymentGatewaySuggestions/index.js | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js index 084a4711efa..032b85c327a 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js @@ -185,12 +185,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { return gateway; }, [ isResolving, query, paymentGateways ] ); - const [ - wcPayGateway, - enabledGateways, - offlineGateways, - additionalGateways, - ] = useMemo( + const [ wcPayGateway, offlineGateways, additionalGateways ] = useMemo( () => Array.from( paymentGateways.values() ) .sort( ( a, b ) => { @@ -207,7 +202,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { } ) .reduce( ( all, gateway ) => { - const [ wcPay, enabled, offline, additional ] = all; + const [ wcPay, offline, additional ] = all; // WCPay is handled separately when not installed and configured if ( @@ -216,8 +211,6 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { ! ( gateway.installed && ! gateway.needsSetup ) ) { wcPay.push( gateway ); - } else if ( gateway.enabled ) { - enabled.push( gateway ); } else if ( gateway.is_offline ) { offline.push( gateway ); } else { @@ -226,7 +219,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { return all; }, - [ [], [], [], [] ] + [ [], [], [] ] ), [ paymentGateways ] ); @@ -258,14 +251,6 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { ); } - const enabledSection = !! enabledGateways.length && ( - - ); - const additionalSection = !! additionalGateways.length && ( { > { additionalSection } { offlineSection } - { enabledSection } ) : ( <> { additionalSection } { offlineSection } - { enabledSection } ) }
From e1db7ab0f2f1a54de7169c382f5dac88ab989960 Mon Sep 17 00:00:00 2001 From: Jacob Sewell Date: Mon, 11 Apr 2022 21:17:59 -0500 Subject: [PATCH 260/386] Changelog for 32573/32574. --- .../woocommerce/changelog/add-32573-context-arg-for-get-notes | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/add-32573-context-arg-for-get-notes diff --git a/plugins/woocommerce/changelog/add-32573-context-arg-for-get-notes b/plugins/woocommerce/changelog/add-32573-context-arg-for-get-notes new file mode 100644 index 00000000000..d576cf03ea3 --- /dev/null +++ b/plugins/woocommerce/changelog/add-32573-context-arg-for-get-notes @@ -0,0 +1,4 @@ +Significance: minor +Type: performance + +Add a context param with a default value of global to Admin\Notes\DataStore::get_notes(), get_notes_count(), and get_notes_where_clauses(). #32574 From 0b2df9a95657456c2e03dece9e91cb2bc3d7c390 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Tue, 12 Apr 2022 10:52:11 +0800 Subject: [PATCH 261/386] Fix text domain, standardize component conditional, remove unnecessary comment --- .../fills/PaymentGatewaySuggestions/components/Action.js | 2 +- .../fills/PaymentGatewaySuggestions/components/List/List.js | 6 ++---- .../PaymentGatewaySuggestions/components/Setup/Configure.js | 2 +- .../PaymentGatewaySuggestions/components/Toggle/Toggle.js | 2 -- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Action.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Action.js index 90c0486ecbc..ae4c9ad1f2a 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Action.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Action.js @@ -24,7 +24,7 @@ export const Action = ( { markConfigured, onSetUp = () => {}, onSetupCallback, - setupButtonText = __( 'Get started', 'woocommerce-admin' ), + setupButtonText = __( 'Get started', 'woocommerce' ), } ) => { const [ isBusy, setIsBusy ] = useState( false ); diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.js index d9442b284f7..289243ed3c9 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.js @@ -19,7 +19,7 @@ export const List = ( { } ) => { return ( - { heading ? { heading } : null } + { heading && { heading } } { paymentGateways.map( ( paymentGateway ) => { const { id } = paymentGateway; return ( @@ -31,10 +31,8 @@ export const List = ( { /> ); } ) } - { footerLink ? ( + { footerLink && ( { footerLink } - ) : ( - '' ) } ); diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/Configure.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/Configure.js index 5b5e45b5f92..b80430d58e9 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/Configure.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/Configure.js @@ -156,7 +156,7 @@ export const Configure = ( { markConfigured, paymentGateway } ) => {

) } ); diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.js index d096360b166..95ad0f2b078 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Toggle/Toggle.js @@ -38,5 +38,3 @@ export const Toggle = ( { children, heading, onToggle } ) => {
); }; - -// export default Toggle; From b7e156cdfa1801a6416889d1877629251b589786 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Tue, 12 Apr 2022 11:19:09 +0800 Subject: [PATCH 262/386] Add changelog for data package --- packages/js/data/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/js/data/CHANGELOG.md b/packages/js/data/CHANGELOG.md index cb955c6b7e2..f33e3ab6327 100644 --- a/packages/js/data/CHANGELOG.md +++ b/packages/js/data/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - Update dependency `@wordpress/hooks` to ^3.5.0 +- Add `is_offline` attribute for `Plugin` type # 3.1.0 From 1fb2418e57d553a3f607b904e9ddf35da1c8d85e Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Tue, 12 Apr 2022 11:40:02 +0800 Subject: [PATCH 263/386] Add changelog for data package --- packages/js/data/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/data/CHANGELOG.md b/packages/js/data/CHANGELOG.md index f33e3ab6327..a69294d2ca7 100644 --- a/packages/js/data/CHANGELOG.md +++ b/packages/js/data/CHANGELOG.md @@ -1,7 +1,7 @@ # Unreleased - Update dependency `@wordpress/hooks` to ^3.5.0 -- Add `is_offline` attribute for `Plugin` type +- Add `is_offline` attribute for `Plugin` type. #32467 # 3.1.0 From 29e27fff35c967b5fa683625c8cc66024d71dee8 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Tue, 12 Apr 2022 11:47:22 +0800 Subject: [PATCH 264/386] Update e2e --- packages/js/admin-e2e-tests/CHANGELOG.md | 2 ++ packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts | 4 ++++ packages/js/admin-e2e-tests/src/specs/tasks/payment.ts | 3 +++ 3 files changed, 9 insertions(+) diff --git a/packages/js/admin-e2e-tests/CHANGELOG.md b/packages/js/admin-e2e-tests/CHANGELOG.md index d764841fc75..db24c246ffd 100644 --- a/packages/js/admin-e2e-tests/CHANGELOG.md +++ b/packages/js/admin-e2e-tests/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- Update test for payment task. #32467 + # 1.0.0 - Add returned type annotations and remove unused vars. #8020 diff --git a/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts b/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts index 5224a8825a6..4b0720454ca 100644 --- a/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts +++ b/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts @@ -25,6 +25,10 @@ export class PaymentsSetup extends BasePage { await this.clickButtonWithText( 'Got it' ); } + async toggleOtherPaymentMethods(): Promise< void > { + await this.clickButtonWithText( 'Other payment methods' ); + } + async goToPaymentMethodSetup( method: PaymentMethodWithSetupButton ): Promise< void > { diff --git a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts index 0515dce47f3..b017e0ac837 100644 --- a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts +++ b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts @@ -67,6 +67,7 @@ const testAdminPaymentSetupTask = () => { await waitForTimeout( 1000 ); await homeScreen.clickOnTaskList( 'Set up payments' ); await paymentsSetup.isDisplayed(); + await paymentsSetup.toggleOtherPaymentMethods(); await paymentsSetup.methodHasBeenSetup( 'bacs' ); } ); @@ -76,6 +77,8 @@ const testAdminPaymentSetupTask = () => { await homeScreen.isDisplayed(); await waitForTimeout( 1000 ); await homeScreen.clickOnTaskList( 'Set up payments' ); + await paymentsSetup.isDisplayed(); + await paymentsSetup.toggleOtherPaymentMethods(); await paymentsSetup.enableCashOnDelivery(); await homeScreen.navigate(); await homeScreen.isDisplayed(); From 13720a663e679ba94644b5a32981708e5b1eee9c Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 12 Apr 2022 15:47:03 +0800 Subject: [PATCH 265/386] Fix admin clean command --- plugins/woocommerce-admin/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index d8270041af0..6be60f4ffc8 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -15,7 +15,7 @@ "build-storybook": "build-storybook -c ./storybook/.storybook", "build:feature-config": "php ../woocommerce/bin/generate-feature-config.php", "build:packages": "cross-env NODE_ENV=production pnpm run:packages -- build", - "clean": "rimraf ./dist && pnpm run:packages -- clean --parallel", + "clean": "rimraf ../woocommerce/assets/client/admin/* && pnpm run:packages -- clean --parallel", "client:watch": "cross-env WC_ADMIN_PHASE=development pnpm run build:feature-config && cross-env WC_ADMIN_PHASE=development webpack --watch", "create-hook-reference": "node ./bin/hook-reference/index.js", "create-wc-extension": "node ./bin/starter-pack/starter-pack.js", From e13c30cfc4d8624c5e904f20bf53874cdde56575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor=20Soriano?= Date: Tue, 12 Apr 2022 10:32:56 +0200 Subject: [PATCH 266/386] Add changelog file for #32460. --- plugins/woocommerce/changelog/changelog-32460 | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/changelog-32460 diff --git a/plugins/woocommerce/changelog/changelog-32460 b/plugins/woocommerce/changelog/changelog-32460 new file mode 100644 index 00000000000..b4c3c5bb5ae --- /dev/null +++ b/plugins/woocommerce/changelog/changelog-32460 @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +WC_Product_CSV_Importer_Controller::is_file_valid_csv now just invokes wc_is_file_valid_csv. #32460 From 4624ab7ddebdc3e17331ea11db877a0b1751db1a Mon Sep 17 00:00:00 2001 From: Ovidiu Liuta Date: Tue, 12 Apr 2022 11:33:12 +0300 Subject: [PATCH 267/386] adding filter comments --- .../includes/class-wc-download-handler.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/class-wc-download-handler.php b/plugins/woocommerce/includes/class-wc-download-handler.php index 789bc19ef97..e45084ad969 100644 --- a/plugins/woocommerce/includes/class-wc-download-handler.php +++ b/plugins/woocommerce/includes/class-wc-download-handler.php @@ -277,6 +277,13 @@ class WC_Download_Handler { // Paths that begin with '//' are always remote URLs. if ( '//' === substr( $file_path, 0, 2 ) ) { $file_path = ( is_ssl() ? 'https:' : 'http:' ) . $file_path; + + /** + * Filter the remote filepath for download. + * + * @since 6.5.0 + * @param string $file_path File path. + */ return array( 'remote_file' => true, 'file_path' => apply_filters( 'woocommerce_download_parse_remote_file_path', $file_path ), @@ -297,7 +304,14 @@ class WC_Download_Handler { $remote_file = false; $file_path = $parsed_file_path['path']; } - + + /** + * Filter the filepath for download. + * + * @since 6.5.0 + * @param string $file_path File path. + * @param bool $remote_file Remote File Indicator. + */ return array( 'remote_file' => $remote_file, 'file_path' => apply_filters( 'woocommerce_download_parse_file_path', $file_path, $remote_file ) From 9c65d3bbdc51293ba457268ffe270386343e535e Mon Sep 17 00:00:00 2001 From: Ovidiu Liuta Date: Tue, 12 Apr 2022 12:24:12 +0300 Subject: [PATCH 268/386] phpcs fixes --- plugins/woocommerce/includes/class-wc-download-handler.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-download-handler.php b/plugins/woocommerce/includes/class-wc-download-handler.php index e45084ad969..93e0cebad01 100644 --- a/plugins/woocommerce/includes/class-wc-download-handler.php +++ b/plugins/woocommerce/includes/class-wc-download-handler.php @@ -277,7 +277,7 @@ class WC_Download_Handler { // Paths that begin with '//' are always remote URLs. if ( '//' === substr( $file_path, 0, 2 ) ) { $file_path = ( is_ssl() ? 'https:' : 'http:' ) . $file_path; - + /** * Filter the remote filepath for download. * @@ -304,7 +304,7 @@ class WC_Download_Handler { $remote_file = false; $file_path = $parsed_file_path['path']; } - + /** * Filter the filepath for download. * @@ -314,7 +314,7 @@ class WC_Download_Handler { */ return array( 'remote_file' => $remote_file, - 'file_path' => apply_filters( 'woocommerce_download_parse_file_path', $file_path, $remote_file ) + 'file_path' => apply_filters( 'woocommerce_download_parse_file_path', $file_path, $remote_file ), ); } From 29a08dc51520fe952d2f9bf728038c94961e78b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor=20Soriano?= Date: Tue, 12 Apr 2022 11:38:59 +0200 Subject: [PATCH 269/386] Add changelog file for #32317. --- plugins/woocommerce/changelog/changelog-32317 | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/changelog-32317 diff --git a/plugins/woocommerce/changelog/changelog-32317 b/plugins/woocommerce/changelog/changelog-32317 new file mode 100644 index 00000000000..3ab27b4b08d --- /dev/null +++ b/plugins/woocommerce/changelog/changelog-32317 @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +Add woocommerce_download_parse_remote_file_path and woocommerce_download_parse_file_path filters to modify the parse_file_path method file_path return. #32317 \ No newline at end of file From 859ab8884b4b69d98e1a06927e4c88b313b14fb7 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Tue, 12 Apr 2022 18:06:09 +0800 Subject: [PATCH 270/386] Fix e2e tests --- .../src/pages/PaymentsSetup.ts | 17 +++++++-------- .../admin-e2e-tests/src/pages/WcSettings.ts | 16 +++++++++++++- .../src/specs/tasks/payment.ts | 21 ++++++++----------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts b/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts index 4b0720454ca..1dae7adc683 100644 --- a/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts +++ b/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts @@ -25,8 +25,13 @@ export class PaymentsSetup extends BasePage { await this.clickButtonWithText( 'Got it' ); } - async toggleOtherPaymentMethods(): Promise< void > { - await this.clickButtonWithText( 'Other payment methods' ); + async showOtherPaymentMethods(): Promise< void > { + const selector = '.woocommerce-task-payments button.toggle-button'; + await this.page.waitForSelector( selector ); + const toggleButton = await this.page.$( + `${ selector }[aria-expanded=false]` + ); + await toggleButton?.click(); } async goToPaymentMethodSetup( @@ -45,14 +50,6 @@ export class PaymentsSetup extends BasePage { } } - async methodHasBeenSetup( method: PaymentMethod ): Promise< void > { - const selector = `.woocommerce-task-payment-${ method }`; - await this.page.waitForSelector( selector ); - expect( - await getElementByText( '*', 'Manage', selector ) - ).toBeDefined(); - } - async enableCashOnDelivery(): Promise< void > { await this.page.waitForSelector( '.woocommerce-task-payment-cod' ); await this.clickButtonWithText( 'Enable' ); diff --git a/packages/js/admin-e2e-tests/src/pages/WcSettings.ts b/packages/js/admin-e2e-tests/src/pages/WcSettings.ts index ebf95a46033..0b5f78cf0a7 100644 --- a/packages/js/admin-e2e-tests/src/pages/WcSettings.ts +++ b/packages/js/admin-e2e-tests/src/pages/WcSettings.ts @@ -42,8 +42,22 @@ export class WcSettings extends BasePage { ); } + async paymentMethodIsEnabled( method = '' ): Promise< boolean > { + await this.navigate( 'checkout' ); + await waitForElementByText( 'h2', 'Payment methods' ); + const className = await getAttribute( + `tr[data-gateway_id=${ method }] .woocommerce-input-toggle`, + 'className' + ); + return ( + ( className as string ).indexOf( + 'woocommerce-input-toggle--disabled' + ) === -1 + ); + } + async cleanPaymentMethods(): Promise< void > { - this.navigate( 'checkout' ); + await this.navigate( 'checkout' ); await waitForElementByText( 'h2', 'Payment methods' ); const paymentMethods = await page.$$( 'span.woocommerce-input-toggle' ); for ( const method of paymentMethods ) { diff --git a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts index b017e0ac837..27052d002fc 100644 --- a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts +++ b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts @@ -53,6 +53,7 @@ const testAdminPaymentSetupTask = () => { } ); it( 'Saving valid bank account transfer details enables the payment method', async () => { + await paymentsSetup.showOtherPaymentMethods(); await paymentsSetup.goToPaymentMethodSetup( 'bacs' ); await bankTransferSetup.saveAccountDetails( { accountNumber: '1234', @@ -62,13 +63,11 @@ const testAdminPaymentSetupTask = () => { iban: '12 3456 7890', swiftCode: 'ABBA', } ); - - await homeScreen.isDisplayed(); await waitForTimeout( 1000 ); - await homeScreen.clickOnTaskList( 'Set up payments' ); - await paymentsSetup.isDisplayed(); - await paymentsSetup.toggleOtherPaymentMethods(); - await paymentsSetup.methodHasBeenSetup( 'bacs' ); + expect( await settings.paymentMethodIsEnabled( 'bacs' ) ).toBe( + true + ); + await homeScreen.navigate(); } ); it( 'Enabling cash on delivery enables the payment method', async () => { @@ -78,14 +77,12 @@ const testAdminPaymentSetupTask = () => { await waitForTimeout( 1000 ); await homeScreen.clickOnTaskList( 'Set up payments' ); await paymentsSetup.isDisplayed(); - await paymentsSetup.toggleOtherPaymentMethods(); + await paymentsSetup.showOtherPaymentMethods(); await paymentsSetup.enableCashOnDelivery(); - await homeScreen.navigate(); - await homeScreen.isDisplayed(); await waitForTimeout( 1000 ); - await homeScreen.clickOnTaskList( 'Set up payments' ); - await paymentsSetup.isDisplayed(); - await paymentsSetup.methodHasBeenSetup( 'cod' ); + expect( await settings.paymentMethodIsEnabled( 'cod' ) ).toBe( + true + ); } ); } ); }; From db7b1ba035d55b3582ccebf4fa17f332384817c0 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Tue, 12 Apr 2022 09:26:54 -0300 Subject: [PATCH 271/386] Add Pinterest to help panel --- .../woocommerce-admin/client/activity-panel/panels/help.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/woocommerce-admin/client/activity-panel/panels/help.js b/plugins/woocommerce-admin/client/activity-panel/panels/help.js index b13474e4f0b..a27469c37c1 100644 --- a/plugins/woocommerce-admin/client/activity-panel/panels/help.js +++ b/plugins/woocommerce-admin/client/activity-panel/panels/help.js @@ -100,6 +100,10 @@ function getMarketingItems( props ) { link: 'https://woocommerce.com/document/google-listings-and-ads/?utm_medium=product#get-started', }, + activePlugins.includes( 'pinterest-for-woocommerce' ) && { + title: __( 'Set up Pinterest for WooCommerce', 'woocommerce' ), + link: 'https://woocommerce.com/products/pinterest-for-woocommerce/', + }, activePlugins.includes( 'mailchimp-for-woocommerce' ) && { title: __( 'Connect Mailchimp for WooCommerce', 'woocommerce' ), link: From 17ae456868af7e82b8a7ad67a627d865fa4db6ab Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Tue, 12 Apr 2022 09:33:57 -0300 Subject: [PATCH 272/386] Fix text domain --- .../Admin/RemoteFreeExtensions/DefaultFreeExtensions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php b/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php index d5139a05118..5c80e61d5b5 100644 --- a/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php +++ b/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php @@ -125,7 +125,7 @@ class DefaultFreeExtensions { ], 'pinterest-for-woocommerce:alt' => [ 'name' => __( 'Pinterest for WooCommerce', 'woocommerce' ), - 'description' => __( 'Get your products in front of Pinterest users searching for ideas and things to buy. Get started with Pinterest and make your entire product catalog browsable.', 'woocommerce-admin' ), + 'description' => __( 'Get your products in front of Pinterest users searching for ideas and things to buy. Get started with Pinterest and make your entire product catalog browsable.', 'woocommerce' ), 'image_url' => plugins_url( 'images/onboarding/pinterest.png', WC_ADMIN_PLUGIN_FILE ), 'manage_url' => 'admin.php?page=wc-admin&path=%2Fpinterest%2Flanding', 'is_built_by_wc' => false, From d4d70acfd103641d48bf36c43f2279fe36b926c4 Mon Sep 17 00:00:00 2001 From: "Jorge A. Torres" Date: Tue, 12 Apr 2022 09:50:01 -0300 Subject: [PATCH 273/386] =?UTF-8?q?Enable=20=E2=80=9CSave=20changes?= =?UTF-8?q?=E2=80=9D=20in=20the=20admin=20variations=20form=20when=20textf?= =?UTF-8?q?ields=20change?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../legacy/js/admin/meta-boxes-product-variation.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/legacy/js/admin/meta-boxes-product-variation.js b/plugins/woocommerce/legacy/js/admin/meta-boxes-product-variation.js index f21f41a3066..79d3e7b322b 100644 --- a/plugins/woocommerce/legacy/js/admin/meta-boxes-product-variation.js +++ b/plugins/woocommerce/legacy/js/admin/meta-boxes-product-variation.js @@ -350,7 +350,7 @@ jQuery( function( $ ) { .on( 'click','.downloadable_files a.delete', this.input_changed ); $( document.body ) - .on( 'change', '#variable_product_options .woocommerce_variations :input', this.input_changed ) + .on( 'change input', '#variable_product_options .woocommerce_variations :input', this.input_changed ) .on( 'change', '.variations-defaults select', this.defaults_changed ); var postForm = $( 'form#post' ); @@ -705,13 +705,18 @@ jQuery( function( $ ) { /** * Add new class when have changes in some input */ - input_changed: function() { + input_changed: function( event ) { $( this ) .closest( '.woocommerce_variation' ) .addClass( 'variation-needs-update' ); $( 'button.cancel-variation-changes, button.save-variation-changes' ).prop( 'disabled', false ); + // Do not trigger 'woocommerce_variations_input_changed' for 'input' events for backwards compat. + if ( 'input' === event.type && $( this ).is( ':text' ) ) { + return; + } + $( '#variable_product_options' ).trigger( 'woocommerce_variations_input_changed' ); }, From 566076de9b736b82bc95ab0aa610ae7a4fcfb959 Mon Sep 17 00:00:00 2001 From: "Jorge A. Torres" Date: Tue, 12 Apr 2022 10:23:12 -0300 Subject: [PATCH 274/386] Add changelog --- plugins/woocommerce/changelog/issue-31347 | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/issue-31347 diff --git a/plugins/woocommerce/changelog/issue-31347 b/plugins/woocommerce/changelog/issue-31347 new file mode 100644 index 00000000000..c732dbfa7c8 --- /dev/null +++ b/plugins/woocommerce/changelog/issue-31347 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Enable "Save changes" for variations on the admin when a textfield receives input. From d6c216fc7464da71843efc02c23cecccc90b84e9 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Tue, 12 Apr 2022 10:37:49 -0300 Subject: [PATCH 275/386] Remove `welcome-modal.test.js --- .../tests/e2e/specs/admin-homescreen/welcome-modal.test.js | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 plugins/woocommerce/tests/e2e/specs/admin-homescreen/welcome-modal.test.js diff --git a/plugins/woocommerce/tests/e2e/specs/admin-homescreen/welcome-modal.test.js b/plugins/woocommerce/tests/e2e/specs/admin-homescreen/welcome-modal.test.js deleted file mode 100644 index c85d7381958..00000000000 --- a/plugins/woocommerce/tests/e2e/specs/admin-homescreen/welcome-modal.test.js +++ /dev/null @@ -1,5 +0,0 @@ -const { - testAdminHomescreenWelcomeModal, -} = require( '@woocommerce/admin-e2e-tests' ); - -testAdminHomescreenWelcomeModal(); From e219a19229ea1501020e2fa099e5d332743831b1 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Tue, 12 Apr 2022 10:38:27 -0300 Subject: [PATCH 276/386] Renamed `testAdminHomescreenWelcomeModal` --- .../specs/activate-and-setup/complete-onboarding-wizard.ts | 4 ++-- .../activate-and-setup/complete-onboarding-wizard.test.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/js/admin-e2e-tests/src/specs/activate-and-setup/complete-onboarding-wizard.ts b/packages/js/admin-e2e-tests/src/specs/activate-and-setup/complete-onboarding-wizard.ts index 5a8e48dd0b9..717cacf4186 100644 --- a/packages/js/admin-e2e-tests/src/specs/activate-and-setup/complete-onboarding-wizard.ts +++ b/packages/js/admin-e2e-tests/src/specs/activate-and-setup/complete-onboarding-wizard.ts @@ -597,7 +597,7 @@ const testBusinessDetailsForm = () => { } ); }; -const testAdminHomescreenWelcomeModal = () => { +const testAdminHomescreen = () => { describe( 'Homescreen', () => { const profileWizard = new OnboardingWizard( page ); const homeScreen = new WcHomescreen( page ); @@ -629,5 +629,5 @@ module.exports = { testDifferentStoreCurrenciesWCPay, testSubscriptionsInclusion, testBusinessDetailsForm, - testAdminHomescreenWelcomeModal, + testAdminHomescreen, }; diff --git a/plugins/woocommerce/tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.js b/plugins/woocommerce/tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.js index d69d0eb855a..28ca08ce731 100644 --- a/plugins/woocommerce/tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.js +++ b/plugins/woocommerce/tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.js @@ -4,7 +4,7 @@ const { testDifferentStoreCurrenciesWCPay, testSubscriptionsInclusion, testBusinessDetailsForm, - testAdminHomescreenWelcomeModal, + testAdminHomescreen, } = require( '@woocommerce/admin-e2e-tests' ); const { withRestApi, IS_RETEST_MODE } = require( '@woocommerce/e2e-utils' ); @@ -18,4 +18,4 @@ testSelectiveBundleWCPay(); testDifferentStoreCurrenciesWCPay(); testSubscriptionsInclusion(); testBusinessDetailsForm(); -testAdminHomescreenWelcomeModal(); +testAdminHomescreen(); From 23b3250d1068fec57cd591ea075ce9e0276bd225 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Tue, 12 Apr 2022 10:38:50 -0300 Subject: [PATCH 277/386] Add package changelog --- packages/js/admin-e2e-tests/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/js/admin-e2e-tests/CHANGELOG.md b/packages/js/admin-e2e-tests/CHANGELOG.md index d764841fc75..14fdbbcb34b 100644 --- a/packages/js/admin-e2e-tests/CHANGELOG.md +++ b/packages/js/admin-e2e-tests/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- Add E2E tests to disabled welcome modal #32505 + # 1.0.0 - Add returned type annotations and remove unused vars. #8020 From 9f025db18a3a8c1a7af2df3a4b83aacd8b2c774e Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Tue, 12 Apr 2022 08:05:00 -0700 Subject: [PATCH 278/386] Adjust changelog entry. --- plugins/woocommerce/changelog/issue-31347 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/changelog/issue-31347 b/plugins/woocommerce/changelog/issue-31347 index c732dbfa7c8..2a5bacef909 100644 --- a/plugins/woocommerce/changelog/issue-31347 +++ b/plugins/woocommerce/changelog/issue-31347 @@ -1,4 +1,4 @@ Significance: patch Type: fix -Enable "Save changes" for variations on the admin when a textfield receives input. +Enable the "Save changes" button within the variations panel when a textfield receives input. From 97fca30b394d0075fc040920fc0d660e7e17c726 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Tue, 12 Apr 2022 12:07:10 -0300 Subject: [PATCH 279/386] Fix fallback text --- .../Admin/RemoteFreeExtensions/DefaultFreeExtensions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php b/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php index 5c80e61d5b5..84fc8cc9351 100644 --- a/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php +++ b/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php @@ -104,7 +104,7 @@ class DefaultFreeExtensions { 'name' => __( 'Pinterest for WooCommerce', 'woocommerce' ), 'description' => sprintf( /* translators: 1: opening product link tag. 2: closing link tag */ - __( 'aaaa Inspire shoppers with %1$sPinterest for WooCommerce%2$s', 'woocommerce' ), + __( 'Inspire shoppers with %1$sPinterest for WooCommerce%2$s', 'woocommerce' ), '', '' ), From b85648a11744f491bb2f238adc694334c45c0789 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Tue, 12 Apr 2022 14:08:04 -0300 Subject: [PATCH 280/386] Fix fallback images URL --- .../DefaultFreeExtensions.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php b/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php index 84fc8cc9351..202dc7f5982 100644 --- a/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php +++ b/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php @@ -78,7 +78,7 @@ class DefaultFreeExtensions { '', '' ), - 'image_url' => plugins_url( 'images/onboarding/google-listings-and-ads.png', WC_ADMIN_PLUGIN_FILE ), + 'image_url' => plugins_url( '/assets/images/onboarding/google-listings-and-ads.png', WC_PLUGIN_FILE ), 'manage_url' => 'admin.php?page=wc-admin&path=%2Fgoogle%2Fstart', 'is_built_by_wc' => true, 'is_visible' => [ @@ -96,7 +96,7 @@ class DefaultFreeExtensions { 'google-listings-and-ads:alt' => [ 'name' => __( 'Google Listings & Ads', 'woocommerce' ), 'description' => __( 'Reach more shoppers and drive sales for your store. Integrate with Google to list your products for free and launch paid ad campaigns.', 'woocommerce' ), - 'image_url' => plugins_url( 'images/onboarding/google-listings-and-ads.png', WC_ADMIN_PLUGIN_FILE ), + 'image_url' => plugins_url( '/assets/images/onboarding/google-listings-and-ads.png', WC_PLUGIN_FILE ), 'manage_url' => 'admin.php?page=wc-admin&path=%2Fgoogle%2Fstart', 'is_built_by_wc' => true, ], @@ -108,7 +108,7 @@ class DefaultFreeExtensions { '', '' ), - 'image_url' => plugins_url( 'images/onboarding/pinterest.png', WC_ADMIN_PLUGIN_FILE ), + 'image_url' => plugins_url( '/assets/images/onboarding/pinterest.png', WC_PLUGIN_FILE ), 'manage_url' => 'admin.php?page=pinterest-for-woocommerce', 'is_visible' => [ [ @@ -126,28 +126,28 @@ class DefaultFreeExtensions { 'pinterest-for-woocommerce:alt' => [ 'name' => __( 'Pinterest for WooCommerce', 'woocommerce' ), 'description' => __( 'Get your products in front of Pinterest users searching for ideas and things to buy. Get started with Pinterest and make your entire product catalog browsable.', 'woocommerce' ), - 'image_url' => plugins_url( 'images/onboarding/pinterest.png', WC_ADMIN_PLUGIN_FILE ), + 'image_url' => plugins_url( '/assets/images/onboarding/pinterest.png', WC_PLUGIN_FILE ), 'manage_url' => 'admin.php?page=wc-admin&path=%2Fpinterest%2Flanding', 'is_built_by_wc' => false, ], 'mailpoet' => [ 'name' => __( 'MailPoet', 'woocommerce' ), 'description' => __( 'Create and send purchase follow-up emails, newsletters, and promotional campaigns straight from your dashboard.', 'woocommerce' ), - 'image_url' => plugins_url( 'images/onboarding/mailpoet.png', WC_ADMIN_PLUGIN_FILE ), + 'image_url' => plugins_url( '/assets/images/onboarding/mailpoet.png', WC_PLUGIN_FILE ), 'manage_url' => 'admin.php?page=mailpoet-newsletters', 'is_built_by_wc' => true, ], 'mailchimp-for-woocommerce' => [ 'name' => __( 'Mailchimp', 'woocommerce' ), 'description' => __( 'Send targeted campaigns, recover abandoned carts and much more with Mailchimp.', 'woocommerce' ), - 'image_url' => plugins_url( 'images/onboarding/mailchimp-for-woocommerce.png', WC_ADMIN_PLUGIN_FILE ), + 'image_url' => plugins_url( '/assets/images/onboarding/mailchimp-for-woocommerce.png', WC_PLUGIN_FILE ), 'manage_url' => 'admin.php?page=mailchimp-woocommerce', 'is_built_by_wc' => false, ], 'creative-mail-by-constant-contact' => [ 'name' => __( 'Creative Mail for WooCommerce', 'woocommerce' ), 'description' => __( 'Create on-brand store campaigns, fast email promotions and customer retargeting with Creative Mail.', 'woocommerce' ), - 'image_url' => plugins_url( 'images/onboarding/creative-mail-by-constant-contact.png', WC_ADMIN_PLUGIN_FILE ), + 'image_url' => plugins_url( '/assets/images/onboarding/creative-mail-by-constant-contact.png', WC_PLUGIN_FILE ), 'manage_url' => 'admin.php?page=creativemail', 'is_built_by_wc' => false, ], @@ -481,7 +481,7 @@ class DefaultFreeExtensions { 'mailpoet:alt' => [ 'name' => __( 'MailPoet', 'woocommerce' ), 'description' => __( 'Create and send purchase follow-up emails, newsletters, and promotional campaigns straight from your dashboard.', 'woocommerce' ), - 'image_url' => plugins_url( 'images/onboarding/mailpoet.png', WC_ADMIN_PLUGIN_FILE ), + 'image_url' => plugins_url( '/assets/images/onboarding/mailpoet.png', WC_PLUGIN_FILE ), 'manage_url' => 'admin.php?page=mailpoet-newsletters', 'is_built_by_wc' => true, ], From 29a5d0a0db33c0063b185a55a415dac5af1e093e Mon Sep 17 00:00:00 2001 From: moon Date: Tue, 12 Apr 2022 10:21:15 -0700 Subject: [PATCH 281/386] Remove redundant action -- woocommerce_updated and woocommerce_updated_existing are redundant --- plugins/woocommerce/includes/class-wc-install.php | 4 ---- .../Admin/Notes/ManageStoreActivityFromHomeScreen.php | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 4da97c87435..cb8d8153426 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -242,10 +242,6 @@ class WC_Install { if ( ! $wc_version ) { do_action( 'woocommerce_newly_installed' ); do_action_deprecated( 'woocommerce_admin_newly_installed', array(), $wc_code_version, 'woocommerce_newly_installed' ); - } else { - // if there is already a version and we're install, we're updating an existing install. - do_action( 'woocommerce_updated_existing' ); - do_action_deprecated( 'woocommerce_admin_updated_existing', array(), $wc_code_version, 'woocommerce_updated_existing' ); } } } diff --git a/plugins/woocommerce/src/Internal/Admin/Notes/ManageStoreActivityFromHomeScreen.php b/plugins/woocommerce/src/Internal/Admin/Notes/ManageStoreActivityFromHomeScreen.php index 88260ab9ec4..026f6bf5024 100644 --- a/plugins/woocommerce/src/Internal/Admin/Notes/ManageStoreActivityFromHomeScreen.php +++ b/plugins/woocommerce/src/Internal/Admin/Notes/ManageStoreActivityFromHomeScreen.php @@ -29,7 +29,7 @@ class ManageStoreActivityFromHomeScreen { */ public function __construct() { add_action( - 'woocommerce_updated_existing', + 'woocommerce_updated', array( $this, 'possibly_add_note' ) ); } From 529f43e76995fcd48118085be5ce5d0972aa4072 Mon Sep 17 00:00:00 2001 From: moon Date: Tue, 12 Apr 2022 10:50:40 -0700 Subject: [PATCH 282/386] Add install tests --- .../unit-tests/woocommerce-admin/install.php | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php index f27b3353994..b6ae125e4f3 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php @@ -161,4 +161,54 @@ class WC_Admin_Tests_Install extends WP_UnitTestCase { WC_Install::install(); $this->assertEquals( get_option( self::VERSION_OPTION ), WC_ADMIN_VERSION_NUMBER ); } + + /** + * Test the following options are created. + * + * - woocommerce_admin_install_timestamp + * + * @return void + */ + public function test_options_are_set() { + delete_transient( 'wc_installing' ); + WC_Install::install(); + $options = array( 'woocommerce_admin_install_timestamp' ); + foreach ( $options as $option ) { + $this->assertNotFalse( get_option( $option ) ); + } + } + + /** + * Test woocommerce_admin_installed action. + * @return void + */ + public function test_woocommerce_admin_installed_action() { + delete_transient( 'wc_installing' ); + WC_Install::install(); + $this->assertTrue( did_action( 'woocommerce_admin_installed' ) > 0 ); + } + + /** + * Test woocommerce_updated action gets fired. + * + * @return void + */ + public function test_woocommerce_updated_action() { + $versions = array_keys( WC_Install::get_db_update_callbacks() ); + $prev_version = $versions[ count( $versions ) - 2 ]; + update_option( 'woocommerce_version', $prev_version ); + WC_Install::check_version(); + $this->assertTrue( did_action( 'woocommerce_updated' ) > 0 ); + } + + /** + * Test woocommerce_newly_installed action gets fired. + * @return void + */ + public function test_woocommerce_newly_installed_action() { + delete_option( 'woocommerce_version' ); + WC_Install::check_version(); + $this->assertTrue( did_action( 'woocommerce_newly_installed' ) > 0 ); + } + } From 2f21b3592e107482e97a95a6fa9b55bd48e8ea0b Mon Sep 17 00:00:00 2001 From: moon Date: Tue, 12 Apr 2022 11:12:19 -0700 Subject: [PATCH 283/386] Add test for migrate_options --- .../unit-tests/woocommerce-admin/install.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php index b6ae125e4f3..5bc2da088bc 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php @@ -211,4 +211,36 @@ class WC_Admin_Tests_Install extends WP_UnitTestCase { $this->assertTrue( did_action( 'woocommerce_newly_installed' ) > 0 ); } + /** + * Test migrate_options(); + * @return void + */ + public function test_migrate_options() { + delete_transient( 'wc_installing' ); + WC_Install::install(); + $this->assertTrue( defined( 'WC_ADMIN_MIGRATING_OPTIONS' ) ); + $migrated_options = array( + 'woocommerce_onboarding_profile' => 'wc_onboarding_profile', + 'woocommerce_admin_install_timestamp' => 'wc_admin_install_timestamp', + 'woocommerce_onboarding_opt_in' => 'wc_onboarding_opt_in', + 'woocommerce_admin_import_stats' => 'wc_admin_import_stats', + 'woocommerce_admin_version' => 'wc_admin_version', + 'woocommerce_admin_last_orders_milestone' => 'wc_admin_last_orders_milestone', + 'woocommerce_admin-wc-helper-last-refresh' => 'wc-admin-wc-helper-last-refresh', + 'woocommerce_admin_report_export_status' => 'wc_admin_report_export_status', + 'woocommerce_task_list_complete' => 'woocommerce_task_list_complete', + 'woocommerce_task_list_hidden' => 'woocommerce_task_list_hidden', + 'woocommerce_extended_task_list_complete' => 'woocommerce_extended_task_list_complete', + 'woocommerce_extended_task_list_hidden' => 'woocommerce_extended_task_list_hidden', + ); + + foreach ( $migrated_options as $new_option => $old_option ) { + $old_option_value = get_option( $old_option ); + if ( false === $old_option_value ) { + continue; + } + $this->assertNotFalse( get_option( $new_option ), $new_option ); + } + } + } From 4b9c44ebb37e1b53519f048156ddb7dff53e8d27 Mon Sep 17 00:00:00 2001 From: Jacob Sewell Date: Tue, 12 Apr 2022 15:46:02 -0500 Subject: [PATCH 284/386] Docblock linter fixes. --- plugins/woocommerce/src/Admin/Notes/DataStore.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/src/Admin/Notes/DataStore.php b/plugins/woocommerce/src/Admin/Notes/DataStore.php index ea1949979b8..49b14e500b3 100644 --- a/plugins/woocommerce/src/Admin/Notes/DataStore.php +++ b/plugins/woocommerce/src/Admin/Notes/DataStore.php @@ -326,7 +326,7 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter /** * Return an ordered list of notes. * - * @param array $args Query arguments. + * @param array $args Query arguments. * @param string $context Optional argument that the woocommerce_note_where_clauses filter can use to determine whether to apply extra conditions. Extensions should define their own contexts and use them to avoid adding to notes where clauses when not needed. * @return array An array of objects containing a note id. */ @@ -431,7 +431,7 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter * Applies woocommerce_note_where_clauses filter. * * @uses args_to_where_clauses - * @param array $args Array of args to pass. + * @param array $args Array of args to pass. * @param string $context Optional argument that the woocommerce_note_where_clauses filter can use to determine whether to apply extra conditions. Extensions should define their own contexts and use them to avoid adding to notes where clauses when not needed. * @return string Where clauses for the query. */ From 667d2a0be150d956606524b75e3ec5215fff0b57 Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Tue, 12 Apr 2022 14:15:21 -0700 Subject: [PATCH 285/386] Add changelog file --- .../changelog/fix-32539-order-autodraft-validation | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-32539-order-autodraft-validation diff --git a/plugins/woocommerce/changelog/fix-32539-order-autodraft-validation b/plugins/woocommerce/changelog/fix-32539-order-autodraft-validation new file mode 100644 index 00000000000..d283595b90c --- /dev/null +++ b/plugins/woocommerce/changelog/fix-32539-order-autodraft-validation @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Ensure that an existing order with auto-draft status won't be interpreted as pending when determining if the status has changed. From 42838bc2dab60969184bfaf28e09f297fa438dfa Mon Sep 17 00:00:00 2001 From: Christopher Allford Date: Tue, 12 Apr 2022 15:36:24 -0700 Subject: [PATCH 286/386] Added 6.4.0 Changelog --- changelog.txt | 98 ++++++++++++++++++++++++++++++++++ plugins/woocommerce/readme.txt | 2 +- 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 2d055307fe0..ee3248e0e05 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,103 @@ == Changelog == += 6.4.0 2022-04-12 = + +**WooCommerce** + +- Add - Scaffolding for the custom orders table feature. ([#31692](https://github.com/woocommerce/woocommerce/pull/31692)) +- Add - Add DB table structure for custom order tables. ([#31811](https://github.com/woocommerce/woocommerce/pull/31811)) +- Add - Primary key for the product attributes lookup table. ([#32067](https://github.com/woocommerce/woocommerce/pull/32067)) +- Add - Tracks to the dashboard status widget and setup widget. ([#31857](https://github.com/woocommerce/woocommerce/pull/31857)) +- Add - Check around setup widget display when features are disabled. ([#31884](https://github.com/woocommerce/woocommerce/pull/31884)) +- Add - 'woocommerce_get_formatted_meta_data_include_all_meta_lines' filter hook. This can be used to control whether metadata lines are shown in the order meta box. ([#30948](https://github.com/woocommerce/woocommerce/pull/30948)) +- Enhancement - Introduce rate_limit_remaining column in the wc_rate_limits table. ([#32041](https://github.com/woocommerce/woocommerce/pull/32041)) +- Tweak - Update PayPal Standard JS used in the admin environment to avoid deprecated functionality. ([#32076](https://github.com/woocommerce/woocommerce/pull/32076)) +- Tweak - Change level of escaping used to render the CSV import error log. ([#32000](https://github.com/woocommerce/woocommerce/pull/32000)) +- Tweak - Make the payment_url field available via the REST API's orders endpoint. ([#31826](https://github.com/woocommerce/woocommerce/pull/31826)) +- Tweak - Rename WC_API_Exception code woocommerce_api_cannot_edit_product_catgory into woocommerce_api_cannot_edit_product_category ([#31785](https://github.com/woocommerce/woocommerce/pull/31785)) +- Tweak - Updated default email color to new Woo purple ([#30586](https://github.com/woocommerce/woocommerce/pull/30586)) +- Fix - Avoid depending on the presence of a theme header template to clear the cart after payment is made. ([#31877](https://github.com/woocommerce/woocommerce/pull/31877)) +- Fix - Payments tab tracking. ([#31844](https://github.com/woocommerce/woocommerce/pull/31844)) +- Fix - Remove unnecessary duplicate style in email-styles template. ([#31860](https://github.com/woocommerce/woocommerce/pull/31860)) +- Fix - incorrect position value for registering menu pages. ([#31779](https://github.com/woocommerce/woocommerce/pull/31779)) +- Fix - SZL currency symbol. Updated from 'L' to 'E'. ([#30602](https://github.com/woocommerce/woocommerce/pull/30602)) +- Fix - Removed execution of at least one hook ignoring the `woocommerce_load_webhooks_limit` filter value. ([#29002](https://github.com/woocommerce/woocommerce/pull/29002)) +- Dev - Added has_options() to REST API v3 product endpoint response. ([#32031](https://github.com/woocommerce/woocommerce/pull/32031)) +- Dev - Added woocommerce_admin_order_should_render_refunds hook to allow control over the refunds UI within the order editor. ([#31414](https://github.com/woocommerce/woocommerce/pull/31414)) + +**WooCommerce Admin - 3.3.0 & 3.3.1 & 3.3.2** + +- Add - Add asynchronous plugin install and activation endpoints ([#8079](https://github.com/woocommerce/woocommerce-admin/pull/8079)) +- Performance - Avoid expensive get_notes() queries in CouponPageMoved admin_init actions by using new Notes::get_note_by_name() helper method. ([#8202](https://github.com/woocommerce/woocommerce-admin/pull/8202)) +- Enhancement - Add chart color filter for overriding default chart colors. ([#8258](https://github.com/woocommerce/woocommerce-admin/pull/8258)) +- Enhancement - Added Typescript type declarations to build for @woocommerce/components ([#8282](https://github.com/woocommerce/woocommerce-admin/pull/8282)) +- Enhancement - Increase color selection limit to ten and add additional colors. ([#8258](https://github.com/woocommerce/woocommerce-admin/pull/8258)) +- Enhancement - Made @woocommerce/components/Stepper a Typescript file ([#8286](https://github.com/woocommerce/woocommerce-admin/pull/8286)) +- Enhancement - Prompts a modal to save any unsaved changes when the users try to move to a different step ([#8278](https://github.com/woocommerce/woocommerce-admin/pull/8278)) +- Tweak - OBW: Override Country/Region label line-height style to avoid truncated descenders. ([#8186](https://github.com/woocommerce/woocommerce-admin/pull/8186)) +- Tweak - Show single success message for theme install and activation ([#8236](https://github.com/woocommerce/woocommerce-admin/pull/8236)) +- Tweak - Use WC_VERSION as cache buster for assets ([#8308](https://github.com/woocommerce/woocommerce-admin/pull/8308)) +- Update - Adjust time range and add an image for the Jetpack Backup note. ([#8293](https://github.com/woocommerce/woocommerce-admin/pull/8293)) +- Update - Implement MailChimp API request threshold for MailchimpScheduler. ([#8342](https://github.com/woocommerce/woocommerce-admin/pull/8342)) +- Update - Reintroduce CES on product add, product update, and order update. ([#8238](https://github.com/woocommerce/woocommerce-admin/pull/8238)) +- Update - Replace mysql image with mariadb ([#8220](https://github.com/woocommerce/woocommerce-admin/pull/8220)) +- Update - Update country support list for WooCommerce Payments Task. ([#8517](https://github.com/woocommerce/woocommerce-admin/pull/8517)) +- Fix - Fix handling of paid themes in purchase task. ([#8493](https://github.com/woocommerce/woocommerce-admin/pull/8493)) +- Fix - Make sure the paid extension task is also shown for themes. ([#8412](https://github.com/woocommerce/woocommerce-admin/pull/8412)) +- Fix - Reintroduce emphasis on inbox note action button. ([#8411](https://github.com/woocommerce/woocommerce-admin/pull/8411)) +- Fix - Remove class ExtendedPayments. ([#8461](https://github.com/woocommerce/woocommerce-admin/pull/8461)) +- Fix - Added random IDs to SVG checkmarks in stepper component ([#8222](https://github.com/woocommerce/woocommerce-admin/pull/8222)) +- Fix - Fix Google Listings plugin is always shown in free features despite already activated. ([#8330](https://github.com/woocommerce/woocommerce-admin/pull/8330)) +- Fix - Fix hidden notes in `admin/notes` endpoint when the user is not in the tasklist experiment. ([#8328](https://github.com/woocommerce/woocommerce-admin/pull/8328)) +- Fix - Fix missing product name in variation analytic page for the deleted products. ([#8255](https://github.com/woocommerce/woocommerce-admin/pull/8255)) +- Fix - Fix payments extensions displayed below the offline payments options. ([#8232](https://github.com/woocommerce/woocommerce-admin/pull/8232)) +- Fix - Fix setup wizard title and flash of content ([#8201](https://github.com/woocommerce/woocommerce-admin/pull/8201)) +- Fix - Fix too many pending run_remote_notifications actions. ([#8285](https://github.com/woocommerce/woocommerce-admin/pull/8285)) +- Fix - Fix view logic for Setup additional payment providers task. ([#8391](https://github.com/woocommerce/woocommerce-admin/pull/8391)) +- Fix - OBW: fix copy on Business Details when "WooCommerce Shipping" is not listed ([#8324](https://github.com/woocommerce/woocommerce-admin/pull/8324)) +- Fix - Only add product data on REST requests and task list ([#8235](https://github.com/woocommerce/woocommerce-admin/pull/8235)) +- Fix - Stop showing actioned inbox items ([#8394](https://github.com/woocommerce/woocommerce-admin/pull/8394)) +- Fix - WC Payments task is not visible after installing the plugin ([#8514](https://github.com/woocommerce/woocommerce-admin/pull/8514)) +- Fix - PHP warning when default param is missing in payments spec. ([#8519](https://github.com/woocommerce/woocommerce-admin/pull/8519)) +- Dev - Added a test for tracks event recording for PaymentGatewaySuggestions ([#8306](https://github.com/woocommerce/woocommerce-admin/pull/8306)) +- Dev - Add README to hook reference generation script ([#8004](https://github.com/woocommerce/woocommerce-admin/pull/8004)) +- Dev - Add reset WooCommerce functionality to E2E tests, so tests have a fresh state. ([#8219](https://github.com/woocommerce/woocommerce-admin/pull/8219)) +- Dev - Enabled optional typescript checking on ./client subfolder ([#8372](https://github.com/woocommerce/woocommerce-admin/pull/8372)) +- Dev - Fix formatting and add filter param for changelog types for the testing instructions script. ([#8256](https://github.com/woocommerce/woocommerce-admin/pull/8256)) +- Dev - Refactor MerchantEmailNotifications ([#8304](https://github.com/woocommerce/woocommerce-admin/pull/8304)) +- Dev - Remove preloaded countries from data endpoints and use data store instead. ([#8380](https://github.com/woocommerce/woocommerce-admin/pull/8380)) +- Dev - Remove unused pre loaded setting data displaying all the routes. ([#8379](https://github.com/woocommerce/woocommerce-admin/pull/8379)) +- Dev - Remove unused task styling classes ([#8234](https://github.com/woocommerce/woocommerce-admin/pull/8234)) +- Dev - Update dependencies to support react 17 and drop support for IE11. ([#8305](https://github.com/woocommerce/woocommerce-admin/pull/8305)) +- Dev - Update task list data structure to better handle new designs. ([#8332](https://github.com/woocommerce/woocommerce-admin/pull/8332)) + +**WooCommerce Blocks - 7.2.0 & 7.2.1** + +- Enhancement - Add Global Styles support to the Product Price block. ([5950](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5950)) +- Enhancement - Add Global Styles support to the Add To Cart Button block. ([5816](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5816)) +- Enhancement - Store API - Introduced `wc/store/v1` namespace. ([5911](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5911)) +- Enhancement - Renamed WooCommerce block templates to more e-commerce related names. ([5935](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5935)) +- Enhancement - Featured Product block: Add the ability to reset to a previously set custom background image. ([5886](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5886)) +- Enhancement - Add a remove image button to the WooCommerce Feature Category block. ([5719](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5719)) +- Enhancement - Add support for the global style for the On-Sale Badge block. ([5565](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5565)) +- Enhancement - Add support for the global style for the Attribute Filter block. ([5557](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5557)) +- Enhancement - Category List block: Add support for global style. ([5516](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5516)) +- Fix - Fixed typo in `wooocommerce_store_api_validate_add_to_cart` and `wooocommerce_store_api_validate_cart_item` hook names. ([5926](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5926)) +- Fix - Fix loading WC core translations in locales where WC Blocks is not localized for some strings. ([5910](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5910)) +- Fix - Fixed an issue where clear customizations functionality was not working for WooCommerce templates. ([5746](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5746)) +- Fix - Fixed hover and focus states for button components. ([5712](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5712)) +- Fix - Add to Cart button on Products listing blocks will respect the "Redirect to the cart page after successful addition" setting. ([5708](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5708)) +- Fix - Fixes Twenty Twenty Two issues with sales price and added to cart "View Cart" call out styling in the "Products by Category" block. ([5684](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5684)) +- Fix - StoreAPI: Clear all wc notice types in the cart validation context [#5983](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5983) +- Fix - Don't trigger class deprecations notices if headers are already sent [#6074](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/6074) +- Various - Remove v1 string from Store Keys. ([5987](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5987)) +- Various - Introduce the `InvalidCartException` for handling cart validation. ([5904](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5904)) +- Various - Renamed Store API custom headers to remove `X-WC-Store-API` prefixes. [#5983](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5983) +- Various - Normalised Store API error codes [#5992](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5992) +- Various - Deprecated `woocommerce_blocks_checkout_order_processed` in favour of `woocommerce_store_api_checkout_order_processed` +- Various - Deprecated `woocommerce_blocks_checkout_update_order_meta` in favour of `woocommerce_store_api_checkout_update_order_meta` +- Various - Deprecated `woocommerce_blocks_checkout_update_order_from_request` in favour of `woocommerce_store_api_checkout_update_order_from_request` + = 6.3.1 2022-03-10 = **WooCommerce** diff --git a/plugins/woocommerce/readme.txt b/plugins/woocommerce/readme.txt index 5fe323beb36..f6abd5a8f09 100644 --- a/plugins/woocommerce/readme.txt +++ b/plugins/woocommerce/readme.txt @@ -160,6 +160,6 @@ WooCommerce comes with some sample data you can use to see how products look; im == Changelog == -= 6.4.0 2022-XX-XX = += 6.5.0 2022-XX-XX = [See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/changelog.txt). From 372ad23cc82f79de5a8f8518f1735e752ac53eb0 Mon Sep 17 00:00:00 2001 From: Christopher Allford Date: Tue, 12 Apr 2022 15:37:05 -0700 Subject: [PATCH 287/386] Updated Version & Stable Tag --- plugins/woocommerce/includes/class-woocommerce.php | 2 +- plugins/woocommerce/package.json | 2 +- plugins/woocommerce/readme.txt | 2 +- plugins/woocommerce/woocommerce.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/woocommerce/includes/class-woocommerce.php b/plugins/woocommerce/includes/class-woocommerce.php index 5970ed38b90..92a51a6bf57 100644 --- a/plugins/woocommerce/includes/class-woocommerce.php +++ b/plugins/woocommerce/includes/class-woocommerce.php @@ -29,7 +29,7 @@ final class WooCommerce { * * @var string */ - public $version = '6.4.0'; + public $version = '6.5.0'; /** * WooCommerce Schema version. diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index c619fef5231..ea6e35665c2 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -1,7 +1,7 @@ { "name": "woocommerce", "title": "WooCommerce", - "version": "6.4.0", + "version": "6.5.0", "homepage": "https://woocommerce.com/", "repository": { "type": "git", diff --git a/plugins/woocommerce/readme.txt b/plugins/woocommerce/readme.txt index f6abd5a8f09..63cfa9ee1dc 100644 --- a/plugins/woocommerce/readme.txt +++ b/plugins/woocommerce/readme.txt @@ -4,7 +4,7 @@ Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, d Requires at least: 5.7 Tested up to: 5.9 Requires PHP: 7.0 -Stable tag: 6.3.0 +Stable tag: 6.4.0 License: GPLv3 License URI: https://www.gnu.org/licenses/gpl-3.0.html diff --git a/plugins/woocommerce/woocommerce.php b/plugins/woocommerce/woocommerce.php index 62470bec195..e80a3779ba3 100644 --- a/plugins/woocommerce/woocommerce.php +++ b/plugins/woocommerce/woocommerce.php @@ -3,7 +3,7 @@ * Plugin Name: WooCommerce * Plugin URI: https://woocommerce.com/ * Description: An eCommerce toolkit that helps you sell anything. Beautifully. - * Version: 6.4.0-dev + * Version: 6.5.0-dev * Author: Automattic * Author URI: https://woocommerce.com * Text Domain: woocommerce From 630611b668a861769a8b9e725bfd2e8d07678f67 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 13 Apr 2022 12:37:17 +0800 Subject: [PATCH 288/386] Update .github/CONTRIBUTING.md Co-authored-by: Adrian Duffell <9312929+adrianduffell@users.noreply.github.com> --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index cbe382c842e..3e3b424196e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -41,7 +41,7 @@ If you have questions about the process to contribute code or want to discuss de - Make sure to write good and detailed commit messages (see [this post](https://chris.beams.io/posts/git-commit/) for more on this) and follow all the applicable sections of the pull request template. - Please avoid modifying the changelog directly or updating the .pot files. These will be updated by the WooCommerce team. -If you are contributing code to the (Javascript-driven) to Gutenberg blocks, note that it's developed in external package. +If you are contributing code to the (Javascript-driven) Gutenberg blocks, note that it's developed in an external package. - [Blocks](https://github.com/woocommerce/woocommerce-gutenberg-products-block) From 4760b10cc822d8685b6ad198ab19faf0ff426d60 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 13 Apr 2022 14:40:27 +0800 Subject: [PATCH 289/386] Remove admin .github folder --- .../.github/workflows/daily-e2e.yml | 57 ------------ .../.github/workflows/daily-php.yml | 40 --------- .../.github/workflows/e2e.yml | 50 ----------- .../.github/workflows/gh-pages.yml | 33 ------- .../.github/workflows/lint-php.yml | 25 ------ .../.github/workflows/test-php.yml | 87 ------------------- 6 files changed, 292 deletions(-) delete mode 100644 plugins/woocommerce-admin/.github/workflows/daily-e2e.yml delete mode 100644 plugins/woocommerce-admin/.github/workflows/daily-php.yml delete mode 100644 plugins/woocommerce-admin/.github/workflows/e2e.yml delete mode 100644 plugins/woocommerce-admin/.github/workflows/gh-pages.yml delete mode 100644 plugins/woocommerce-admin/.github/workflows/lint-php.yml delete mode 100644 plugins/woocommerce-admin/.github/workflows/test-php.yml diff --git a/plugins/woocommerce-admin/.github/workflows/daily-e2e.yml b/plugins/woocommerce-admin/.github/workflows/daily-e2e.yml deleted file mode 100644 index 5977439770c..00000000000 --- a/plugins/woocommerce-admin/.github/workflows/daily-e2e.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: 'Daily E2E Tests' - -on: - schedule: - - cron: '0 0 * * *' - -jobs: - e2e-tests: - runs-on: ubuntu-18.04 - strategy: - fail-fast: false - matrix: - wordpress: ['https://wordpress.org/latest.zip', 'https://wordpress.org/nightly-builds/wordpress-latest.zip'] - woocommerce: ['https://downloads.wordpress.org/plugin/woocommerce.zip', 'https://github.com/woocommerce/woocommerce/releases/download/nightly/woocommerce-trunk-nightly.zip'] - exclude: - - {'wordpress': 'https://wordpress.org/nightly-builds/wordpress-latest.zip', 'woocommerce': 'https://github.com/woocommerce/woocommerce/releases/download/nightly/woocommerce-trunk-nightly.zip'} - steps: - - name: Check out repository code - uses: actions/checkout@v2 - - name: Install PHP dependencies - run: | - composer install --no-dev - - name: Setup Node.js - uses: actions/setup-node@v2-beta - with: - node-version: '14' - - name: Install PNPM and install dependencies - uses: pnpm/action-setup@v2.2.1 - with: - version: ^6.24.2 - run_install: true - - name: Build - run: | - composer require wp-cli/i18n-command - pnpm run build:feature-config - pnpm run build - - name: Setup wp-env - env: - WP_ENV_CONFIG: '{ core: "${{ matrix.wordpress }}", plugins: [ ".", "${{ matrix.woocommerce }}" ] }' - run: | - pnpm -g i @wordpress/env - printf '%s\n' "$WP_ENV_CONFIG" > .wp-env-override.json - WP_ENV_TESTS_PORT=8084 wp-env start - wp-env run tests-cli "wp post create --post_type=page --post_status=publish --post_title='Ready' --post_content='E2E-tests.'" - - name: Test - env: - WC_E2E_SCREENSHOTS: 1 - run: | - pnpm exec wc-e2e test:e2e - - name: Archive e2e test screenshots - if: ${{ always() }} - uses: actions/upload-artifact@v2 - with: - name: e2e-screenshots - path: tests/e2e/screenshots - if-no-files-found: ignore - retention-days: 5 diff --git a/plugins/woocommerce-admin/.github/workflows/daily-php.yml b/plugins/woocommerce-admin/.github/workflows/daily-php.yml deleted file mode 100644 index 766333cd5d9..00000000000 --- a/plugins/woocommerce-admin/.github/workflows/daily-php.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: 'Daily PHP Tests' - -on: - schedule: - - cron: '0 0 * * *' - -jobs: - daily-test-php: - name: "Test PHP" - runs-on: ubuntu-18.04 - strategy: - fail-fast: false - matrix: - wordpress: ['latest', 'nightly'] - woocommerce: ['latest', 'nightly'] - exclude: - - {'wordpress': 'nightly', 'woocommerce': 'nightly'} - steps: - - name: Check out repository code - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2-beta - with: - node-version: '14' - - name: Install PNPM and install dependencies - uses: pnpm/action-setup@v2.2.1 - with: - version: ^6.24.2 - run_install: true - - name: Build - run: | - pnpm run build:feature-config - composer install --no-dev - shell: bash - - name: Run the PHP unit tests - env: - WP_VERSION: ${{ matrix.wordpress }} - WC_VERSION: ${{ matrix.woocommerce }} - run: pnpm run test:php - shell: bash diff --git a/plugins/woocommerce-admin/.github/workflows/e2e.yml b/plugins/woocommerce-admin/.github/workflows/e2e.yml deleted file mode 100644 index f6d0aec120f..00000000000 --- a/plugins/woocommerce-admin/.github/workflows/e2e.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: E2E tests -on: [pull_request] - -jobs: - e2e-tests: - runs-on: ubuntu-18.04 - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.7.0 - with: - access_token: ${{ github.token }} - - name: Check out repository code - uses: actions/checkout@v2 - - name: Setup PHP - uses: shivammathur/setup-php@2.9.0 - with: - php-version: '7.3' - - name: Install PHP dependencies - run: | - composer self-update 2.0.6 - composer i - - name: Setup Node.js - uses: actions/setup-node@v2-beta - with: - node-version: '14' - - name: Install PNPM and install dependencies - uses: pnpm/action-setup@v2.2.1 - with: - version: ^6.24.2 - run_install: true - - name: Build and run E2E Tests - env: - WC_E2E_SCREENSHOTS: 1 - E2E_SLACK_CHANNEL: ${{ secrets.E2E_SLACK_CHANNEL }} - E2E_SLACK_TOKEN: ${{ secrets.E2E_SLACK_TOKEN }} - WP_VERSION: '5.8.0' - run: | - composer require wp-cli/i18n-command - pnpm run build - pnpm run e2e:docker-up - sleep 10 - pnpm exec wc-e2e test:e2e - - name: Archive e2e test screenshots - if: ${{ always() }} - uses: actions/upload-artifact@v2 - with: - name: e2e-screenshots - path: tests/e2e/screenshots - if-no-files-found: ignore - retention-days: 5 diff --git a/plugins/woocommerce-admin/.github/workflows/gh-pages.yml b/plugins/woocommerce-admin/.github/workflows/gh-pages.yml deleted file mode 100644 index 74bc643012d..00000000000 --- a/plugins/woocommerce-admin/.github/workflows/gh-pages.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Publish docs -on: - push: - branches: - - main -jobs: - deploy: - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v2 - - - name: Setup Node.js - uses: actions/setup-node@v2-beta - with: - node-version: '14' - - - name: Install PNPM and install dependencies - uses: pnpm/action-setup@v2.2.1 - with: - version: ^6.24.2 - run_install: true - - - name: Build - run: | - pnpm run build - pnpm run docs - - - name: Deploy docs - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_branch: gh-pages - publish_dir: ./docs diff --git a/plugins/woocommerce-admin/.github/workflows/lint-php.yml b/plugins/woocommerce-admin/.github/workflows/lint-php.yml deleted file mode 100644 index 456b3ffba26..00000000000 --- a/plugins/woocommerce-admin/.github/workflows/lint-php.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Lint the PHP -on: [pull_request] - -jobs: - lint-php: - runs-on: ubuntu-latest - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.7.0 - with: - access_token: ${{ github.token }} - - name: Check out repository code - uses: actions/checkout@v2 - - name: Determine changed files - id: changed-files - uses: wyrihaximus/github-action-files-in-commit@v1.0 - - name: Setup PHP - uses: shivammathur/setup-php@2.9.0 - with: - php-version: 7.3 - - name: Lint the PHP - env: - CHANGED_FILES: ${{ steps.changed-files.outputs.files }} - run: bin/phpcs.sh - shell: bash diff --git a/plugins/woocommerce-admin/.github/workflows/test-php.yml b/plugins/woocommerce-admin/.github/workflows/test-php.yml deleted file mode 100644 index 4311062ce3a..00000000000 --- a/plugins/woocommerce-admin/.github/workflows/test-php.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: Run PHP unit tests -on: [pull_request] - -jobs: - test-php: - env: - WP_CORE_DIR: '/tmp/wordpress' - COMPOSER_DEV: 1 - - runs-on: ubuntu-18.04 - strategy: - fail-fast: false - matrix: - php: ['7.1', '7.2', '7.3'] - wordpress: ['5.4', '5.6'] - woocommerce: ['4.8.0', '4.9.1'] - phpunit: ['7.5.20'] - composer: ['2.0.6'] - include: - - php: '7.0' - wordpress: '5.6' - woocommerce: 'latest' - phpunit: '6.5.9' - composer: '1.10.19' - - php: '7.0' - wordpress: '5.6' - woocommerce: '4.9.1' - phpunit: '6.5.9' - composer: '2.0.6' - - php: '8.0' - wordpress: '5.6' - woocommerce: '5.1.0' - phpunit: '7.5.20' - composer: '2.0.6' - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.7.0 - with: - access_token: ${{ github.token }} - - name: Check out repository code - uses: actions/checkout@v2 - - name: Setup PHP - uses: shivammathur/setup-php@2.9.0 - with: - php-version: ${{matrix.php}} - tools: phpunit:${{matrix.phpunit}} - extensions: mysqli - - name: Setup Node.js - uses: actions/setup-node@v2-beta - with: - node-version: '14' - - name: Install PNPM and install dependencies - uses: pnpm/action-setup@v2.2.1 - with: - version: ^6.24.2 - run_install: true - - name: Set up the tests - env: - WP_VERSION: ${{matrix.wordpress}} - WC_VERSION: ${{matrix.woocommerce}} - PHP_UNIT: ${{matrix.phpunit}} - COMPOSER_VERSION: ${{matrix.composer}} - run: | - sudo /etc/init.d/mysql start - bash bin/ci/gh-install-wp-tests.sh wc_admin_test root 'root' localhost - cd "$WP_CORE_DIR/wp-content/plugins/woocommerce-admin/" - pnpm run build:feature-config - composer install - node --version - pnpm --version - timedatectl - - name: Add PHP8 Compatibility. - run: | - if [ "$(php -r "echo version_compare(PHP_VERSION,'8.0','>=');")" ]; then - cd "$WP_CORE_DIR/wp-content/plugins/woocommerce-admin/" - composer install - curl -L https://github.com/woocommerce/phpunit/archive/add-compatibility-with-php8-to-phpunit-7.zip -o /tmp/phpunit-7.5-fork.zip - unzip -d /tmp/phpunit-7.5-fork /tmp/phpunit-7.5-fork.zip - composer bin phpunit config --unset platform - composer bin phpunit config repositories.0 '{"type": "path", "url": "/tmp/phpunit-7.5-fork/phpunit-add-compatibility-with-php8-to-phpunit-7", "options": {"symlink": false}}' - composer bin phpunit require --dev -W phpunit/phpunit:@dev --ignore-platform-reqs - rm -rf ./vendor/phpunit/ - composer dump-autoload - fi - - name: Run the PHP unit tests - run: bin/phpunit.sh - shell: bash From 7bebe192e9d9d7e9c9b7e17ed84b61d99f7d96af Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Wed, 13 Apr 2022 15:13:15 +0800 Subject: [PATCH 290/386] Update logic to show only additional category when applicable --- .../fills/PaymentGatewaySuggestions/index.js | 89 +++++++++++++++---- .../payment-gateway-suggestions.scss | 4 + 2 files changed, 77 insertions(+), 16 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js index 032b85c327a..78a2c35dbac 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js @@ -7,6 +7,7 @@ import { OPTIONS_STORE_NAME, ONBOARDING_STORE_NAME, PAYMENT_GATEWAYS_STORE_NAME, + SETTINGS_STORE_NAME, } from '@woocommerce/data'; import { recordEvent } from '@woocommerce/tracks'; import { useMemo, useCallback, useEffect } from '@wordpress/element'; @@ -24,6 +25,7 @@ import { Setup, Placeholder as SetupPlaceholder } from './components/Setup'; import { Toggle } from './components/Toggle/Toggle'; import { WCPaySuggestion } from './components/WCPay'; import { getPluginSlug } from '~/utils'; +import { getCountryCode } from '~/dashboard/utils'; import './plugins/Bacs'; import './payment-gateway-suggestions.scss'; @@ -40,7 +42,10 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { paymentGatewaySuggestions, installedPaymentGateways, isResolving, + countryCode, } = useSelect( ( select ) => { + const { getSettings } = select( SETTINGS_STORE_NAME ); + const { general: settings = {} } = getSettings( 'general' ); return { getPaymentGateway: select( PAYMENT_GATEWAYS_STORE_NAME ) .getPaymentGateway, @@ -54,6 +59,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { paymentGatewaySuggestions: select( ONBOARDING_STORE_NAME ).getPaymentGatewaySuggestions(), + countryCode: getCountryCode( settings.woocommerce_default_country ), }; }, [] ); @@ -185,6 +191,37 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { return gateway; }, [ isResolving, query, paymentGateways ] ); + const isWCPayOrOtherCategory = useMemo( () => { + for ( const [ , gateway ] of paymentGateways.entries() ) { + if ( ! gateway.installed || gateway.needsSetup ) { + continue; + } + + if ( + gateway.plugins?.length === 1 && + gateway.plugins[ 0 ] === 'woocommerce-payments' + ) { + return true; + } + + if ( + gateway.category_other && + gateway.category_other.indexOf( countryCode ) !== -1 + ) { + return true; + } + } + return false; + }, [ countryCode, paymentGateways ] ); + + const isEligibleWCPay = + Array.from( paymentGateways.values() ).findIndex( ( gateway ) => { + return ( + gateway.plugins?.length === 1 && + gateway.plugins[ 0 ] === 'woocommerce-payments' + ); + } ) !== -1; + const [ wcPayGateway, offlineGateways, additionalGateways ] = useMemo( () => Array.from( paymentGateways.values() ) @@ -213,6 +250,22 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { wcPay.push( gateway ); } else if ( gateway.is_offline ) { offline.push( gateway ); + } else if ( gateway.enabled ) { + // Enabled gateways should be ignored. + } else if ( + isEligibleWCPay && + isWCPayOrOtherCategory + ) { + // If WCPay or "other" gateway is enabled in an WCPay-eligible country, only + // allow to list "additional" gateways or the ones without it defined. + if ( + gateway.category_additional && + gateway.category_additional.indexOf( + countryCode + ) !== -1 + ) { + additional.push( gateway ); + } } else { additional.push( gateway ); } @@ -221,11 +274,14 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { }, [ [], [], [] ] ), - [ paymentGateways ] + [ + countryCode, + isEligibleWCPay, + isWCPayOrOtherCategory, + paymentGateways, + ] ); - const isEligibleWCPay = !! wcPayGateway.length; - const trackSeeMore = () => { recordEvent( 'tasklist_payment_see_more', {} ); }; @@ -254,23 +310,24 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { const additionalSection = !! additionalGateways.length && ( - { __( 'See more', 'woocommerce' ) } - - + ! isWCPayOrOtherCategory && ( + + ) } > ); @@ -288,7 +345,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => {
{ ! paymentGateways.size && } - { isEligibleWCPay ? ( + { wcPayGateway.length ? ( <> Date: Wed, 13 Apr 2022 15:43:07 +0800 Subject: [PATCH 291/386] Add test --- .../PaymentGatewaySuggestions/test/index.js | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js index c2ca2617ff0..935c6f04c83 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js @@ -31,6 +31,8 @@ const paymentGatewaySuggestions = [ plugins: [ 'woocommerce-gateway-stripe' ], is_visible: true, recommendation_priority: 3, + category_other: [ 'US' ], + category_additional: [], }, { id: 'ppcp-gateway', @@ -41,6 +43,8 @@ const paymentGatewaySuggestions = [ 'http://localhost:8888/wp-content/plugins/woocommerce/assets/images/paypal.png', plugins: [ 'woocommerce-paypal-payments' ], is_visible: true, + category_other: [ 'US' ], + category_additional: [ 'US' ], }, { id: 'cod', @@ -212,6 +216,48 @@ describe( 'PaymentGatewaySuggestions', () => { expect( getByText( 'Finish setup' ) ).toBeInTheDocument(); } ); + + test( 'should show "category_additional" gateways only after WCPay is set up', () => { + const onComplete = jest.fn(); + const query = {}; + useSelect.mockImplementation( () => ( { + isResolving: false, + getPaymentGateway: jest.fn(), + paymentGatewaySuggestions, + installedPaymentGateways: [ + { + id: 'woocommerce_payments', + title: 'WooCommerce Payments', + plugins: [ 'woocommerce-payments' ], + is_visible: true, + needs_setup: false, + }, + ], + countryCode: 'US', + } ) ); + + const { container } = render( + + ); + + const paymentTitleElements = container.querySelectorAll( + '.woocommerce-task-payment__title' + ); + + const paymentTitles = Array.from( paymentTitleElements ).map( + ( e ) => e.textContent + ); + + expect( paymentTitles ).toEqual( [ + 'PayPal Payments', + 'Cash on delivery', + 'Direct bank transfer', + ] ); + } ); + test( 'should record event correctly when finish setup is clicked', () => { const onComplete = jest.fn(); const query = {}; From 9dcd761c2a7429ecf427aac2ba135fe85da09d21 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Wed, 13 Apr 2022 15:46:48 +0800 Subject: [PATCH 292/386] Add gateway categories for stripe, paypal, square, klarna payments, eway --- .../DefaultPaymentGateways.php | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php index 4b64d63b5ac..684a00f9fcb 100644 --- a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php +++ b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php @@ -86,10 +86,16 @@ class DefaultPaymentGateways { 'GB', 'US', 'PR', + 'UK', + 'HU', + 'SL', + 'ID', ) ), self::get_rules_for_cbd( false ), ), + 'category_other' => array( 'US', 'CA', 'UK', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DK', 'EE', 'ES', 'FI', 'FR', 'DE', 'GB', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SL', 'SE', 'MX', 'BR', 'AU', 'NZ', 'HK', 'JP', 'SG', 'ID', 'IN' ), + 'category_additional' => array(), 'recommendation_priority' => 3, ), array( @@ -134,10 +140,17 @@ class DefaultPaymentGateways { 'FR', 'IT', 'GB', + 'UK', + 'ES', + 'FI', + 'NO', + 'SE', ) ), self::get_rules_for_cbd( false ), ), + 'category_other' => array(), + 'category_additional' => array( 'UK', 'AT', 'BE', 'CH', 'DK', 'ES', 'FI', 'FR', 'DE', 'GB', 'IT', 'NL', 'NO', 'PL', 'SE' ), ), array( 'id' => 'mollie_wc_gateway_banktransfer', @@ -189,6 +202,8 @@ class DefaultPaymentGateways { ), self::get_rules_for_cbd( false ), ), + 'category_other' => array( 'US', 'CA', 'UK', 'AT', 'BE', 'BG', 'HR', 'CH', 'CY', 'CZ', 'DK', 'EE', 'ES', 'FI', 'FR', 'DE', 'GB', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SL', 'SE', 'MX', 'BR', 'AR', 'CL', 'CO', 'EC', 'PE', 'UY', 'VE', 'AU', 'NZ', 'HK', 'JP', 'SG', 'CN', 'ID', 'ZA', 'NG', 'GH' ), + 'category_additional' => array( 'US', 'CA', 'UK', 'AT', 'BE', 'BG', 'HR', 'CH', 'CY', 'CZ', 'DK', 'EE', 'ES', 'FI', 'FR', 'DE', 'GB', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SL', 'SE', 'MX', 'BR', 'AR', 'CL', 'CO', 'EC', 'PE', 'UY', 'VE', 'AU', 'NZ', 'HK', 'JP', 'SG', 'CN', 'ID', 'IN', 'ZA', 'NG', 'GH' ), ), array( 'id' => 'cod', @@ -360,6 +375,8 @@ class DefaultPaymentGateways { self::get_rules_for_countries( array( 'AU', 'NZ' ) ), self::get_rules_for_cbd( false ), ), + 'category_other' => array( 'AU', 'NZ' ), + 'category_additional' => array(), ), array( 'id' => 'square_credit_card', @@ -376,12 +393,14 @@ class DefaultPaymentGateways { self::get_rules_for_cbd( true ), ), array( - self::get_rules_for_countries( array( 'US', 'CA', 'JP', 'GB', 'AU', 'IE', 'FR', 'ES' ) ), + self::get_rules_for_countries( array( 'US', 'CA', 'JP', 'GB', 'AU', 'IE', 'FR', 'ES', 'UK', 'FI' ) ), self::get_rules_for_selling_venues( array( 'brick-mortar', 'brick-mortar-other' ) ), ), ), ), ), + 'category_other' => array( 'US', 'CA', 'JP', 'GB', 'AU', 'IE', 'FR', 'ES', 'UK', 'FI' ), + 'category_additional' => array(), ), ); } From c829aacfc71a454e97af8252032938856546dfd9 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 13 Apr 2022 16:57:58 +0800 Subject: [PATCH 293/386] Fix typescript annotations in react admin ./dashboard --- .../settings/general/store-address.tsx | 70 ++++++++++--------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.tsx b/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.tsx index a1bb9c58f35..8323309c442 100644 --- a/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.tsx +++ b/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.tsx @@ -2,13 +2,13 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { COUNTRIES_STORE_NAME, Country } from '@woocommerce/data'; +import { COUNTRIES_STORE_NAME, Country, Locale } from '@woocommerce/data'; import { decodeEntities } from '@wordpress/html-entities'; import { escapeRegExp } from 'lodash'; import { useEffect, useMemo, useState, useRef } from '@wordpress/element'; import { SelectControl, TextControl } from '@woocommerce/components'; import { Spinner } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; +import { useSelect, select as wpDataSelect } from '@wordpress/data'; /** * Internal dependencies @@ -21,7 +21,7 @@ const storeAddressFields = [ 'city', 'countryState', 'postCode', -]; +] as const; type Option = { key: string; label: string }; @@ -33,8 +33,8 @@ type Option = { key: string; label: string }; * @return {boolean} Field requirement. */ export function isAddressFieldRequired( - fieldName: string, - locale: unknown = {} + fieldName: keyof Locale, + locale: Locale = {} ): boolean { if ( locale[ fieldName ]?.hasOwnProperty( 'required' ) ) { return locale[ fieldName ]?.required as boolean; @@ -53,18 +53,19 @@ export function isAddressFieldRequired( * @param {Object} locale The store locale. * @return {Function} Validator function. */ -export function getStoreAddressValidator( locale = {} ) { +export function getStoreAddressValidator( locale: Locale = {} ) { /** * Form validator. * * @param {Object} values Keyed values of all fields in the form. * @return {Object} Key value of fields and error messages, { myField: 'This field is required' } */ - return ( values ) => { + return ( + values: Record< typeof storeAddressFields[ number ], string > + ) => { const errors: { [ key: string ]: string; } = {}; - if ( isAddressFieldRequired( 'address_1', locale ) && ! values.addressLine1.trim().length @@ -100,30 +101,33 @@ export function getStoreAddressValidator( locale = {} ) { * @return {Object} Select options, { value: 'US:GA', label: 'United States - Georgia' } */ export function getCountryStateOptions( countries: Country[] ) { - const countryStateOptions = countries.reduce( ( acc, country ) => { - if ( ! country.states.length ) { - acc.push( { - key: country.code, - label: decodeEntities( country.name ), + const countryStateOptions = countries.reduce( + ( acc: Option[], country ) => { + if ( ! country.states.length ) { + acc.push( { + key: country.code, + label: decodeEntities( country.name ), + } ); + + return acc; + } + + const countryStates = country.states.map( ( state ) => { + return { + key: country.code + ':' + state.code, + label: + decodeEntities( country.name ) + + ' — ' + + decodeEntities( state.name ), + }; } ); + acc.push( ...countryStates ); + return acc; - } - - const countryStates = country.states.map( ( state ) => { - return { - key: country.code + ':' + state.code, - label: - decodeEntities( country.name ) + - ' — ' + - decodeEntities( state.name ), - }; - } ); - - acc.push( ...countryStates ); - - return acc; - }, [] ); + }, + [] + ); return countryStateOptions; } @@ -195,9 +199,7 @@ export function useGetCountryStateAutofill( ): JSX.Element { const [ autofillCountry, setAutofillCountry ] = useState( '' ); const [ autofillState, setAutofillState ] = useState( '' ); - const isAutofillChange: { - current: boolean; - } = useRef(); + const isAutofillChange = useRef< boolean >(); // Sync the autofill fields on first render and the countryState value changes. useEffect( () => { @@ -242,7 +244,7 @@ export function useGetCountryStateAutofill( const isCountryAbbreviation = autofillCountry.length < 3; const isStateAbbreviation = autofillState.length < 3 && !! autofillState.match( /^[\w]+$/ ); - let filteredOptions = []; + let filteredOptions: Option[] = []; if ( autofillCountry.length && autofillState.length ) { filteredOptions = options.filter( ( option ) => @@ -337,7 +339,7 @@ export function StoreAddress( { hasFinishedResolution, countries, loadingCountries, - } = useSelect( ( select ) => { + } = useSelect( ( select: typeof wpDataSelect ) => { const { getLocale, getCountries, From 202f9df078d7e7da07fedbed574e843a595cebea Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Wed, 13 Apr 2022 19:12:25 +0800 Subject: [PATCH 294/386] Fix tests --- .../fills/PaymentGatewaySuggestions/index.js | 22 +++++++++++-------- .../PaymentGatewaySuggestions/test/index.js | 22 ++++++++++++------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js index 78a2c35dbac..c7422657572 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js @@ -191,7 +191,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { return gateway; }, [ isResolving, query, paymentGateways ] ); - const isWCPayOrOtherCategory = useMemo( () => { + const isWCPayOrOtherCategoryDoneSetup = useMemo( () => { for ( const [ , gateway ] of paymentGateways.entries() ) { if ( ! gateway.installed || gateway.needsSetup ) { continue; @@ -252,12 +252,12 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { offline.push( gateway ); } else if ( gateway.enabled ) { // Enabled gateways should be ignored. - } else if ( - isEligibleWCPay && - isWCPayOrOtherCategory - ) { + } else if ( ! isEligibleWCPay ) { + // When WCPay-ineligible, just show all gateways. + additional.push( gateway ); + } else if ( isWCPayOrOtherCategoryDoneSetup ) { // If WCPay or "other" gateway is enabled in an WCPay-eligible country, only - // allow to list "additional" gateways or the ones without it defined. + // allow to list "additional" gateways. if ( gateway.category_additional && gateway.category_additional.indexOf( @@ -266,7 +266,11 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { ) { additional.push( gateway ); } - } else { + } else if ( + gateway.category_other && + gateway.category_other.indexOf( countryCode ) !== -1 + ) { + // When nothing is set up and eligible for WCPay, only show "other" gateways. additional.push( gateway ); } @@ -277,7 +281,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { [ countryCode, isEligibleWCPay, - isWCPayOrOtherCategory, + isWCPayOrOtherCategoryDoneSetup, paymentGateways, ] ); @@ -317,7 +321,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { paymentGateways={ additionalGateways } markConfigured={ markConfigured } footerLink={ - ! isWCPayOrOtherCategory && ( + ! isWCPayOrOtherCategoryDoneSetup && ( ); + const EnableButton = () => ( + + ); + if ( ! hasSetup ) { if ( ! isEnabled ) { - return ( - - ); + return ; } return ; diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/Item.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/Item.js index aabd4e12b06..2c4ba03a8b9 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/Item.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/Item.js @@ -27,6 +27,7 @@ export const Item = ( { isRecommended, markConfigured, paymentGateway } ) => { requiredSettings, settingsUrl: manageUrl, is_local_partner: isLocalPartner, + external_link: externalLink, } = paymentGateway; const connectSlot = useSlot( @@ -39,7 +40,7 @@ export const Item = ( { isRecommended, markConfigured, paymentGateway } ) => { Boolean( setupSlot?.fills?.length ); const hasSetup = Boolean( - plugins.length || requiredSettings.length || hasFills + plugins.length || requiredSettings.length || hasFills || externalLink ); const showRecommendedRibbon = isRecommended && needsSetup; @@ -85,6 +86,7 @@ export const Item = ( { isRecommended, markConfigured, paymentGateway } ) => { isRecommended={ isRecommended } isLoading={ loading } markConfigured={ markConfigured } + externalLink={ externalLink } />
From dae3396854168ad3c5859b44b3c2b81e2408f0eb Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Wed, 13 Apr 2022 21:40:45 +0800 Subject: [PATCH 311/386] Add affirm, amazon pay, afterpay --- .../DefaultPaymentGateways.php | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php index caf5dc77833..221e0fc06a5 100644 --- a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php +++ b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php @@ -410,6 +410,43 @@ class DefaultPaymentGateways { 'category_other' => array( 'US', 'CA', 'JP', 'GB', 'AU', 'IE', 'FR', 'ES', 'FI' ), 'category_additional' => array(), ), + array( + 'id' => 'afterpay', + 'title' => __( 'Afterpay', 'woocommerce' ), + 'content' => __( 'Afterpay allows customers to receive products immediately and pay for purchases over four installments, always interest-free.', 'woocommerce' ), + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/afterpay.png', + 'plugins' => array( 'afterpay-gateway-for-woocommerce' ), + 'is_visible' => array( + self::get_rules_for_countries( array( 'US', 'CA' ) ), + ), + 'category_other' => array(), + 'category_additional' => array( 'US', 'CA' ), + ), + array( + 'id' => 'amazon_payments_advanced', + 'title' => __( 'Amazon Pay', 'woocommerce' ), + 'content' => __( 'Enable a familiar, fast checkout for hundreds of millions of active Amazon customers globally.', 'woocommerce' ), + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/amazonpay.png', + 'plugins' => array( 'woocommerce-gateway-amazon-payments-advanced' ), + 'is_visible' => array( + self::get_rules_for_countries( array( 'US', 'CA' ) ), + ), + 'category_other' => array(), + 'category_additional' => array( 'US', 'CA' ), + ), + array( + 'id' => 'affirm', + 'title' => __( 'Affirm', 'woocommerce' ), + 'content' => __( 'Affirm’s tailored Buy Now Pay Later programs remove price as a barrier, turning browsers into buyers, increasing average order value, and expanding your customer base.', 'woocommerce' ), + 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/affirm.png', + 'plugins' => array(), + 'external_link' => 'https://woocommerce.com/products/woocommerce-gateway-affirm', + 'is_visible' => array( + self::get_rules_for_countries( array( 'US', 'CA' ) ), + ), + 'category_other' => array(), + 'category_additional' => array( 'US', 'CA' ), + ), ); } From b37421fb981b121ba8956f46bdd06f69cf4a1358 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 13 Apr 2022 06:43:06 -0700 Subject: [PATCH 312/386] Do not call unnecessary create_tables() -- tables should be ready by the time we call the method --- plugins/woocommerce/src/Internal/Admin/CategoryLookup.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php b/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php index a3b97d911f3..ee16d188c40 100644 --- a/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php +++ b/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php @@ -62,8 +62,6 @@ class CategoryLookup { public function regenerate() { global $wpdb; - // Delete existing data and ensure schema is current. - \WC_Install::create_tables(); $wpdb->query( "TRUNCATE TABLE $wpdb->wc_category_lookup" ); $terms = get_terms( From 818a1dcff78bf63e92ddc13d82b808538b5d7748 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 13 Apr 2022 06:46:02 -0700 Subject: [PATCH 313/386] Make is_installing private --- plugins/woocommerce/includes/class-wc-install.php | 2 +- .../src/Admin/Features/OnboardingTasks/DeprecatedOptions.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index cb8d8153426..0d3fd7ec0d6 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -388,7 +388,7 @@ class WC_Install { * * @return bool */ - public static function is_installing() { + private static function is_installing() { return 'yes' === get_transient( 'wc_installing' ); } diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php index cfa45232665..60ffbadb185 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php @@ -30,9 +30,9 @@ class DeprecatedOptions { * @return string */ public static function get_deprecated_options( $pre_option, $option ) { - if ( WC_Install::is_installing() ) { + if ( defined( 'WC_INSTALLING' ) && WC_INSTALLING === true ) { return $pre_option; - }; + } $hidden = get_option( 'woocommerce_task_list_hidden_lists', array() ); switch ( $option ) { From 0c91a56f1f6af19db974bea6a6cb1dc448d89162 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 13 Apr 2022 11:20:26 -0300 Subject: [PATCH 314/386] Address minor PR feedback and UI issues --- packages/js/data/src/index.ts | 1 + packages/js/data/src/onboarding/utils.ts | 16 +++++ .../progress-header/progress-header.tsx | 13 ++-- .../client/tasks/task-list.tsx | 13 ++-- .../headers/section-header.scss | 11 +++- .../headers/section-header.tsx | 12 ++-- .../two-column-tasks/section-panel-title.tsx | 54 ++++++++++++++++ .../two-column-tasks/sectioned-task-list.scss | 14 +++- .../two-column-tasks/sectioned-task-list.tsx | 64 +++++-------------- .../client/two-column-tasks/task-list.tsx | 8 +-- .../OnboardingTasks/Tasks/StoreCreation.php | 6 +- 11 files changed, 129 insertions(+), 83 deletions(-) create mode 100644 packages/js/data/src/onboarding/utils.ts create mode 100644 plugins/woocommerce-admin/client/two-column-tasks/section-panel-title.tsx diff --git a/packages/js/data/src/index.ts b/packages/js/data/src/index.ts index 9e32bacc9c4..469218ce89f 100644 --- a/packages/js/data/src/index.ts +++ b/packages/js/data/src/index.ts @@ -34,6 +34,7 @@ export { withPluginsHydration } from './plugins/with-plugins-hydration'; export { ONBOARDING_STORE_NAME } from './onboarding'; export { withOnboardingHydration } from './onboarding/with-onboarding-hydration'; +export { getVisibleTasks } from './onboarding/utils'; export type { TaskType, TaskListType } from './onboarding/types'; export { USER_STORE_NAME } from './user'; diff --git a/packages/js/data/src/onboarding/utils.ts b/packages/js/data/src/onboarding/utils.ts new file mode 100644 index 00000000000..11f44817f72 --- /dev/null +++ b/packages/js/data/src/onboarding/utils.ts @@ -0,0 +1,16 @@ +/** + * Internal dependencies + */ +import { TaskType } from './types'; + +/** + * Filters tasks to only visible tasks, taking in account snoozed tasks. + */ +export function getVisibleTasks( tasks: TaskType[] ) { + const nowTimestamp = Date.now(); + return tasks.filter( + ( task ) => + ! task.isDismissed && + ( ! task.isSnoozed || task.snoozedUntil < nowTimestamp ) + ); +} diff --git a/plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.tsx b/plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.tsx index fa97af5ba9d..0c537ed207c 100644 --- a/plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.tsx +++ b/plugins/woocommerce-admin/client/task-lists/progress-header/progress-header.tsx @@ -4,7 +4,11 @@ import { __, sprintf } from '@wordpress/i18n'; import { useMemo } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; -import { ONBOARDING_STORE_NAME, TaskListType } from '@woocommerce/data'; +import { + getVisibleTasks, + ONBOARDING_STORE_NAME, + TaskListType, +} from '@woocommerce/data'; import { getSetting } from '@woocommerce/settings'; /** @@ -33,12 +37,7 @@ export const ProgressHeader: React.FC< ProgressHeaderProps > = ( { const finishedResolution = select( ONBOARDING_STORE_NAME ).hasFinishedResolution( 'getTaskList', [ taskListId ] ); - const nowTimestamp = Date.now(); - const visibleTasks = taskList?.tasks.filter( - ( task ) => - ! task.isDismissed && - ( ! task.isSnoozed || task.snoozedUntil < nowTimestamp ) - ); + const visibleTasks = getVisibleTasks( taskList?.tasks ); return { loading: ! finishedResolution, diff --git a/plugins/woocommerce-admin/client/tasks/task-list.tsx b/plugins/woocommerce-admin/client/tasks/task-list.tsx index 43601642724..25168c76142 100644 --- a/plugins/woocommerce-admin/client/tasks/task-list.tsx +++ b/plugins/woocommerce-admin/client/tasks/task-list.tsx @@ -6,7 +6,11 @@ import { useEffect, useRef, useState } from '@wordpress/element'; import { Card, CardHeader } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { Badge } from '@woocommerce/components'; -import { ONBOARDING_STORE_NAME, TaskListType } from '@woocommerce/data'; +import { + getVisibleTasks, + ONBOARDING_STORE_NAME, + TaskListType, +} from '@woocommerce/data'; import { recordEvent } from '@woocommerce/tracks'; import { Text, List, CollapsibleList } from '@woocommerce/experimental'; @@ -45,12 +49,7 @@ export const TaskList: React.FC< TaskListProps > = ( { }; } ); const prevQueryRef = useRef( query ); - const nowTimestamp = Date.now(); - const visibleTasks = tasks.filter( - ( task ) => - ! task.isDismissed && - ( ! task.isSnoozed || task.snoozedUntil < nowTimestamp ) - ); + const visibleTasks = getVisibleTasks( tasks ); const incompleteTasks = tasks.filter( ( task ) => ! task.isComplete && ! task.isDismissed diff --git a/plugins/woocommerce-admin/client/two-column-tasks/headers/section-header.scss b/plugins/woocommerce-admin/client/two-column-tasks/headers/section-header.scss index f8fc10b71ee..067d50e0266 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/headers/section-header.scss +++ b/plugins/woocommerce-admin/client/two-column-tasks/headers/section-header.scss @@ -1,10 +1,17 @@ .woocommerce-task-section-header__container { display: flex; - .illustration-background { + .woocommerce-task-header__illustration { max-width: 150px; + width: 34%; margin-left: auto; - margin-right: 44px; + margin-right: 7%; + display: flex; + align-items: center; + + .illustration-background { + max-width: 100%; + } } .woocommerce-task-header__contents p { diff --git a/plugins/woocommerce-admin/client/two-column-tasks/headers/section-header.tsx b/plugins/woocommerce-admin/client/two-column-tasks/headers/section-header.tsx index 99960e4c2d1..c84a892084e 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/headers/section-header.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/headers/section-header.tsx @@ -16,11 +16,13 @@ const SectionHeader: React.FC< Props > = ( { title, description, image } ) => {

{ title }

{ description }

- { +
+ { +
); }; diff --git a/plugins/woocommerce-admin/client/two-column-tasks/section-panel-title.tsx b/plugins/woocommerce-admin/client/two-column-tasks/section-panel-title.tsx new file mode 100644 index 00000000000..793a69a3cc8 --- /dev/null +++ b/plugins/woocommerce-admin/client/two-column-tasks/section-panel-title.tsx @@ -0,0 +1,54 @@ +/** + * External dependencies + */ +import { Badge } from '@woocommerce/components'; +import { TaskListSection, TaskType } from '@woocommerce/data'; +import { Icon, check } from '@wordpress/icons'; +import { Text } from '@woocommerce/experimental'; + +/** + * Internal dependencies + */ +import SectionHeader from './headers/section-header'; + +type SectionPanelTitleProps = { + section: TaskListSection; + active: boolean; + tasks: TaskType[]; +}; + +export const SectionPanelTitle: React.FC< SectionPanelTitleProps > = ( { + section, + active, + tasks, +} ) => { + if ( active ) { + return ( +
+
+ +
+
+ ); + } + + const uncompletedTasksCount = tasks.filter( + ( task ) => ! task.isComplete && section.tasks.includes( task.id ) + ).length; + + return ( + <> + + { section.title } + + { ! section.isComplete && ( + + ) } + { section.isComplete && ( +
+ +
+ ) } + + ); +}; diff --git a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.scss b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.scss index ce3501bdc21..b7352b0b022 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.scss +++ b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.scss @@ -11,10 +11,18 @@ background: #fff; border: 1px solid $gray-200; + &.is-opened { + padding-bottom: 0; + } + .components-panel__body-title { margin-bottom: 0; border-bottom: 1px solid #e0e0e0; + &:hover { + border-bottom: 1px solid #e0e0e0; + } + > .components-button { font-size: 20px; font-weight: 400; @@ -51,16 +59,16 @@ padding-top: $gap; padding-bottom: $gap; - &.task-disabled { + &.is-disabled { pointer-events: none; } - &:not(.complete) .woocommerce-task-list__item-before .woocommerce-task__icon { + &:not(.is-complete) .woocommerce-task-list__item-before .woocommerce-task__icon { border-color: $gray-300; } } - .woocommerce-task-list__item.complete .woocommerce-task__icon { + .woocommerce-task-list__item.is-complete .woocommerce-task__icon { background-color: $alert-green; } diff --git a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx index 32866665f12..9fb472401da 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx @@ -5,17 +5,15 @@ import { __ } from '@wordpress/i18n'; import { useEffect, useRef, useState } from '@wordpress/element'; import { Panel, PanelBody, PanelRow } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; -import { Icon, check } from '@wordpress/icons'; import { updateQueryString } from '@woocommerce/navigation'; -import { Badge } from '@woocommerce/components'; import { OPTIONS_STORE_NAME, ONBOARDING_STORE_NAME, TaskType, - TaskListSection, + getVisibleTasks, } from '@woocommerce/data'; import { recordEvent } from '@woocommerce/tracks'; -import { List, TaskItem, Text } from '@woocommerce/experimental'; +import { List, TaskItem } from '@woocommerce/experimental'; import classnames from 'classnames'; /** @@ -25,8 +23,8 @@ import '../tasks/task-list.scss'; import './sectioned-task-list.scss'; import TaskListCompleted from './completed'; import { TaskListProps } from '~/tasks/task-list'; -import SectionHeader from './headers/section-header'; import { ProgressHeader } from '~/task-lists/progress-header'; +import { SectionPanelTitle } from './section-panel-title'; type PanelBodyProps = Omit< PanelBody.Props, 'title' | 'onToggle' > & { title: string | React.ReactNode | undefined; @@ -61,12 +59,7 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( { const prevQueryRef = useRef( query ); - const nowTimestamp = Date.now(); - const visibleTasks = tasks.filter( - ( task ) => - ! task.isDismissed && - ( ! task.isSnoozed || task.snoozedUntil < nowTimestamp ) - ); + const visibleTasks = getVisibleTasks( tasks ); const recordTaskListView = () => { if ( query.task ) { @@ -93,7 +86,7 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( { } }, [ query ] ); - const onDismissTask = ( taskId: string, onDismiss?: () => void ) => { + const onDismissTask = ( taskId: string ) => { dismissTask( taskId ); createNotice( 'success', __( 'Task dismissed' ), { actions: [ @@ -103,10 +96,6 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( { }, ], } ); - - if ( onDismiss ) { - onDismiss(); - } }; const hideTasks = () => { @@ -153,36 +142,6 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( { ); }; - const getPanelTitle = ( section: TaskListSection ) => { - if ( openPanel === section.id ) { - return ( -
-
- -
-
- ); - } - const completedTasksCount = tasks.filter( - ( task ) => ! task.isComplete && section.tasks.includes( task.id ) - ).length; - return ( - <> - - { section.title } - - { ! section.isComplete && ( - - ) } - { section.isComplete && ( -
- -
- ) } - - ); - }; - if ( ! visibleTasks.length ) { return
; } @@ -213,7 +172,13 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( { { ( sections || [] ).map( ( section ) => ( + } opened={ openPanel === section.id } onToggle={ ( isOpen: boolean ) => { if ( ! isOpen && openPanel === section.id ) { @@ -231,8 +196,9 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( { const className = classnames( 'woocommerce-task-list__item', { - complete: task.isComplete, - 'task-disabled': + 'is-complete': + task.isComplete, + 'is-disabled': task.isDisabled, } ); diff --git a/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx b/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx index 0bb00f0751a..a0ed8cdd85d 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx @@ -16,6 +16,7 @@ import { ONBOARDING_STORE_NAME, TaskType, useUserPreferences, + getVisibleTasks, } from '@woocommerce/data'; import { recordEvent } from '@woocommerce/tracks'; import { List, TaskItem } from '@woocommerce/experimental'; @@ -63,12 +64,7 @@ export const TaskList: React.FC< TaskListProps > = ( { const prevQueryRef = useRef( query ); - const nowTimestamp = Date.now(); - const visibleTasks = tasks.filter( - ( task ) => - ! task.isDismissed && - ( ! task.isSnoozed || task.snoozedUntil < nowTimestamp ) - ); + const visibleTasks = getVisibleTasks( tasks ); const recordTaskListView = () => { if ( query.task ) { diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/StoreCreation.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/StoreCreation.php index 93cc08b1e33..03a178779a5 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/StoreCreation.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/StoreCreation.php @@ -25,10 +25,8 @@ class StoreCreation extends Task { * @return string */ public function get_title() { - if ( $this->is_complete() ) { - /* translators: Store name */ - return sprintf( __( 'You created %s', 'woocommerce' ), get_bloginfo( 'name' ) ); - } + /* translators: Store name */ + return sprintf( __( 'You created %s', 'woocommerce' ), get_bloginfo( 'name' ) ); } /** From 60e288c84920cd2201f7d6934c70b2a75685af95 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 13 Apr 2022 12:15:05 -0300 Subject: [PATCH 315/386] Update illustrations and fix potential update delay --- .../two-column-tasks/section-panel-title.tsx | 7 +++---- .../client/two-column-tasks/style.scss | 9 ++++++--- .../client/two-column-tasks/task-list.tsx | 2 +- .../task_list/basics-section-illustration.png | Bin 63845 -> 16174 bytes .../task_list/expand-section-illustration.png | Bin 48357 -> 16097 bytes .../task_list/sales-section-illustration.png | Bin 105347 -> 29679 bytes 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/plugins/woocommerce-admin/client/two-column-tasks/section-panel-title.tsx b/plugins/woocommerce-admin/client/two-column-tasks/section-panel-title.tsx index 793a69a3cc8..54c0b8dec77 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/section-panel-title.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/section-panel-title.tsx @@ -35,16 +35,15 @@ export const SectionPanelTitle: React.FC< SectionPanelTitleProps > = ( { const uncompletedTasksCount = tasks.filter( ( task ) => ! task.isComplete && section.tasks.includes( task.id ) ).length; + const isComplete = section.isComplete || uncompletedTasksCount === 0; return ( <> { section.title } - { ! section.isComplete && ( - - ) } - { section.isComplete && ( + { ! isComplete && } + { isComplete && (
diff --git a/plugins/woocommerce-admin/client/two-column-tasks/style.scss b/plugins/woocommerce-admin/client/two-column-tasks/style.scss index a6f1dc686a1..16aa121bdcd 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/style.scss +++ b/plugins/woocommerce-admin/client/two-column-tasks/style.scss @@ -103,7 +103,7 @@ margin: 0 auto; justify-content: space-between; - ul li.complete .woocommerce-task-list__item-title { + ul li.is-complete .woocommerce-task-list__item-title { font-weight: 600; color: $gray-600; } @@ -178,10 +178,13 @@ height: 100%; } } - .woocommerce-task-list__item:not(.complete) .woocommerce-task__icon { + .woocommerce-task-list__item:not(.is-complete) .woocommerce-task__icon { border: 1px solid var(--wp-admin-theme-color); background: transparent; } + .woocommerce-task-list__item.is-complete:not(.complete) .woocommerce-task__icon { + border: none; + } .woocommerce-task-list__item-before { display: block; @@ -203,7 +206,7 @@ } @for $i from 1 through 10 { - .woocommerce-task-list__item:not(.complete).index-#{$i} .woocommerce-task__icon::after { + .woocommerce-task-list__item:not(.is-complete).index-#{$i} .woocommerce-task__icon::after { content: '#{$i}'; @extend .numbered-circle; color: var(--wp-admin-theme-color); diff --git a/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx b/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx index a0ed8cdd85d..9f9c4f468e2 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/task-list.tsx @@ -291,7 +291,7 @@ export const TaskList: React.FC< TaskListProps > = ( { const className = classnames( 'woocommerce-task-list__item index-' + index, { - complete: task.isComplete, + 'is-complete': task.isComplete, 'is-active': task.id === activeTaskId, } ); diff --git a/plugins/woocommerce/assets/images/task_list/basics-section-illustration.png b/plugins/woocommerce/assets/images/task_list/basics-section-illustration.png index f598d83ba4306c27ecb1bb2d4b0c1fb13dbf8199..988a281048f1e0f4f41435d605bf6cc027d41c17 100644 GIT binary patch literal 16174 zcmV+}KheO6P)nr@bK{O@bL8Y_3iKP^Yrx4(a|U`HSO;1>FewE`1s=E z?E-J35ET~o_xJVp_Yf2o@9^;CWqCnu%w^LPI;nutwA8}%} zz0$#+_4fAE#iP=~q21KF0duOjxw>YzzuDQ_0CA_;%CFbX!Rh7S-O#e**uUuH-u>#q zJ)EQ4+uhsL%Rrx|+0n(^&92SCvE<#z?P)B~zMIp_zSYE^xuuKy`S!@UsLH*p=HlGs z;M(Ke*WT94zresrID4F-Pg}*vd+!9l^-7<;M&yw?Wjaa zPSD4>;MvaN+|#t5gjuh)>EqcoB^VJ42LJix{N}h2S$##Ksl~LP{p6MY*Fx0Exh^m< zKQ1Bv`|@hIzPqcIPEl9a*xCN_%1NcHUtnQXKQF<;#8j-aOHEP1v7SjbD7vMBO{cCZ z9TCB=m1}Kq^1-6_&90!JppcM|wxD^Cl$QC{zRI+XfPsKfs<8dkG#wQUvDDrE;&|Sr ze*WEEzptCs=R$S{@PE;%iYk>(4V5C zMoWzT;A^CZXzAtQtdw}eh+)^9U3XMK|Mu6ry}t3qf$O(vw2x^ulDrRltr1#&D=>WB zqH9-LmuW{asHw=8nZ7wagfTfge}S*UtAgK8Nd5BOmTpq$^!54R-(;w)^4O|zb*EQw zZ}s53T7re-b#eUh?(gZ`e89%+fi%P6<(tmZjmFOEmy=(YnoMM3^18XCsi;q&qtBLx zcYJ^Bl0QR@k@WEAouH#ZbayL_xZc2_NA$ff0000bbW%=J00ajM4}_qgz@_11fgz)t zN$~!moZ!I3b2Nt0=fY^PHs3_ZpbpBz&j0{B#7RU!RCwC#+`&%UKoEdok42H15k|nfCF1fG+Vz2t0r;J3ON!owvQaWAjJr&lT_!J-P!l zy3lm!zv#ZF+q!w9bX>VB-~H{hKS$m9KtHeZJ~!Lm9#`y8Sw@|C=;`^HUg775^Lns@ z2aNUCbRG100qC-~kFofF2u0TmyL`YadK(N5=W%;ax9~I4kL&W{{C2U5$6Ks#uYdG= z0Daju2k_FxdKE`_peTy3&X&;9#eEKFz$y5Gdmku@Pbs?IoRbf>mTLZbD|%Qk_2Gt?K+FjskPpck%WTqx<0i9 zfZlAEq+QhMsc|0R)Z!)Ru3Z)y^4=OJ7hmWGP8ccyEq{fRi>`J1CJj%y@5i5z{0ji+ zi*rf1_-Aa`+xzVLI!zex4oe;+n>x?S`tfO0@~@!#H%Vx%J!Hy*O&X?UT~(X9$XuR` zm~vkic2R)&{@&z4?aCy;o~@j#_D;F)>z-Ybmt(#+`Nq`&;zLyyS(X-cGvbNM?7rlB zA(sWnnB+E1Ee2I;wp?xzE4LXn<@(`AA;24(+`1a^(V5qBOWznyt~+))064inoZPw! zgTlPg{Bu?hZr^&!dCM`}KlTsLr1bnmH*+OcQ#j^bH>h=EIn z7k0fJBLs||@;c|@uUTv)E*MD8(e(q)|5h_^%pXW@o$Z2Wp)qL@bSLInf{g_0a%jr`yW}VL zm)~6|U$!H8mE#jOQbTIwlN1{z24V1i^55ZG-!E@Yyd!q}7kx{j(6CvXRNzrFV?xRD4(Ry2gUa^2*N3JoUH6JOt*@(u%!%GsZL6 z!Y7QRUmlptmvba5b6FJeA>_KWh7OXw3X{Fu?`x($-4%I^%pZwGX&AYpJsZOO*eeb+ zC`Ta|Z#I!>IXk!iA`g>YGZaOe>lS$-M<9P4*}nZHzO)~=%67kxzip?EKjwJjb{V

|Z;UyL`Dp;TsVKq)KZm(u2FYHr13d`@AA|h) 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

{ showRecommendedRibbon && ( diff --git a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php index da84a6c4afb..d6ce06fb493 100644 --- a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php +++ b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php @@ -25,7 +25,8 @@ class DefaultPaymentGateways { 'id' => 'payfast', 'title' => __( 'PayFast', 'woocommerce' ), 'content' => __( 'The PayFast extension for WooCommerce enables you to accept payments by Credit Card and EFT via one of South Africa’s most popular payment gateways. No setup fees or monthly subscription costs. Selecting this extension will configure your store to use South African rands as the selected currency.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/payfast.png', + 'image' => WC()->plugin_url() . '/assets/images/payfast.png', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/payfast.png', 'plugins' => array( 'woocommerce-payfast-gateway' ), 'is_visible' => array( self::get_rules_for_countries( array( 'ZA', 'GH', 'NG' ) ), @@ -38,7 +39,8 @@ class DefaultPaymentGateways { 'id' => 'stripe', 'title' => __( ' Stripe', 'woocommerce' ), 'content' => __( 'Accept debit and credit cards in 135+ currencies, methods such as Alipay, and one-touch checkout with Apple Pay.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/stripe.png', + 'image' => WC()->plugin_url() . '/assets/images/stripe.png', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/stripe.png', 'plugins' => array( 'woocommerce-gateway-stripe' ), 'is_visible' => array( // https://stripe.com/global. @@ -55,7 +57,8 @@ class DefaultPaymentGateways { 'id' => 'paystack', 'title' => __( 'Paystack', 'woocommerce' ), 'content' => __( 'Paystack helps African merchants accept one-time and recurring payments online with a modern, safe, and secure payment gateway.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/paystack.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/paystack.png', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/paystack.png', 'plugins' => array( 'woo-paystack' ), 'is_visible' => array( self::get_rules_for_countries( array( 'ZA', 'GH', 'NG' ) ), @@ -68,7 +71,8 @@ class DefaultPaymentGateways { 'id' => 'kco', 'title' => __( 'Klarna Checkout', 'woocommerce' ), 'content' => __( 'Choose the payment that you want, pay now, pay later or slice it. No credit card numbers, no passwords, no worries.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/klarna.png', + 'image' => WC()->plugin_url() . '/assets/images/klarna-black.png', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/klarna.png', 'plugins' => array( 'klarna-checkout-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( array( 'SE', 'FI', 'NO' ) ), @@ -81,7 +85,8 @@ class DefaultPaymentGateways { 'id' => 'klarna_payments', 'title' => __( 'Klarna Payments', 'woocommerce' ), 'content' => __( 'Choose the payment that you want, pay now, pay later or slice it. No credit card numbers, no passwords, no worries.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/klarna.png', + 'image' => WC()->plugin_url() . '/assets/images/klarna-black.png', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/klarna.png', 'plugins' => array( 'klarna-payments-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( @@ -96,7 +101,8 @@ class DefaultPaymentGateways { 'id' => 'mollie_wc_gateway_banktransfer', 'title' => __( 'Mollie', 'woocommerce' ), 'content' => __( 'Effortless payments by Mollie: Offer global and local payment methods, get onboarded in minutes, and supported in your language.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/mollie.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/mollie.svg', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/mollie.png', 'plugins' => array( 'mollie-payments-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( @@ -110,7 +116,8 @@ class DefaultPaymentGateways { 'id' => 'woo-mercado-pago-custom', 'title' => __( 'Mercado Pago Checkout Pro & Custom', 'woocommerce' ), 'content' => __( 'Accept credit and debit cards, offline (cash or bank transfer) and logged-in payments with money in Mercado Pago. Safe and secure payments with the leading payment processor in LATAM.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/mercadopago.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/mercadopago.png', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/mercadopago.png', 'plugins' => array( 'woocommerce-mercadopago' ), 'is_visible' => array( self::get_rules_for_countries( array( 'AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY' ) ), @@ -124,7 +131,8 @@ class DefaultPaymentGateways { 'id' => 'ppcp-gateway', 'title' => __( 'PayPal Payments', 'woocommerce' ), 'content' => __( "Safe and secure payments using credit cards or your customer's PayPal account.", 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/paypal.png', + 'image' => WC()->plugin_url() . '/assets/images/paypal.png', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/paypal.png', 'plugins' => array( 'woocommerce-paypal-payments' ), 'is_visible' => array( (object) array( @@ -141,7 +149,8 @@ class DefaultPaymentGateways { 'id' => 'cod', 'title' => __( 'Cash on delivery', 'woocommerce' ), 'content' => __( 'Take payments in cash upon delivery.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/cod.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/cod.svg', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/cod.png', 'is_visible' => array( self::get_rules_for_cbd( false ), ), @@ -151,7 +160,8 @@ class DefaultPaymentGateways { 'id' => 'bacs', 'title' => __( 'Direct bank transfer', 'woocommerce' ), 'content' => __( 'Take payments via bank transfer.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/bacs.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/bacs.svg', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/bacs.png', 'is_visible' => array( self::get_rules_for_cbd( false ), ), @@ -165,6 +175,7 @@ class DefaultPaymentGateways { 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', 'plugins' => array( 'woocommerce-payments' ), 'description' => 'With WooCommerce Payments, you can securely accept major cards, Apple Pay, and payments in over 100 currencies. Track cash flow and manage recurring revenue directly from your store’s dashboard - with no setup costs or monthly fees.', 'is_visible' => array( @@ -207,6 +218,7 @@ class DefaultPaymentGateways { 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', 'plugins' => array( 'woocommerce-payments' ), 'description' => 'With WooCommerce Payments, you can securely accept major cards, Apple Pay, and payments in over 100 currencies. Track cash flow and manage recurring revenue directly from your store’s dashboard - with no setup costs or monthly fees.', 'is_visible' => array( @@ -241,6 +253,7 @@ class DefaultPaymentGateways { 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', 'plugins' => array( 'woocommerce-payments' ), 'description' => 'With WooCommerce Payments, you can securely accept major cards, Apple Pay, and payments in over 100 currencies – with no setup costs or monthly fees – and you can now accept in-person payments with the Woo mobile app.', 'is_visible' => array( @@ -271,7 +284,8 @@ class DefaultPaymentGateways { 'id' => 'razorpay', 'title' => __( 'Razorpay', 'woocommerce' ), 'content' => __( 'The official Razorpay extension for WooCommerce allows you to accept credit cards, debit cards, netbanking, wallet, and UPI payments.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/razorpay.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/razorpay.svg', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/razorpay.png', 'plugins' => array( 'woo-razorpay' ), 'is_visible' => array( (object) array( @@ -288,7 +302,8 @@ class DefaultPaymentGateways { 'id' => 'payubiz', 'title' => __( 'PayU for WooCommerce', 'woocommerce' ), 'content' => __( 'Enable PayU’s exclusive plugin for WooCommerce to start accepting payments in 100+ payment methods available in India including credit cards, debit cards, UPI, & more!', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/payu.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/payu.svg', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/payu.png', 'plugins' => array( 'payu-india' ), 'is_visible' => array( (object) array( @@ -305,7 +320,8 @@ class DefaultPaymentGateways { 'id' => 'eway', 'title' => __( 'Eway', 'woocommerce' ), 'content' => __( 'The Eway extension for WooCommerce allows you to take credit card payments directly on your store without redirecting your customers to a third party site to make payment.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/eway.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/eway.png', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/eway.png', 'plugins' => array( 'woocommerce-gateway-eway' ), 'is_visible' => array( self::get_rules_for_countries( array( 'AU', 'NZ' ) ), @@ -318,7 +334,8 @@ class DefaultPaymentGateways { 'id' => 'square_credit_card', 'title' => __( 'Square', 'woocommerce' ), 'content' => __( 'Securely accept credit and debit cards with one low rate, no surprise fees (custom rates available). Sell online and in store and track sales and inventory in one place.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/square.png', + 'image' => WC()->plugin_url() . '/assets/images/square-black.png', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/square.png', 'plugins' => array( 'woocommerce-square' ), 'is_visible' => array( (object) array( @@ -343,6 +360,7 @@ class DefaultPaymentGateways { 'title' => __( 'Afterpay', 'woocommerce' ), 'content' => __( 'Afterpay allows customers to receive products immediately and pay for purchases over four installments, always interest-free.', 'woocommerce' ), 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/afterpay.png', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/afterpay.png', 'plugins' => array( 'afterpay-gateway-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( array( 'US', 'CA' ) ), @@ -355,6 +373,7 @@ class DefaultPaymentGateways { 'title' => __( 'Amazon Pay', 'woocommerce' ), 'content' => __( 'Enable a familiar, fast checkout for hundreds of millions of active Amazon customers globally.', 'woocommerce' ), 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/amazonpay.png', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/amazonpay.png', 'plugins' => array( 'woocommerce-gateway-amazon-payments-advanced' ), 'is_visible' => array( self::get_rules_for_countries( array( 'US', 'CA' ) ), @@ -367,6 +386,7 @@ class DefaultPaymentGateways { 'title' => __( 'Affirm', 'woocommerce' ), 'content' => __( 'Affirm’s tailored Buy Now Pay Later programs remove price as a barrier, turning browsers into buyers, increasing average order value, and expanding your customer base.', 'woocommerce' ), 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/affirm.png', + 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/affirm.png', 'plugins' => array(), 'external_link' => 'https://woocommerce.com/products/woocommerce-gateway-affirm', 'is_visible' => array( From 1a129a235409e4fd1b94a53e37cccaa7e22db714 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 14 Apr 2022 10:52:12 +0800 Subject: [PATCH 344/386] Change recommended ribbon to Pill --- packages/js/components/CHANGELOG.md | 1 + packages/js/components/src/pill/pill.js | 5 +++-- .../components/List/Item.js | 20 +++++++++++++------ .../components/List/List.scss | 9 +++++++++ .../PaymentGatewaySuggestions/test/index.js | 2 +- 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/js/components/CHANGELOG.md b/packages/js/components/CHANGELOG.md index c0d21596f66..e7782c11e57 100644 --- a/packages/js/components/CHANGELOG.md +++ b/packages/js/components/CHANGELOG.md @@ -3,6 +3,7 @@ - Fix documentation for `TableCard` component - Update dependency `@wordpress/hooks` to ^3.5.0 - Update dependency `@wordpress/icons` to ^8.1.0 +- Add `className` prop for Pill component. #32605 # 10.0.0 - Replace deprecated wp.compose.withState with wp.element.useState. #8338 diff --git a/packages/js/components/src/pill/pill.js b/packages/js/components/src/pill/pill.js index 662bb11fa45..33f91cf9bdf 100644 --- a/packages/js/components/src/pill/pill.js +++ b/packages/js/components/src/pill/pill.js @@ -2,16 +2,17 @@ * External dependencies */ import { createElement } from '@wordpress/element'; +import classnames from 'classnames'; /** * Internal dependencies */ import { Text } from '../experimental'; -export function Pill( { children } ) { +export function Pill( { children, className } ) { return ( { const hasSetup = Boolean( plugins.length || requiredSettings.length || hasFills || externalLink ); - const showRecommendedRibbon = isRecommended && needsSetup; + const showRecommended = isRecommended && needsSetup; const classes = classnames( 'woocommerce-task-payment', @@ -61,11 +63,17 @@ export const Item = ( { isRecommended, markConfigured, paymentGateway } ) => { {
- { showRecommendedRibbon && ( - - ) } - { title } + { title } + { showRecommended && ( + + { isLocalPartner + ? __( 'Local Partner', 'woocommerce' ) + : __( 'Recommended', 'woocommerce' ) } + + ) } { isInstalled && needsSetup && !! plugins.length && ( ) } diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.scss b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.scss index cb524da6945..da05a93d13e 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.scss +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/List.scss @@ -42,6 +42,15 @@ color: $studio-gray-80; margin-top: 0; margin-bottom: $gap-smaller; + + .woocommerce-pill { + margin-left: 8px; + + &.pill-green { + color: #008a20; + border-color: #008a20; + } + } } .woocommerce-task-payment__content { diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js index 48adc770f71..c1d710c865b 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/test/index.js @@ -146,7 +146,7 @@ describe( 'PaymentGatewaySuggestions', () => { ); const paymentTitleElements = container.querySelectorAll( - '.woocommerce-task-payment__title' + '.woocommerce-task-payment__title > span:first-child' ); const paymentTitles = Array.from( paymentTitleElements ).map( From 942fa8d5de30c47044524a15665daf23b2291ff6 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 14 Apr 2022 10:54:03 +0800 Subject: [PATCH 345/386] More e2e --- packages/js/admin-e2e-tests/src/specs/tasks/payment.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts index ba4b081fe91..9cbe436167b 100644 --- a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts +++ b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts @@ -57,6 +57,7 @@ const testAdminPaymentSetupTask = () => { await takeScreenshotFor( 'Payment setup task show other payment methods' ); + await waitForTimeout( 500 ); await paymentsSetup.goToPaymentMethodSetup( 'bacs' ); await bankTransferSetup.saveAccountDetails( { accountNumber: '1234', @@ -81,6 +82,7 @@ const testAdminPaymentSetupTask = () => { await homeScreen.clickOnTaskList( 'Set up payments' ); await paymentsSetup.isDisplayed(); await paymentsSetup.showOtherPaymentMethods(); + await waitForTimeout( 500 ); await takeScreenshotFor( 'Payment setup task show other payment methods' ); From bc76636f3f1400731ef2e9eb36e5dcd313e51bed Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 14 Apr 2022 11:42:32 +0800 Subject: [PATCH 346/386] Try fix e2e again --- packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts | 6 ++++-- packages/js/admin-e2e-tests/src/specs/tasks/payment.ts | 9 ++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts b/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts index f2fbe95d01b..ea711a63e85 100644 --- a/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts +++ b/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts @@ -21,8 +21,10 @@ export class PaymentsSetup extends BasePage { await waitForElementByText( 'h1', 'Set up payments' ); } - async closeHelpModal(): Promise< void > { - await this.clickButtonWithText( 'Got it' ); + async possiblyCloseHelpModal(): Promise< void > { + try { + await this.clickButtonWithText( 'Got it' ); + } catch ( e ) {} } async showOtherPaymentMethods(): Promise< void > { diff --git a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts index 9cbe436167b..8e769e2dec1 100644 --- a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts +++ b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts @@ -48,15 +48,12 @@ const testAdminPaymentSetupTask = () => { it( 'Can visit the payment setup task from the homescreen if the setup wizard has been skipped', async () => { await homeScreen.clickOnTaskList( 'Set up payments' ); - await paymentsSetup.closeHelpModal(); + await paymentsSetup.possiblyCloseHelpModal(); await paymentsSetup.isDisplayed(); } ); it( 'Saving valid bank account transfer details enables the payment method', async () => { await paymentsSetup.showOtherPaymentMethods(); - await takeScreenshotFor( - 'Payment setup task show other payment methods' - ); await waitForTimeout( 500 ); await paymentsSetup.goToPaymentMethodSetup( 'bacs' ); await bankTransferSetup.saveAccountDetails( { @@ -80,12 +77,10 @@ const testAdminPaymentSetupTask = () => { await homeScreen.isDisplayed(); await waitForTimeout( 1000 ); await homeScreen.clickOnTaskList( 'Set up payments' ); + await paymentsSetup.possiblyCloseHelpModal(); await paymentsSetup.isDisplayed(); await paymentsSetup.showOtherPaymentMethods(); await waitForTimeout( 500 ); - await takeScreenshotFor( - 'Payment setup task show other payment methods' - ); await paymentsSetup.enableCashOnDelivery(); await waitForTimeout( 1500 ); expect( await settings.paymentMethodIsEnabled( 'cod' ) ).toBe( From afc8217bcc6017204ca47f962e1fb0e728cb9c44 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 14 Apr 2022 11:48:52 +0800 Subject: [PATCH 347/386] Remove woocommerce-admin/tests --- plugins/woocommerce-admin/tests/bootstrap.php | 217 ------------------ .../tests/e2e/config/default.json | 98 -------- .../tests/e2e/config/env.setup.js | 5 - .../tests/e2e/config/jest-puppeteer.config.js | 10 - .../tests/e2e/config/jest.config.js | 23 -- .../tests/e2e/docker/initialize.sh | 17 -- .../activate-and-setup/basic-setup.test.tsx | 3 - .../complete-onboarding-wizard.test.tsx | 13 -- .../analytics/analytics-overview.test.tsx | 3 - .../e2e/specs/analytics/analytics.test.tsx | 3 - .../specs/homescreen/activity-panel.test.ts | 3 - .../e2e/specs/homescreen/task-list.test.ts | 3 - .../e2e/specs/marketing/coupons.test.tsx | 3 - .../tests/e2e/specs/tasks/payment.test.tsx | 3 - 14 files changed, 404 deletions(-) delete mode 100755 plugins/woocommerce-admin/tests/bootstrap.php delete mode 100644 plugins/woocommerce-admin/tests/e2e/config/default.json delete mode 100644 plugins/woocommerce-admin/tests/e2e/config/env.setup.js delete mode 100644 plugins/woocommerce-admin/tests/e2e/config/jest-puppeteer.config.js delete mode 100644 plugins/woocommerce-admin/tests/e2e/config/jest.config.js delete mode 100755 plugins/woocommerce-admin/tests/e2e/docker/initialize.sh delete mode 100644 plugins/woocommerce-admin/tests/e2e/specs/activate-and-setup/basic-setup.test.tsx delete mode 100644 plugins/woocommerce-admin/tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.tsx delete mode 100644 plugins/woocommerce-admin/tests/e2e/specs/analytics/analytics-overview.test.tsx delete mode 100644 plugins/woocommerce-admin/tests/e2e/specs/analytics/analytics.test.tsx delete mode 100644 plugins/woocommerce-admin/tests/e2e/specs/homescreen/activity-panel.test.ts delete mode 100644 plugins/woocommerce-admin/tests/e2e/specs/homescreen/task-list.test.ts delete mode 100644 plugins/woocommerce-admin/tests/e2e/specs/marketing/coupons.test.tsx delete mode 100644 plugins/woocommerce-admin/tests/e2e/specs/tasks/payment.test.tsx diff --git a/plugins/woocommerce-admin/tests/bootstrap.php b/plugins/woocommerce-admin/tests/bootstrap.php deleted file mode 100755 index 7d7832339e5..00000000000 --- a/plugins/woocommerce-admin/tests/bootstrap.php +++ /dev/null @@ -1,217 +0,0 @@ -tests_dir = dirname( __FILE__ ); - $this->plugin_dir = dirname( $this->tests_dir ); - $this->wc_core_dir = getenv( 'WC_CORE_DIR' ) ? getenv( 'WC_CORE_DIR' ) : dirname( $this->plugin_dir ) . '/woocommerce'; - $this->wp_tests_dir = getenv( 'WP_TESTS_DIR' ) ? getenv( 'WP_TESTS_DIR' ) : rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib'; - - $wc_tests_framework_base_dir = $this->wc_core_dir . '/tests'; - - if ( ! is_dir( $wc_tests_framework_base_dir . '/framework' ) ) { - $wc_tests_framework_base_dir .= '/legacy'; - } - $this->wc_core_tests_dir = $wc_tests_framework_base_dir; - - // load test function so tests_add_filter() is available. - require_once $this->wp_tests_dir . '/includes/functions.php'; - - // load WC. - tests_add_filter( 'muplugins_loaded', array( $this, 'load_wc' ) ); - - // install WC. - tests_add_filter( 'setup_theme', array( $this, 'install_wc' ) ); - - // Set up WC-Admin config. - tests_add_filter( 'woocommerce_admin_get_feature_config', array( $this, 'add_development_features' ) ); - - /* - * Load PHPUnit Polyfills for the WP testing suite. - * @see https://github.com/WordPress/wordpress-develop/pull/1563/ - */ - define( 'WP_TESTS_PHPUNIT_POLYFILLS_PATH', __DIR__ . '/../vendor/yoast/phpunit-polyfills/phpunitpolyfills-autoload.php' ); - - // load the WP testing environment. - require_once $this->wp_tests_dir . '/includes/bootstrap.php'; - - // load WC testing framework. - $this->includes(); - - // replace LegacyProxy class to MockableLegacyProxy from WC container. - $this->replace_legacy_proxy(); - } - - /** - * Load WooCommerce Admin. - */ - public function load_wc() { - define( 'WC_TAX_ROUNDING_MODE', 'auto' ); - define( 'WC_USE_TRANSACTIONS', false ); - update_option( 'woocommerce_enable_coupons', 'yes' ); - update_option( 'woocommerce_calc_taxes', 'yes' ); - update_option( 'woocommerce_onboarding_opt_in', 'yes' ); - - require_once $this->wc_core_dir . '/woocommerce.php'; - require $this->plugin_dir . '/vendor/autoload.php'; - } - - /** - * Install WooCommerce after the test environment and WC have been loaded. - */ - public function install_wc() { - // Clean existing install first. - define( 'WP_UNINSTALL_PLUGIN', true ); - define( 'WC_REMOVE_ALL_DATA', true ); - include $this->plugin_dir . '/uninstall.php'; - - WC_Install::install(); - - // Initialize the WC API extensions. - \Automattic\WooCommerce\Internal\Admin\Install::create_tables(); - \Automattic\WooCommerce\Internal\Admin\Install::create_events(); - - // Reload capabilities after install, see https://core.trac.wordpress.org/ticket/28374. - if ( version_compare( $GLOBALS['wp_version'], '4.7', '<' ) ) { - $GLOBALS['wp_roles']->reinit(); - } else { - $GLOBALS['wp_roles'] = null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - wp_roles(); - } - - echo esc_html( 'Installing WooCommerce and WooCommerce Admin...' . PHP_EOL ); - } - - /** - * Load WC-specific test cases and factories. - */ - public function includes() { - // WooCommerce test classes. - $wc_tests_framework_base_dir = $this->wc_core_tests_dir; - - // Framework. - require_once $wc_tests_framework_base_dir . '/framework/class-wc-unit-test-factory.php'; - require_once $wc_tests_framework_base_dir . '/framework/class-wc-mock-session-handler.php'; - require_once $wc_tests_framework_base_dir . '/framework/class-wc-mock-wc-data.php'; - require_once $wc_tests_framework_base_dir . '/framework/class-wc-mock-wc-object-query.php'; - require_once $wc_tests_framework_base_dir . '/framework/class-wc-mock-payment-gateway.php'; - require_once $this->tests_dir . '/framework/class-wc-mock-enhanced-payment-gateway.php'; - require_once $wc_tests_framework_base_dir . '/framework/class-wc-payment-token-stub.php'; - require_once $wc_tests_framework_base_dir . '/framework/vendor/class-wp-test-spy-rest-server.php'; - - // Test cases. - require_once $wc_tests_framework_base_dir . '/includes/wp-http-testcase.php'; - require_once $wc_tests_framework_base_dir . '/framework/class-wc-unit-test-case.php'; - require_once $wc_tests_framework_base_dir . '/framework/class-wc-api-unit-test-case.php'; - require_once $wc_tests_framework_base_dir . '/framework/class-wc-rest-unit-test-case.php'; - - // Helpers. - require_once $wc_tests_framework_base_dir . '/framework/helpers/class-wc-helper-product.php'; - require_once $wc_tests_framework_base_dir . '/framework/helpers/class-wc-helper-coupon.php'; - require_once $wc_tests_framework_base_dir . '/framework/helpers/class-wc-helper-fee.php'; - require_once $wc_tests_framework_base_dir . '/framework/helpers/class-wc-helper-shipping.php'; - require_once $wc_tests_framework_base_dir . '/framework/helpers/class-wc-helper-customer.php'; - require_once $wc_tests_framework_base_dir . '/framework/helpers/class-wc-helper-order.php'; - require_once $wc_tests_framework_base_dir . '/framework/helpers/class-wc-helper-shipping-zones.php'; - require_once $wc_tests_framework_base_dir . '/framework/helpers/class-wc-helper-payment-token.php'; - require_once $wc_tests_framework_base_dir . '/framework/helpers/class-wc-helper-settings.php'; - - // Include wc-admin helpers. - require_once $this->tests_dir . '/framework/helpers/class-wc-helper-reports.php'; - require_once $this->tests_dir . '/framework/helpers/class-wc-helper-admin-notes.php'; - require_once $this->tests_dir . '/framework/helpers/class-wc-test-action-queue.php'; - require_once $this->tests_dir . '/framework/helpers/class-wc-helper-queue.php'; - } - - /** - * Use the `development` features for testing. - * - * @param array $flags Existing feature flags. - * @return array Filtered feature flags. - */ - public function add_development_features( $flags ) { - $config = json_decode( file_get_contents( $this->plugin_dir . '/config/development.json' ) ); // @codingStandardsIgnoreLine. - foreach ( $config->features as $feature => $bool ) { - $flags[ $feature ] = $bool; - } - return $flags; - } - - /** - * Get the single class instance. - * @return WC_Admin_Unit_Tests_Bootstrap - */ - public static function instance() { - if ( is_null( self::$instance ) ) { - self::$instance = new self(); - } - - return self::$instance; - } - - /** - * Replace LegacyProxy to MockableLegacyProxy from the WC container. - * - * @throws \Exception Thrown when reflection fails. - */ - private function replace_legacy_proxy() { - try { - $inner_container_property = new \ReflectionProperty( \Automattic\WooCommerce\Container::class, 'container' ); - } catch ( ReflectionException $ex ) { - throw new \Exception( "Error when trying to get the private 'container' property from the " . \Automattic\WooCommerce\Container::class . ' class using reflection during unit testing bootstrap, has the property been removed or renamed?' ); - } - - $inner_container_property->setAccessible( true ); - $inner_container = $inner_container_property->getValue( wc_get_container() ); - - $inner_container->replace( LegacyProxy::class, MockableLegacyProxy::class ); - $inner_container->reset_all_resolved(); - - $GLOBALS['wc_container'] = $inner_container; - } -} - -WC_Admin_Unit_Tests_Bootstrap::instance(); diff --git a/plugins/woocommerce-admin/tests/e2e/config/default.json b/plugins/woocommerce-admin/tests/e2e/config/default.json deleted file mode 100644 index be040ef9036..00000000000 --- a/plugins/woocommerce-admin/tests/e2e/config/default.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "url": "http://localhost:8084/", - "appName": "woocommerce-admin-e2e", - "users": { - "admin": { - "username": "admin", - "password": "password" - }, - "customer": { - "username": "customer", - "password": "password" - } - }, - "products": { - "simple": { - "name": "Simple product" - }, - "variable": { - "name": "Variable Product with Three Variations" - }, - "grouped": { - "name": "Grouped Product with Three Children", - "groupedProducts": [ - { - "name": "Base Unit", - "regularPrice": "29.99" - }, - { - "name": "Add-on A", - "regularPrice": "11.95" - }, - { - "name": "Add-on B", - "regularPrice": "18.97" - } - ] - } - }, - "addresses": { - "admin": { - "store": { - "firstname": "John", - "lastname": "Doe", - "company": "Automattic", - "country": "United States (US)", - "addressfirstline": "addr 1", - "addresssecondline": "addr 2", - "countryandstate": "United States (US) -- California", - "city": "San Francisco", - "state": "CA", - "postcode": "94107", - "email": "john.doe@example.com" - } - }, - "customer": { - "billing": { - "firstname": "John", - "lastname": "Doe", - "company": "Automattic", - "country": "United States (US)", - "addressfirstline": "addr 1", - "addresssecondline": "addr 2", - "city": "San Francisco", - "state": "CA", - "postcode": "94107", - "phone": "123456789", - "email": "john.doe@example.com" - }, - "shipping": { - "firstname": "John", - "lastname": "Doe", - "company": "Automattic", - "country": "United States (US)", - "addressfirstline": "addr 1", - "addresssecondline": "addr 2", - "city": "San Francisco", - "state": "CA", - "postcode": "94107" - } - } - }, - "onboardingwizard": { - "industry": "Test industry", - "numberofproducts": "1 - 10", - "sellingelsewhere": "No", - "sellingOnAnotherPlatform": "Yes, on another platform", - "number_employees": "< 10", - "revenue": "Up to $2,500.00", - "other_platform_name": "Etsy" - }, - "settings": { - "shipping": { - "zonename": "United States", - "zoneregions": "United States (US)", - "shippingmethod": "Free shipping" - } - } -} diff --git a/plugins/woocommerce-admin/tests/e2e/config/env.setup.js b/plugins/woocommerce-admin/tests/e2e/config/env.setup.js deleted file mode 100644 index 6647b49d176..00000000000 --- a/plugins/woocommerce-admin/tests/e2e/config/env.setup.js +++ /dev/null @@ -1,5 +0,0 @@ -global.process.env = { - ...global.process.env, - // Gutenberg test util functions expect the test url to be at :8889, we change it to 8084. - WP_BASE_URL: 'http://localhost:8084', -}; diff --git a/plugins/woocommerce-admin/tests/e2e/config/jest-puppeteer.config.js b/plugins/woocommerce-admin/tests/e2e/config/jest-puppeteer.config.js deleted file mode 100644 index 30af9325973..00000000000 --- a/plugins/woocommerce-admin/tests/e2e/config/jest-puppeteer.config.js +++ /dev/null @@ -1,10 +0,0 @@ -const { useE2EJestPuppeteerConfig } = require( '@woocommerce/e2e-environment' ); - -const puppeteerConfig = useE2EJestPuppeteerConfig( { - launch: { - browserContext: 'incognito', - args: [ '--incognito' ], - }, -} ); - -module.exports = puppeteerConfig; diff --git a/plugins/woocommerce-admin/tests/e2e/config/jest.config.js b/plugins/woocommerce-admin/tests/e2e/config/jest.config.js deleted file mode 100644 index 96983f0607d..00000000000 --- a/plugins/woocommerce-admin/tests/e2e/config/jest.config.js +++ /dev/null @@ -1,23 +0,0 @@ -const path = require( 'path' ); -const { useE2EJestConfig } = require( '@woocommerce/e2e-environment' ); - -const config = useE2EJestConfig( { - moduleFileExtensions: [ 'js', 'ts', 'tsx' ], - roots: [ path.resolve( __dirname, '../specs' ) ], - testMatch: [ '**/*.(test|spec).(js|ts|tsx)', '*.(test|spec).(js|ts|tsx)' ], - testTimeout: 30000, - transform: { - '\\.[jt]sx?$': [ - 'babel-jest', - { - configFile: path.join( - __dirname, - '../../../', - 'babel.config.js' - ), - }, - ], - }, -} ); - -module.exports = config; diff --git a/plugins/woocommerce-admin/tests/e2e/docker/initialize.sh b/plugins/woocommerce-admin/tests/e2e/docker/initialize.sh deleted file mode 100755 index 335c5ea7736..00000000000 --- a/plugins/woocommerce-admin/tests/e2e/docker/initialize.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -echo "Initializing WooCommerce E2E" - -# Turn off error display temporarily. This is to prevent deprecated function -# notices from breaking the display of some screens and then E2E tests. -# Message was for WC_Admin_Notes_Deactivate_Plugin usage in core WC. -wp config set WP_DEBUG_DISPLAY false --raw -wp config set JETPACK_AUTOLOAD_DEV true --raw -wp plugin install woocommerce --activate -wp plugin install https://github.com/woocommerce/woocommerce-reset/zipball/trunk/ --activate -wp theme install twentynineteen --activate -wp plugin activate woocommerce-admin -wp user create customer customer@woocommercecoree2etestsuite.com --user_pass=password --role=customer --path=/var/www/html - -# we cannot create API keys for the API, so we using basic auth, this plugin allows that. -wp plugin install https://github.com/WP-API/Basic-Auth/archive/master.zip --activate diff --git a/plugins/woocommerce-admin/tests/e2e/specs/activate-and-setup/basic-setup.test.tsx b/plugins/woocommerce-admin/tests/e2e/specs/activate-and-setup/basic-setup.test.tsx deleted file mode 100644 index 4b26f2133a4..00000000000 --- a/plugins/woocommerce-admin/tests/e2e/specs/activate-and-setup/basic-setup.test.tsx +++ /dev/null @@ -1,3 +0,0 @@ -const { testAdminBasicSetup } = require( '@woocommerce/admin-e2e-tests' ); - -testAdminBasicSetup(); diff --git a/plugins/woocommerce-admin/tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.tsx b/plugins/woocommerce-admin/tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.tsx deleted file mode 100644 index a688787906d..00000000000 --- a/plugins/woocommerce-admin/tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.tsx +++ /dev/null @@ -1,13 +0,0 @@ -const { - testAdminOnboardingWizard, - testSelectiveBundleWCPay, - testDifferentStoreCurrenciesWCPay, - testSubscriptionsInclusion, - testBusinessDetailsForm, -} = require( '@woocommerce/admin-e2e-tests' ); - -testAdminOnboardingWizard(); -testSelectiveBundleWCPay(); -testDifferentStoreCurrenciesWCPay(); -testSubscriptionsInclusion(); -testBusinessDetailsForm(); diff --git a/plugins/woocommerce-admin/tests/e2e/specs/analytics/analytics-overview.test.tsx b/plugins/woocommerce-admin/tests/e2e/specs/analytics/analytics-overview.test.tsx deleted file mode 100644 index 9bc1b4e7467..00000000000 --- a/plugins/woocommerce-admin/tests/e2e/specs/analytics/analytics-overview.test.tsx +++ /dev/null @@ -1,3 +0,0 @@ -const { testAdminAnalyticsOverview } = require( '@woocommerce/admin-e2e-tests' ); - -testAdminAnalyticsOverview(); diff --git a/plugins/woocommerce-admin/tests/e2e/specs/analytics/analytics.test.tsx b/plugins/woocommerce-admin/tests/e2e/specs/analytics/analytics.test.tsx deleted file mode 100644 index 5a38d6f3234..00000000000 --- a/plugins/woocommerce-admin/tests/e2e/specs/analytics/analytics.test.tsx +++ /dev/null @@ -1,3 +0,0 @@ -const { testAdminAnalyticsPages } = require( '@woocommerce/admin-e2e-tests' ); - -testAdminAnalyticsPages(); diff --git a/plugins/woocommerce-admin/tests/e2e/specs/homescreen/activity-panel.test.ts b/plugins/woocommerce-admin/tests/e2e/specs/homescreen/activity-panel.test.ts deleted file mode 100644 index b8865709e32..00000000000 --- a/plugins/woocommerce-admin/tests/e2e/specs/homescreen/activity-panel.test.ts +++ /dev/null @@ -1,3 +0,0 @@ -const { testAdminHomescreenActivityPanel } = require( '@woocommerce/admin-e2e-tests' ); - -testAdminHomescreenActivityPanel(); diff --git a/plugins/woocommerce-admin/tests/e2e/specs/homescreen/task-list.test.ts b/plugins/woocommerce-admin/tests/e2e/specs/homescreen/task-list.test.ts deleted file mode 100644 index ae76561b357..00000000000 --- a/plugins/woocommerce-admin/tests/e2e/specs/homescreen/task-list.test.ts +++ /dev/null @@ -1,3 +0,0 @@ -const { testAdminHomescreenTasklist } = require( '@woocommerce/admin-e2e-tests' ); - -testAdminHomescreenTasklist(); diff --git a/plugins/woocommerce-admin/tests/e2e/specs/marketing/coupons.test.tsx b/plugins/woocommerce-admin/tests/e2e/specs/marketing/coupons.test.tsx deleted file mode 100644 index 28629da035c..00000000000 --- a/plugins/woocommerce-admin/tests/e2e/specs/marketing/coupons.test.tsx +++ /dev/null @@ -1,3 +0,0 @@ -const { testAdminCouponsPage } = require( '@woocommerce/admin-e2e-tests' ); - -testAdminCouponsPage(); diff --git a/plugins/woocommerce-admin/tests/e2e/specs/tasks/payment.test.tsx b/plugins/woocommerce-admin/tests/e2e/specs/tasks/payment.test.tsx deleted file mode 100644 index a0600e5d656..00000000000 --- a/plugins/woocommerce-admin/tests/e2e/specs/tasks/payment.test.tsx +++ /dev/null @@ -1,3 +0,0 @@ -const { testAdminPaymentSetupTask } = require( '@woocommerce/admin-e2e-tests' ); - -testAdminPaymentSetupTask(); From adda46cc7bdd6b888aa87c959b86b3b03c4aa05d Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 12 Apr 2022 14:47:03 +0800 Subject: [PATCH 348/386] =?UTF-8?q?Document=20Admin=E2=80=99s=20webpack=20?= =?UTF-8?q?config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/woocommerce-admin/webpack.config.js | 47 +++++++++++++++------ 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/plugins/woocommerce-admin/webpack.config.js b/plugins/woocommerce-admin/webpack.config.js index dbbf852059c..67634a1c9c2 100644 --- a/plugins/woocommerce-admin/webpack.config.js +++ b/plugins/woocommerce-admin/webpack.config.js @@ -41,7 +41,8 @@ const entryPoints = {}; wcAdminPackages.forEach( ( name ) => { entryPoints[ name ] = `../../packages/js/${ name }`; } ); - +// wpAdminScripts are loaded on wp-admin pages outside the context of WooCommerce Admin +// See ./client/wp-admin-scripts/README.md for more details const wpAdminScripts = [ 'marketing-coupons', 'navigation-opt-out', @@ -67,14 +68,18 @@ const webpackConfig = { }, output: { filename: ( data ) => { + // Output wpAdminScripts to wp-admin-scripts folder + // See https://github.com/woocommerce/woocommerce-admin/pull/3061 return wpAdminScripts.includes( data.chunk.name ) ? `wp-admin-scripts/[name]${ suffix }.js` : `[name]/index${ suffix }.js`; }, chunkFilename: `chunks/[name]${ suffix }.js`, path: path.join( __dirname, '/../woocommerce/assets/client/admin' ), + // Expose the exports of entry points so we can consume the libraries in window.wc.[modulename] with WooCommerceDependencyExtractionWebpackPlugin. library: [ 'wc', '[modulename]' ], libraryTarget: 'window', + // A unique name of the webpack build to avoid multiple webpack runtimes to conflict when using globals. uniqueName: '__wcAdmin_webpackJsonp', }, module: { @@ -82,6 +87,8 @@ const webpackConfig = { { test: /\.js$/, parser: { + // Disable AMD to fix an issue where underscore and lodash where clashing + // See https://github.com/woocommerce/woocommerce-admin/pull/1004 and https://github.com/Automattic/woocommerce-services/pull/1522 amd: false, }, }, @@ -101,6 +108,8 @@ const webpackConfig = { [ '@babel/preset-env', { + // Add polyfills such as Array.flat based on their usage in the code + // See https://github.com/woocommerce/woocommerce-admin/pull/6411/ corejs: '3', useBuiltIns: 'usage', }, @@ -119,8 +128,10 @@ const webpackConfig = { ], }, resolve: { - fallback:{ - 'crypto': 'empty' + fallback: { + // Reduce bundle size by omitting Node crypto library. + // See https://github.com/woocommerce/woocommerce-admin/pull/5768 + crypto: 'empty', }, extensions: [ '.json', '.js', '.jsx', '.ts', '.tsx' ], alias: { @@ -133,11 +144,14 @@ const webpackConfig = { }, plugins: [ ...styleConfig.plugins, + // Runs TypeScript type checker on a separate process. new ForkTsCheckerWebpackPlugin(), new CustomTemplatedPathPlugin( { modulename( outputPath, data ) { const entryName = get( data, [ 'chunk', 'name' ] ); if ( entryName ) { + // Convert the dash-case name to a camel case module name. + // For example, 'csv-export' -> 'csvExport' return entryName.replace( /-([a-z])/g, ( match, letter ) => letter.toUpperCase() ); @@ -145,26 +159,31 @@ const webpackConfig = { return outputPath; }, } ), - new CopyWebpackPlugin({ - + // The package build process doesn't handle extracting CSS from JS files, so we copy them separately. + new CopyWebpackPlugin( { patterns: wcAdminPackages.map( ( packageName ) => ( { from: `../../packages/js/${ packageName }/build-style/*.css`, to: `./${ packageName }/[name][ext]`, - noErrorOnMissing: true - } ) ) - } - ), + noErrorOnMissing: true, + } ) ), + } ), // We reuse this Webpack setup for Storybook, where we need to disable dependency extraction. ! process.env.STORYBOOK && new WooCommerceDependencyExtractionWebpackPlugin(), + // Reduces data for moment-timezone. new MomentTimezoneDataPlugin( { - startYear: 2000, // This strips out timezone data before the year 2000 to make a smaller file. + // This strips out timezone data before the year 2000 to make a smaller file. + startYear: 2000, } ), process.env.ANALYZE && new BundleAnalyzerPlugin(), - // Partially replace with __webpack_get_script_filename__ in app once using Webpack 5.x. + // Adds the script version parameter to the chunk URLs for cache busting + // TODO: Partially replace with __webpack_get_script_filename__ in app with Webpack 5.x. // The CSS chunk portion will need to remain, as it originates in MiniCssExtractPlugin. new AsyncChunkSrcVersionParameterPlugin(), + // Generate unminified files to load the unminified version when `define( 'SCRIPT_DEBUG', true );` is set in wp-config. + // This is also required to publish human readeable code in the deployed "plugin". + // See https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/#4-code-must-be-mostly-human-readable WC_ADMIN_PHASE !== 'core' && new UnminifyWebpackPlugin( { test: /\.js($|\?)/i, @@ -174,8 +193,10 @@ const webpackConfig = { optimization: { minimize: NODE_ENV !== 'development', splitChunks: { - name: false - } + // Not to generate chunk names because it caused a stressful workflow when deploying the plugin to WP.org + // See https://github.com/woocommerce/woocommerce-admin/pull/5229 + name: false, + }, }, }; From b69a923695dd03973e9c7fb5cd9def9fb5e03100 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 12 Apr 2022 15:29:50 +0800 Subject: [PATCH 349/386] Update admin's webpack config - Remove unnessary settings - Refactor config to make it easier to read --- plugins/woocommerce-admin/webpack.config.js | 56 +++++++++------------ 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/plugins/woocommerce-admin/webpack.config.js b/plugins/woocommerce-admin/webpack.config.js index 67634a1c9c2..37da6d8c215 100644 --- a/plugins/woocommerce-admin/webpack.config.js +++ b/plugins/woocommerce-admin/webpack.config.js @@ -36,11 +36,6 @@ const wcAdminPackages = [ 'tracks', 'onboarding', ]; - -const entryPoints = {}; -wcAdminPackages.forEach( ( name ) => { - entryPoints[ name ] = `../../packages/js/${ name }`; -} ); // wpAdminScripts are loaded on wp-admin pages outside the context of WooCommerce Admin // See ./client/wp-admin-scripts/README.md for more details const wpAdminScripts = [ @@ -54,51 +49,54 @@ const wpAdminScripts = [ 'beta-features-tracking-modal', 'payment-method-promotions', ]; -wpAdminScripts.forEach( ( name ) => { - entryPoints[ name ] = `./client/wp-admin-scripts/${ name }`; -} ); +const getEntryPoints = () => { + const entryPoints = { + app: './client/index.js', + }; + wcAdminPackages.forEach( ( name ) => { + entryPoints[ name ] = `../../packages/js/${ name }`; + } ); + wpAdminScripts.forEach( ( name ) => { + entryPoints[ name ] = `./client/wp-admin-scripts/${ name }`; + } ); + return entryPoints; +}; -const suffix = WC_ADMIN_PHASE === 'core' ? '' : '.min'; +const outputSuffix = WC_ADMIN_PHASE === 'core' ? '' : '.min'; const webpackConfig = { mode: NODE_ENV, - entry: { - app: './client/index.js', - ...entryPoints, - }, + entry: getEntryPoints(), output: { filename: ( data ) => { // Output wpAdminScripts to wp-admin-scripts folder // See https://github.com/woocommerce/woocommerce-admin/pull/3061 return wpAdminScripts.includes( data.chunk.name ) - ? `wp-admin-scripts/[name]${ suffix }.js` - : `[name]/index${ suffix }.js`; + ? `wp-admin-scripts/[name]${ outputSuffix }.js` + : `[name]/index${ outputSuffix }.js`; }, - chunkFilename: `chunks/[name]${ suffix }.js`, + chunkFilename: `chunks/[name]${ outputSuffix }.js`, path: path.join( __dirname, '/../woocommerce/assets/client/admin' ), - // Expose the exports of entry points so we can consume the libraries in window.wc.[modulename] with WooCommerceDependencyExtractionWebpackPlugin. - library: [ 'wc', '[modulename]' ], - libraryTarget: 'window', + library: { + // Expose the exports of entry points so we can consume the libraries in window.wc.[modulename] with WooCommerceDependencyExtractionWebpackPlugin. + name: [ 'wc', '[modulename]' ], + type: 'window', + }, // A unique name of the webpack build to avoid multiple webpack runtimes to conflict when using globals. uniqueName: '__wcAdmin_webpackJsonp', }, module: { rules: [ { - test: /\.js$/, + test: /\.(t|j)sx?$/, parser: { // Disable AMD to fix an issue where underscore and lodash where clashing // See https://github.com/woocommerce/woocommerce-admin/pull/1004 and https://github.com/Automattic/woocommerce-services/pull/1522 amd: false, }, - }, - { - test: /\.(t|j)sx?$/, exclude: [ - // Exclude node_modules/ but not node_modules/debug* and node_modules/explat-client-react-helpers - // explat-client-react-helpers module contains optional chaining operators which need to be processed via babel loader for webpack 4. - // see webpack issue for details: https://github.com/webpack/webpack/issues/10227#issue-547480527 - /node_modules(\/|\\)\.pnpm(\/|\\)(?!(debug|\@automattic\+explat-client-react-helpers))/, + // Exclude node_modules/.pnpm but not node_modules/.pnpm/debug* + /node_modules(\/|\\)\.pnpm(\/|\\)(?!(debug))/, ], use: { loader: 'babel-loader', @@ -136,10 +134,6 @@ const webpackConfig = { extensions: [ '.json', '.js', '.jsx', '.ts', '.tsx' ], alias: { '~': path.resolve( __dirname + '/client' ), - 'gutenberg-components': path.resolve( - __dirname, - 'node_modules/@wordpress/components/src' - ), }, }, plugins: [ From fdd9b1bbb8b3a9212dd3aeee4917f223fd5c154b Mon Sep 17 00:00:00 2001 From: RJChow Date: Thu, 14 Apr 2022 12:53:45 +0800 Subject: [PATCH 350/386] Added type guard for locale and unit tests for isAddressFieldRequired --- .../settings/general/store-address.tsx | 20 ++++++++++-- .../settings/general/test/store-address.js | 31 ++++++++++++++++++- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.tsx b/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.tsx index 8323309c442..1187d102815 100644 --- a/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.tsx +++ b/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.tsx @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { COUNTRIES_STORE_NAME, Country, Locale } from '@woocommerce/data'; import { decodeEntities } from '@wordpress/html-entities'; -import { escapeRegExp } from 'lodash'; +import { escapeRegExp, has } from 'lodash'; import { useEffect, useMemo, useState, useRef } from '@wordpress/element'; import { SelectControl, TextControl } from '@woocommerce/components'; import { Spinner } from '@wordpress/components'; @@ -25,6 +25,20 @@ const storeAddressFields = [ type Option = { key: string; label: string }; +/** + * Type guard to ensure that the specified locale object has a .required property + * + * @param fieldName field of Locale + * @param locale unknown object to be checked + * @return Boolean indicating if locale has a .required property + */ +const isLocaleRecord = ( + fieldName: keyof Locale, + locale: unknown +): locale is Record< keyof Locale, { required: boolean } > => { + return !! locale && has( locale, `${ fieldName }.required` ); +}; + /** * Check if a given address field is required for the locale. * @@ -36,8 +50,8 @@ export function isAddressFieldRequired( fieldName: keyof Locale, locale: Locale = {} ): boolean { - if ( locale[ fieldName ]?.hasOwnProperty( 'required' ) ) { - return locale[ fieldName ]?.required as boolean; + if ( isLocaleRecord( fieldName, locale ) ) { + return locale[ fieldName ].required; } if ( fieldName === 'address_2' ) { diff --git a/plugins/woocommerce-admin/client/dashboard/components/settings/general/test/store-address.js b/plugins/woocommerce-admin/client/dashboard/components/settings/general/test/store-address.js index 4f05554f9ba..70473a74cf8 100644 --- a/plugins/woocommerce-admin/client/dashboard/components/settings/general/test/store-address.js +++ b/plugins/woocommerce-admin/client/dashboard/components/settings/general/test/store-address.js @@ -7,7 +7,11 @@ import { render, fireEvent } from '@testing-library/react'; /** * Internal dependencies */ -import { useGetCountryStateAutofill, getStateFilter } from '../store-address'; +import { + isAddressFieldRequired, + useGetCountryStateAutofill, + getStateFilter, +} from '../store-address'; const AutofillWrapper = ( { options, value, onChange } ) => { const [ values, setValues ] = useState( { countryState: value || '' } ); @@ -211,3 +215,28 @@ describe( 'getStateFilter', () => { } ); } ); + +describe( 'isAddressFieldRequired', () => { + it( 'should return true if fieldName is not a key in locale', () => { + expect( isAddressFieldRequired( 'address_1', { foo: 'bar' } ) ).toBe( + true + ); + } ); + it( 'should return true if locale object marks it as required', () => { + expect( + isAddressFieldRequired( 'address_1', { + address_1: { required: true }, + } ) + ).toBe( true ); + } ); + it( 'should return false if locale object marks it as not required', () => { + expect( + isAddressFieldRequired( 'address_1', { + address_1: { required: false }, + } ) + ).toBe( false ); + } ); + it( 'should return false if fieldName is address_2', () => { + expect( isAddressFieldRequired( 'address_2', {} ) ).toBe( false ); + } ); +} ); From a97df494e52f23c6e027ce7ca7666158dc64475a Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 13 Apr 2022 12:43:20 +0800 Subject: [PATCH 351/386] Update woocommerce-admin README.md --- plugins/woocommerce-admin/README.md | 117 ++-------------------------- 1 file changed, 5 insertions(+), 112 deletions(-) diff --git a/plugins/woocommerce-admin/README.md b/plugins/woocommerce-admin/README.md index 59d6f0acc72..8f8020b53a0 100644 --- a/plugins/woocommerce-admin/README.md +++ b/plugins/woocommerce-admin/README.md @@ -1,120 +1,13 @@ # WooCommerce Admin -This is a feature plugin for a modern, javascript-driven WooCommerce Admin experience. - -## Prerequisites - -[WordPress 5.6 or greater](https://wordpress.org/download/) and [WooCommerce 5.7.0 or greater](https://wordpress.org/plugins/woocommerce/) should be installed prior to activating the WooCommerce Admin feature plugin. - -For better debugging, it's also recommended you add `define( 'SCRIPT_DEBUG', true );` to your wp-config. This will load the unminified version of all libraries, and specifically the development build of React. +This is a javascript-driven, React-based admin interface for WooCommerce. ## Development -After cloning the repo, install dependencies: - -- `pnpm install` to install JavaScript dependencies. -- `composer install` to gather PHP dependencies. - -Now you can build the files using one of these commands: - -- `pnpm run build` : Build a production version -- `pnpm run dev` : Build a development version -- `pnpm start` : Build a development version, watch files for changes -- `pnpm run build:release` : Build a WordPress plugin ZIP file (`woocommerce-admin.zip` will be created in the repository root) -- `DRY_RUN=1 pnpm run build:release` : Builds a Wordpress plugin ZIP **without** pushing it to Github and creating a release. - -For more helper scripts [see here](./CONTRIBUTING.md#helper-scripts) - -For some debugging tools/help [see here](./CONTRIBUTING.md#debugging) - -For local development setup using Docker [see here](./docker/wc-admin-wp-env/README.md) - -### Typescript - -The `npm run ts:check` command will check your TypeScript files for errors, and has been added to `.vscode/tasks.json`. -Running this task in vscode will highlight the errors in your editor file navigator. - -If you allow the `npm run ts:check:watch` command to run automatically as configured, it will run in the background and pick up any errors as you save the files. -Note: Even if you don't run this task, the IDE uses its language server to pick up type errors in files that are open. This is only necessary for picking up errors -across the entire repository even when they haven't been opened in the IDE. - -### Testing - -#### End-to-end tests - -Tests live in `./tests/e2e`. An existing build is required prior running, please refer to the section above for steps. E2E tests use the `@woocommerce/e2e-environment` package which hosts a Docker container for testing, by default the container can be accessed at `http://localhost:8084` - -All the commands from `@woocommerce/e2e-environment` can be run through `pnpm exec`. - -``` -# Set up the e2e environment -pnpm i -pnpm exec wc-e2e docker:up -``` - -Run tests using: - -``` -pnpm exec wc-e2e test:e2e-dev -``` - -or in headless mode: - -``` -pnpm exec wc-e2e test:e2e -``` - -Run a single test by adding the path to the file name: - -``` -pnpm exec wc-e2e test:e2e-dev tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.ts -``` - -### Documentation - -There is documentation in 2 forms available in the repo. A static set of documentation supported by docsify and also a Storybook containing component documentation for `@woocommerce/components`. - -To view the docsify docs locally you can do: - -``` -pnpm install -cd docs -pnpm exec docsify serve -``` - -When deployed the docsify docs also host an embedded version of the storybook docs. To generate that and test it locally in docsify you'll need to run: - -``` -pnpm install -pnpm run docs -cd docs -pnpm exec docsify serve -``` - -Then navigate to `Components` from the left hand menu in the docs. - -If you would like to view the storybook docs hosted standalone, then you can run: - -``` -pnpm install -pnpm run storybook -``` - -If you would like to view the storybook docs in right-to-left styling, you can run this instead: - -``` -pnpm install -pnpm run storybook-rtl -``` +Please refer to the [WooCommerce Admin Development](https://github.com/woocommerce/woocommerce/wiki/How-to-set-up-WooCommerce-development-environment#wooCommerce-admin-development) +## End-to-end tests +Please refer to the [WooCommerce End to End Tests](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/tests/e2e/README.md) ## Common Issues -If you're encountering any issue setting things up, chances are we have been there too. Please have a look at our [wiki](https://github.com/woocommerce/woocommerce-admin/wiki/Common-Issues) for a list of common problems. - -## Privacy - -If you have enabled WooCommerce usage tracking ( option `woocommerce_allow_tracking` ) then, in addition to the tracking described in https://woocommerce.com/usage-tracking/, this plugin also sends information about the actions that site administrators perform to Automattic - see https://automattic.com/privacy/#information-we-collect-automatically for more information. - -## Contributing - -There are many ways to contribute – reporting bugs, adding translations, feature suggestions and fixing bugs. For full details, please see [CONTRIBUTING.md](./CONTRIBUTING.md) +If you're encountering any issue setting things up, chances are we have been there too. Please have a look at our [wiki](https://github.com/woocommerce/woocommerce/wiki/Common-Issues) for a list of common problems. From 0ed89118750437fcbc632765fffd4832b90fcc75 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 13 Apr 2022 13:32:12 +0800 Subject: [PATCH 352/386] Update admin docs README.md --- plugins/woocommerce-admin/docs/README.md | 34 +++++------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/plugins/woocommerce-admin/docs/README.md b/plugins/woocommerce-admin/docs/README.md index 31fb6545f6e..8f8020b53a0 100644 --- a/plugins/woocommerce-admin/docs/README.md +++ b/plugins/woocommerce-admin/docs/README.md @@ -1,35 +1,13 @@ # WooCommerce Admin -This is a feature plugin for a modern, javascript-driven WooCommerce Admin experience. - -## Prerequisites - -[WordPress 5.4 or greater](https://wordpress.org/download/) and [WooCommerce 4.8.0 or greater](https://wordpress.org/plugins/woocommerce/) should be installed prior to activating the WooCommerce Admin feature plugin. - -For better debugging, it's also recommended you add `define( 'SCRIPT_DEBUG', true );` to your wp-config. This will load the unminified version of all libraries, and specifically the development build of React. +This is a javascript-driven, React-based admin interface for WooCommerce. ## Development -After cloning the repo, install dependencies: +Please refer to the [WooCommerce Admin Development](https://github.com/woocommerce/woocommerce/wiki/How-to-set-up-WooCommerce-development-environment#wooCommerce-admin-development) +## End-to-end tests -- `pnpm install` to install JavaScript dependencies. -- `composer install` to gather PHP dependencies. +Please refer to the [WooCommerce End to End Tests](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/tests/e2e/README.md) +## Common Issues -Now you can build the files using one of these commands: - -- `pnpm run build` : Build a production version -- `pnpm run dev` : Build a development version -- `pnpm start` : Build a development version, watch files for changes -- `pnpm run build:release` : Build a WordPress plugin ZIP file (`woocommerce-admin.zip` will be created in the repository root) - -For more helper scripts [see here](./CONTRIBUTING.md#helper-scripts) - -For some debugging tools/help [see here](./CONTRIBUTING.md#debugging) - -## Privacy - -If you have enabled WooCommerce usage tracking ( option `woocommerce_allow_tracking` ) then, in addition to the tracking described in https://woocommerce.com/usage-tracking/, this plugin also sends information about the actions that site administrators perform to Automattic - see https://automattic.com/privacy/#information-we-collect-automatically for more information. - -## Contributing - -There are many ways to contribute – reporting bugs, adding translations, feature suggestions and fixing bugs. For full details, please see [CONTRIBUTING.md](./CONTRIBUTING.md) +If you're encountering any issue setting things up, chances are we have been there too. Please have a look at our [wiki](https://github.com/woocommerce/woocommerce/wiki/Common-Issues) for a list of common problems. From ed8885f0b75b8fb581dbf613f242696e51362407 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 13 Apr 2022 13:58:39 +0800 Subject: [PATCH 353/386] Fix admin extensions webpack config --- .../examples/extensions/examples.config.js | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/plugins/woocommerce-admin/docs/examples/extensions/examples.config.js b/plugins/woocommerce-admin/docs/examples/extensions/examples.config.js index f5527356bd4..36ff57ad22a 100644 --- a/plugins/woocommerce-admin/docs/examples/extensions/examples.config.js +++ b/plugins/woocommerce-admin/docs/examples/extensions/examples.config.js @@ -10,13 +10,11 @@ const woocommerceAdminConfig = require( path.resolve( ) ); const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' ); -const extArg = process.argv.find( ( arg ) => arg.startsWith( '--ext=' ) ); - -if ( ! extArg ) { +if ( ! process.env.WC_EXT ) { throw new Error( 'Please provide an extension.' ); } -const extension = extArg.slice( 6 ); +const extension = process.env.WC_EXT; const extensionPath = path.join( __dirname, `${ extension }/js/index.js` ); if ( ! fs.existsSync( extensionPath ) ) { @@ -33,7 +31,7 @@ const webpackConfig = { output: { filename: '[name]/dist/index.js', path: path.resolve( __dirname ), - libraryTarget: 'this', + libraryTarget: 'window', }, externals: woocommerceAdminConfig.externals, module: { @@ -89,12 +87,17 @@ const webpackConfig = { }, }, plugins: [ - new CopyWebpackPlugin( [ - { - from: path.join( __dirname, `${ extension }/` ), - to: path.resolve( __dirname, `../../../../${ extension }/` ), - }, - ] ), + new CopyWebpackPlugin( { + patterns: [ + { + from: path.join( __dirname, `${ extension }/` ), + to: path.resolve( + __dirname, + `../../../../${ extension }/` + ), + }, + ], + } ), new MiniCssExtractPlugin( { filename: '[name]/dist/style.css', } ), From 96a5a7b75dc5a605b5ab91acb2a8f7e27d19c26b Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 13 Apr 2022 14:03:20 +0800 Subject: [PATCH 354/386] Remove building ./docs storybook --- plugins/woocommerce-admin/docs/components/README.md | 1 - plugins/woocommerce-admin/package.json | 1 - 2 files changed, 2 deletions(-) delete mode 100644 plugins/woocommerce-admin/docs/components/README.md diff --git a/plugins/woocommerce-admin/docs/components/README.md b/plugins/woocommerce-admin/docs/components/README.md deleted file mode 100644 index 2142fa90cf8..00000000000 --- a/plugins/woocommerce-admin/docs/components/README.md +++ /dev/null @@ -1 +0,0 @@ -[storybook](storybook/index.html ':include :type=iframe width=100% height=100%') diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 6be60f4ffc8..26193f6ae93 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -21,7 +21,6 @@ "create-wc-extension": "node ./bin/starter-pack/starter-pack.js", "predev": "pnpm run -s install-if-deps-outdated", "dev": "cross-env WC_ADMIN_PHASE=development pnpm run build:feature-config && cross-env WC_ADMIN_PHASE=development pnpm run build:packages && cross-env WC_ADMIN_PHASE=development webpack", - "docs": "./bin/import-wp-css-storybook.sh && BABEL_ENV=storybook STORYBOOK=true pnpm exec build-storybook -c storybook/.storybook -o ./docs/components/storybook", "example": "webpack --config docs/examples/extensions/examples.config.js --watch", "preinstall": "npx only-allow pnpm", "install-if-deps-outdated": "node bin/install-if-deps-outdated.js", From dfa51ac74719e0cff1cbee2cee1fb784591641f3 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 13 Apr 2022 14:13:01 +0800 Subject: [PATCH 355/386] Update docs --- plugins/woocommerce-admin/docs/README.md | 18 +++--- .../docs/examples/extensions/README.md | 4 +- .../docs/features/feature-flags.md | 14 +---- .../docs/features/navigation.md | 4 +- .../docs/features/onboarding.md | 11 ---- .../features/payment-gateway-suggestions.md | 4 +- .../woocommerce-admin/docs/page-controller.md | 6 +- plugins/woocommerce-admin/docs/stylesheets.md | 63 +------------------ .../docs/woocommerce.com/README.md | 4 +- 9 files changed, 21 insertions(+), 107 deletions(-) diff --git a/plugins/woocommerce-admin/docs/README.md b/plugins/woocommerce-admin/docs/README.md index 8f8020b53a0..2a2bb84447e 100644 --- a/plugins/woocommerce-admin/docs/README.md +++ b/plugins/woocommerce-admin/docs/README.md @@ -1,13 +1,9 @@ # WooCommerce Admin -This is a javascript-driven, React-based admin interface for WooCommerce. - -## Development - -Please refer to the [WooCommerce Admin Development](https://github.com/woocommerce/woocommerce/wiki/How-to-set-up-WooCommerce-development-environment#wooCommerce-admin-development) -## End-to-end tests - -Please refer to the [WooCommerce End to End Tests](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/tests/e2e/README.md) -## Common Issues - -If you're encountering any issue setting things up, chances are we have been there too. Please have a look at our [wiki](https://github.com/woocommerce/woocommerce/wiki/Common-Issues) for a list of common problems. +- [CSS Structure](stylesheets.md) +- [Data](data.md) +- [Examples](examples/) +- [Features](features/) +- [Layout](layout.md) +- [Page Controller](page-controller.md) +- [woocommerce.com](woocommerce.com/) diff --git a/plugins/woocommerce-admin/docs/examples/extensions/README.md b/plugins/woocommerce-admin/docs/examples/extensions/README.md index b74cceac443..b4d39518ec7 100644 --- a/plugins/woocommerce-admin/docs/examples/extensions/README.md +++ b/plugins/woocommerce-admin/docs/examples/extensions/README.md @@ -13,10 +13,10 @@ pnpm install Build the example extension by running the pnpm script and passing the example name. ```bash -pnpm run example -- --ext= +WC_EXT= pnpm nx example woocommerce-admin ``` -Go to your WordPress installation's plugins page and activate the plugin. WooCommerce Analytics reports will now reflect the changes made by the example extension. +Include the output plugin in your `.wp-env.json` and `.wp-env.override.json` and restart the WordPress instance. WooCommerce Analytics reports will now reflect the changes made by the example extension. You can make changes to Javascript and PHP files in the example and see changes reflected upon refresh. diff --git a/plugins/woocommerce-admin/docs/features/feature-flags.md b/plugins/woocommerce-admin/docs/features/feature-flags.md index 5b1a98cac19..99db0a449c4 100644 --- a/plugins/woocommerce-admin/docs/features/feature-flags.md +++ b/plugins/woocommerce-admin/docs/features/feature-flags.md @@ -7,8 +7,8 @@ We currently support the following environments: | Environment | Description | |-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | development | Development - All features should be enabled in development. These flags are also used in both JS and PHP tests. Ran using `pnpm start`. | -| plugin | Plugin - A packaged release of the featured plugin, for GitHub WordPress.org. Ran using `pnpm run-script build:release`. | | -| core | Core - assets/files ready and stable enough for core merge. Ran using `pnpm pack`. (@todo update this with publish command). +| plugin | Plugin - A packaged release of the featured plugin, for GitHub WordPress.org. | | +| core | Core - assets/files ready and stable enough for core merge. Ran using `pnpm pack`. (@todo). ## Adding a new flag @@ -16,16 +16,6 @@ We currently support the following environments: Flags can be added to the files located in the `config/` directory. Make sure to add a flag for each environment and explicitly set the flag to false. Please add new feature flags alphabetically so they are easy to find. -## Building custom plugin builds - -Sometimes it is useful to create a test zip of a plugin, separate from the released WordPress.org version. This makes internal testing easier for non developers, removing the requirment of using Git and NPM commands. These releases are usually uploaded to GitHub releases as a pre release. - -You can use the `build:release` command with the `--slug` and `--features` arguments to create a custom build. Base feature flags will be pulled from `config/plugin.json` and your additional changes are overlaid on top. When the build is complete, a `woocommerce-admin-$slug.zip` file will be generated. - -For example, to create a `woocommerce-admin-onboarding.zip` build by enabling onboarding in addition to the feature flags defined in `config/plugin.json`, you would run: - -`pnpm run build:release -- --slug onboarding --features '{"onboarding":true}'`. - ## Basic Use - Client The `window.wcAdminFeatures` constant is a global variable containing the feature flags. diff --git a/plugins/woocommerce-admin/docs/features/navigation.md b/plugins/woocommerce-admin/docs/features/navigation.md index eb73d1d9199..7ef025f0c31 100644 --- a/plugins/woocommerce-admin/docs/features/navigation.md +++ b/plugins/woocommerce-admin/docs/features/navigation.md @@ -8,9 +8,9 @@ This API will allow you to add in your own items to the navigation and register This feature is hidden behind a feature flag and can be turned on or off by visiting WooCommerce -> Settngs -> Advanced -> Features and checking the box next to the `Navigation` option. It can also by controlled programmatically by setting the option `woocommerce_navigation_enable` to `yes` or `no`. -The fastest way to get started is by creating an example plugin from WooCommerce Admin. Inside your `woocommerce-admin` directory, enter the following command: +The fastest way to get started is by creating an example plugin from WooCommerce Admin. Enter the following command: -`pnpm run example -- --ext=add-navigation-items` +`WC_EXT=add-navigation-items pnpm nx example woocommerce-admin` This will create a new plugin that covers various features of the navigation and helps to register some intial items and categories within the new navigation menu. After running the command above, you can make edits directly to the files at `docs/examples/extensions/add-navigation-items` and they will be built and copied to your `wp-content/add-navigation-items` folder on save. diff --git a/plugins/woocommerce-admin/docs/features/onboarding.md b/plugins/woocommerce-admin/docs/features/onboarding.md index a2769e79584..4328d8aaf74 100644 --- a/plugins/woocommerce-admin/docs/features/onboarding.md +++ b/plugins/woocommerce-admin/docs/features/onboarding.md @@ -110,14 +110,3 @@ Logic for the Calypso flows are gated behind two separate [Calypso feature flags ### Testing If you are running the development version of WooCommerce Admin, and have [`WP_DEBUG`](https://codex.wordpress.org/WP_DEBUG) set to `true`, two Calypso connection buttons are displayed under the `WooCommerce > Settings > Help > Setup Wizard` menu, making it easier to access and test the flows. - -## Building the onboarding feature plugin - -The `onboarding` feature flag is enabled in the main WooCommerce Admin plugin build. That means the published version of the plugin on WordPress.org contains the onboarding feature, but it is visually off by default. See the "enable onboarding" section above. - -Sometimes, it may be necessary to generate a separate build of the plugin between public releases for internal testing or debugging. This can be done using the [building custom plugin builds](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce-admin/docs/features/feature-flags.md#building-custom-plugin-builds) feature of our build system. - -* Switch to the latest `main` branch and pull down any changes -* Run `pnpm run build:release -- --slug onboarding --features '{"onboarding":true}'` -* A special `woocommerce-admin-onboarding.zip` release will be generated, containing the latest onboarding code -* Make sure to follow the directions in the "enabling onboarding" section above to properly use the build diff --git a/plugins/woocommerce-admin/docs/features/payment-gateway-suggestions.md b/plugins/woocommerce-admin/docs/features/payment-gateway-suggestions.md index 66f8a41a0e2..a81c112250e 100644 --- a/plugins/woocommerce-admin/docs/features/payment-gateway-suggestions.md +++ b/plugins/woocommerce-admin/docs/features/payment-gateway-suggestions.md @@ -8,9 +8,9 @@ After merchants click on a recommendation, plugins from this source will then wa Gateway suggestions are retreived from a REST API and can be added via a remote JSON data source or filtered with the `woocommerce_admin_payment_gateway_suggestion_specs` filter. -To quickly get started with an example plugin, run the following from your `woocommerce-admin` directory: +To quickly get started with an example plugin, run the following: -`pnpm run example -- --ext=payment-gateway-suggestions` +`WC_EXT=payment-gateway-suggestions pnpm nx example woocommerce-admin` This will create a new plugin that when activated will add two new gateway suggestions. The first is a simple gateway demonstrating how configuration fields can be pulled from the gateway class to create a configuration form. The second gateway shows a more customized approach via SlotFill. diff --git a/plugins/woocommerce-admin/docs/page-controller.md b/plugins/woocommerce-admin/docs/page-controller.md index 381db8bfb30..ce4fd1eb29b 100644 --- a/plugins/woocommerce-admin/docs/page-controller.md +++ b/plugins/woocommerce-admin/docs/page-controller.md @@ -181,6 +181,6 @@ addFilter( 'woocommerce_admin_pages_list', 'my-namespace', ( pages ) => { ### Further Reading -- Check out the [`PageController`](../src/PageController.php) class. -- See how we're [connecting existing WooCommerce pages](../includes/page-controller-functions.php). -- See how we're [registering Analytics Reports](../src/Features/Analytics.php). +- Check out the [`PageController`](../woocommerce/src/Admin/PageController.php) class. +- See how we're [connecting existing WooCommerce pages](../woocommerce/includes/react-admin/page-controller-functions.php). +- See how we're [registering Analytics Reports](../woocommerce/src/Internal/Admin/Analytics.php). diff --git a/plugins/woocommerce-admin/docs/stylesheets.md b/plugins/woocommerce-admin/docs/stylesheets.md index 496b6486837..cc8918c279f 100644 --- a/plugins/woocommerce-admin/docs/stylesheets.md +++ b/plugins/woocommerce-admin/docs/stylesheets.md @@ -8,65 +8,4 @@ ## Naming: Component classes -To avoid class name collisions between elements of the woo app and to the enclosing WordPress dashboard, class names **must** adhere to the following guidelines: - -Any default export of a folder's `index.js` **must** be prefixed with `woocommerce-` followed by the directory name in which it resides: - -``` -.woocommerce-[ directory name ] -``` - -(Example: `.woocommerce-card` from `components/card/index.js`) - -For any descendant of the top-level (`index.js`) element, prefix using the top-level element's class name separated by two underscores: - -``` -.woocommerce-[ directory name ]__[ descendant description ] -``` - -(Example: `.woocommerce-card__title`, or `.woocommerce-ellipsis-menu__item`) - -For optional variations of an element or its descendants, you may use a modifier class, but you **must not** apply styles to the modifier class directly; only as an additional selector to the element to which the modifier applies: - -``` -.woocommerce-[ directory name ].is-[ modifier description ] -.woocommerce-[ directory name ]__[ descendant description ].is-[ modifier description ] -``` - -(Example: `.woocommerce-ellipsis-menu__item.is-active` ) - -In all of the above cases, except in separating the top-level element from its descendants, you **must** use dash delimiters when expressing multiple terms of a name. You can use `.is-*` or `.has-*` to describe element states. - -You may observe that these conventions adhere closely to the [BEM (Blocks, Elements, Modifiers)](http://getbem.com/introduction/) CSS methodology, with minor adjustments to the application of modifiers. - -## Naming: Layout classes - -All layout classes use the `.woocommerce-layout__` prefix: - -``` -.woocommerce-layout__[ section ] -``` - -(Example: `.woocommerce-layout__activity-panel` ) - -If the section has children elements, prefix a description with the section class name: - -``` -.woocommerce-layout__[ section ]-[ descendant description ] -``` - -(Example: `.woocommerce-layout__activity-panel-title` ) - -## Naming: Dashboard classes - -All dashboard components use the `.woocommerce-dashboard__` prefix: - -``` -.woocommerce-dashboard__[ section ] -``` - -(Example: `.woocommerce-dashboard__widget` ) - -## Naming: Analytics classes - -All analytics components use the `.woocommerce-analytics__` prefix. +Please refer to [CSS SASS coding guidelines and naming conventions](https://github.com/woocommerce/woocommerce/wiki/CSS-SASS-coding-guidelines-and-naming-conventions) diff --git a/plugins/woocommerce-admin/docs/woocommerce.com/README.md b/plugins/woocommerce-admin/docs/woocommerce.com/README.md index a3d165d5c16..2ed099f6070 100644 --- a/plugins/woocommerce-admin/docs/woocommerce.com/README.md +++ b/plugins/woocommerce-admin/docs/woocommerce.com/README.md @@ -1,6 +1,6 @@ # WooCommerce Admin -The WooCommerce Admin plugin is where the next iteration of the administrative experience for WooCommerce is being developed. The project is being built on top of the modern JavaScript packages that are being released from the [core WordPress editor project](https://github.com/wordpress/gutenberg). By building with these `@wordpress/components` - WooCommerce Admin seeks to create a cohesive experience with the latest WordPress core design system, and to deliver a fast and modern set of tools to manage one's WooCommerce Store with. +The WooCommerce Admin is where the next iteration of the administrative experience for WooCommerce is being developed. The project is being built on top of the modern JavaScript packages that are being released from the [core WordPress editor project](https://github.com/wordpress/gutenberg). By building with these `@wordpress/components` - WooCommerce Admin seeks to create a cohesive experience with the latest WordPress core design system, and to deliver a fast and modern set of tools to manage one's WooCommerce Store with. Currently, development efforts have been focused on two primary areas: @@ -10,7 +10,7 @@ Currently, development efforts have been focused on two primary areas: ## Analytics -With WooCommerce Admin installed, a new Analytics menu item is created in the wp-admin menu system. This menu item, and the reports contained insde of it are available to all wp-admin users that have the `view_woocommerce_reports` capability, so per a standard WooCommerce install this would give `shop_manager`s and `administrator`s access to the reports. +With WooCommerce installed, a new Analytics menu item is created in the wp-admin menu system. This menu item, and the reports contained insde of it are available to all wp-admin users that have the `view_woocommerce_reports` capability, so per a standard WooCommerce install this would give `shop_manager`s and `administrator`s access to the reports. Each report is quite unique with its own set of filtering options and chart types. To learn more about each individual report, please view the pages below: From 20736a5280a61918389d24503547d60ad3c1e65d Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 13 Apr 2022 14:21:47 +0800 Subject: [PATCH 356/386] Remove docsify-cli and releated files --- plugins/woocommerce-admin/docs/.nojekyll | 0 plugins/woocommerce-admin/docs/_sidebar.md | 7 - .../docs/examples/_sidebar.md | 5 - .../docs/features/_sidebar.md | 6 - plugins/woocommerce-admin/docs/index.html | 70 --- plugins/woocommerce-admin/package.json | 1 - pnpm-lock.yaml | 427 +----------------- 7 files changed, 19 insertions(+), 497 deletions(-) delete mode 100644 plugins/woocommerce-admin/docs/.nojekyll delete mode 100644 plugins/woocommerce-admin/docs/_sidebar.md delete mode 100644 plugins/woocommerce-admin/docs/examples/_sidebar.md delete mode 100644 plugins/woocommerce-admin/docs/features/_sidebar.md delete mode 100644 plugins/woocommerce-admin/docs/index.html diff --git a/plugins/woocommerce-admin/docs/.nojekyll b/plugins/woocommerce-admin/docs/.nojekyll deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/woocommerce-admin/docs/_sidebar.md b/plugins/woocommerce-admin/docs/_sidebar.md deleted file mode 100644 index 64494ed4f0a..00000000000 --- a/plugins/woocommerce-admin/docs/_sidebar.md +++ /dev/null @@ -1,7 +0,0 @@ -- [Overview](/) -- [Components](components/) -- [Features](features/) -- [Data](data) -- [Layout](layout) -- [CSS Structure](stylesheets) -- [Examples](examples/) diff --git a/plugins/woocommerce-admin/docs/examples/_sidebar.md b/plugins/woocommerce-admin/docs/examples/_sidebar.md deleted file mode 100644 index f4b7a7ef134..00000000000 --- a/plugins/woocommerce-admin/docs/examples/_sidebar.md +++ /dev/null @@ -1,5 +0,0 @@ -* [Home](/) - -* [Examples](examples/) - - * [Activity Panel Inbox](examples/activity-panel-inbox.md) diff --git a/plugins/woocommerce-admin/docs/features/_sidebar.md b/plugins/woocommerce-admin/docs/features/_sidebar.md deleted file mode 100644 index 125acd6b675..00000000000 --- a/plugins/woocommerce-admin/docs/features/_sidebar.md +++ /dev/null @@ -1,6 +0,0 @@ -* [Home](/) - -* [Features](features/) - - * [Feature Flags](features/feature-flags.md) - * [Onboarding](features/onboarding.md) diff --git a/plugins/woocommerce-admin/docs/index.html b/plugins/woocommerce-admin/docs/index.html deleted file mode 100644 index bce714671e1..00000000000 --- a/plugins/woocommerce-admin/docs/index.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - WooCommerce Admin - - - - - - - - - - - - -
- - - - - - diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 26193f6ae93..de54c198e5d 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -198,7 +198,6 @@ "copy-webpack-plugin": "^10.2.4", "cross-env": "^7.0.3", "css-loader": "^6.7.0", - "docsify-cli": "^4.4.3", "eslint": "^8.10.0", "eslint-import-resolver-typescript": "^2.5.0", "eslint-import-resolver-webpack": "^0.13.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 386d5766141..a1ac8e52afa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1274,7 +1274,6 @@ importers: cross-env: ^7.0.3 css-loader: ^6.7.0 debug: ^4.3.3 - docsify-cli: ^4.4.3 dompurify: ^2.3.6 eslint: ^8.10.0 eslint-import-resolver-typescript: ^2.5.0 @@ -1473,7 +1472,6 @@ importers: copy-webpack-plugin: 10.2.4_webpack@5.70.0 cross-env: 7.0.3 css-loader: 6.7.1_webpack@5.70.0 - docsify-cli: 4.4.4 eslint: 8.11.0 eslint-import-resolver-typescript: 2.5.0_fe22d862ffeecaee86c93a006d59e41e eslint-import-resolver-webpack: 0.13.2_bac363bc2c2f46a65300020741b6cf5e @@ -8592,7 +8590,7 @@ packages: react-refresh: 0.11.0 schema-utils: 3.1.1 source-map: 0.7.3 - webpack: 4.46.0_webpack-cli@4.9.2 + webpack: 4.46.0_webpack-cli@3.3.12 dev: true /@pmmmwh/react-refresh-webpack-plugin/0.5.1_92cb4b81c6b9f71cf92f0bdb85e4210c: @@ -8786,11 +8784,6 @@ packages: lodash.merge: 4.6.2 postcss: 5.2.18 - /@sindresorhus/is/0.14.0: - resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} - engines: {node: '>=6'} - dev: true - /@sindresorhus/is/4.6.0: resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -8980,7 +8973,7 @@ packages: peerDependencies: '@storybook/addon-actions': '*' dependencies: - '@storybook/addon-actions': 6.4.19_react-dom@17.0.2+react@17.0.2 + '@storybook/addon-actions': 6.4.19 global: 4.4.0 dev: true @@ -11293,7 +11286,7 @@ packages: react-docgen-typescript: 2.2.2_typescript@4.6.2 tslib: 2.3.1 typescript: 4.6.2 - webpack: 4.46.0_webpack-cli@4.9.2 + webpack: 4.46.0_webpack-cli@3.3.12 transitivePeerDependencies: - supports-color dev: true @@ -11816,13 +11809,6 @@ packages: - supports-color dev: true - /@szmarczak/http-timer/1.1.2: - resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} - engines: {node: '>=6'} - dependencies: - defer-to-connect: 1.1.3 - dev: true - /@szmarczak/http-timer/4.0.6: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} @@ -17098,20 +17084,6 @@ packages: widest-line: 2.0.1 dev: true - /boxen/4.2.0: - resolution: {integrity: sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==} - engines: {node: '>=8'} - dependencies: - ansi-align: 3.0.1 - camelcase: 5.3.1 - chalk: 3.0.0 - cli-boxes: 2.2.1 - string-width: 4.2.3 - term-size: 2.2.1 - type-fest: 0.8.1 - widest-line: 3.1.0 - dev: true - /boxen/5.1.2: resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} engines: {node: '>=10'} @@ -17448,19 +17420,6 @@ packages: engines: {node: '>=10.6.0'} dev: false - /cacheable-request/6.1.0: - resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} - engines: {node: '>=8'} - dependencies: - clone-response: 1.0.2 - get-stream: 5.2.0 - http-cache-semantics: 4.1.0 - keyv: 3.1.0 - lowercase-keys: 2.0.0 - normalize-url: 4.5.1 - responselike: 1.0.2 - dev: true - /cacheable-request/7.0.2: resolution: {integrity: sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==} engines: {node: '>=8'} @@ -18059,6 +18018,7 @@ packages: resolution: {integrity: sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=} dependencies: mimic-response: 1.0.1 + dev: false /clone-stats/1.0.0: resolution: {integrity: sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=} @@ -18376,37 +18336,11 @@ packages: xdg-basedir: 3.0.0 dev: true - /configstore/5.0.1: - resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==} - engines: {node: '>=8'} - dependencies: - dot-prop: 5.3.0 - graceful-fs: 4.2.9 - make-dir: 3.1.0 - unique-string: 2.0.0 - write-file-atomic: 3.0.3 - xdg-basedir: 4.0.0 - dev: true - /connect-history-api-fallback/1.6.0: resolution: {integrity: sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==} engines: {node: '>=0.8'} dev: true - /connect-livereload/0.6.1: - resolution: {integrity: sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==} - dev: true - - /connect/3.7.0: - resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} - engines: {node: '>= 0.10.0'} - dependencies: - debug: 2.6.9 - finalhandler: 1.1.2 - parseurl: 1.3.3 - utils-merge: 1.0.1 - dev: true - /console-browserify/1.2.0: resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==} dev: true @@ -18731,11 +18665,6 @@ packages: engines: {node: '>=4'} dev: true - /crypto-random-string/2.0.0: - resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} - engines: {node: '>=8'} - dev: true - /css-color-function/1.3.3: resolution: {integrity: sha1-jtJMLAIFBzM5+voAS8jBQfzLKC4=} dependencies: @@ -19393,13 +19322,6 @@ packages: resolution: {integrity: sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=} engines: {node: '>=0.10'} - /decompress-response/3.3.0: - resolution: {integrity: sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=} - engines: {node: '>=4'} - dependencies: - mimic-response: 1.0.1 - dev: true - /decompress-response/6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -19473,10 +19395,6 @@ packages: dependencies: clone: 1.0.4 - /defer-to-connect/1.1.3: - resolution: {integrity: sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==} - dev: true - /defer-to-connect/2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} @@ -19706,62 +19624,6 @@ packages: buffer-indexof: 1.1.1 dev: true - /docsify-cli/4.4.4: - resolution: {integrity: sha512-NAZgg6b0BsDuq/Pe+P19Qb2J1d+ZVbS0eGkeCNxyu4F9/CQSsRqZqAvPJ9/0I+BCHn4sgftA2jluqhQVzKzrSA==} - engines: {node: '>= 10', npm: '>= 6'} - hasBin: true - dependencies: - chalk: 2.4.2 - connect: 3.7.0 - connect-history-api-fallback: 1.6.0 - connect-livereload: 0.6.1 - cp-file: 7.0.0 - docsify: 4.12.2 - docsify-server-renderer: 4.12.2 - enquirer: 2.3.6 - fs-extra: 8.1.0 - get-port: 5.1.1 - livereload: 0.9.3 - lru-cache: 5.1.1 - open: 6.4.0 - serve-static: 1.14.1 - update-notifier: 4.1.3 - yargonaut: 1.1.4 - yargs: 15.4.1 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - dev: true - - /docsify-server-renderer/4.12.2: - resolution: {integrity: sha512-/sCq0U0iGvc8mNN6VC5SeodiHUsA98rMsMFYXtQbWsS/jWArkSee8ATlH5KzGDJ/zjf9QOFrkjoanHCNaFWiPQ==} - dependencies: - debug: 4.3.3 - docsify: 4.12.2 - dompurify: 2.3.6 - node-fetch: 2.6.7 - resolve-pathname: 3.0.0 - transitivePeerDependencies: - - encoding - - supports-color - dev: true - - /docsify/4.12.2: - resolution: {integrity: sha512-hpRez5upcvkYigT2zD8P5kH5t9HpSWL8yn/ZU/g04/WfAfxVNW6CPUVOOF1EsQUDxTRuyNTFOb6uUv+tPij3tg==} - requiresBuild: true - dependencies: - dompurify: 2.3.6 - marked: 1.2.9 - medium-zoom: 1.0.6 - opencollective-postinstall: 2.0.3 - prismjs: 1.27.0 - strip-indent: 3.0.0 - tinydate: 1.3.0 - tweezer.js: 1.5.0 - dev: true - /doctrine/2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -19853,6 +19715,7 @@ packages: /dompurify/2.3.6: resolution: {integrity: sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==} + dev: false /domutils/1.7.0: resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} @@ -20391,11 +20254,6 @@ packages: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} - /escape-goat/2.1.1: - resolution: {integrity: sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==} - engines: {node: '>=8'} - dev: true - /escape-html/1.0.3: resolution: {integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=} dev: true @@ -22284,11 +22142,6 @@ packages: resolution: {integrity: sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==} dev: true - /figlet/1.5.2: - resolution: {integrity: sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==} - engines: {node: '>= 0.4.0'} - dev: true - /figures/1.7.0: resolution: {integrity: sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=} engines: {node: '>=0.10.0'} @@ -22331,7 +22184,7 @@ packages: dependencies: loader-utils: 2.0.2 schema-utils: 3.1.1 - webpack: 4.46.0_webpack-cli@4.9.2 + webpack: 4.46.0_webpack-cli@3.3.12 dev: true /file-loader/6.2.0_webpack@5.64.1: @@ -23099,11 +22952,6 @@ packages: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} - /get-port/5.1.1: - resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} - engines: {node: '>=8'} - dev: true - /get-stdin/4.0.1: resolution: {integrity: sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=} engines: {node: '>=0.10.0'} @@ -23306,13 +23154,6 @@ packages: ini: 1.3.8 dev: true - /global-dirs/2.1.0: - resolution: {integrity: sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==} - engines: {node: '>=8'} - dependencies: - ini: 1.3.7 - dev: true - /global-modules/0.2.3: resolution: {integrity: sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=} engines: {node: '>=0.10.0'} @@ -23541,23 +23382,6 @@ packages: url-parse-lax: 1.0.0 dev: true - /got/9.6.0: - resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==} - engines: {node: '>=8.6'} - dependencies: - '@sindresorhus/is': 0.14.0 - '@szmarczak/http-timer': 1.1.2 - cacheable-request: 6.1.0 - decompress-response: 3.3.0 - duplexer3: 0.1.4 - get-stream: 4.1.0 - lowercase-keys: 1.0.1 - mimic-response: 1.0.1 - p-cancelable: 1.1.0 - to-readable-stream: 1.0.0 - url-parse-lax: 3.0.0 - dev: true - /graceful-fs/4.2.8: resolution: {integrity: sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==} @@ -23933,11 +23757,6 @@ packages: is-number: 3.0.0 kind-of: 4.0.0 - /has-yarn/2.1.0: - resolution: {integrity: sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==} - engines: {node: '>=8'} - dev: true - /has/1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} @@ -24205,7 +24024,7 @@ packages: pretty-error: 2.1.2 tapable: 1.1.3 util.promisify: 1.0.0 - webpack: 4.46.0_webpack-cli@4.9.2 + webpack: 4.46.0_webpack-cli@3.3.12 dev: true /html-webpack-plugin/5.5.0_webpack@5.70.0: @@ -24645,10 +24464,6 @@ packages: /inherits/2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - /ini/1.3.7: - resolution: {integrity: sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==} - dev: true - /ini/1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} @@ -25096,14 +24911,6 @@ packages: is-path-inside: 1.0.1 dev: true - /is-installed-globally/0.3.2: - resolution: {integrity: sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==} - engines: {node: '>=8'} - dependencies: - global-dirs: 2.1.0 - is-path-inside: 3.0.3 - dev: true - /is-interactive/1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} @@ -25143,11 +24950,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /is-npm/4.0.0: - resolution: {integrity: sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==} - engines: {node: '>=8'} - dev: true - /is-number-object/1.0.6: resolution: {integrity: sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==} engines: {node: '>= 0.4'} @@ -25388,10 +25190,6 @@ packages: dependencies: is-docker: 2.2.1 - /is-yarn-global/0.3.0: - resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==} - dev: true - /is/3.3.0: resolution: {integrity: sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==} dev: false @@ -27878,10 +27676,6 @@ packages: engines: {node: '>=4'} hasBin: true - /json-buffer/3.0.0: - resolution: {integrity: sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=} - dev: true - /json-buffer/3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} dev: false @@ -27987,12 +27781,6 @@ packages: resolution: {integrity: sha512-X00TokkRIDotUIf3EV4xUm6ELc/IkqhS/vPSHdWnsM5y0HoNMfEqrazizI7g78lpHvnRSRt/PFfKtRqJCOGIuQ==} dev: true - /keyv/3.1.0: - resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} - dependencies: - json-buffer: 3.0.0 - dev: true - /keyv/4.1.1: resolution: {integrity: sha512-tGv1yP6snQVDSM4X6yxrv2zzq/EvpW+oYiUz6aueW1u9CtS8RzUQYxxmFwgZlO2jSgCxQbchhxaqXXp2hnKGpQ==} dependencies: @@ -28066,13 +27854,6 @@ packages: package-json: 4.0.1 dev: true - /latest-version/5.1.0: - resolution: {integrity: sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==} - engines: {node: '>=8'} - dependencies: - package-json: 6.5.0 - dev: true - /lazy-cache/0.2.7: resolution: {integrity: sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=} engines: {node: '>=0.10.0'} @@ -28237,24 +28018,6 @@ packages: resolution: {integrity: sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==} dev: true - /livereload-js/3.3.3: - resolution: {integrity: sha512-a7Jipme3XIBIryJluWP5LQrEAvhobDPyScBe+q+MYwxBiMT2Ck7msy4tAdF8TAa33FMdJqX4guP81Yhiu6BkmQ==} - dev: true - - /livereload/0.9.3: - resolution: {integrity: sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw==} - engines: {node: '>=8.0.0'} - hasBin: true - dependencies: - chokidar: 3.5.3 - livereload-js: 3.3.3 - opts: 2.0.2 - ws: 7.5.5 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: true - /load-json-file/1.1.0: resolution: {integrity: sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=} engines: {node: '>=0.10.0'} @@ -28573,6 +28336,7 @@ packages: /lowercase-keys/2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} + dev: false /lowlight/1.20.0: resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} @@ -28803,12 +28567,6 @@ packages: hasBin: true dev: true - /marked/1.2.9: - resolution: {integrity: sha512-H8lIX2SvyitGX+TRdtS06m1jHMijKN/XjfH6Ooii9fvxMlh8QdqBfBDkGUpMWH2kQNrtixjzYUa3SH8ROTgRRw==} - engines: {node: '>= 8.16.2'} - hasBin: true - dev: true - /mathml-tag-names/2.1.3: resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} dev: true @@ -28908,10 +28666,6 @@ packages: engines: {node: '>= 0.6'} dev: true - /medium-zoom/1.0.6: - resolution: {integrity: sha512-UdiUWfvz9fZMg1pzf4dcuqA0W079o0mpqbTnOz5ip4VGYX96QjmbM+OgOU/0uOzAytxC0Ny4z+VcYQnhdifimg==} - dev: true - /mem-fs-editor/9.4.0_mem-fs@2.2.1: resolution: {integrity: sha512-HSSOLSVRrsDdui9I6i96dDtG+oAez/4EB2g4cjSrNhgNQ3M+L57/+22NuPdORSoxvOHjIg/xeOE+C0wwF91D2g==} engines: {node: '>=12.10.0'} @@ -29188,6 +28942,7 @@ packages: /mimic-response/1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} + dev: false /mimic-response/3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} @@ -29871,11 +29626,6 @@ packages: resolution: {integrity: sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==} engines: {node: '>=6'} - /normalize-url/4.5.1: - resolution: {integrity: sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==} - engines: {node: '>=8'} - dev: true - /normalize-url/6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} @@ -30260,13 +30010,6 @@ packages: dependencies: mimic-fn: 2.1.0 - /open/6.4.0: - resolution: {integrity: sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==} - engines: {node: '>=8'} - dependencies: - is-wsl: 1.1.0 - dev: true - /open/7.4.2: resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} engines: {node: '>=8'} @@ -30284,11 +30027,6 @@ packages: is-wsl: 2.2.0 dev: true - /opencollective-postinstall/2.0.3: - resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==} - hasBin: true - dev: true - /opener/1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -30324,10 +30062,6 @@ packages: word-wrap: 1.2.3 dev: true - /opts/2.0.2: - resolution: {integrity: sha512-k41FwbcLnlgnFh69f4qdUfvDQ+5vaSDnVPFI/y5XuhKRq97EnVVneO9F1ESVCdiVu4fCS2L8usX3mU331hB7pg==} - dev: true - /ora/5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} @@ -30380,11 +30114,6 @@ packages: p-map: 2.1.0 dev: true - /p-cancelable/1.1.0: - resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} - engines: {node: '>=6'} - dev: true - /p-cancelable/2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} @@ -30544,16 +30273,6 @@ packages: semver: 5.7.1 dev: true - /package-json/6.5.0: - resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} - engines: {node: '>=8'} - dependencies: - got: 9.6.0 - registry-auth-token: 4.2.1 - registry-url: 5.1.0 - semver: 6.3.0 - dev: true - /pacote/12.0.3: resolution: {integrity: sha512-CdYEl03JDrRO3x18uHjBYA9TyoW8gy+ThVcypcDkxPtKlw76e4ejhYB6i9lJ+/cebbjpqPW/CijjqxwDTts8Ow==} engines: {node: ^12.13.0 || ^14.15.0 || >=16} @@ -30617,11 +30336,6 @@ packages: dependencies: callsites: 3.1.0 - /parent-require/1.0.0: - resolution: {integrity: sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=} - engines: {node: '>= 0.4.0'} - dev: true - /parse-asn1/5.1.6: resolution: {integrity: sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==} dependencies: @@ -31288,7 +31002,7 @@ packages: postcss: 7.0.39 schema-utils: 3.1.1 semver: 7.3.5 - webpack: 4.46.0_webpack-cli@4.9.2 + webpack: 4.46.0_webpack-cli@3.3.12 dev: true /postcss-loader/6.2.0_postcss@8.3.0+webpack@5.64.1: @@ -32280,11 +31994,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /prepend-http/2.0.0: - resolution: {integrity: sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=} - engines: {node: '>=4'} - dev: true - /prettier-linter-helpers/1.0.0: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} @@ -32544,13 +32253,6 @@ packages: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} - /pupa/2.1.1: - resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} - engines: {node: '>=8'} - dependencies: - escape-goat: 2.1.1 - dev: true - /puppeteer-core/1.12.2: resolution: {integrity: sha512-M+atMV5e+MwJdR+OwQVZ1xqAIwh3Ou4nUxNuf334GwpcLG+LDj5BwIph4J9y8YAViByRtWGL+uF8qX2Ggzb+Fg==} engines: {node: '>=6.4.0'} @@ -32761,7 +32463,7 @@ packages: dependencies: loader-utils: 2.0.2 schema-utils: 3.1.1 - webpack: 4.46.0_webpack-cli@4.9.2 + webpack: 4.46.0_webpack-cli@3.3.12 dev: true /raw-loader/4.0.2_webpack@5.64.1: @@ -33993,13 +33695,6 @@ packages: safe-buffer: 5.2.1 dev: true - /registry-auth-token/4.2.1: - resolution: {integrity: sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==} - engines: {node: '>=6.0.0'} - dependencies: - rc: 1.2.8 - dev: true - /registry-url/3.1.0: resolution: {integrity: sha1-PU74cPc93h138M+aOBQyRE4XSUI=} engines: {node: '>=0.10.0'} @@ -34007,13 +33702,6 @@ packages: rc: 1.2.8 dev: true - /registry-url/5.1.0: - resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==} - engines: {node: '>=8'} - dependencies: - rc: 1.2.8 - dev: true - /regjsgen/0.5.2: resolution: {integrity: sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==} @@ -34332,6 +34020,7 @@ packages: /resolve-pathname/3.0.0: resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==} + dev: false /resolve-url/0.2.1: resolution: {integrity: sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=} @@ -34357,12 +34046,6 @@ packages: is-core-module: 2.8.0 path-parse: 1.0.7 - /responselike/1.0.2: - resolution: {integrity: sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=} - dependencies: - lowercase-keys: 1.0.1 - dev: true - /responselike/2.0.0: resolution: {integrity: sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==} dependencies: @@ -34853,13 +34536,6 @@ packages: semver: 5.7.1 dev: true - /semver-diff/3.1.1: - resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==} - engines: {node: '>=8'} - dependencies: - semver: 6.3.0 - dev: true - /semver/5.7.1: resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} hasBin: true @@ -35743,7 +35419,7 @@ packages: dependencies: loader-utils: 2.0.2 schema-utils: 2.7.1 - webpack: 4.46.0_webpack-cli@4.9.2 + webpack: 4.46.0_webpack-cli@3.3.12 dev: true /style-loader/2.0.0_webpack@5.70.0: @@ -36409,11 +36085,6 @@ packages: execa: 0.7.0 dev: true - /term-size/2.2.1: - resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} - engines: {node: '>=8'} - dev: true - /terminal-link/2.1.1: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} @@ -36489,7 +36160,7 @@ packages: serialize-javascript: 5.0.1 source-map: 0.6.1 terser: 5.10.0_acorn@7.4.1 - webpack: 4.46.0_webpack-cli@4.9.2 + webpack: 4.46.0_webpack-cli@3.3.12 webpack-sources: 1.4.3 transitivePeerDependencies: - acorn @@ -36509,7 +36180,7 @@ packages: serialize-javascript: 5.0.1 source-map: 0.6.1 terser: 5.10.0 - webpack: 4.46.0_webpack-cli@4.9.2 + webpack: 4.46.0_webpack-cli@3.3.12 webpack-sources: 1.4.3 transitivePeerDependencies: - acorn @@ -36813,11 +36484,6 @@ packages: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} dev: false - /tinydate/1.3.0: - resolution: {integrity: sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w==} - engines: {node: '>=4'} - dev: true - /title-case/1.1.2: resolution: {integrity: sha1-+uSmrlRr+iLQg6DuqRCkDRLtT1o=} dependencies: @@ -36867,11 +36533,6 @@ packages: dependencies: kind-of: 3.2.2 - /to-readable-stream/1.0.0: - resolution: {integrity: sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==} - engines: {node: '>=6'} - dev: true - /to-regex-range/2.1.1: resolution: {integrity: sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=} engines: {node: '>=0.10.0'} @@ -37280,10 +36941,6 @@ packages: /tweetnacl/0.14.5: resolution: {integrity: sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=} - /tweezer.js/1.5.0: - resolution: {integrity: sha512-aSiJz7rGWNAQq7hjMK9ZYDuEawXupcCWgl3woQQSoDP2Oh8O4srWb/uO1PzzHIsrPEOqrjJ2sUb9FERfzuBabQ==} - dev: true - /type-check/0.3.2: resolution: {integrity: sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=} engines: {node: '>= 0.8.0'} @@ -37519,13 +37176,6 @@ packages: crypto-random-string: 1.0.0 dev: true - /unique-string/2.0.0: - resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} - engines: {node: '>=8'} - dependencies: - crypto-random-string: 2.0.0 - dev: true - /unist-builder/2.0.3: resolution: {integrity: sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==} dev: true @@ -37665,25 +37315,6 @@ packages: xdg-basedir: 3.0.0 dev: true - /update-notifier/4.1.3: - resolution: {integrity: sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==} - engines: {node: '>=8'} - dependencies: - boxen: 4.2.0 - chalk: 3.0.0 - configstore: 5.0.1 - has-yarn: 2.1.0 - import-lazy: 2.1.0 - is-ci: 2.0.0 - is-installed-globally: 0.3.2 - is-npm: 4.0.0 - is-yarn-global: 0.3.0 - latest-version: 5.1.0 - pupa: 2.1.1 - semver-diff: 3.1.1 - xdg-basedir: 4.0.0 - dev: true - /upper-case-first/1.1.2: resolution: {integrity: sha1-XXm+3P8UQZUY/S7bCgUHybaFkRU=} dependencies: @@ -37754,7 +37385,7 @@ packages: loader-utils: 2.0.2 mime-types: 2.1.34 schema-utils: 3.1.1 - webpack: 4.46.0_webpack-cli@4.9.2 + webpack: 4.46.0_webpack-cli@3.3.12 dev: true /url-parse-lax/1.0.0: @@ -37764,13 +37395,6 @@ packages: prepend-http: 1.0.4 dev: true - /url-parse-lax/3.0.0: - resolution: {integrity: sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=} - engines: {node: '>=4'} - dependencies: - prepend-http: 2.0.0 - dev: true - /url/0.10.3: resolution: {integrity: sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=} dependencies: @@ -38302,7 +37926,7 @@ packages: mime: 2.5.2 mkdirp: 0.5.5 range-parser: 1.2.1 - webpack: 4.46.0_webpack-cli@4.9.2 + webpack: 4.46.0_webpack-cli@3.3.12 webpack-log: 2.0.0 dev: true @@ -38385,7 +38009,7 @@ packages: peerDependencies: webpack: ^2.0.0 || ^3.0.0 || ^4.0.0 dependencies: - webpack: 4.46.0_webpack-cli@4.9.2 + webpack: 4.46.0_webpack-cli@3.3.12 dev: true /webpack-fix-style-only-entries/0.6.1: @@ -38569,7 +38193,7 @@ packages: tapable: 1.1.3 terser-webpack-plugin: 1.4.5_webpack@4.46.0 watchpack: 1.7.5 - webpack-cli: 3.3.12_webpack@4.46.0 + webpack-cli: 3.3.12_webpack@5.70.0 webpack-sources: 1.4.3 dev: true @@ -39078,11 +38702,6 @@ packages: engines: {node: '>=4'} dev: true - /xdg-basedir/4.0.0: - resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} - engines: {node: '>=8'} - dev: true - /xml-name-validator/3.0.0: resolution: {integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==} @@ -39140,14 +38759,6 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - /yargonaut/1.1.4: - resolution: {integrity: sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==} - dependencies: - chalk: 1.1.3 - figlet: 1.5.2 - parent-require: 1.0.0 - dev: true - /yargs-parser/13.1.2: resolution: {integrity: sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==} dependencies: From 7f55756ffe6ca321b2fd86ac47c3857713614e79 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 14 Apr 2022 16:24:31 +0800 Subject: [PATCH 357/386] Add changelog --- plugins/woocommerce/changelog/remove-admin-tests-folder | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 plugins/woocommerce/changelog/remove-admin-tests-folder diff --git a/plugins/woocommerce/changelog/remove-admin-tests-folder b/plugins/woocommerce/changelog/remove-admin-tests-folder new file mode 100644 index 00000000000..7d8c33d848d --- /dev/null +++ b/plugins/woocommerce/changelog/remove-admin-tests-folder @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: Simply remove woocommerce-admin tests folder + + From c813c3bc5f8749898df4ab8a9a00b18c776e9b2a Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 14 Apr 2022 16:40:35 +0800 Subject: [PATCH 358/386] Remove "plugin" environment from admin feature plugin --- .../docs/features/feature-flags.md | 7 +++-- .../bin/generate-feature-config.php | 5 ++-- .../client/admin/config/plugin.json | 27 ------------------- 3 files changed, 5 insertions(+), 34 deletions(-) delete mode 100644 plugins/woocommerce/client/admin/config/plugin.json diff --git a/plugins/woocommerce-admin/docs/features/feature-flags.md b/plugins/woocommerce-admin/docs/features/feature-flags.md index 5b1a98cac19..dac1f658f7d 100644 --- a/plugins/woocommerce-admin/docs/features/feature-flags.md +++ b/plugins/woocommerce-admin/docs/features/feature-flags.md @@ -1,14 +1,13 @@ # Feature Flags -Features inside the `woocommerce-admin` repository can be in various states of completeness. In addition to the development copy of `woocommerce-admin`, feature plugin versions are bundled, and code is merged to WooCommerce core. To provide a way for improved control over how these features are released in these different environments, `woocommerce-admin` has a system for feature flags. +Features inside the `woocommerce` repository can be in various states of completeness. To provide a way for improved control over how these features are released in these different environments, `woocommerce` has a system for feature flags. We currently support the following environments: | Environment | Description | |-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| development | Development - All features should be enabled in development. These flags are also used in both JS and PHP tests. Ran using `pnpm start`. | -| plugin | Plugin - A packaged release of the featured plugin, for GitHub WordPress.org. Ran using `pnpm run-script build:release`. | | -| core | Core - assets/files ready and stable enough for core merge. Ran using `pnpm pack`. (@todo update this with publish command). +| development | Development - All features should be enabled in development. These flags are also used in both JS and PHP tests. Ran using `pnpm start`. | | +| core | Core - assets/files ready and stable enough. Ran using `WC_ADMIN_PHASE=core pnpm build` & `pnpm pack`. ## Adding a new flag diff --git a/plugins/woocommerce/bin/generate-feature-config.php b/plugins/woocommerce/bin/generate-feature-config.php index 0ab6248a35c..ffe637c880b 100644 --- a/plugins/woocommerce/bin/generate-feature-config.php +++ b/plugins/woocommerce/bin/generate-feature-config.php @@ -8,14 +8,13 @@ /** * Get phase for feature flags * - development: All features should be enabled in development. - * - plugin: For the standalone feature plugin, for GitHub and WordPress.org. * - core: Stable features for WooCommerce core merge. */ $phase = getenv( 'WC_ADMIN_PHASE' ); -if ( ! in_array( $phase, array( 'development', 'plugin', 'core' ), true ) ) { - $phase = 'plugin'; // Default to plugin when running `pnpm run build`. +if ( ! in_array( $phase, array( 'development', 'core' ), true ) ) { + $phase = 'core'; // Default to core when running `pnpm run build`. } $config_json = file_get_contents( __DIR__ . '/../client/admin/config/' . $phase . '.json' ); $config = json_decode( $config_json ); diff --git a/plugins/woocommerce/client/admin/config/plugin.json b/plugins/woocommerce/client/admin/config/plugin.json deleted file mode 100644 index c531800be95..00000000000 --- a/plugins/woocommerce/client/admin/config/plugin.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "features": { - "activity-panels": true, - "analytics": true, - "coupons": true, - "customer-effort-score-tracks": true, - "homescreen": true, - "marketing": true, - "minified-js": true, - "mobile-app-banner": true, - "navigation": true, - "onboarding": true, - "onboarding-tasks": true, - "remote-inbox-notifications": true, - "remote-free-extensions": true, - "payment-gateway-suggestions": true, - "settings": false, - "shipping-label-banner": true, - "subscriptions": true, - "store-alerts": true, - "transient-notices": true, - "wc-pay-promotion": true, - "wc-pay-welcome-page": true, - "tasklist-setup-experiment-1": false, - "tasklist-setup-experiment-2": false - } -} From e6458fafe093c89574aaf393c7da24917259481c Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 14 Apr 2022 17:08:13 +0800 Subject: [PATCH 359/386] Standardize image path --- .../DefaultPaymentGateways.php | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php index d6ce06fb493..31b7556793e 100644 --- a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php +++ b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php @@ -25,8 +25,8 @@ class DefaultPaymentGateways { 'id' => 'payfast', 'title' => __( 'PayFast', 'woocommerce' ), 'content' => __( 'The PayFast extension for WooCommerce enables you to accept payments by Credit Card and EFT via one of South Africa’s most popular payment gateways. No setup fees or monthly subscription costs. Selecting this extension will configure your store to use South African rands as the selected currency.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payfast.png', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/payfast.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/payfast.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/payfast.png', 'plugins' => array( 'woocommerce-payfast-gateway' ), 'is_visible' => array( self::get_rules_for_countries( array( 'ZA', 'GH', 'NG' ) ), @@ -39,8 +39,8 @@ class DefaultPaymentGateways { 'id' => 'stripe', 'title' => __( ' Stripe', 'woocommerce' ), 'content' => __( 'Accept debit and credit cards in 135+ currencies, methods such as Alipay, and one-touch checkout with Apple Pay.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/stripe.png', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/stripe.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/stripe.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/stripe.png', 'plugins' => array( 'woocommerce-gateway-stripe' ), 'is_visible' => array( // https://stripe.com/global. @@ -58,7 +58,7 @@ class DefaultPaymentGateways { 'title' => __( 'Paystack', 'woocommerce' ), 'content' => __( 'Paystack helps African merchants accept one-time and recurring payments online with a modern, safe, and secure payment gateway.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/paystack.png', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/paystack.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/paystack.png', 'plugins' => array( 'woo-paystack' ), 'is_visible' => array( self::get_rules_for_countries( array( 'ZA', 'GH', 'NG' ) ), @@ -71,8 +71,8 @@ class DefaultPaymentGateways { 'id' => 'kco', 'title' => __( 'Klarna Checkout', 'woocommerce' ), 'content' => __( 'Choose the payment that you want, pay now, pay later or slice it. No credit card numbers, no passwords, no worries.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/klarna-black.png', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/klarna.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/klarna-black.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/klarna.png', 'plugins' => array( 'klarna-checkout-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( array( 'SE', 'FI', 'NO' ) ), @@ -85,8 +85,8 @@ class DefaultPaymentGateways { 'id' => 'klarna_payments', 'title' => __( 'Klarna Payments', 'woocommerce' ), 'content' => __( 'Choose the payment that you want, pay now, pay later or slice it. No credit card numbers, no passwords, no worries.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/klarna-black.png', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/klarna.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/klarna-black.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/klarna.png', 'plugins' => array( 'klarna-payments-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( @@ -102,7 +102,7 @@ class DefaultPaymentGateways { 'title' => __( 'Mollie', 'woocommerce' ), 'content' => __( 'Effortless payments by Mollie: Offer global and local payment methods, get onboarded in minutes, and supported in your language.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/mollie.svg', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/mollie.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/mollie.png', 'plugins' => array( 'mollie-payments-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( @@ -117,7 +117,7 @@ class DefaultPaymentGateways { 'title' => __( 'Mercado Pago Checkout Pro & Custom', 'woocommerce' ), 'content' => __( 'Accept credit and debit cards, offline (cash or bank transfer) and logged-in payments with money in Mercado Pago. Safe and secure payments with the leading payment processor in LATAM.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/mercadopago.png', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/mercadopago.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/mercadopago.png', 'plugins' => array( 'woocommerce-mercadopago' ), 'is_visible' => array( self::get_rules_for_countries( array( 'AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY' ) ), @@ -131,8 +131,8 @@ class DefaultPaymentGateways { 'id' => 'ppcp-gateway', 'title' => __( 'PayPal Payments', 'woocommerce' ), 'content' => __( "Safe and secure payments using credit cards or your customer's PayPal account.", 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/paypal.png', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/paypal.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/paypal.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/paypal.png', 'plugins' => array( 'woocommerce-paypal-payments' ), 'is_visible' => array( (object) array( @@ -150,7 +150,7 @@ class DefaultPaymentGateways { 'title' => __( 'Cash on delivery', 'woocommerce' ), 'content' => __( 'Take payments in cash upon delivery.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/cod.svg', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/cod.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/cod.png', 'is_visible' => array( self::get_rules_for_cbd( false ), ), @@ -161,7 +161,7 @@ class DefaultPaymentGateways { 'title' => __( 'Direct bank transfer', 'woocommerce' ), 'content' => __( 'Take payments via bank transfer.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/bacs.svg', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/bacs.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/bacs.png', 'is_visible' => array( self::get_rules_for_cbd( false ), ), @@ -175,7 +175,7 @@ class DefaultPaymentGateways { 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', - 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', 'plugins' => array( 'woocommerce-payments' ), 'description' => 'With WooCommerce Payments, you can securely accept major cards, Apple Pay, and payments in over 100 currencies. Track cash flow and manage recurring revenue directly from your store’s dashboard - with no setup costs or monthly fees.', 'is_visible' => array( @@ -218,7 +218,7 @@ class DefaultPaymentGateways { 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', - 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', 'plugins' => array( 'woocommerce-payments' ), 'description' => 'With WooCommerce Payments, you can securely accept major cards, Apple Pay, and payments in over 100 currencies. Track cash flow and manage recurring revenue directly from your store’s dashboard - with no setup costs or monthly fees.', 'is_visible' => array( @@ -253,7 +253,7 @@ class DefaultPaymentGateways { 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', - 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', 'plugins' => array( 'woocommerce-payments' ), 'description' => 'With WooCommerce Payments, you can securely accept major cards, Apple Pay, and payments in over 100 currencies – with no setup costs or monthly fees – and you can now accept in-person payments with the Woo mobile app.', 'is_visible' => array( @@ -285,7 +285,7 @@ class DefaultPaymentGateways { 'title' => __( 'Razorpay', 'woocommerce' ), 'content' => __( 'The official Razorpay extension for WooCommerce allows you to accept credit cards, debit cards, netbanking, wallet, and UPI payments.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/razorpay.svg', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/razorpay.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/razorpay.png', 'plugins' => array( 'woo-razorpay' ), 'is_visible' => array( (object) array( @@ -303,7 +303,7 @@ class DefaultPaymentGateways { 'title' => __( 'PayU for WooCommerce', 'woocommerce' ), 'content' => __( 'Enable PayU’s exclusive plugin for WooCommerce to start accepting payments in 100+ payment methods available in India including credit cards, debit cards, UPI, & more!', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/payu.svg', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/payu.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/payu.png', 'plugins' => array( 'payu-india' ), 'is_visible' => array( (object) array( @@ -321,7 +321,7 @@ class DefaultPaymentGateways { 'title' => __( 'Eway', 'woocommerce' ), 'content' => __( 'The Eway extension for WooCommerce allows you to take credit card payments directly on your store without redirecting your customers to a third party site to make payment.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/eway.png', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/eway.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/eway.png', 'plugins' => array( 'woocommerce-gateway-eway' ), 'is_visible' => array( self::get_rules_for_countries( array( 'AU', 'NZ' ) ), @@ -334,8 +334,8 @@ class DefaultPaymentGateways { 'id' => 'square_credit_card', 'title' => __( 'Square', 'woocommerce' ), 'content' => __( 'Securely accept credit and debit cards with one low rate, no surprise fees (custom rates available). Sell online and in store and track sales and inventory in one place.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/square-black.png', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/square.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/square-black.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/square.png', 'plugins' => array( 'woocommerce-square' ), 'is_visible' => array( (object) array( @@ -359,8 +359,8 @@ class DefaultPaymentGateways { 'id' => 'afterpay', 'title' => __( 'Afterpay', 'woocommerce' ), 'content' => __( 'Afterpay allows customers to receive products immediately and pay for purchases over four installments, always interest-free.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/afterpay.png', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/afterpay.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/afterpay.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/afterpay.png', 'plugins' => array( 'afterpay-gateway-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( array( 'US', 'CA' ) ), @@ -372,8 +372,8 @@ class DefaultPaymentGateways { 'id' => 'amazon_payments_advanced', 'title' => __( 'Amazon Pay', 'woocommerce' ), 'content' => __( 'Enable a familiar, fast checkout for hundreds of millions of active Amazon customers globally.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/amazonpay.png', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/amazonpay.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/amazonpay.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/amazonpay.png', 'plugins' => array( 'woocommerce-gateway-amazon-payments-advanced' ), 'is_visible' => array( self::get_rules_for_countries( array( 'US', 'CA' ) ), @@ -385,8 +385,8 @@ class DefaultPaymentGateways { 'id' => 'affirm', 'title' => __( 'Affirm', 'woocommerce' ), 'content' => __( 'Affirm’s tailored Buy Now Pay Later programs remove price as a barrier, turning browsers into buyers, increasing average order value, and expanding your customer base.', 'woocommerce' ), - 'image' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/affirm.png', - 'image_72x72' => WC()->plugin_url() . '/assets/images/payment_methods/72x72/affirm.png', + 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/affirm.png', + 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/affirm.png', 'plugins' => array(), 'external_link' => 'https://woocommerce.com/products/woocommerce-gateway-affirm', 'is_visible' => array( From 3668ea36c5ef73b8282bd28cbab98c8c3a83915d Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 14 Apr 2022 17:09:10 +0800 Subject: [PATCH 360/386] Small refactor to consolidate logic --- .../fills/PaymentGatewaySuggestions/index.js | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js index 53997144cee..fdadacde358 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js @@ -35,6 +35,18 @@ const SEE_MORE_LINK = const comparePaymentGatewaysByPriority = ( a, b ) => a.recommendation_priority - b.recommendation_priority; +const isGatewayWCPay = ( gateway ) => + gateway.plugins?.length === 1 && + gateway.plugins[ 0 ] === 'woocommerce-payments'; + +const isGatewayOtherCategory = ( gateway, countryCode ) => + gateway.category_other && + gateway.category_other.indexOf( countryCode ) !== -1; + +const isGatewayAdditionalCategory = ( gateway, countryCode ) => + gateway.category_additional && + gateway.category_additional.indexOf( countryCode ) !== -1; + export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { const { updatePaymentGateway } = useDispatch( PAYMENT_GATEWAYS_STORE_NAME ); const { @@ -197,17 +209,11 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { continue; } - if ( - gateway.plugins?.length === 1 && - gateway.plugins[ 0 ] === 'woocommerce-payments' - ) { + if ( isGatewayWCPay( gateway ) ) { return true; } - if ( - gateway.category_other && - gateway.category_other.indexOf( countryCode ) !== -1 - ) { + if ( isGatewayOtherCategory( gateway, countryCode ) ) { return true; } } @@ -215,12 +221,8 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { }, [ countryCode, paymentGateways ] ); const isWCPaySupported = - Array.from( paymentGateways.values() ).findIndex( ( gateway ) => { - return ( - gateway.plugins?.length === 1 && - gateway.plugins[ 0 ] === 'woocommerce-payments' - ); - } ) !== -1; + Array.from( paymentGateways.values() ).findIndex( isGatewayWCPay ) !== + -1; const [ wcPayGateway, offlineGateways, additionalGateways ] = useMemo( () => @@ -243,8 +245,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { // WCPay is handled separately when not installed and configured if ( - gateway.plugins?.length === 1 && - gateway.plugins[ 0 ] === 'woocommerce-payments' && + isGatewayWCPay( gateway ) && ! ( gateway.installed && ! gateway.needsSetup ) ) { wcPay.push( gateway ); @@ -256,10 +257,10 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { // If WCPay or "other" gateway is enabled in an WCPay-eligible country, only // allow to list "additional" gateways. if ( - gateway.category_additional && - gateway.category_additional.indexOf( + isGatewayAdditionalCategory( + gateway, countryCode - ) !== -1 + ) ) { additional.push( gateway ); } @@ -267,8 +268,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { // When WCPay-ineligible, just show all gateways. additional.push( gateway ); } else if ( - gateway.category_other && - gateway.category_other.indexOf( countryCode ) !== -1 + isGatewayOtherCategory( gateway, countryCode ) ) { // When nothing is set up and eligible for WCPay, only show "other" gateways. additional.push( gateway ); From 30a2fc18d611151cb4c441814473f71185ef1010 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 14 Apr 2022 17:13:23 +0800 Subject: [PATCH 361/386] Pass WC_ADMIN_PHASE=core to build commands --- plugins/woocommerce-admin/package.json | 2 +- plugins/woocommerce/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 6be60f4ffc8..8ecb2e9cb57 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -11,7 +11,7 @@ "scripts": { "analyze": "cross-env NODE_ENV=production ANALYZE=true webpack", "prebuild": "pnpm run install-if-deps-outdated", - "build": "pnpm run build:feature-config && cross-env NODE_ENV=production webpack", + "build": "pnpm run build:feature-config && cross-env NODE_ENV=production WC_ADMIN_PHASE=core webpack", "build-storybook": "build-storybook -c ./storybook/.storybook", "build:feature-config": "php ../woocommerce/bin/generate-feature-config.php", "build:packages": "cross-env NODE_ENV=production pnpm run:packages -- build", diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index cec666d55a8..ac076b7e508 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -15,7 +15,7 @@ "preinstall": "npx only-allow pnpm", "build": "./bin/build-zip.sh", "build:feature-config": "php bin/generate-feature-config.php", - "build:core": "pnpm run build:feature-config && pnpm nx build woocommerce-admin && pnpm nx build woocommerce-legacy-assets && pnpm run makepot", + "build:core": "WC_ADMIN_PHASE=core pnpm run build:feature-config && pnpm nx build woocommerce-admin && pnpm nx build woocommerce-legacy-assets && pnpm run makepot", "build:zip": "pnpm run build", "lint:js": "eslint assets/js --ext=js", "docker:down": "pnpx wc-e2e docker:down", From b936e3243f1297dc5acc06f05395b5b8a8874976 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 14 Apr 2022 17:15:12 +0800 Subject: [PATCH 362/386] Update feature-flags.md --- plugins/woocommerce-admin/docs/features/feature-flags.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/docs/features/feature-flags.md b/plugins/woocommerce-admin/docs/features/feature-flags.md index dac1f658f7d..199e9afc090 100644 --- a/plugins/woocommerce-admin/docs/features/feature-flags.md +++ b/plugins/woocommerce-admin/docs/features/feature-flags.md @@ -7,7 +7,7 @@ We currently support the following environments: | Environment | Description | |-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | development | Development - All features should be enabled in development. These flags are also used in both JS and PHP tests. Ran using `pnpm start`. | | -| core | Core - assets/files ready and stable enough. Ran using `WC_ADMIN_PHASE=core pnpm build` & `pnpm pack`. +| core | Core - assets/files ready and stable enough. Ran using `pnpm build` & `pnpm pack`. ## Adding a new flag From 075be718e99295c0460ce2b2b7ab1952c1740c7d Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 14 Apr 2022 17:23:50 +0800 Subject: [PATCH 363/386] Add changelog --- plugins/woocommerce/changelog/remove-admin-feature-plugin-env | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/remove-admin-feature-plugin-env diff --git a/plugins/woocommerce/changelog/remove-admin-feature-plugin-env b/plugins/woocommerce/changelog/remove-admin-feature-plugin-env new file mode 100644 index 00000000000..53ced9ee0a1 --- /dev/null +++ b/plugins/woocommerce/changelog/remove-admin-feature-plugin-env @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Pass `WC_ADMIN_PHASE=core` to build commands & remove "plugin" env From 5a74e9cba6d673ee9d4f973fde10a0b66bd0830d Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 14 Apr 2022 17:25:29 +0800 Subject: [PATCH 364/386] Add WC_ADMIN_PHASE=core to build:feature-config --- plugins/woocommerce-admin/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 8ecb2e9cb57..0cc7f181a3d 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -11,7 +11,7 @@ "scripts": { "analyze": "cross-env NODE_ENV=production ANALYZE=true webpack", "prebuild": "pnpm run install-if-deps-outdated", - "build": "pnpm run build:feature-config && cross-env NODE_ENV=production WC_ADMIN_PHASE=core webpack", + "build": "WC_ADMIN_PHASE=core pnpm run build:feature-config && cross-env NODE_ENV=production WC_ADMIN_PHASE=core webpack", "build-storybook": "build-storybook -c ./storybook/.storybook", "build:feature-config": "php ../woocommerce/bin/generate-feature-config.php", "build:packages": "cross-env NODE_ENV=production pnpm run:packages -- build", From 133e40bf571f97efa6431aa5c705362c9275a803 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 14 Apr 2022 18:02:03 +0800 Subject: [PATCH 365/386] Skip e2e tests to be addressed in follow-up --- packages/js/admin-e2e-tests/src/specs/tasks/payment.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts index 8e769e2dec1..1571fea5fcc 100644 --- a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts +++ b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts @@ -52,7 +52,7 @@ const testAdminPaymentSetupTask = () => { await paymentsSetup.isDisplayed(); } ); - it( 'Saving valid bank account transfer details enables the payment method', async () => { + it.skip( 'Saving valid bank account transfer details enables the payment method', async () => { await paymentsSetup.showOtherPaymentMethods(); await waitForTimeout( 500 ); await paymentsSetup.goToPaymentMethodSetup( 'bacs' ); @@ -71,7 +71,7 @@ const testAdminPaymentSetupTask = () => { await homeScreen.navigate(); } ); - it( 'Enabling cash on delivery enables the payment method', async () => { + it.skip( 'Enabling cash on delivery enables the payment method', async () => { await settings.cleanPaymentMethods(); await homeScreen.navigate(); await homeScreen.isDisplayed(); From 08466d8de175f09fff4980049311e7dfaf148117 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Thu, 14 Apr 2022 15:39:15 +0800 Subject: [PATCH 366/386] Update payment method link to the internal Extensions Marketplace --- .../client/tasks/fills/PaymentGatewaySuggestions/index.js | 6 ++---- .../admin/settings/class-wc-settings-payment-gateways.php | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js index fdadacde358..4bb7b9bd83d 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js @@ -14,6 +14,7 @@ import { useMemo, useCallback, useEffect } from '@wordpress/element'; import { registerPlugin } from '@wordpress/plugins'; import { WooOnboardingTask } from '@woocommerce/onboarding'; import { getNewPath } from '@woocommerce/navigation'; +import { getAdminLink } from '@woocommerce/settings'; import { Button } from '@wordpress/components'; import ExternalIcon from 'gridicons/dist/external'; @@ -29,9 +30,6 @@ import { getCountryCode } from '~/dashboard/utils'; import './plugins/Bacs'; import './payment-gateway-suggestions.scss'; -const SEE_MORE_LINK = - 'https://woocommerce.com/product-category/woocommerce-extensions/payment-gateways/?utm_source=payments_recommendations'; - const comparePaymentGatewaysByPriority = ( a, b ) => a.recommendation_priority - b.recommendation_priority; @@ -323,7 +321,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { footerLink={ ! isWCPayOrOtherCategoryDoneSetup && (
); From 1b52d6d76dd5d7a48479d2847d12c17d19e203e6 Mon Sep 17 00:00:00 2001 From: Vedanshu Jain Date: Thu, 14 Apr 2022 18:14:35 +0530 Subject: [PATCH 370/386] Revert "Use integers for menu page priority." --- plugins/woocommerce/includes/admin/class-wc-admin-menus.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-menus.php b/plugins/woocommerce/includes/admin/class-wc-admin-menus.php index 3528be7e071..1f6542918b8 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-menus.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-menus.php @@ -61,7 +61,7 @@ class WC_Admin_Menus { $menu[] = array( '', 'read', 'separator-woocommerce', '', 'wp-menu-separator woocommerce' ); // WPCS: override ok. } - add_menu_page( __( 'WooCommerce', 'woocommerce' ), __( 'WooCommerce', 'woocommerce' ), 'edit_others_shop_orders', 'woocommerce', null, $woocommerce_icon, 55 ); + add_menu_page( __( 'WooCommerce', 'woocommerce' ), __( 'WooCommerce', 'woocommerce' ), 'edit_others_shop_orders', 'woocommerce', null, $woocommerce_icon, '55.5' ); add_submenu_page( 'edit.php?post_type=product', __( 'Attributes', 'woocommerce' ), __( 'Attributes', 'woocommerce' ), 'manage_product_terms', 'product_attributes', array( $this, 'attributes_page' ) ); } @@ -73,7 +73,7 @@ class WC_Admin_Menus { if ( self::can_view_woocommerce_menu_item() ) { add_submenu_page( 'woocommerce', __( 'Reports', 'woocommerce' ), __( 'Reports', 'woocommerce' ), 'view_woocommerce_reports', 'wc-reports', array( $this, 'reports_page' ) ); } else { - add_menu_page( __( 'Sales reports', 'woocommerce' ), __( 'Sales reports', 'woocommerce' ), 'view_woocommerce_reports', 'wc-reports', array( $this, 'reports_page' ), 'dashicons-chart-bar', 56 ); + add_menu_page( __( 'Sales reports', 'woocommerce' ), __( 'Sales reports', 'woocommerce' ), 'view_woocommerce_reports', 'wc-reports', array( $this, 'reports_page' ), 'dashicons-chart-bar', '55.6' ); } } From d59fa94d2f801d88ae52d97bec2d8ef19050bf4a Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Thu, 14 Apr 2022 09:48:53 -0300 Subject: [PATCH 371/386] Add sectioned task list loading placeholder component --- .../woocommerce-admin/client/tasks/tasks.tsx | 3 + .../sectioned-task-list-placeholder.tsx | 80 +++++++++++++++++++ .../two-column-tasks/sectioned-task-list.scss | 31 ++++++- 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list-placeholder.tsx diff --git a/plugins/woocommerce-admin/client/tasks/tasks.tsx b/plugins/woocommerce-admin/client/tasks/tasks.tsx index 5ca3fba71fc..552693006c0 100644 --- a/plugins/woocommerce-admin/client/tasks/tasks.tsx +++ b/plugins/woocommerce-admin/client/tasks/tasks.tsx @@ -23,6 +23,7 @@ import { SectionedTaskList } from '../two-column-tasks/sectioned-task-list'; import TwoColumnTaskListPlaceholder from '../two-column-tasks/placeholder'; import '../two-column-tasks/style.scss'; import { getAdminSetting } from '~/utils/admin-settings'; +import { SectionedTaskListPlaceholder } from '~/two-column-tasks/sectioned-task-list-placeholder'; export type TasksProps = { query: { task?: string }; @@ -45,6 +46,8 @@ function getTaskListPlaceholderComponent( switch ( taskListId ) { case 'setup_experiment_1': return TwoColumnTaskListPlaceholder; + case 'setup_experiment_2': + return SectionedTaskListPlaceholder; default: return TasksPlaceholder; } diff --git a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list-placeholder.tsx b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list-placeholder.tsx new file mode 100644 index 00000000000..baabeadfdcc --- /dev/null +++ b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list-placeholder.tsx @@ -0,0 +1,80 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import './style.scss'; + +type TasksPlaceholderProps = { + numTasks?: number; + query: { + task?: string; + }; +}; + +const SectionedTaskListPlaceholder: React.FC< TasksPlaceholderProps > = ( + props +) => { + const { numTasks = 3 } = props; + + return ( +
+
+
+
+
+
+
+
    + { Array.from( new Array( numTasks ) ).map( ( v, i ) => ( +
  • +
    +
    +
    +
    +
    +
    +
  • + ) ) } +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +}; + +export { SectionedTaskListPlaceholder }; diff --git a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.scss b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.scss index b7352b0b022..d9e379ad043 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.scss +++ b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.scss @@ -63,7 +63,9 @@ pointer-events: none; } - &:not(.is-complete) .woocommerce-task-list__item-before .woocommerce-task__icon { + &:not(.is-complete) + .woocommerce-task-list__item-before + .woocommerce-task__icon { border-color: $gray-300; } } @@ -89,4 +91,31 @@ } } } + + > .is-loading { + border: none; + margin-bottom: 8px; + + .woocommerce-task-list__item .woocommerce-task-list__item-before { + padding: 0 0 0 $gap-large; + } + + &.components-panel__body .components-panel__body-title .woocommerce-task-list__item-text { + width: 50%; + + .is-placeholder { + width: 100%; + } + } + + &.components-panel__body .woocommerce-task-list__item-after { + margin-left: $gap; + + .is-placeholder { + height: 24px; + width: 24px; + border-radius: 50%; + } + } + } } From 821ee8dc582957ff93a85b56f1252af90c2cdf80 Mon Sep 17 00:00:00 2001 From: vedanshujain Date: Thu, 14 Apr 2022 18:33:37 +0530 Subject: [PATCH 372/386] Adds changelog --- .../changelog/revert-31779-fix-31729-add-menu-page-arg | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/revert-31779-fix-31729-add-menu-page-arg diff --git a/plugins/woocommerce/changelog/revert-31779-fix-31729-add-menu-page-arg b/plugins/woocommerce/changelog/revert-31779-fix-31729-add-menu-page-arg new file mode 100644 index 00000000000..5d92d0b212e --- /dev/null +++ b/plugins/woocommerce/changelog/revert-31779-fix-31729-add-menu-page-arg @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Revert back menu position to floats as string for WP compatibility. From 3c69e4f2b08be93ef504d7be09083a561ee5fa03 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Thu, 14 Apr 2022 10:47:53 -0300 Subject: [PATCH 373/386] Remove unused dependency --- .../two-column-tasks/sectioned-task-list-placeholder.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list-placeholder.tsx b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list-placeholder.tsx index baabeadfdcc..70b234eaf0a 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list-placeholder.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list-placeholder.tsx @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - /** * Internal dependencies */ From 26a27650a3555b817cf7cf429fabfc9503ed6816 Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Thu, 14 Apr 2022 10:19:27 -0400 Subject: [PATCH 374/386] Add check for tasklist layout experiment (#32593) * Add check for tasklist layout experiment * Remove task list setup feature flags * Remove errant config file * Add negative check for experiment 1 in experiment 2 --- .../woocommerce-admin/client/header/index.js | 14 ++++++-- .../woocommerce/client/admin/config/core.json | 4 +-- .../client/admin/config/development.json | 4 +-- .../Features/OnboardingTasks/TaskLists.php | 35 +++++++++++++++++-- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/plugins/woocommerce-admin/client/header/index.js b/plugins/woocommerce-admin/client/header/index.js index 5ef3c246801..711b04fce3c 100644 --- a/plugins/woocommerce-admin/client/header/index.js +++ b/plugins/woocommerce-admin/client/header/index.js @@ -6,7 +6,9 @@ import { useEffect, useLayoutEffect, useRef } from '@wordpress/element'; import classnames from 'classnames'; import { decodeEntities } from '@wordpress/html-entities'; import { getSetting } from '@woocommerce/settings'; +import { ONBOARDING_STORE_NAME } from '@woocommerce/data'; import { Text, useSlot } from '@woocommerce/experimental'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -93,12 +95,18 @@ export const Header = ( { sections, isEmbedded = false, query } ) => { } }, [ isEmbedded, sections, siteTitle ] ); - const tasksReminderFeature = - window.wcAdminFeatures[ 'tasklist-setup-experiment-1' ]; + const { hasTasksReminderFeature } = useSelect( ( select ) => { + const taskLists = select( ONBOARDING_STORE_NAME ).getTaskLists(); + return { + hasTasksReminderFeature: taskLists.some( + ( list ) => list.id === 'setup_experiment_1' + ), + }; + } ); return (
- { tasksReminderFeature && ( + { hasTasksReminderFeature && ( setTimeZone( new \DateTimeZone( 'UTC' ) ); + + $experiment_name = sprintf( + '%s_%s_%s', + $name, + $date->format( 'Y' ), + $date->format( 'm' ) + ); + return $abtest->get_variation( $experiment_name ) === 'treatment'; + } + /** * Initialize default lists. */ @@ -93,7 +120,8 @@ class TaskLists { 'Appearance', ), 'event_prefix' => 'tasklist_', - 'visible' => ! Features::is_enabled( 'tasklist-setup-experiment-1' ) && ! Features::is_enabled( 'tasklist-setup-experiment-2' ), + 'visible' => ! self::is_experiment_treatment( 'woocommerce_tasklist_setup_experiment_1' ) + && ! self::is_experiment_treatment( 'woocommerce_tasklist_setup_experiment_2' ), ) ); @@ -117,7 +145,7 @@ class TaskLists { 'options' => array( 'use_completed_title' => true, ), - 'visible' => Features::is_enabled( 'tasklist-setup-experiment-1' ), + 'visible' => self::is_experiment_treatment( 'woocommerce_tasklist_setup_experiment_1' ), ) ); @@ -138,7 +166,8 @@ class TaskLists { 'Appearance', ), 'event_prefix' => 'tasklist_', - 'visible' => Features::is_enabled( 'tasklist-setup-experiment-2' ), + 'visible' => self::is_experiment_treatment( 'woocommerce_tasklist_setup_experiment_2' ) + && ! self::is_experiment_treatment( 'woocommerce_tasklist_setup_experiment_1' ), 'options' => array( 'use_completed_title' => true, ), From 1998203b6e7691000f717020d2c862020a909ca2 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Thu, 14 Apr 2022 11:36:53 -0300 Subject: [PATCH 375/386] Remove Pinterest extension from OBW --- .../src/Admin/API/OnboardingProfile.php | 1 - .../DefaultFreeExtensions.php | 28 ++----------------- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/plugins/woocommerce/src/Admin/API/OnboardingProfile.php b/plugins/woocommerce/src/Admin/API/OnboardingProfile.php index d30956054d3..a148810ed2e 100644 --- a/plugins/woocommerce/src/Admin/API/OnboardingProfile.php +++ b/plugins/woocommerce/src/Admin/API/OnboardingProfile.php @@ -397,7 +397,6 @@ class OnboardingProfile extends \WC_REST_Data_Controller { 'creative-mail-by-constant-contact', 'facebook-for-woocommerce', 'google-listings-and-ads', - 'pinterest-for-woocommerce', 'mailpoet', ), 'type' => 'string', diff --git a/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php b/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php index 202dc7f5982..3cf47a240df 100644 --- a/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php +++ b/plugins/woocommerce/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensions.php @@ -36,7 +36,6 @@ class DefaultFreeExtensions { 'plugins' => [ self::get_plugin( 'mailpoet' ), self::get_plugin( 'google-listings-and-ads' ), - self::get_plugin( 'pinterest-for-woocommerce' ), ], ], [ @@ -53,7 +52,7 @@ class DefaultFreeExtensions { 'title' => __( 'Grow your store', 'woocommerce' ), 'plugins' => [ self::get_plugin( 'google-listings-and-ads:alt' ), - self::get_plugin( 'pinterest-for-woocommerce:alt' ), + self::get_plugin( 'pinterest-for-woocommerce' ), ], ], ]; @@ -100,30 +99,7 @@ class DefaultFreeExtensions { 'manage_url' => 'admin.php?page=wc-admin&path=%2Fgoogle%2Fstart', 'is_built_by_wc' => true, ], - 'pinterest-for-woocommerce' => [ - 'name' => __( 'Pinterest for WooCommerce', 'woocommerce' ), - 'description' => sprintf( - /* translators: 1: opening product link tag. 2: closing link tag */ - __( 'Inspire shoppers with %1$sPinterest for WooCommerce%2$s', 'woocommerce' ), - '', - '' - ), - 'image_url' => plugins_url( '/assets/images/onboarding/pinterest.png', WC_PLUGIN_FILE ), - 'manage_url' => 'admin.php?page=pinterest-for-woocommerce', - 'is_visible' => [ - [ - 'type' => 'not', - 'operand' => [ - [ - 'type' => 'plugins_activated', - 'plugins' => [ 'pinterest-for-woocommerce' ], - ], - ], - ], - ], - 'is_built_by_wc' => false, - ], - 'pinterest-for-woocommerce:alt' => [ + 'pinterest-for-woocommerce' => [ 'name' => __( 'Pinterest for WooCommerce', 'woocommerce' ), 'description' => __( 'Get your products in front of Pinterest users searching for ideas and things to buy. Get started with Pinterest and make your entire product catalog browsable.', 'woocommerce' ), 'image_url' => plugins_url( '/assets/images/onboarding/pinterest.png', WC_PLUGIN_FILE ), From ab319ad9ef8cc6df73d700644b15a469b9356289 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Thu, 14 Apr 2022 11:50:03 -0300 Subject: [PATCH 376/386] Add changelog --- .../changelog/fix-32141_remove_pinterest_extension_from_obw | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-32141_remove_pinterest_extension_from_obw diff --git a/plugins/woocommerce/changelog/fix-32141_remove_pinterest_extension_from_obw b/plugins/woocommerce/changelog/fix-32141_remove_pinterest_extension_from_obw new file mode 100644 index 00000000000..e8e565aaf0d --- /dev/null +++ b/plugins/woocommerce/changelog/fix-32141_remove_pinterest_extension_from_obw @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Remove Pinterest extension from OBW #32626 From f384581e6765fe0877454a6164b8cf70551fd170 Mon Sep 17 00:00:00 2001 From: Josh Betz Date: Wed, 13 Apr 2022 14:43:51 -0500 Subject: [PATCH 377/386] Reports: Don't include auto-draft orders in reports We use auto-draft in the the API (https://github.com/woocommerce/woocommerce/pull/31290) and to some extent in wp-admin. These orders should not impact reporting. Fixes https://github.com/woocommerce/woocommerce-ios/issues/6626 --- plugins/woocommerce/src/Admin/API/Reports/DataStore.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Admin/API/Reports/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/DataStore.php index aebda100324..bf2a95cf2ac 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/DataStore.php +++ b/plugins/woocommerce/src/Admin/API/Reports/DataStore.php @@ -577,7 +577,7 @@ class DataStore extends SqlQuery { */ protected static function get_excluded_report_order_statuses() { $excluded_statuses = \WC_Admin_Settings::get_option( 'woocommerce_excluded_report_order_statuses', array( 'pending', 'failed', 'cancelled' ) ); - $excluded_statuses = array_merge( array( 'trash' ), array_map( 'esc_sql', $excluded_statuses ) ); + $excluded_statuses = array_merge( array( 'auto-draft', 'trash' ), array_map( 'esc_sql', $excluded_statuses ) ); return apply_filters( 'woocommerce_analytics_excluded_order_statuses', $excluded_statuses ); } From de086a684e022eac835bdf30cf39326ff5c9573a Mon Sep 17 00:00:00 2001 From: Josh Betz Date: Thu, 14 Apr 2022 10:13:19 -0500 Subject: [PATCH 378/386] Add changelog --- plugins/woocommerce/changelog/fix-exclude-drafts-in-reports | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-exclude-drafts-in-reports diff --git a/plugins/woocommerce/changelog/fix-exclude-drafts-in-reports b/plugins/woocommerce/changelog/fix-exclude-drafts-in-reports new file mode 100644 index 00000000000..354111e694b --- /dev/null +++ b/plugins/woocommerce/changelog/fix-exclude-drafts-in-reports @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Don't include draft orders in reports From d56f00e8320410ee5f1d563aafbfcc06acdbdeb5 Mon Sep 17 00:00:00 2001 From: Saad Tarhi Date: Thu, 14 Apr 2022 15:30:31 +0000 Subject: [PATCH 379/386] Update WooCommerce blocks package to 7.4.0 --- plugins/woocommerce/composer.json | 2 +- plugins/woocommerce/composer.lock | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/plugins/woocommerce/composer.json b/plugins/woocommerce/composer.json index 47977376e15..656bbc231d0 100644 --- a/plugins/woocommerce/composer.json +++ b/plugins/woocommerce/composer.json @@ -21,7 +21,7 @@ "pelago/emogrifier": "^6.0", "psr/container": "1.0.0", "woocommerce/action-scheduler": "3.4.0", - "woocommerce/woocommerce-blocks": "7.2.1" + "woocommerce/woocommerce-blocks": "7.4.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.4", diff --git a/plugins/woocommerce/composer.lock b/plugins/woocommerce/composer.lock index 5899a2c583d..c52f596a63d 100644 --- a/plugins/woocommerce/composer.lock +++ b/plugins/woocommerce/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "362a4c80079f72e193bb35b1e22c8598", + "content-hash": "71f2f725ab133e12dde802b2ecf180f4", "packages": [ { "name": "automattic/jetpack-autoloader", @@ -681,16 +681,16 @@ }, { "name": "woocommerce/woocommerce-blocks", - "version": "v7.2.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git", - "reference": "52fd37a82aa522b9e39fcad11a67b3560c0ea9f0" + "reference": "7f5430fa6c4a8a64eb28ac392c446099f1024c6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/52fd37a82aa522b9e39fcad11a67b3560c0ea9f0", - "reference": "52fd37a82aa522b9e39fcad11a67b3560c0ea9f0", + "url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/7f5430fa6c4a8a64eb28ac392c446099f1024c6a", + "reference": "7f5430fa6c4a8a64eb28ac392c446099f1024c6a", "shasum": "" }, "require": { @@ -734,9 +734,9 @@ ], "support": { "issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues", - "source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v7.2.0" + "source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v7.4.0" }, - "time": "2022-03-15T11:18:07+00:00" + "time": "2022-04-14T12:11:29+00:00" } ], "packages-dev": [ @@ -1198,16 +1198,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.6.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706" + "reference": "77a32518733312af16a44300404e945338981de3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706", - "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", "shasum": "" }, "require": { @@ -1242,9 +1242,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" }, - "time": "2022-01-04T19:58:01+00:00" + "time": "2022-03-15T21:29:03+00:00" }, { "name": "phpspec/prophecy", From 3561b87f837097bb7cfde4ed753123443e0313d3 Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Thu, 14 Apr 2022 12:21:56 -0400 Subject: [PATCH 380/386] Track when task list sections are closed (#32625) * Track when task list sections are closed * Add tracks for when task list section is opened --- .../two-column-tasks/sectioned-task-list.tsx | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx index 9fb472401da..2703c84d0cf 100644 --- a/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx +++ b/plugins/woocommerce-admin/client/two-column-tasks/sectioned-task-list.tsx @@ -35,7 +35,7 @@ const PanelBodyWithUpdatedType = PanelBody as React.ComponentType< PanelBodyProp export const SectionedTaskList: React.FC< TaskListProps > = ( { query, id, - eventName, + eventPrefix, tasks, keepCompletedTaskList, isComplete, @@ -66,7 +66,7 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( { return; } - recordEvent( `${ eventName }_view`, { + recordEvent( `${ eventPrefix }view`, { number_tasks: visibleTasks.length, store_connected: profileItems.wccom_connected, } ); @@ -122,7 +122,7 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( { } const trackClick = ( task: TaskType ) => { - recordEvent( `${ eventName }_click`, { + recordEvent( `${ eventPrefix }_click`, { task_name: task.id, } ); }; @@ -182,10 +182,34 @@ export const SectionedTaskList: React.FC< TaskListProps > = ( { opened={ openPanel === section.id } onToggle={ ( isOpen: boolean ) => { if ( ! isOpen && openPanel === section.id ) { + recordEvent( + `${ eventPrefix }section_closed`, + { + id: section.id, + all: true, + } + ); setOpenPanel( null ); } else { + if ( openPanel ) { + recordEvent( + `${ eventPrefix }section_closed`, + { + id: openPanel, + all: false, + } + ); + } setOpenPanel( section.id ); } + if ( isOpen ) { + recordEvent( + `${ eventPrefix }section_opened`, + { + id: section.id, + } + ); + } } } initialOpen={ false } > From 99bf4afd262280ad4e45386ce4ad00ce3425af93 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 14 Apr 2022 13:27:08 -0300 Subject: [PATCH 381/386] Added changelog entry --- plugins/woocommerce/changelog/update-woocommerce-blocks-7.4.0 | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/update-woocommerce-blocks-7.4.0 diff --git a/plugins/woocommerce/changelog/update-woocommerce-blocks-7.4.0 b/plugins/woocommerce/changelog/update-woocommerce-blocks-7.4.0 new file mode 100644 index 00000000000..9f03b6cec0c --- /dev/null +++ b/plugins/woocommerce/changelog/update-woocommerce-blocks-7.4.0 @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Woo Blocks 7.3.0 & 7.4.0 From 5040a10d01896bcf40fd0ac538f2b7bc584ffe0a Mon Sep 17 00:00:00 2001 From: Nadir Seghir Date: Thu, 14 Apr 2022 17:59:02 +0100 Subject: [PATCH 382/386] update to wc blocks to 7.4.1 --- .../bin/composer/mozart/composer.lock | 52 ++++++------- .../bin/composer/phpcs/composer.lock | 2 +- .../bin/composer/phpunit/composer.lock | 2 +- .../woocommerce/bin/composer/wp/composer.lock | 73 +++++++++++++++++-- ...-7.4.0 => update-woocommerce-blocks-7.4.1} | 2 +- plugins/woocommerce/composer.json | 2 +- plugins/woocommerce/composer.lock | 16 ++-- 7 files changed, 104 insertions(+), 45 deletions(-) rename plugins/woocommerce/changelog/{update-woocommerce-blocks-7.4.0 => update-woocommerce-blocks-7.4.1} (57%) diff --git a/plugins/woocommerce/bin/composer/mozart/composer.lock b/plugins/woocommerce/bin/composer/mozart/composer.lock index 4c8ffa51e84..c151f23629d 100644 --- a/plugins/woocommerce/bin/composer/mozart/composer.lock +++ b/plugins/woocommerce/bin/composer/mozart/composer.lock @@ -162,16 +162,16 @@ }, { "name": "league/mime-type-detection", - "version": "1.9.0", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "aa70e813a6ad3d1558fc927863d47309b4c23e69" + "reference": "3e4a35d756eedc67096f30240a68a3149120dae7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/aa70e813a6ad3d1558fc927863d47309b4c23e69", - "reference": "aa70e813a6ad3d1558fc927863d47309b4c23e69", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3e4a35d756eedc67096f30240a68a3149120dae7", + "reference": "3e4a35d756eedc67096f30240a68a3149120dae7", "shasum": "" }, "require": { @@ -202,7 +202,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.9.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.10.0" }, "funding": [ { @@ -214,7 +214,7 @@ "type": "tidelift" } ], - "time": "2021-11-21T11:48:40+00:00" + "time": "2022-04-11T12:49:04+00:00" }, { "name": "psr/container", @@ -266,16 +266,16 @@ }, { "name": "symfony/console", - "version": "v5.4.5", + "version": "v5.4.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "d8111acc99876953f52fe16d4c50eb60940d49ad" + "reference": "900275254f0a1a2afff1ab0e11abd5587a10e1d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/d8111acc99876953f52fe16d4c50eb60940d49ad", - "reference": "d8111acc99876953f52fe16d4c50eb60940d49ad", + "url": "https://api.github.com/repos/symfony/console/zipball/900275254f0a1a2afff1ab0e11abd5587a10e1d6", + "reference": "900275254f0a1a2afff1ab0e11abd5587a10e1d6", "shasum": "" }, "require": { @@ -345,7 +345,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.5" + "source": "https://github.com/symfony/console/tree/v5.4.7" }, "funding": [ { @@ -361,20 +361,20 @@ "type": "tidelift" } ], - "time": "2022-02-24T12:45:35+00:00" + "time": "2022-03-31T17:09:19+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.0", + "version": "v2.5.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", "shasum": "" }, "require": { @@ -412,7 +412,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.1" }, "funding": [ { @@ -428,7 +428,7 @@ "type": "tidelift" } ], - "time": "2021-07-12T14:48:14+00:00" + "time": "2022-01-02T09:53:40+00:00" }, { "name": "symfony/finder", @@ -987,22 +987,22 @@ }, { "name": "symfony/service-contracts", - "version": "v2.5.0", + "version": "v2.5.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" + "reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/24d9dc654b83e91aa59f9d167b131bc3b5bea24c", + "reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c", "shasum": "" }, "require": { "php": ">=7.2.5", "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1" + "symfony/deprecation-contracts": "^2.1|^3" }, "conflict": { "ext-psr": "<1.1|>=2" @@ -1050,7 +1050,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.1" }, "funding": [ { @@ -1066,7 +1066,7 @@ "type": "tidelift" } ], - "time": "2021-11-04T16:48:04+00:00" + "time": "2022-03-13T20:07:29+00:00" }, { "name": "symfony/string", @@ -1167,5 +1167,5 @@ "platform-overrides": { "php": "7.3" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.1.0" } diff --git a/plugins/woocommerce/bin/composer/phpcs/composer.lock b/plugins/woocommerce/bin/composer/phpcs/composer.lock index 8dce372de81..6cf9a6b7cc1 100644 --- a/plugins/woocommerce/bin/composer/phpcs/composer.lock +++ b/plugins/woocommerce/bin/composer/phpcs/composer.lock @@ -416,5 +416,5 @@ "platform-overrides": { "php": "7.0" }, - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.1.0" } diff --git a/plugins/woocommerce/bin/composer/phpunit/composer.lock b/plugins/woocommerce/bin/composer/phpunit/composer.lock index 8e227b9dd3a..7fc2f48ec7a 100644 --- a/plugins/woocommerce/bin/composer/phpunit/composer.lock +++ b/plugins/woocommerce/bin/composer/phpunit/composer.lock @@ -1697,5 +1697,5 @@ "platform-overrides": { "php": "7.0" }, - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.1.0" } diff --git a/plugins/woocommerce/bin/composer/wp/composer.lock b/plugins/woocommerce/bin/composer/wp/composer.lock index b06f269acc1..8fee101ebe0 100644 --- a/plugins/woocommerce/bin/composer/wp/composer.lock +++ b/plugins/woocommerce/bin/composer/wp/composer.lock @@ -7,6 +7,64 @@ "content-hash": "4d4f2befccefe100869d30305083672b", "packages": [], "packages-dev": [ + { + "name": "eftec/bladeone", + "version": "3.52", + "source": { + "type": "git", + "url": "https://github.com/EFTEC/BladeOne.git", + "reference": "a19bf66917de0b29836983db87a455a4f6e32148" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EFTEC/BladeOne/zipball/a19bf66917de0b29836983db87a455a4f6e32148", + "reference": "a19bf66917de0b29836983db87a455a4f6e32148", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.6" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16.1", + "phpunit/phpunit": "^5.7", + "squizlabs/php_codesniffer": "^3.5.4" + }, + "suggest": { + "eftec/bladeonehtml": "Extension to create forms", + "ext-mbstring": "This extension is used if it's active" + }, + "type": "library", + "autoload": { + "psr-4": { + "eftec\\bladeone\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jorge Patricio Castro Castillo", + "email": "jcastro@eftec.cl" + } + ], + "description": "The standalone version Blade Template Engine from Laravel in a single php file", + "homepage": "https://github.com/EFTEC/BladeOne", + "keywords": [ + "blade", + "php", + "template", + "templating", + "view" + ], + "support": { + "issues": "https://github.com/EFTEC/BladeOne/issues", + "source": "https://github.com/EFTEC/BladeOne/tree/3.52" + }, + "time": "2021-04-17T13:49:01+00:00" + }, { "name": "gettext/gettext", "version": "v4.8.6", @@ -376,19 +434,20 @@ }, { "name": "wp-cli/i18n-command", - "version": "v2.2.13", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/wp-cli/i18n-command.git", - "reference": "77ece9e2c914bb56ea72b9ee9f00556dece07b3f" + "reference": "bcb1a8159679cafdf1da884dbe5830122bae2c4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/77ece9e2c914bb56ea72b9ee9f00556dece07b3f", - "reference": "77ece9e2c914bb56ea72b9ee9f00556dece07b3f", + "url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/bcb1a8159679cafdf1da884dbe5830122bae2c4d", + "reference": "bcb1a8159679cafdf1da884dbe5830122bae2c4d", "shasum": "" }, "require": { + "eftec/bladeone": "3.52", "gettext/gettext": "^4.8", "mck89/peast": "^1.13.11", "wp-cli/wp-cli": "^2.5" @@ -434,9 +493,9 @@ "homepage": "https://github.com/wp-cli/i18n-command", "support": { "issues": "https://github.com/wp-cli/i18n-command/issues", - "source": "https://github.com/wp-cli/i18n-command/tree/v2.2.13" + "source": "https://github.com/wp-cli/i18n-command/tree/v2.3.0" }, - "time": "2022-01-13T01:40:51+00:00" + "time": "2022-04-06T15:32:48+00:00" }, { "name": "wp-cli/mustangostang-spyc", @@ -625,5 +684,5 @@ "platform-overrides": { "php": "7.0" }, - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.1.0" } diff --git a/plugins/woocommerce/changelog/update-woocommerce-blocks-7.4.0 b/plugins/woocommerce/changelog/update-woocommerce-blocks-7.4.1 similarity index 57% rename from plugins/woocommerce/changelog/update-woocommerce-blocks-7.4.0 rename to plugins/woocommerce/changelog/update-woocommerce-blocks-7.4.1 index 9f03b6cec0c..85231c63d41 100644 --- a/plugins/woocommerce/changelog/update-woocommerce-blocks-7.4.0 +++ b/plugins/woocommerce/changelog/update-woocommerce-blocks-7.4.1 @@ -1,4 +1,4 @@ Significance: minor Type: update -Woo Blocks 7.3.0 & 7.4.0 +Woo Blocks 7.3.0 & 7.4.1 diff --git a/plugins/woocommerce/composer.json b/plugins/woocommerce/composer.json index 656bbc231d0..0c4777b486a 100644 --- a/plugins/woocommerce/composer.json +++ b/plugins/woocommerce/composer.json @@ -21,7 +21,7 @@ "pelago/emogrifier": "^6.0", "psr/container": "1.0.0", "woocommerce/action-scheduler": "3.4.0", - "woocommerce/woocommerce-blocks": "7.4.0" + "woocommerce/woocommerce-blocks": "7.4.1" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.4", diff --git a/plugins/woocommerce/composer.lock b/plugins/woocommerce/composer.lock index c52f596a63d..0ff06545e2f 100644 --- a/plugins/woocommerce/composer.lock +++ b/plugins/woocommerce/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "71f2f725ab133e12dde802b2ecf180f4", + "content-hash": "03a229b123645dbf035c87685be1043a", "packages": [ { "name": "automattic/jetpack-autoloader", @@ -681,16 +681,16 @@ }, { "name": "woocommerce/woocommerce-blocks", - "version": "v7.4.0", + "version": "v7.4.1", "source": { "type": "git", "url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git", - "reference": "7f5430fa6c4a8a64eb28ac392c446099f1024c6a" + "reference": "bde2a5771ddc7970c2114da621c28b0f7b6296ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/7f5430fa6c4a8a64eb28ac392c446099f1024c6a", - "reference": "7f5430fa6c4a8a64eb28ac392c446099f1024c6a", + "url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/bde2a5771ddc7970c2114da621c28b0f7b6296ca", + "reference": "bde2a5771ddc7970c2114da621c28b0f7b6296ca", "shasum": "" }, "require": { @@ -734,9 +734,9 @@ ], "support": { "issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues", - "source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v7.4.0" + "source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v7.4.1" }, - "time": "2022-04-14T12:11:29+00:00" + "time": "2022-04-14T16:44:52+00:00" } ], "packages-dev": [ @@ -3021,5 +3021,5 @@ "platform-overrides": { "php": "7.2" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.1.0" } From ac95d54669607e6326ca327d3b42b8d4762523d3 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Fri, 15 Apr 2022 12:08:59 +0800 Subject: [PATCH 383/386] Add more comments to admin webpacks config & simplify the logic --- plugins/woocommerce-admin/webpack.config.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce-admin/webpack.config.js b/plugins/woocommerce-admin/webpack.config.js index 67634a1c9c2..af43fc54f34 100644 --- a/plugins/woocommerce-admin/webpack.config.js +++ b/plugins/woocommerce-admin/webpack.config.js @@ -58,6 +58,7 @@ wpAdminScripts.forEach( ( name ) => { entryPoints[ name ] = `./client/wp-admin-scripts/${ name }`; } ); +// WordPress.org’s translation infrastructure ignores files named “.min.js” so we need to name our JS files without min when releasing the plugin. const suffix = WC_ADMIN_PHASE === 'core' ? '' : '.min'; const webpackConfig = { @@ -181,10 +182,9 @@ const webpackConfig = { // TODO: Partially replace with __webpack_get_script_filename__ in app with Webpack 5.x. // The CSS chunk portion will need to remain, as it originates in MiniCssExtractPlugin. new AsyncChunkSrcVersionParameterPlugin(), - // Generate unminified files to load the unminified version when `define( 'SCRIPT_DEBUG', true );` is set in wp-config. - // This is also required to publish human readeable code in the deployed "plugin". - // See https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/#4-code-must-be-mostly-human-readable - WC_ADMIN_PHASE !== 'core' && + // We only want to generate unminified files in the development phase. + WC_ADMIN_PHASE === 'development' && + // Generate unminified files to load the unminified version when `define( 'SCRIPT_DEBUG', true );` is set in wp-config. new UnminifyWebpackPlugin( { test: /\.js($|\?)/i, mainEntry: 'app/index.min.js', @@ -200,7 +200,11 @@ const webpackConfig = { }, }; -if ( webpackConfig.mode !== 'production' && WC_ADMIN_PHASE !== 'core' ) { +// Use the source map if we're in development mode, . +if ( + webpackConfig.mode === 'development' || + WC_ADMIN_PHASE === 'development' +) { webpackConfig.devtool = process.env.SOURCEMAP || 'source-map'; } From eb6b7d2b0eec3a3219c50d48a42e05affaebc583 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Fri, 15 Apr 2022 11:03:53 -0300 Subject: [PATCH 384/386] Update changelogs --- plugins/woocommerce/changelog/changelog-32317 | 8 ++++---- plugins/woocommerce/changelog/changelog-32460 | 8 ++++---- plugins/woocommerce/changelog/fix-protocol-agnostic-urls | 3 +-- plugins/woocommerce/changelog/update-32509_deasync_issue | 3 +-- plugins/woocommerce/changelog/update-simpliy-init-routine | 2 +- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/plugins/woocommerce/changelog/changelog-32317 b/plugins/woocommerce/changelog/changelog-32317 index 3ab27b4b08d..00343122e63 100644 --- a/plugins/woocommerce/changelog/changelog-32317 +++ b/plugins/woocommerce/changelog/changelog-32317 @@ -1,4 +1,4 @@ -Significance: patch -Type: enhancement - -Add woocommerce_download_parse_remote_file_path and woocommerce_download_parse_file_path filters to modify the parse_file_path method file_path return. #32317 \ No newline at end of file +Significance: patch +Type: enhancement + +Add woocommerce_download_parse_remote_file_path and woocommerce_download_parse_file_path filters to modify the parse_file_path method file_path return. #32317 diff --git a/plugins/woocommerce/changelog/changelog-32460 b/plugins/woocommerce/changelog/changelog-32460 index b4c3c5bb5ae..0048b30d431 100644 --- a/plugins/woocommerce/changelog/changelog-32460 +++ b/plugins/woocommerce/changelog/changelog-32460 @@ -1,4 +1,4 @@ -Significance: patch -Type: tweak - -WC_Product_CSV_Importer_Controller::is_file_valid_csv now just invokes wc_is_file_valid_csv. #32460 +Significance: patch +Type: tweak + +WC_Product_CSV_Importer_Controller::is_file_valid_csv now just invokes wc_is_file_valid_csv. #32460 diff --git a/plugins/woocommerce/changelog/fix-protocol-agnostic-urls b/plugins/woocommerce/changelog/fix-protocol-agnostic-urls index 740508dbf36..21c068f95bf 100644 --- a/plugins/woocommerce/changelog/fix-protocol-agnostic-urls +++ b/plugins/woocommerce/changelog/fix-protocol-agnostic-urls @@ -1,5 +1,4 @@ Significance: patch Type: tweak -Comment: Changelog entry not needed, because this is an adjustment to unreleased code. - +Changelog entry not needed, because this is an adjustment to unreleased code. diff --git a/plugins/woocommerce/changelog/update-32509_deasync_issue b/plugins/woocommerce/changelog/update-32509_deasync_issue index 5e972dddd67..1cad85035fb 100644 --- a/plugins/woocommerce/changelog/update-32509_deasync_issue +++ b/plugins/woocommerce/changelog/update-32509_deasync_issue @@ -1,5 +1,4 @@ Significance: patch Type: update + Updating deasync package to resolve install script issue with Linux - - diff --git a/plugins/woocommerce/changelog/update-simpliy-init-routine b/plugins/woocommerce/changelog/update-simpliy-init-routine index 43026464398..599e35cb61e 100644 --- a/plugins/woocommerce/changelog/update-simpliy-init-routine +++ b/plugins/woocommerce/changelog/update-simpliy-init-routine @@ -1,4 +1,4 @@ Significance: patch Type: update -simplify the WooCommerce Admin init routine. #32489 +Simplify the WooCommerce Admin init routine. #32489 From 42589dcc337fec5d7e2b84326ea45fd8e40c3cf8 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Fri, 15 Apr 2022 15:49:55 -0300 Subject: [PATCH 385/386] Update changelog --- plugins/woocommerce/NEXT_CHANGELOG.md | 41 ++++++++++++++++++- plugins/woocommerce/changelog/COTMigrations | 4 -- .../add-32141_pinterest_extension_to_obw | 4 -- .../add-32157_tests_to_disable_welcome_modals | 4 -- .../add-32573-context-arg-for-get-notes | 4 -- .../changelog/add-block-theme-tracking | 4 -- plugins/woocommerce/changelog/changelog-32317 | 4 -- plugins/woocommerce/changelog/changelog-32460 | 4 -- ...2131-ui-changes-additional-payment-section | 4 -- .../dev-32417-clean-up-wc-admin-package-json | 4 -- .../feature-32164_new_task_list_version_2 | 4 -- .../changelog/fix-32140_wcpay_completion_task | 4 -- ...-32141_remove_pinterest_extension_from_obw | 4 -- plugins/woocommerce/changelog/fix-32399-husky | 4 -- plugins/woocommerce/changelog/fix-32425 | 4 -- .../fix-32428_reminder_bar_invalid_screens | 4 -- .../fix-32539-order-autodraft-validation | 4 -- .../woocommerce/changelog/fix-ascii-uasort | 4 -- .../changelog/fix-broken-i18n-from-wca-merge | 4 -- .../changelog/fix-downloadable-file-data | 4 -- .../fix-feature-config-file-location | 4 -- .../changelog/fix-hidden_wcpay_task | 4 -- .../changelog/fix-protocol-agnostic-urls | 4 -- .../changelog/fix-unsupported-operand-types | 4 -- plugins/woocommerce/changelog/fix-wcpay_task | 4 -- plugins/woocommerce/changelog/issue-31347 | 4 -- ...-32444-featured-request-user-agent-headers | 4 -- .../changelog/remove-admin-feature-plugin-env | 4 -- .../revert-31779-fix-31729-add-menu-page-arg | 4 -- ...changes-to-the-recommended-payment-options | 4 -- ...e-32132-payment-logic-in-task-and-settings | 4 -- ...date-32176-merge-wca-install-logic-to-core | 4 -- .../changelog/update-32509_deasync_issue | 4 -- ...e-32522-remove-deactivation-hooks-from-wca | 4 -- .../update-32617-payment-method-links | 4 -- ...ilter-out-product-variation-line-item-meta | 4 -- .../changelog/update-simpliy-init-routine | 4 -- 37 files changed, 40 insertions(+), 145 deletions(-) delete mode 100644 plugins/woocommerce/changelog/COTMigrations delete mode 100644 plugins/woocommerce/changelog/add-32141_pinterest_extension_to_obw delete mode 100644 plugins/woocommerce/changelog/add-32157_tests_to_disable_welcome_modals delete mode 100644 plugins/woocommerce/changelog/add-32573-context-arg-for-get-notes delete mode 100644 plugins/woocommerce/changelog/add-block-theme-tracking delete mode 100644 plugins/woocommerce/changelog/changelog-32317 delete mode 100644 plugins/woocommerce/changelog/changelog-32460 delete mode 100644 plugins/woocommerce/changelog/dev-32131-ui-changes-additional-payment-section delete mode 100644 plugins/woocommerce/changelog/dev-32417-clean-up-wc-admin-package-json delete mode 100644 plugins/woocommerce/changelog/feature-32164_new_task_list_version_2 delete mode 100644 plugins/woocommerce/changelog/fix-32140_wcpay_completion_task delete mode 100644 plugins/woocommerce/changelog/fix-32141_remove_pinterest_extension_from_obw delete mode 100644 plugins/woocommerce/changelog/fix-32399-husky delete mode 100644 plugins/woocommerce/changelog/fix-32425 delete mode 100644 plugins/woocommerce/changelog/fix-32428_reminder_bar_invalid_screens delete mode 100644 plugins/woocommerce/changelog/fix-32539-order-autodraft-validation delete mode 100644 plugins/woocommerce/changelog/fix-ascii-uasort delete mode 100644 plugins/woocommerce/changelog/fix-broken-i18n-from-wca-merge delete mode 100644 plugins/woocommerce/changelog/fix-downloadable-file-data delete mode 100644 plugins/woocommerce/changelog/fix-feature-config-file-location delete mode 100644 plugins/woocommerce/changelog/fix-hidden_wcpay_task delete mode 100644 plugins/woocommerce/changelog/fix-protocol-agnostic-urls delete mode 100644 plugins/woocommerce/changelog/fix-unsupported-operand-types delete mode 100644 plugins/woocommerce/changelog/fix-wcpay_task delete mode 100644 plugins/woocommerce/changelog/issue-31347 delete mode 100644 plugins/woocommerce/changelog/remove-32444-featured-request-user-agent-headers delete mode 100644 plugins/woocommerce/changelog/remove-admin-feature-plugin-env delete mode 100644 plugins/woocommerce/changelog/revert-31779-fix-31729-add-menu-page-arg delete mode 100644 plugins/woocommerce/changelog/update-32130-ui-changes-to-the-recommended-payment-options delete mode 100644 plugins/woocommerce/changelog/update-32132-payment-logic-in-task-and-settings delete mode 100644 plugins/woocommerce/changelog/update-32176-merge-wca-install-logic-to-core delete mode 100644 plugins/woocommerce/changelog/update-32509_deasync_issue delete mode 100644 plugins/woocommerce/changelog/update-32522-remove-deactivation-hooks-from-wca delete mode 100644 plugins/woocommerce/changelog/update-32617-payment-method-links delete mode 100644 plugins/woocommerce/changelog/update-filter-out-product-variation-line-item-meta delete mode 100644 plugins/woocommerce/changelog/update-simpliy-init-routine diff --git a/plugins/woocommerce/NEXT_CHANGELOG.md b/plugins/woocommerce/NEXT_CHANGELOG.md index ef1933df641..58ed6441ae4 100644 --- a/plugins/woocommerce/NEXT_CHANGELOG.md +++ b/plugins/woocommerce/NEXT_CHANGELOG.md @@ -1,5 +1,44 @@ # Changelog +## [6.5](https://github.com/woocommerce/woocommerce/releases/tag/6.5) - 2022-04-15 + +- Minor - Added a temporary filter to patch the WCA JS packages i18n json files #32603 +- Patch - Enable the "Save changes" button within the variations panel when a textfield receives input. +- Patch - Ensure that an existing order with auto-draft status won't be interpreted as pending when determining if the status has changed. +- Patch - Fixing bug in which tasks reminder bar was displayed on product screens +- Patch - Fix issue where some tasks where not being tracked as completed, when tracking is enabled. #32493 +- Patch - Fix WooCommerce Payments task not showing up in some supported countries. #32496 +- Minor - Remove Pinterest extension from OBW #32626 +- Patch - Revert back menu position to floats as string for WP compatibility. +- Minor - String sorting when using different locales. +- Minor - WCPayments task is not visible after installing the plugin #32506 +- Minor - Add E2E tests to disabled welcome modal #32505 +- Minor - Add Pinterest extension to onboarding wizard and marketing task #32527 +- Minor - Add `order_item_display_meta` option to orders endpoint (REST API), to osupport filtering out variation meta. +- Minor - Generic migration support for migration from posts + postsmeta table to any custom table. Additionaly, implement migrations to various COT tables using this generic support. +- Minor - Make it possible for downloadable files to be in an enabled or disabled state. +- Patch - Adds Other payment methods link to the payment setting page when the store is located in WC Payments eligible country. +- Minor - Merge WCA install routines to the core +- Minor - Remove load_plugin_textdomain method from admin plugin. +- Patch - Simplify the WooCommerce Admin init routine. #32489 +- Minor - UI changes for set up payments task +- Minor - Update payment gateway logic in payment task +- Patch - Update payment method link to the internal extension marketplace +- Patch - Update WCA deactivation hooks to work with WC deactvation. +- Patch - Updating deasync package to resolve install script issue with Linux +- Minor - Add tracking for block themes. +- Minor - Fix husky git hooks. +- Patch - Move feature flag config files to Woocommerce plugin to support unit test execution in the wp-env environment. +- Patch - Pass `WC_ADMIN_PHASE=core` to build commands & remove "plugin" env +- Patch - Avoid unsupported operand type errors from within `WC_Admin_Post_Types::set_new_price()`. +- Patch - Changelog entry not needed, because this is an adjustment to unreleased code. +- Minor - Remove custom user-agent from featured extensions requests. +- Patch - Update progress header bar styles in task list #32498 +- Patch - WC_Product_CSV_Importer_Controller::is_file_valid_csv now just invokes wc_is_file_valid_csv. #32460 +- Minor - Add a context param with a default value of global to Admin\Notes\DataStore::get_notes(), get_notes_count(), and get_notes_where_clauses(). #32574 +- Minor - Add new sectioned task list component. #32302 +- Patch - Add woocommerce_download_parse_remote_file_path and woocommerce_download_parse_file_path filters to modify the parse_file_path method file_path return. #32317 + --- -[See changelogs for previous versions](https://github.com/woocommerce/woocommerce/blob/77ccfc56ca5680f3bc1496d8b2f93befa28e1483/changelog.txt). +[See changelogs for previous versions](https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/changelog.txt). diff --git a/plugins/woocommerce/changelog/COTMigrations b/plugins/woocommerce/changelog/COTMigrations deleted file mode 100644 index 1dabd6c2ca5..00000000000 --- a/plugins/woocommerce/changelog/COTMigrations +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Generic migration support for migration from posts + postsmeta table to any custom table. Additionaly, implement migrations to various COT tables using this generic support. diff --git a/plugins/woocommerce/changelog/add-32141_pinterest_extension_to_obw b/plugins/woocommerce/changelog/add-32141_pinterest_extension_to_obw deleted file mode 100644 index 83bce430bb6..00000000000 --- a/plugins/woocommerce/changelog/add-32141_pinterest_extension_to_obw +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Add Pinterest extension to onboarding wizard and marketing task #32527 diff --git a/plugins/woocommerce/changelog/add-32157_tests_to_disable_welcome_modals b/plugins/woocommerce/changelog/add-32157_tests_to_disable_welcome_modals deleted file mode 100644 index 21116006a6f..00000000000 --- a/plugins/woocommerce/changelog/add-32157_tests_to_disable_welcome_modals +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Add E2E tests to disabled welcome modal #32505 diff --git a/plugins/woocommerce/changelog/add-32573-context-arg-for-get-notes b/plugins/woocommerce/changelog/add-32573-context-arg-for-get-notes deleted file mode 100644 index d576cf03ea3..00000000000 --- a/plugins/woocommerce/changelog/add-32573-context-arg-for-get-notes +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: performance - -Add a context param with a default value of global to Admin\Notes\DataStore::get_notes(), get_notes_count(), and get_notes_where_clauses(). #32574 diff --git a/plugins/woocommerce/changelog/add-block-theme-tracking b/plugins/woocommerce/changelog/add-block-theme-tracking deleted file mode 100644 index 8fc5311f580..00000000000 --- a/plugins/woocommerce/changelog/add-block-theme-tracking +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Add tracking for block themes. diff --git a/plugins/woocommerce/changelog/changelog-32317 b/plugins/woocommerce/changelog/changelog-32317 deleted file mode 100644 index 00343122e63..00000000000 --- a/plugins/woocommerce/changelog/changelog-32317 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: enhancement - -Add woocommerce_download_parse_remote_file_path and woocommerce_download_parse_file_path filters to modify the parse_file_path method file_path return. #32317 diff --git a/plugins/woocommerce/changelog/changelog-32460 b/plugins/woocommerce/changelog/changelog-32460 deleted file mode 100644 index 0048b30d431..00000000000 --- a/plugins/woocommerce/changelog/changelog-32460 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: tweak - -WC_Product_CSV_Importer_Controller::is_file_valid_csv now just invokes wc_is_file_valid_csv. #32460 diff --git a/plugins/woocommerce/changelog/dev-32131-ui-changes-additional-payment-section b/plugins/woocommerce/changelog/dev-32131-ui-changes-additional-payment-section deleted file mode 100644 index f914a935799..00000000000 --- a/plugins/woocommerce/changelog/dev-32131-ui-changes-additional-payment-section +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -UI changes for set up payments task diff --git a/plugins/woocommerce/changelog/dev-32417-clean-up-wc-admin-package-json b/plugins/woocommerce/changelog/dev-32417-clean-up-wc-admin-package-json deleted file mode 100644 index 498e9121c9e..00000000000 --- a/plugins/woocommerce/changelog/dev-32417-clean-up-wc-admin-package-json +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Remove load_plugin_textdomain method from admin plugin. diff --git a/plugins/woocommerce/changelog/feature-32164_new_task_list_version_2 b/plugins/woocommerce/changelog/feature-32164_new_task_list_version_2 deleted file mode 100644 index 502e6a1268d..00000000000 --- a/plugins/woocommerce/changelog/feature-32164_new_task_list_version_2 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -Add new sectioned task list component. #32302 diff --git a/plugins/woocommerce/changelog/fix-32140_wcpay_completion_task b/plugins/woocommerce/changelog/fix-32140_wcpay_completion_task deleted file mode 100644 index d8b06ce4cb0..00000000000 --- a/plugins/woocommerce/changelog/fix-32140_wcpay_completion_task +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix issue where some tasks where not being tracked as completed, when tracking is enabled. #32493 diff --git a/plugins/woocommerce/changelog/fix-32141_remove_pinterest_extension_from_obw b/plugins/woocommerce/changelog/fix-32141_remove_pinterest_extension_from_obw deleted file mode 100644 index e8e565aaf0d..00000000000 --- a/plugins/woocommerce/changelog/fix-32141_remove_pinterest_extension_from_obw +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Remove Pinterest extension from OBW #32626 diff --git a/plugins/woocommerce/changelog/fix-32399-husky b/plugins/woocommerce/changelog/fix-32399-husky deleted file mode 100644 index da836bbc7d1..00000000000 --- a/plugins/woocommerce/changelog/fix-32399-husky +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Fix husky git hooks. diff --git a/plugins/woocommerce/changelog/fix-32425 b/plugins/woocommerce/changelog/fix-32425 deleted file mode 100644 index ebb4792044a..00000000000 --- a/plugins/woocommerce/changelog/fix-32425 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: tweak - -Update progress header bar styles in task list #32498 diff --git a/plugins/woocommerce/changelog/fix-32428_reminder_bar_invalid_screens b/plugins/woocommerce/changelog/fix-32428_reminder_bar_invalid_screens deleted file mode 100644 index 7043ac660e4..00000000000 --- a/plugins/woocommerce/changelog/fix-32428_reminder_bar_invalid_screens +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fixing bug in which tasks reminder bar was displayed on product screens diff --git a/plugins/woocommerce/changelog/fix-32539-order-autodraft-validation b/plugins/woocommerce/changelog/fix-32539-order-autodraft-validation deleted file mode 100644 index d283595b90c..00000000000 --- a/plugins/woocommerce/changelog/fix-32539-order-autodraft-validation +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Ensure that an existing order with auto-draft status won't be interpreted as pending when determining if the status has changed. diff --git a/plugins/woocommerce/changelog/fix-ascii-uasort b/plugins/woocommerce/changelog/fix-ascii-uasort deleted file mode 100644 index dbd0e8f6d4d..00000000000 --- a/plugins/woocommerce/changelog/fix-ascii-uasort +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -String sorting when using different locales. diff --git a/plugins/woocommerce/changelog/fix-broken-i18n-from-wca-merge b/plugins/woocommerce/changelog/fix-broken-i18n-from-wca-merge deleted file mode 100644 index 9f64a19f88a..00000000000 --- a/plugins/woocommerce/changelog/fix-broken-i18n-from-wca-merge +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Added a temporary filter to patch the WCA JS packages i18n json files #32603 diff --git a/plugins/woocommerce/changelog/fix-downloadable-file-data b/plugins/woocommerce/changelog/fix-downloadable-file-data deleted file mode 100644 index f99ba74d55b..00000000000 --- a/plugins/woocommerce/changelog/fix-downloadable-file-data +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Make it possible for downloadable files to be in an enabled or disabled state. diff --git a/plugins/woocommerce/changelog/fix-feature-config-file-location b/plugins/woocommerce/changelog/fix-feature-config-file-location deleted file mode 100644 index e366c8e91d2..00000000000 --- a/plugins/woocommerce/changelog/fix-feature-config-file-location +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Move feature flag config files to Woocommerce plugin to support unit test execution in the wp-env environment. diff --git a/plugins/woocommerce/changelog/fix-hidden_wcpay_task b/plugins/woocommerce/changelog/fix-hidden_wcpay_task deleted file mode 100644 index cc3627b2ba0..00000000000 --- a/plugins/woocommerce/changelog/fix-hidden_wcpay_task +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -WCPayments task is not visible after installing the plugin #32506 diff --git a/plugins/woocommerce/changelog/fix-protocol-agnostic-urls b/plugins/woocommerce/changelog/fix-protocol-agnostic-urls deleted file mode 100644 index 21c068f95bf..00000000000 --- a/plugins/woocommerce/changelog/fix-protocol-agnostic-urls +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: tweak - -Changelog entry not needed, because this is an adjustment to unreleased code. diff --git a/plugins/woocommerce/changelog/fix-unsupported-operand-types b/plugins/woocommerce/changelog/fix-unsupported-operand-types deleted file mode 100644 index ce5ecc73871..00000000000 --- a/plugins/woocommerce/changelog/fix-unsupported-operand-types +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: tweak - -Avoid unsupported operand type errors from within `WC_Admin_Post_Types::set_new_price()`. diff --git a/plugins/woocommerce/changelog/fix-wcpay_task b/plugins/woocommerce/changelog/fix-wcpay_task deleted file mode 100644 index 03a6800fa7a..00000000000 --- a/plugins/woocommerce/changelog/fix-wcpay_task +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix WooCommerce Payments task not showing up in some supported countries. #32496 diff --git a/plugins/woocommerce/changelog/issue-31347 b/plugins/woocommerce/changelog/issue-31347 deleted file mode 100644 index 2a5bacef909..00000000000 --- a/plugins/woocommerce/changelog/issue-31347 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Enable the "Save changes" button within the variations panel when a textfield receives input. diff --git a/plugins/woocommerce/changelog/remove-32444-featured-request-user-agent-headers b/plugins/woocommerce/changelog/remove-32444-featured-request-user-agent-headers deleted file mode 100644 index 8aabb35193e..00000000000 --- a/plugins/woocommerce/changelog/remove-32444-featured-request-user-agent-headers +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: tweak - -Remove custom user-agent from featured extensions requests. diff --git a/plugins/woocommerce/changelog/remove-admin-feature-plugin-env b/plugins/woocommerce/changelog/remove-admin-feature-plugin-env deleted file mode 100644 index 53ced9ee0a1..00000000000 --- a/plugins/woocommerce/changelog/remove-admin-feature-plugin-env +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Pass `WC_ADMIN_PHASE=core` to build commands & remove "plugin" env diff --git a/plugins/woocommerce/changelog/revert-31779-fix-31729-add-menu-page-arg b/plugins/woocommerce/changelog/revert-31779-fix-31729-add-menu-page-arg deleted file mode 100644 index 5d92d0b212e..00000000000 --- a/plugins/woocommerce/changelog/revert-31779-fix-31729-add-menu-page-arg +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Revert back menu position to floats as string for WP compatibility. diff --git a/plugins/woocommerce/changelog/update-32130-ui-changes-to-the-recommended-payment-options b/plugins/woocommerce/changelog/update-32130-ui-changes-to-the-recommended-payment-options deleted file mode 100644 index ed8117ebe3c..00000000000 --- a/plugins/woocommerce/changelog/update-32130-ui-changes-to-the-recommended-payment-options +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Adds Other payment methods link to the payment setting page when the store is located in WC Payments eligible country. diff --git a/plugins/woocommerce/changelog/update-32132-payment-logic-in-task-and-settings b/plugins/woocommerce/changelog/update-32132-payment-logic-in-task-and-settings deleted file mode 100644 index ba445758a66..00000000000 --- a/plugins/woocommerce/changelog/update-32132-payment-logic-in-task-and-settings +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Update payment gateway logic in payment task diff --git a/plugins/woocommerce/changelog/update-32176-merge-wca-install-logic-to-core b/plugins/woocommerce/changelog/update-32176-merge-wca-install-logic-to-core deleted file mode 100644 index 761a33e3bf5..00000000000 --- a/plugins/woocommerce/changelog/update-32176-merge-wca-install-logic-to-core +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Merge WCA install routines to the core diff --git a/plugins/woocommerce/changelog/update-32509_deasync_issue b/plugins/woocommerce/changelog/update-32509_deasync_issue deleted file mode 100644 index 1cad85035fb..00000000000 --- a/plugins/woocommerce/changelog/update-32509_deasync_issue +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Updating deasync package to resolve install script issue with Linux diff --git a/plugins/woocommerce/changelog/update-32522-remove-deactivation-hooks-from-wca b/plugins/woocommerce/changelog/update-32522-remove-deactivation-hooks-from-wca deleted file mode 100644 index a34843d86a6..00000000000 --- a/plugins/woocommerce/changelog/update-32522-remove-deactivation-hooks-from-wca +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Update WCA deactivation hooks to work with WC deactvation. diff --git a/plugins/woocommerce/changelog/update-32617-payment-method-links b/plugins/woocommerce/changelog/update-32617-payment-method-links deleted file mode 100644 index 3172fe74eb5..00000000000 --- a/plugins/woocommerce/changelog/update-32617-payment-method-links +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Update payment method link to the internal extension marketplace diff --git a/plugins/woocommerce/changelog/update-filter-out-product-variation-line-item-meta b/plugins/woocommerce/changelog/update-filter-out-product-variation-line-item-meta deleted file mode 100644 index dac8fdd6c51..00000000000 --- a/plugins/woocommerce/changelog/update-filter-out-product-variation-line-item-meta +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Add `order_item_display_meta` option to orders endpoint (REST API), to osupport filtering out variation meta. diff --git a/plugins/woocommerce/changelog/update-simpliy-init-routine b/plugins/woocommerce/changelog/update-simpliy-init-routine deleted file mode 100644 index 599e35cb61e..00000000000 --- a/plugins/woocommerce/changelog/update-simpliy-init-routine +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Simplify the WooCommerce Admin init routine. #32489 From 8c0d4baf3d45d97dddb9680e55aafdc560aa5820 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Fri, 15 Apr 2022 17:10:45 -0300 Subject: [PATCH 386/386] Empty next changelog --- plugins/woocommerce/NEXT_CHANGELOG.md | 39 --------------------------- 1 file changed, 39 deletions(-) diff --git a/plugins/woocommerce/NEXT_CHANGELOG.md b/plugins/woocommerce/NEXT_CHANGELOG.md index 58ed6441ae4..6a8f33db4c0 100644 --- a/plugins/woocommerce/NEXT_CHANGELOG.md +++ b/plugins/woocommerce/NEXT_CHANGELOG.md @@ -1,44 +1,5 @@ # Changelog -## [6.5](https://github.com/woocommerce/woocommerce/releases/tag/6.5) - 2022-04-15 - -- Minor - Added a temporary filter to patch the WCA JS packages i18n json files #32603 -- Patch - Enable the "Save changes" button within the variations panel when a textfield receives input. -- Patch - Ensure that an existing order with auto-draft status won't be interpreted as pending when determining if the status has changed. -- Patch - Fixing bug in which tasks reminder bar was displayed on product screens -- Patch - Fix issue where some tasks where not being tracked as completed, when tracking is enabled. #32493 -- Patch - Fix WooCommerce Payments task not showing up in some supported countries. #32496 -- Minor - Remove Pinterest extension from OBW #32626 -- Patch - Revert back menu position to floats as string for WP compatibility. -- Minor - String sorting when using different locales. -- Minor - WCPayments task is not visible after installing the plugin #32506 -- Minor - Add E2E tests to disabled welcome modal #32505 -- Minor - Add Pinterest extension to onboarding wizard and marketing task #32527 -- Minor - Add `order_item_display_meta` option to orders endpoint (REST API), to osupport filtering out variation meta. -- Minor - Generic migration support for migration from posts + postsmeta table to any custom table. Additionaly, implement migrations to various COT tables using this generic support. -- Minor - Make it possible for downloadable files to be in an enabled or disabled state. -- Patch - Adds Other payment methods link to the payment setting page when the store is located in WC Payments eligible country. -- Minor - Merge WCA install routines to the core -- Minor - Remove load_plugin_textdomain method from admin plugin. -- Patch - Simplify the WooCommerce Admin init routine. #32489 -- Minor - UI changes for set up payments task -- Minor - Update payment gateway logic in payment task -- Patch - Update payment method link to the internal extension marketplace -- Patch - Update WCA deactivation hooks to work with WC deactvation. -- Patch - Updating deasync package to resolve install script issue with Linux -- Minor - Add tracking for block themes. -- Minor - Fix husky git hooks. -- Patch - Move feature flag config files to Woocommerce plugin to support unit test execution in the wp-env environment. -- Patch - Pass `WC_ADMIN_PHASE=core` to build commands & remove "plugin" env -- Patch - Avoid unsupported operand type errors from within `WC_Admin_Post_Types::set_new_price()`. -- Patch - Changelog entry not needed, because this is an adjustment to unreleased code. -- Minor - Remove custom user-agent from featured extensions requests. -- Patch - Update progress header bar styles in task list #32498 -- Patch - WC_Product_CSV_Importer_Controller::is_file_valid_csv now just invokes wc_is_file_valid_csv. #32460 -- Minor - Add a context param with a default value of global to Admin\Notes\DataStore::get_notes(), get_notes_count(), and get_notes_where_clauses(). #32574 -- Minor - Add new sectioned task list component. #32302 -- Patch - Add woocommerce_download_parse_remote_file_path and woocommerce_download_parse_file_path filters to modify the parse_file_path method file_path return. #32317 - --- [See changelogs for previous versions](https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/changelog.txt).

@^&_Mtkt;RMNmxVB4=W)bR@*=FN+#TnNT3oQp41XtRH!pM%^YNNU@ixkh(r*0U8TRy)m15Q&>loB|FH zv7j&_Bp{VxHF~WhNFvX@1w6;O>p$-EUJ?%ApPil6v!AISL@F{DlP6C;vtj*uSna@% z^xJ#)X1#Yw1f3o0hG^)qUv}vwJ#NVQ57`*l1ZuSb7#bQZXeKgna-e-a{EQzf=8sr@ zWKy=(ctR$fQU@$=gHx-qYCpRk#uDgF<+9n#y44B$q;ab?($X<{vsKx#6tdtfX!9I+M4skj=TQnz=qg51*CKRMY+Ev9q2#i*2do6H2bPJIA$OvE zoJ9oH5132gMgv@Ukre7J z(R~;*m`4|F?c~^xXr!6rn^clHjvv>y@hmKBlDW7SO3n*SoMQ8F@dYKZ3Hhq2UFP2i zi~9!9XRJoBKaT=ahbkx-CA9|*znN*IM5_5mq@H9-zh@7550-2|HX7`pRs$0=nEhB2y zDKhtHPRZ*_)daG`qbGXRE<$^yR|?zhmR@PVWFAmnR}}5zWFtqZ+?^_$U`U)eO0e>v zU7U5qs`OgNwPPRGAR7c20>?G;8nm%*X}(yTwZ`PvY7*>e&CSn$tg(un-kRzoz5js+ zTP6gNve>Bc51XbP^@VF)Yf@&rj!ao6kj`<-idbHRo;#lmh3Dm(Dq8!rF};+?3o7Xj zUK8JUXd5>>@GF~poYFJLw2YK3;isu3Y>+guNp2U{!_{znxsufD%{13+Bxc;UQKxG! zb)b%fTUXF(_tM+SH1lRSSpwWgheE&dZFkAXm?yne*_(I7*vE0g6hV`wpgP?|616rP z9C}7vWJXo#W1i07+LFit3K4PWL1^-p>}UU4k=@DF+Q?#nhj+a5J*-j6r4UZwywUPW>1!3aDI#r=%zcu&kGMl7jELodOL|@s zEHN`Jy+(zGWs&1bs;B!DmX=#lsH1gXO>>*IT82h8!tlgq7+Jps#@26uk?{!_8rz_j z^)NU(1_L9bFgP|2!)h5E9argUdwe~NjILMbj_OHA^mC~%=pkuob6%_1^_=AT5-dQA zWX`en`R4Z|3KDm2OV6G==3^aV*?kAe?KJlcrUS<(+~Su-;gCz@Bt!xfhMq~!w_jS> zRXb@4nZzL{aT^qdEz%dD-qGZUg0z|C_mG$c&86L48k@q}Q1J)-27G|^r zAu0m^1EY|r1(v{xrrD$^7)f%nuTyU8Yq67O$I>+Ki!MoH(_@eXnyi57XuCm(q!GT( zn6*(CO?t2trZ`>~cRPF2jI$WapE;s|Ccf&>W~kK0LF!?l**vA^sAeydlrxDH1HhPI zsvsGUXd0C?A9-r>rqEPrI1FumZVC?Xe=?hPG&S+u3-!oCw$gsy{HEZOE%jF*&PT2v0!~v(stWT^h`4z;JObuR z@Zq9YAto5rx>bXiXg%Uz{^ei(VU?-~Sb8o_q?@#LGY5p{{3wPW&9o@-H=-29E}18JQ`@#@CnsTK z-5Ascs(KnyjDd~>s~y(t&xb=PNv45pEwh64J4xBh2{mjB)@_8!Kn=#%ZGxvC{wh?G zY*G}QCfhJaStOEz$F800|euXeRoT)RD|<+1wJk=8D8^5PL3(n>Fm& zvxm&%_J-)&Hx6*YYpv!lE_))(1JF#+UdV*|_yBR6BE2!yhx>dqYolcT$xnXr+KYGZ z{^#1jz{E;#xbS7c`L*x(owva!KK0K4rZ-PvM}4!Jk)**L+}KN3=xKlT3wOiN;Ns>` zZ=OC4$KU-!MwR-#Iy9v64rKOd{Jyvr3Om(i8dD_u#LRIn#6Xsg9R%zIB$*mM(>By$ z_SoaWj&ip+@3LE7RVg3658;m5*4(n|`3{6SmDS4{9+F?fA1+oue zH4Z<^n$Vn31XDZdhvJeuu8q@MOL0(qLv%q;J@ph$h5F#Wg9rcfrVShZ4rjnxsbo$h z{*HIO7e4XHf65Z6gABQ9&?kjd4AHyy&WZm_1d)X&pM-Q2mf{JF!2zi)1C?wRHa^kl~d21)$w zfAl}$FW&W@+=gJBOj0L869;dgTn9KVi7d83pMUHz*!N4n0COFZI3ZD*tg(w103Lr7 zxVgtZ2GjTbxt>RPG02`nk-#a9p$y{}-t z*-RT?m|Raeic^d>anW|_RF6i{5__6Wc#^BKAvd6r`ONG{8{1s^FG~BOm?z0(N}!?7 zQ(!I90>5*cx=Z|L8LHD^_(4AuPU0wy8%OGv3bR-y8pL5{_?c+e2Cmi+ zt=_SBOqE$UhRy~~@OqkZPCs}W2WW;A2qls0V`wwKaOci@9(dq^@12{Ue`ujz?}~KJ z7O;+WRo5qYMVJJ(FOs&rbrgy18Kl|tl+ zm7Z1yLIh67LelnfJOll1$m|4OJ{mzWn zKx4B7Hx0F1oux<*naXUAa+&~zrV?!_#YoyM9U{o3b3ScKwqfkzf?vN+pxy`ar9vlZ zHiM=O1jA-R3-*u?T5#xJ7? zJ=w8i2fX;jFMe9BU-Y%FeeDg(4)&Yshum4MRQjq>(>636AGryql-(K0`)~g4Lohix zWjskC^Bs}Ju%evIsT^EFQOmMpf=qPX-#^;d{`PNS{#)OIZZWh8U~r2llqH^?0=a%b zv3oatRtd8Ye-H+?UIzo)u7~bHogjLxs5-3#?UU*duI)9HuisFuuKdYyeB|v*=f5F= z`;XB6QgrUQvzhKJ=lyRpbSurwN@VIOC}xHIi_?Bekr8h8wgxnC0BlZihq$do?s<=W zN@9k^&B35+9=OO^hH4j;c}a)$Mz3`wbCSx@_X-`T(R1B|X~i>Km4}891@}JT6F@mu zC6Uuj8!fu)cjO1NqGlfYP~~nVCtj*M-EsZ{I$xNoec{0IWaf z>a6e%fcK7Z*5~IJ%)TxOa`RSE3U|qzcaT^G_-n5q3r7FkV_(i`VAFDPfTATOkJH&%BVF_v9R zm>z?PzNW8B277a+E>%Zu0rHQtQZ@kV6eVd<`tP{$;2!J!)5%p_px0&ynlu_+oRABL zp-B_n&E`j8@~W$@qA9j%2pW-?&eN_Fzgj0v0d5Kxd5Cp4hOJ%SOy!a|4#xY@^_L*Yz>;c}i~a4m2dfl%3NIk%}2TN=_(A;Q%_I+>%Tzr)G6o|0~4zK8VkJaCl#^QhgGKTEJ=;S4?yGevrwJ5u;X)3 zBCXX%?Qd8YgFc!|5$oik15LeIRHYIRyRYgqzGCXM5eFH*IG znHE%sTSY?8Is;l96uB)tm(}SSVA>Mj{a9wtj*EPrkNo~QMSI#Hk*N}jp&U}J+NTrI zY>;JJ8QLBrjiV&48$Y97mox*K85JeXJRi6Sa(p9B;wClOK`(IC+Q^}(vJg&VsCVe0 z8A;rZD|fxuja07BF>{pbvJIh2B6B2h+*jnS%d1o>Qjxjbx^=5W634Y_$R(>EC=!>7 z+@&IPP2$IoAD8O9wD4y8diK)ZSc|61o#n_&;=I=@SSLVax5riLJoGAWq&Qi*s`qTW zHJD5pWTOGoci#nzL2Uvc#CKvr)nlp--2$D0>IYH%)m;PZ^W@j{@Tg&ZYFVD2(j%4+ z?NsF)A>Rf%R(KD3)FBgQcvezTQa29&s#f#}5{BE{9NMPSdIZ5Dy8*jv z+Y-qffh11#ptVu6i$W^314eD0sk^fMR$F)L3(!Iq;7ALi zl|X!wW5~J3g}t6$I`8VrsY#Htv(PQ{_gQO`X<>0hF6T~kWgckfAjP#B{5SA-g!~=S zh^2!oVRP+MNct{Qw->&b9{rXmg;q%-uh2SouGKSt=}2lMwGU)|9%3L1w1VW9fbk5^ zC78VarmbP>aq^kLsBld^7r6;RjKii=(_zOnV^PO?Rca%;vr*AOWvUsu3ThLK6`dw9 z5yBi7W?X!BJu95>XG(5)NoIV{?EA{RAhNX%N|Uti>-=UvH49Ll9*N{^9Jz+Ek84TX zQLXEA8eL^1b76!`NgV>#5iG6XsR8E10^$(bsep%KK*jGBc2O!^4S1KrmQuQ@3rmF{5 zB}VEk(OO=ML;BMxUkAj}O?Qk#yBd%=t7jd1T&G}E%to3;B2KdLX<6WwX0|Qx9Wzd5 z8Eep#sJSGLQn9vpV;MlRoHf*S6Ma5u+|XsNDb2MZi`9;|jgzfheB-TP#>eR;6^YRX zHnW-43|Z^*tQuVuzGWNqhCgSW-rwJbiL*`|VFfzm5azLNBaMhZr8# zsz00Ii&V8aMNj)4n}s0>9|qACWmL*>(oJmxWahQc)mq z_A|4Y>3=fShE6bKPGiaoMVS27>u`2M4NwTOg~Y$MUn{1NZZ}UJnn=kxt$)mlE{;> zaY)v!gRY?(lxDj?Y(tBC)swO9UEKqR?>9pD2%ge?#1KjS?82u}33gtcj<0hNdVyx6 z3H60JwamgoeO9e!6tT}heO@hd*>*#1H}rZ|kv9gaTF~U$ko9aPKwU{=lQ!j2yP_rJ zCdQno>rO*`eh#LNAAnP*4(fTvwcQ((bPm}BEZGc*X4`~-m1YieT$*Xtu|U#K&9*f2 zoRbrr-jvyh8JwB0GB{-GWhQLPLH!0N5DY$hE!D%>g=zZ{Emfi6%(%@-ToHR>0>B92 znB1a`JO_okBm0A88X9uuZnHVcC2gaV0{#f7YIg?nlg->daf<$iX=pPi&vr9JE!RIA zAfYRXoK)_TA~fmBd!#(O4-RQVu6|;yZ;o-2%}KmKyq?B}(khioAg^G1=m#J+m2X%__R~)8rM#x z_Qk=(O~<8~YE`zSrFBFzlZKL#!#Gf`AYMpBJI-VzFW~0_Kiy;xCce?gsYi8yx zyhoKkov*{f>@?J8rr_u^-+-qdy%(N)`kOFybU(~b9)sq>l)BEG{s@yaljQXxjdPha z@zs07#SObQCvnw^sH%__RxfU?ZPxBgaEgdTTWn15psZb zJlBag#N_0pFjBdVDoUnZ1L645ls+wwWe3F^V>Hx3^%4i9t7<6-u= zbk3r`S$&iGQB#uCk;JjbMyhv|)Qxjo;T)`KJKJh5STo(b*%n;b7Nq75zYVVRt$Vl5 zIB0AbT#3WY2SCHvOw^_>8ZmT`S@*p^P&%)82=(KT4Wsoyj;Kaqw0j~KgX>@b1HyEB zUWbi%I~97!ToSSL;GHbD6XnxREynb;Sh$)VTsMrwizJNX-fYyBB&?RElBnx*u%IOL z`PnJ8Ov9W~%d4+Bv!$EX#Bw#r;u~0#jRGM`nLREverpaIDxxP zJ#H>ooylhdkpt%xUNR>|Tb<)c6 zL@YfxNxCHMb9m##L3x(yH`D$sdI5H=?32@~ZIr?n<~0G&PM?OEsZ%gDsmOQos3P1$ zaQgURmAPVU>}h6gles2M&zhzPyQ$Rb&H6mdPGK1*F|!G&ng9jItcpq&rbU>Jfwsdp zwUcOyk>%3VG)t+T$VFyztU(I7mM~4E4*H_+Ly`?${Y}zryg3M`9gzj?qKyr2mMq#_ zzF;7kL@$Yr`MwNalR%*k7vn6g6PW<$1?QjJZ@=A|Sle~v*94KH6mD3XOSyunEch`g z^TE%0cz~H1O^+`}>D+ZxFh35d+=bcJ$2ggr9vXVMr!-W}EM!Bs``WfIi=h|nenDl; zeqidM3f2G#a{!9p0`KJc8X%W^d<`1U&)2CLhO!tbvbuf(HvY_i1Tiww)p;n5&nU%) zbe+t{s~|TILAPISGIlNuUH0m%hj0eyR8kA2xe{N*(u4m7zUX|R--Lf7w7(=|F0yED zIIK$#E2B9bh|EpIw%m5z$Xv)g31Fsd&T?9E$2SsU&Wc_=_ZPAc z0mFNfu*U^J1PyftCQQ;XA%t{=f)LgOkqa}mDdcZbMwI3-6o2x97fCW(81KlMu$U5JtKAJhm*x5`fvsNM}_g*vLCE02<#>;Cm9~S z&9~0Ss0uIH6to3KJm{n+>BIQTUjgf0^@CkY^NneM>1Sm=Y1x2M(_J*KdI#N=BdWtN z{-R%nL`mameR0#7+OmXy7$B`XQNg>A25c+p210$4#9UGrJKEK|AMHeig=F@rthP<+BIX zazJLbwo4R=4|O-}OjL(p+bU#-MO#2b?bijcKzx+ z>YS$nkuPmL1oJ}kBar9&z6IqvOd3B~%=3HmzoF6q?Wo^jn5*4%LT(^EjNUd~g{ikU zPuDKMbmIc7Zd`)(3!AX!*Q?$hz3gq%6HoH3X}nd1XYC`2-4pqqSFNK*{J}1IzVE<3 zs?3`Pgr%-S_IS+p4ti)?{wzKDyrF4vu3K>KxgT6OuOZvrc?tG*UydgI=u{iF{QzX* z2O^VMP}gVmhJnCA7&WwxcgGKDQf0gl9Nj2_C_r+&PS=t@7YU%6Zx_dTjO>=Ab1p^F zc>?=U9Xl3O>!fZcun|@wc9hPeq)t+Ls)w?u#Y8LtnCcD*Jcjm5iYk{kA-0pEAwlv{ zA?cg;1D7^s66V|dHz3KIAJhH_@mN1oSC@39iF&@nc3=~)TEFtaUx!!!?9X{S_1YX) zHgK@^R+#?SKZ54f55jo$nz#2oa1}QH@-M^scm3p%NKo>(m*lK{dm0(= zZ#28Py#6?}DZIuuV9G4mt%H>ACGY8E1(v-uK3&~}3l|@N3!AToiw`^k>zkLs`N1mM zzsY{x5+Qk$qqq7&$a{dx-kw*nd&#_=hfaMBFl|}W*VNH(5dJN-lC`5&cV)sJX$;eU z5ZmN60y{VT@-pmhzX-c`Ui6xQ7h!Mvrq`0)3EuZ)I)E6kCQO~3b)6te@kr_i4MwBG zz%|gDNfZ9gjyP}+3SETDVvTMmLG?;@A+GP z`w;Wk1vI|_9<5DkKqTQK>bqq1p#BXnF2xU}n!3w!|4`lsaQgA&`5`qBzc z^IC=V^-b7(@DcdV_3K{Z-t}ttyPG#klREP#4p^rwu7hRZD#$|Lu-0JS&0Bq?BNb zoZTq#7PFD3xFsz3SdWw9v1*5U?ueX2{t>XzG#^1VVrp9YK`C<$N#{i9G+oV;`GiQF z<`1%f01c_$@f=N2h_T%1({8t#JoGAkz#N+NC!dc$GcTv@OVW{~Qj#Xyd&PSy?VJ}F z7kce&Z-XmOe-L*6$xXP$xWFa8Pa`;G`n9p|2}y#5Wa{?2#7`u9EwOAmHw&@lbz z#UJ_?u<_*kVDH;s^4I(c%FEq}aPZ+kfWMbzN0d8(g!+ ziWHYpT!R&Nf?IKiLW@%3a2N$XYpj>$v!P&07zKW(ZY(iH;p2_Yy6!!tA>2CU(?MADsK@m5gCLR61SM0sv zlYVrzjl;4l#?0NRzh?Qdz9Pg8wXp3oIe@$ko&<9T1GxVTJ{(9SoY`%A3AndqNcqCj zJ3d$Ycx+CWaimoAjvULNE((4OZ8`c$3G3(!eR41cckLS#p7gVNGVbWDX1*B3du;~A zsX~ejp(sS|9q+RIXVk{FYk7#nl2F^s(UYL(7tmMc*Dq(MYsTjp zdU|@QM!x6W*nR1{TiW+V7?2G6sJ|D$-66`uZWRRD*|!$sY->;Y3cDKH=18()yT5=W z0y5X}*CH8az;XXYw6#&1Pu?vS-v0Q8e0W~gU)!n`I`E`o{TE6s_wIG=vE|L(R`!$x!H)cm$$g!`gv>c$ct8`~KI?#Fkr(&+B>GTtH zy^st!h`DSX>y+1kxwWdbW)Iqkp~vo1qp9ct^rq%S9g`Cs;Upsc^tlZ!-bWETl9^dg?0fC|~x89BHny1ksbgZF?Vfzj`Y zksrk|4;EzBU5TeHoA7l$;*I2c#Evw(!d{eUHPR+8w{OL^Hpb;Mp$e2^+CiFRH10}o z&SPx)zCg?r0Hc$`QIf4Pt=hD%vEn2qvL}zfuB1qa7GkIVt_j^VnQCAl_;F`~RG#w4 zxv3<%WJ}uI)~8;vOqFrz5TZ4}Rye=p{Dfrrif{Z}@(mm8-iy*sBKp;ma;nDrhkT8qOnF0zCiL>|u`yG-5) z;Z1^k8vyx83J_-o8OdS zW0v`-atBCLwLMy;DPh?)zd8u*V}oo-t&_(I^WuaeHvJsxDlzkI7AfA*6A`|fpU5En zYyM!kSk9yP>LPyVASD!IwZ(GhU&1Ls-pRnpdd)F*%# zW)5&71zH)a(=bRftQ)_6$$JX9QrRU=)n_S!8bnn^2z8w4Vz zCQdFr|DqadYp8_mjgr_l=Q`oNs)ZrH6Py3g8fy)T*xY-K!8TT>8_#{0TlV=vRDwoF z$$n;FO!;d>1+jH5n&{63>bA0Q?*8e}%up7qTzrc8Oc}yywn(vA7Nu{2bKw+q?+0R} z-D&iFu!GhNy=nqKzdi1sJH~YPptZ0#hU^Gm>L$#i2vqW2azBwFdG1=mvU|xR>)GV4A))Z3EFt~+NUV3GP0Mx{ZS_}ox;?L$Q+1;po?_B(z zA2z4KiB34%I+H;oQ4P9!l=3@A#fQ%}8S}}JwjhwMNEDWntjdQXjC-HZ60LVbo#oUw zh2Bv}qC<8C9eLDodkHsRN#pVDCY-A4%vM>qoCz&Hm9b}{`D^Pd$u3*NnU`yKffVJU zC2wt8M6JusBW;&!nNl<_Vje*Xg&&VW!UI0JIQr%5WtM## zzj1+qrYzFOpxCA|5eT{h1``@{$3YQkV5J+{yK_ zP;7)cL50B;NNdB(#X*)xH5~CB;(hudW~6F+LjwuG2$Q!Q_3!wDZsy|mJ3S*K((8c% zK|a1^=Yxo;3$PPa;H238l8v99+J)MH}jW*|g?1wWk??e7g z_Lr_!l=;pgT)FK=s4>PN`QhC0gR$igVV+zfl6>Mw3XR7JUX}`-klT9GXR__${TN#N zwtKR+`yQPxM~7sU#jR@ojwd;QvuG3Cb{$G0GNltTGJTjR-0D zUP++d$4vL9et)6Hpn{W6J|zB=9qsLyFfwo7sA)(X1Xz*AZ1`CsF2^~saGb9q1A4+Jy@-$2DI`|!6zf_1lb$pe2YsNZiiC5oX@F*9Oe{Q|j zmG2zo_hO^kYvcuu8haOPJ6+w{h~jR)5wf&(y5+&S-&$lkafFqn zA=v47u^od4{OWMWw(5NWsj7W>n=X4DjQS#sf_HzsOxNW40Ft%NMU{RXVOBNj@CM55 zCc~j?>I}l_F_r57wt6H^(K7gm{=3IB1h`d0Y~3xKDYu4y4v^WC?}zuin)D)XzUB0s ze}u+LvyI~Nl$19ZwKUwyX3yu;O9@dZYBDu>Y#}^1V*j;hdJN%DM|&q!ItTT%@_ss7 zJMm!RBDKHTWx9%Pj=3K;rhwUan-A{t+dp=woO`#w11{Y=AqJh2OiE6VOG$c@H|Nx9 zXP6X3N?JSdrh$0Pz}E3FxmAGEH(F-ZZ3$@M(xAdF!(D-b3I(%)>Rz2EZ@s;8z@i|7 zZ+pBzvu<@6dY_1an%Ljf$-kN&XQUg9SR5+o(S0!bi3C(&X6{1S=Jzh$_x5yA@=Wx;^WQ`SA%$5$g>NKRHat$HPK;4A915?;{xNNs zS5Wv0C~8vvRg>8Llk<%E)vOoZLv((*e7`9j_)9~{pFzdbB#`O5VHGSVhD@*11lcXI z*9C{3ol3u#63trEr?5WgdUIUv@&%TkU#DXAk3&&hpJgheQ7?JWb1nie=HWYELXLdx zBy&ItYXlW-CwYCuYSm||u;<~%&X=S5`g-D$dE9(mi%5HuEIO5=l9eT2Sh_6I7)tg! zoQk0QOcXiX{n-2Cu;;r~)XJN6%y$V7$r9pLa2zuA6Oh9s_a*vMk=teFbMC+AMOzGh z&x6L_1cUwI4=#r8tn`2kDcWfC&x)zvrL$rBy-BY*k#hvwz~|OSK=9 zm#3$;xJhgaP}k?uhgt5mtkLI_ z*Xu?`8ogYy_oQuH%hVfaq*0+r#ESEty9k%1@$kz< z$EyIay6WGleJZ!H+XOf+%U{&x@>T6ECsPlCMj3aNlpsC@wboj+X{YPryZ6F!RmX@_}ia%mvD9PGgjTXDKcko3c8sY4)DHQL1hYsdDBh$ zL}B_RQ5PzE)50&ZhPg{_TF!tcJDi@rZk9FjT=bLthq4%VF;SiZL%{X{?;=UQjvU!7rVq=^~DEvPDx zx@Zw8tU8mZJ&hN%=wl)8-F$h_yk)}-?VT^v>7C=Y6lx7A%*^3AL(7Od?72vGpiK}P z6JP(asul!jN?nT{Y5qXnIFYLSoTNcW_~W;pJD-I{J+{wgY<_{60j<`!EtL@DTKYsl zrL)D+gciVgB~ zAX|vWOiB;3cnzitW0p>19q%>ENwGq!`)J5(6?Xf`H+n?vU^VG+>L9PcB?`9bONf=8 zp`Mp$c^eszqsC&E&LV}Q5rTbV(jE7z(Ybqh$*2wVQ)1&C`0RVE$IwR=36nzs@x+DH zlU?Oi6hSeW?=&#*AafSt$08Li;niL`*@G!FPW3;n@V`7M(9k&pzAmD+NGCow@u@Lp z>`byj3qZT{!R(^e8|K@^VV@Q8SXQ(OHY--#`>$ZrXDa|u3rM$NS9h;(6%G8mhj93)UFEU!+#S$3V zkalLdjS0T=OPr=ny|UeJ_-haoO$fAYI-V}q^W0$c*2NAA!e^`}dx{|9foEyR!&l}f z`v2jeQ1Ump@+vZ7p}ofXd^{&erHM1*rUC6lHlVE12YSC1`E3tB6vK-IS0u|oeeY=vXjFRJ6_ z_Pgy(j7uGRKqMbc9tZESjUgv^d7kK|Cmf#l2MoM`uK=y42PaDk52*fP4>P4rnCl*= zCEE*Q*UY|t5(-VnQxP2dt0+%piVKH-Ae^5Fi^1icuY+Z8Sa;{&vhrHeinNG`h|PLf z(VQP>ngK9Xbk-=l0`JzaBft=M_D+L_dQPx7l_1Ui*QNd+2tq&4@D*(WGjP`GZhJ94 zQy%YkeBT4t{?bFe6pW^6&U6MHEY(TUFHUC~1CZFr*Ur#?jNvr?wX`o0mYb2=ZhGmd zoM0uM%=~M-h^0E<0>45^_rL)wH@JS(jBR+-po-)4_+@7?lBSi~Uvq(5^%xT!6<-tw z;1oO5+pnCcv5e^>E3+FS;mDP$(-V#7%1vd^IC#`y64eMWkYy}sP<&d3lk#j)9Gb2! z)LuNEuVBxSxebYvJvFd|>RMaONo=6SdQwfw^_l!#W{>Pl#zwI8!{XWx{NBKN6?1Vy z`nz0ipqQ8l-IiJNfT6rkJ|}!L>4-p{X3#En1b4TbVjcp6vIDx?%iBhl(kcp538z0i zg$(}SA^jWS%DIB1qoMPS*GH5sa#df4O8e6e-g=L6Z~2d^2iEzc^EbRq`l2pJz9Dg; zx39^r`H2TM%Y~kDn{t-$nc~I^B;RU7@hS+V0jW?`sY!$7tsiPM2dCN5qPU8;h_=pJcHEIE29a_Gb{?MH78ju_8 zMl-(e;+IXU&!4IK2jWN-en)C;%|ESL7wG91M$vI)Y_&UJE4DASGpzf*oIfF4P#mvM zpPkwJ+Qw^=b~bw5JefFSNKg2~o18buZ4)eE`fuq@hp(*+ajpx-ctnrUrnuX=vF*{K zRDI_Nq5TfXiXDlmtiNi*|1B@jVv0*}I{&<$I+A8Y+X!AM7ClW-$UH1Ks900AX*F@8 zLNQ$KQp!w|b#ciTFRI}85x`ls)PJC}q)FjT5}_933Y8+pJ4zG0OxJ&z^1i#hlf=Wm zyS1}4pLrnC_heV1Nb4PQ6aWmTa%IL?XM8P3mu-Zr(8f;4 zCNjjq0Q?%l44eWSr~cH>Jjl>3mzovqp@8Rm0Qb$zXjF^J7t$fn=Ptw+&mps!BD2o~ zelc|aJ(Fr;F9*BBVYVA}$n>h1eRV>hgCL`Uez`X>%*X>YdUmIch-CL;`bf{vL?^`& z3btggo;JmEo4Eq?X~Xz?(0wkKjW9l3Nn|5$-*h4m{=CgV;ceEbG&SQN`ACuPICQ#) zM#B!SG-S#_1h@hO&k8$hmAP<)2@96C zN^jD~kVYaY=M9opX0YRG4bP^R%_&-2pMH*L;|*(*o|9tyrZhYDhR!Oo7jV(vZ?b{u zqsJH#=f*G(LO^WD(Zi_EzPP>ZHKdfQ+0QsbYt+4)@`f(ocvQ&DV3LW2$!7bI$)^YO z$f1!HWo_xoZT;A-U#}B)&YBX{GM2U{3dWPS`gZSIqHkw}Mc`o}w=}5$5Y1OvRBMqj z99_x7H23h~{Y4G#eF?r@lT%i=lbA}sjPoAsE-7<^m1D*^KqlK?U8{^&wbz*lzPf4X zp5m$2U2%ldupt$_qt5Fae`BsfT?x`C$M?TFvIr6gU9}nnr_SQRkP<2x@#`SLyiO&q zth9EbikRL|nOlvUe41N~AItP@kbj>Jy0e?l=MtoWjfDLfI_$*me zDr3RbIo9!z^T@*)Z#`{wG82WI#tVBix9EqHUdfIWwp3ZX&XA;w*Y8z(WZ>a&HzOMw zII?-uooCR=uUxl)V;JlPMe(g`z5BkjDYv=t1atnFTJ#pApH=AKE0dM+gP=bg9WX7ZbB7~g}4!)R~kKw9>P^QMvkqf?rTQ6PsL!mNEOvbv6 zo~qiTr;twb1|B(0)?Pr=(%MOP z^wf*4nzQtX)39F)OF~6nS%TFlcIa>!yle;X_3Z9M>{u8%f4LS(G2cH4#60mpt{Wd$ z4z<2HyiXK_KWmPRv@iY|wGmFLxQ+UcSkbPE8*pU{%ol#BYkDPB%ma>ey=(%JJJ{+G z3%F=6X5q=wo#fN9eIUH2lkN8rL@G?%T`=}Z4ky!{DcogB0jQ|2-E-2o5R(nmOROu= z@RMOH01Q9m#VobW97k)h(H-lO&jhydaS*TYmFQ+1c=o=$>EMs(+Y>7H*>TPEGrgc9 znn2;WA{K{LYPf}$Uh#mYYgLBE5#kIu&J@QaFA!T}IZ4I3MGzIh4my&w4hybAsoiPM zX2WJ0ITM=(Q*|*c2%K=_tU(U6szqw$u=C)tkCEqi&2=$Fc6(m6;9VHY$rO^&#j+Yq zR?Z$rA2cbyRvGR}nF+gXnh5wzb!4T2`>UIGT*n--a8aB&t=@igLkC3Su8CfRsq(N) zc+v;M@>1ZN6Z6j%!Fdb0BR(qKhC7tyHIZ}OANR+dsPKXD%dzV5HqI*#Nlz2kMJbS7X1 z1#u0B0SIwopr&hXTWjdk}RM5?v;9*MGmXCQ)JF>r$?k}b3I?SLMittk?i=lTNMY(8sGFv zZii2L@8iGnu7Br7jgUL~Y`3gKrA6cxK&s)Sm|%G%yum+w{G4s3%@o5Y={tM;eID88 z06}M^#6mgUneaPcV(xfLV0q{pGLI!%J2e7>HvjyiRY=;iXu8WemIg$QS7TxB!BlID z_g#zp`T;`M^c5>nY(6M(Dr!S|iywJ}-R z!cQra!Ur1=hX4~cN3>z%WE9gEhWqB3Ul_}51~vkLY5{1t$r1X9SvY`*pVua!9q zAO8}=l%EY@0=OmgVC#j7%Fn$)@OBR`#G{@vrU@yhfB}1_bHSfXcuQ0 zLRGc3hzD;J6&0%*8VD1gei=gs4()rESfxRE*9A^u@_QGp@^Qn1sPr6_}GWbI`GKc33BgnHlgRIiS%fH zW36!#OwkYj-rOi#oy9vG9U;Ii6!U~9&^ z?5mZT?q$t=I13IkN^2E6DRnp-Q2O36;>cHgtvzqa7Fe%*e=oep>{-Hu$FrMIybt-^j{8xA>q~S}GCb=TbrFUoQfHL(WNkqc7&(y7jGG7}giLwwvm4toaGDz&pz+>q!5zB3 z*D&q6b}HoFQB&RVDv(ZPZ)a!Xwb=$g4BhSzuksDZggp6j9QZ+t*v0NQQ5^q{JNR-V z2g?&roUi;@UBwvIbd0E8P$xN{fJCpridZ?QRmY`C+j9_MV9XFs)Tr{ZIJkZ`>hu*M z&^IzFLL%P6GbS$I6n(3dmN};OK}0nc*dg>+^eoK^p3WHwCT`N^d%l@??jC$q;Vs59 z!IG7o5_Q18?vO()H|V|S?qmi{@i&&(9zd|{-en3&?RC62E1J4bSY)7#rSNM+2Dg1B-1u9X}Pl@0g!gx}g1)NjrkxvKM-idMZ-kl#D9+V;S4a(8_;& zgMlm+Y1m0r;CPx|=selL2}U;Wg=OCc^$z=MK~y_lPF0Os-B^KIoBGbNBWQ+#*nCc_ z+5jgcL-x-4TrGP;2y$Te6g99|qH||#E|=>KUHIyrT7iOQRCHgrqrLdU5H0}@^8B&) z7S7cWZY02w-fMN#^^_9=lXCh!UTgG2dmAjL=cb($FMdK*V!F zQfvg7;M4tEpZ8-~G=BznxJ)3|?adQ9fsyY_@cuV2c_HLd8;KMsMp5wYHe2IK%V~5Vmfl zCbk_dT0l-TI|-tDA8*yzr{w9k98)M~UcesrfC+3~!w*GW9FaYB9{?012rfU+8IUzn zNMim0A_%{^pW3akDI!)tX6tb#sbkJD3KW#C5ya;lKU8VhzO1I8_}SQaWOfz~;H;$F z!j;j3kQWA+C~y*;isPpA%=x_2#Yv}bg^aN$vkSuqPWR*JZvBKta)?^$Hr`24V8#2Q z?2i_OYV@%W7%Kjn7bgUJtg>82pJ^Q83I)j8rpcTSL%)Fl5wzR<2_@q|r&cy;N8J7T ze+q%1VwM?;5QB=mLlef}C7`eao6gDES%`|~^g)a&;oRA86Lq^?cos_F_~+~{ag6Sw zfbDC1wH>JyHs9~+B%$3auzy%^5OFbPFa%qU2Xy1)#O3Lwn-d`P{Qmu0pe9b^priP+ zo>Ay59GRoB=R)usMG$FTYr&?tz@P99+4~R<5EJ(_(ndHcTMYIDbtvZ>7%sNba}JFA zgmh{WG*3Vq68tosuBk1A^JNmox~`Qs85tSTw{`LEFTSS7^G8X1{3cdbXoR@C^{+lq zk3p1NBWbD=0WXOwU2Ge=1y60hw+9pZasROH!Q-3*>=1q;cD)*!)xT^~15~~v+=9%F z(cT4hIEFWdw|+z!r@b>LNSEy(TqIbs`#3>YbYhV`Nt+b@Z;ks%*imTjXpHd4dEWe$ z+xikSR;}5HQ*J@3`$Ic%A`zimE3jvipRkjccRB6f?~ykzy?0YG@Ps}m3h65B>55)X zrNjl9OZzrZ1Dtsm1h3_VrBTgh1oo*Wx64~`W7c3x@5b3`IsaX!9alKjBPS9gAiwJ* zZ)Oi`B7p~FaE=i!D$;!)DEX=WjK%6FY>Zzhgs$@l&I2Xu zF0J3M*{nJ8L|?UkxgbF zydaN6r$K!nEYuD{W zBzL+<_OO|a4Fny>B_@ss>tT+&d5XVpoHwO5Eqo)3n}#2@4-cC}{(~w!@=EHS8i>8w z@vz(KLxdiH&_j87xfsvQ;}T-&e5Fz6EB6c^)*4HQ^rkb_P=V7$M1*!3>F7TVr_`me zB+d^k%EEYJ(PyR`%kE=J&T6^HcoF;4=6ckqqodvB$oGBY$6}rLi=xr?2jC~5ngLHc;KbM)-DdrENG;OS=rLXtoIQ(gO~|Ab_^3hEdZg%UyEySeFz;~UM8&8|!JlYb zz=Z7-nZ7+fv}B>gw4g*%e_W-yb~pvCr3q4IOS1e}BzjZQxW2UBVWK&$BIY=4{h)07BGYG3|kL$8F1E zyZs9+BD}Jg9$gxGzDC%{V7RFCyNt{7ChgEXjPs~UP$UJaCgv^;lE)2o|5*Kpf`~DS zGo%!5`SgWRn!`|xc@n|fVz+)3Kk^-C(v&O}W4;C<_cP(UG49E>gmng(Bg*HH4itA* z1*HHA5+BBf{&h0Y(~tB{rCKvU&PqXEiwX~@Q!h3QCRI3)ISfYwoVT}&#P>L~UC$`0?s(CH1ZkaEL00UrUAGEn zdo&Vw5vn!jfN3=c`{$yi^R}r9MTRBX%bwhrhYxzOKiAkvo4g-qvwT-ZVOq1elF2fK zPm)X~nq)9jo}faq#ZDgg1xF8m7bBhap)}XF9xlWG_Fa2ElOBsWdUHn4ynk z2xps#0O!T1M#Esvc9>Dv9oqlV>SoA(S|o~p@Up#4cd?KCx{2Kb9y1Fjp8k_=;;a1m zNkz;|>dNNarX1V8kXlHz82GO;rR%z$Xz*YybHU;`B@*blcc+Y8w zm6MP#IJjWY(t&*Hxg>hpq2|Hoi)qJ<+G680LD|yW-u@C?>2p)|_elf_o^)|dPP%|{ zoIbf93CDy!?EGfg7=ZRZKoPgyX-0>hX1y;Z+FSnfTz2JNXh4M(?-IV+M&1gm0*M=Y zolf#`7b}ghpE9Qqr92Xt3u~47&V+nICB8alp~8o)DSH3Ans9vPtpsCwL-aNjPCf6p zLN(X0MXqt$?zh9g+XU(S$UZywPIVV{i za7Jx$Xk&{naQw-M}QGMpKQaW7-crm_sk{BMS2r2W(v48OE83kIuP??o#@j))-j0&k~Ry2 zf9iA13u>0p7xC}+Ps~HQ4I@jx*z@0w4kR$^1dOb)zjwUobMD5U_^7+H9p$jSJ6L zSy!wHQ(O^{b^z%Pv9YzyH7m4J7xB{pm{Xj)V0wZP`W@1?*gU(^$E3b4-U!o1@Xu@H zG3|*VD%@s(D~>c4@L{TkY2iI_iPOwMeu}|pSX`Fo*%#Vtt;jQ`(ajFBAB>aTl?kVl zL*$cT4R@KEb=1*(t*ZBSPB|MSf;5wIWJoDeXLnUSdCk?yPh8)u%-^GM-i(1VA>Ic4 za=pPYe5O-oYsbx{G|}G(F0nN08XztYIr$f}^(R9N`%|?B>ZCy|rPgUgd8G4=Sf3g( zwwiRRTZ{KWnXdPOYoX=pc4Q;b%YQ1lSIPy}`!2VZ!0qa5N~KWx#6bwTfz(s=gyXNd zxj8J|=ht6Zp&hJ15&rqfPwO7o!YVi0&4<0wT6@pHqekOXENhor(vY6%Apb%0Hr9K# zWLC1%&HMG5r~5m_4>yh&J7uzzVK}B4W2~sxB|kb{WM%+9;eU?lWB*B2B%q9c-pW!~ z2Uw!s@0th5n4SYLr69&lHA`2>9ppQ!YM8}qHwVc-Ejz2*T7*X{u>X9lhp<~NGJ&k` zFrR{Niy)uZo?A|c74Nclo{9Ueq6Y$f7uOxw3mndJCN9S)2aUyIG_Dc@P((7tffS;+f4;`Gg^r#1*L|Xw8C8KAn3!;C%hzGHhTtV z%^z*9%AiiI!l{81lI4ZtFuS$QgZkS5B3n5jfor=hE?vDsNed|Jo9D2g4i?(xFgosG z0m4?w{bc+9{DX&>@l%lJB>}6H?pp#xFos(*0iDaLxrT`Y7PqTe{E+gpS4`765h<#E zwrTWAkS0*uLR+l0LN-L#=UHHl^wM8Ww~rH8wYg|8wXECZ=a4PFSmm#=oY*P!^zvXF zevq2=vmnWfEZI(9u&G%%Ap6btfA04V0yd!(#fg;R$*;cV9ZkZHgc9>Ovq<#VP{M^z zp6XH=7>oT6wv>?Vy`@{>MJM(rLm!s!3JZKH*}-1rfhzX}5IzOF1>xx><1my1w5M%% z>|+;|?d^8ulz{@-&44D}gNBgt9~^l9nBP>I^GuM@+KoEKB1lCuo=T_sF5g9lLVqul zFfoWu{wUTM{e!7;L0eAz4#*m038^dpD3Rx*ZDF3ZAL}HqyAS%X{z|GqT7U3)$3$EH z7`ad(X?MIKE-Xlu7qL)f@Qw}H3zIQST|d)h^*^$UB%WbnQdVY`I}qVw8044P7ak4) zTuD>J&QUX5X4>fpj>iy;N7*d8JPFVDL#9R~Q?zUpq3drV5c!$ok0&zu@!YD{p6~KL zMmLz_tHw5+gQKl)G6l)sV%bk+@!S6wG7rMz+O(%C1?O`x!E)~lNp9)l1;p|y(7~wbP2tH%qzu&zi_avVh;B)-!Tg*_dS~e{0BEn#iHWIa-<^ z|LrO9t&gXKvyU+<7?STpgp8`gGJ_O-@}dhqE5ZI{*8e~OAO2MS;XtP!jscf{EzUuT z&1cmAlY?i9T7iYUI#p~_Y$=asnIZ#!m8Ct5LLF(;=$INNPWE&?RJPz4k}_Sf)FtCJ zXs&1TZfzmIP!$h62QNf_Cb?->INH&e(kRX21Y|hoi_BNF3S9Qj^94JdzFL&JQ*66@_|z)w z*2+a!Dm#(=$1?}&-~P5I+_0LIAvZz7PTDbEt3f>Z3%TTMm4>A|G!@sZGgshu$+0sI zmTfftz1A4EDzq$H1sWre#a!VdWgPk6VxFlczlx%G3>jj5<#E223#bC-Qzq3)aolido7G=ZDY}?^3r}mq>1F)EZleoX_a{9at)s-Y zl}unP&Kq%THnV@0Vh7p9vv)04y5O6<9)ifF9*sTj`lk@^c)lxJxm4CvQxI?uFcPLN zmoFW!X&9PJQzTP}pR%@F&s=aTkMQlFum($_Mdb2eSV8=Pn^Af}bDa3i(;Xfo(!{z2 zD`T|aAAG~(|H4p5jcC4!0O`!ae5=`++|v}RJ0k6k}3qXX$D)0+2uq@Wf2yU!U|7tWE}0aJdhh# z&Fd`^+c0P(N@scGuE;r?)(?H!KB3MkYdsKPf0j^zq<@6`-0{V7qj9&2&R~Q9Iz}dSS#Cd2^6;>R+79BH%$-77NgJ^p5R|miN(kOYkJZ)FvK6jLS`<7>H~v z&Hlrlpj&FGLD)P+{F^v}+Ptu(hFhgUFoc2jJ@8PYh+3QQ-@#5gP5wk{uA2^iwWF6H zDw=wQM6vrvTL(}p&fiuY3;7%NQ8ZX~@ekl-uSJ{bsSgxeQi=-{c0ZGUH>&yNoh&Lb zEJy+$MB9dqok4J|2`vqhYtq$#?85vNt zMp=<$$C8KwVwS)EG>*Ro^oNkpCEWtre#XDkk|HuhYd1I(RdU^?I(|UG44FA&bfIf0 zqRfn|T?7XBWpy7Lme`ZzZT|0cMZ^^NTvmlY!iXtk9_NC*H}_q0)rMfYNWt&&TmL3y z-j$KTCCON9%*_;8vJL+zO+8#Ev!32mfZ_&C=b~(B5G$#pUZJ2mv*n`8uiK%A$vrhYBmk9k7Lv`+TDW-Pa z0V}3;F@>7kC()Im4e|1oqez<+eA88w1+2I(SYWu7E)g3f%ti+VAJB)|(M7t`w~u&< zr_}k&w-M)9?G55t4HIKP67TRk-%KtU*w#9$zP71(nm@3N_=qp@{R_f#g7>xFG|wfU zhC7R^XsgjP;oLezu+T9f#)lCrP|MrI3Sz`DTEb!nYP*G+ao2Qg+j#g$+kZkP!r0l8 z#=$KT8k}yHs3DcO}-|KCEu7k}ZYOLqPBZeGD~u#fyl73m5|vp`oFnpP!$mrl$D#_?nuUWkQ#xrl#B5+oGbPrKP22L6_Ot z+5Z0irlzLs?CfYpnd0K&^78WZ^z?I9n%vynYf74DLz({m{$)XzXhD}}Lz&Xj(*OVe z`}_OB!NL9g{bxj(`uh6({QRky!u9p_`}_O+{QT9;-}w0W`uh6%`uhL>|7Sy)+mZP5 z^YiKI>UeyCJSbskM4A2l{b@#-Ye|~?`~2_k@Amfg`1tti>+9|9?QBb$YDbyz@$u;B z==AjT=H}-5`ufSp$!A2FXGECN*4oR<%;eb%F4>o)Y!+z$8S%X z;Nalkp84&_{LRhH#Kgtl-`~c@#&A)aXhoRZ+}+R9*X`-@+S=OA($>$<(9_r4!o$PQ z(9@E6vz33exT($4*4xzA+|tt0aZ{V2x5}Qh#?;i*zP`Vtxy-`B!J4wh(bLwbzR=Ry z-@(JjmafCl*WIYQ&D7Z4qmRInqqLQ&yr`?Jsld~tywH!Htev&W!^X*ups}*X*V)t*x&6^8U-u)P$6xij0un;^pP8`lq(UnVg`pzR8QEy65EU)Y{&3R-M4n-h!8` zxyRI?vA~jivZ=AVq@|{9aB|h$;hw9vca5Qic%94C+>finn6}MGHEY_zvfI6>QCC~t znEBe(#1!wSHlz`0M_~(c8kj z&XITZhPe2?9j=@ z&bzUPY^|=G#-E|4$-dawvX)>`eqd038YC@j5rh!{001&{QchC<0tE~R4iO3#6N?mJ z88jpwpf=3l=sEY^b8{V|O(c|! zyd!^}xqma>Voql4-82ge3kwSi3kwSi6V6W0+f)UgUOa88;p{xsM0>hws^B!$M*Fs@ zfETGkDmc%o)Fq{}=D?}%9IN9rdz)(Iyg6tpJ;)CJFspaNq;%Tc0Y^YRB1LOjN6ie! zwZ;alCe2b-nLbS^C+QhRx>_;7f(#gNvQA@2ti;(GXovMNZKc586>YOGxVG-7NtBq! z2pe%x)K6grkN$!yCz7QVu`pR;FJ%~t$C+4WraA^Zb$xk1fTP%!-3fbn{QKWO+CDi? z!XUlCk*vC0l`9W29wk)9HxSmGQU+9fx=4Ac`Zdh`yUdXQ8{9Jkhc0b87H}r3JNcAl zeR&8CSo}N;(v-o)L{OKXgF-^%r=W4)=%&-12#uy3fG|9}ChI&Q z3IW70AMRS(JQQ45K3F5u(e5JWQEYkfQW+1?J4w)>q$zV3=HEa3z0vB5W&=TCAf>Ol zbR7!;rtl6rzoE0CDFdxEhtDO6M$BCp-6;(<8FT+WIxM_(Eiy(F6d1mzl@!cg7>9=y7n_`%Xg9=5?iC$l z(Q^hUtI;5Q+wFx+H!Y0>^0)2aK!kxRhOaC+R$~?y@pv+eo+Y#??RLBEaH$3G<|G)^ ztTEdP9OT5f(@fm1Qwaj-f~KS-CYZM{M^30Bi@GmF7+uDmtBu&T>N_uo0PcGkmk54* zw}z<6j2>Yxp<1aMS=AY+OzfMP3nLexDkq`JgX0Gd!`*Po&9wd2)=!Pc<}BQT$dGH} z6vnXb_)!*k+nO=elsL{{$ee|Vhra8gk|?Xy3jLO8`zaeb8u7H#LB{af5CjohnF5LL zwyunRFFFii#=79Satut8N9f`%Sein$| z0Ol(Ul!@ny$)ciAlV@nRO}p8A*#J7i{06>65TCMRD%i*cLK;8ET$%X_(~R~Db$R#Z z!^?F&{Qmi!Zw~%?mIPQ~ro!;ovCsvwJbu5fjc*s&HU8yB--uC- zX?|^d&j_j5VK#GNpo{O9$LmV@{HK>q!{^Kae8G$dzdT>wtg8jeV9)R@OKI*$+0}#W z`MP4h{eUtdOfImq73RtlX0`gbu9=Gsgu$NCf8~@}zm@SfGJnFnT35|q^6jRzi6{yK zu=QK~8~%b33rhV!D{6G2g-j3zmg&E;4;Ets1KM7M(mY$ zZsfhP$PaPQj4c^I%A^moZF9g}?~ea~IS3c}Fj&dG& z4xfG)3i^D$fZy*AA^x#YXbhQ4L|5dt$c@`K61U|`=TAzc0vH4^m)W)qAr7;hZ_2!2 zj@^tzf+!A`uFfGSJxPm7&!y~i7%H}lSjxDH3p1{&Gk~!bTo!JnI6vGyEYXO7;kz=l7`iZnddx9D zU=W{=wU|CI3}F`Q6^nRPZIo_gJi`)=0LJMtXGza7Y`$ErVy;BOtYCPHSqYO!B)H4m zOxHjaBErW_l1Rz$9^>3D?;mel=$PCCSi9%2@-KFd- z;iYU)^MA&BjJuMFiiz1PSt1c)F6}RnSybetAV`viu?I}Rkl!wIm|guRf0^}|6)+)& zg6|ky7|W7xu>%^&z=V9oTo6~w>)~B8t1M;DB7I@(q6xA7vJ{9aaybT?PC{|&C7C1( zBd(TN#gKj{CS*L%%0!O|V^`K^w;Y&!{rmTlFRCicqnrTE$7a-lNz#R3J%&&Ir0-*f zynBob(;Z_VBUpS-TgBFkR&$1pm}R*feQ(}tRVp8vGw3zkB_;ZeZIq+~BR*NSJ|_QN zUNDiK-jCP?;}1?&t5%`-?Nec@QfYk9)0(b-p8q_hX?mkMQ8gbm^CVZQXef|;KA+B- z6KRdfOqix-WENN{fOz}xYH!^7W={$-hn>Dkhmd|E2?ej##eE-oOgG|>Y)eCcGcun({ zy7T>MoQ~tTCdTCc9SlpH;_%lt#6wL4ADr>W8AMDtZF990%5b$^S<`OG)w&I%*|KEu z0m+#o1R==a&H(Z5k%q*C@W4ktBJoW8@zD3@+wV~r?BLd$oA1%Cw1fwH<^6eof3=IA zvsE_uKVBsRFrRQRZ@u-&)xR^<68uuFW=q@K-X<)=Lf@1~uATxPHzlg>X3AQbzr@Kv z)M7pYrZ>`7#m)l;mU*I^!JL&ba12`_!wfV39r~3B{z?UJIZrE!vU*!K!Goe-*CkO> z3`v#kWzpK$9=SArd6=g^Ua@ACtRrBaeCPi$A9KG_hLAzP5SXh!Ffi3>HH}tTE?-tv zI7SvE6fl5MOWsUrps^jd*rO}N5+s|LRa(i zul7p03_6!AW72tzy7{tcDvm@U<4Q%zFbs9oPD$3v_U`tj@yTI~modZ49sv`5=q%!q zK+m4t^Y_Vg^-9d(7R>uO23$r6%>0+tQmKMkmMLW`SxpsHl`_Vm_OfE@k_S4jxTB|B zH@8@wfB$_>X7&n&f1udIq7lc2U7V5OTYt1C8l{c)c;oVqjxCWzJ@dbkfxck|?{okc zQP-2svmgBSa))OyAHUE9jPDp$%f8($l@|9Ztbdu)WRX=e!7zlB>DZRL>AJ2WA2^m< zsbs#uYK?KM(Yy%5@KP3#c{V(jIDz5s{t+EN*Uvn8Y!@i{;j6rsy~DUXMW(?EAn1r4 z@^NQ~U0}!j+<$+o-($@k^9f&#fyFW(e17%2yP3tkYL$92o~BBuWhh^+Axna8*^1}t ziZ1?s$J(rBwugudIR=FcYKY|i_Nb5BBiQ%gbtr&&mB8>vWV6pc5K*D|vq5qb+wlj^ z@Qd6!FF)Bm;SW*#fAIO=cdOOKJv5msX*fm_RlFSPycjMV;~B1HrG8(vb}~P&z%Yyp zHOyv1j+I4eqgPk~49GAv?+_XefOPDgk!jE%ANB;N|6=(PbS4-SGJv6&`QmF>X1BCR zmeCL|s;0^g+GCQWSSV%KS*hKfmAjek>)?WEXP*m96AH{pE`28yWxK{R4JSXOr^<5A zfXxg1n>dINeYpl+#0&2bnF^;H@lXb_!q@&%)*Q^_G>yq{%P=&bRWrMbnY{{zma~W# zO;t@rcTy?Suq_2(YBjgAvvGHK1BS{*&;(pJTu!(8%=scf0ky#cW>7%8pPh%d)8}7{$D!TQn3?TTiY`U%#<2 z3N90{%k1ox*;%S(t%BhpPGUgt!__~~Pr?x$_&a*>V;u0mVeb$QZoUEWoPh|BPY6e} z9Xsuxg8WOgW4@vIp+-OlSIG<$7(`64Oy>2suHH&lcPqKPS65A`tf7?IcFL3;yJl@F zuC*S&@!E~cV^d>Hzyh~`%WQ~J7MKMeZaCe18v+`#43)H#dY)~~#QUZo1Iu)9&VTrb zWB7ebBh$CCsAM@WUy)66jG)+xDVVxdvz8UB@MGi}(q(LGYi(_I?aCT)p)CX$W*I=? zSYr5TSvNoa7+kn(fajh7>1_FE4hvfQ?^?b;m}84EE)BgjeJfL812T2haA;7*uysK- z2+XEr6=ou1SZzMG5LsGdXl4P0lnB)rgIoNf)E7MzyViKv({QmtvG+pgr(gd1li0Ah z394DU9dn_1DH{pal?}Z{c|3yE{-f_rTK`1J5ev^03rxnX}^%CHcFaiL{p^8k~rcwXH!P|8Hd(eZcU+<`Cwu#IwtT-m)yqr51tZuct3g@?lJkc$6jFb7E-!VXg(w%b*Stf7{ER)G1WmeMwBb4(3g};dKH);T5F))+kmnWto zhe|G>5fJNXVG5IjNxsrOPooK*MNeQrrX#EUhvx{)(NadWEW4A&Y)ri_8&WYXn~rW3 zO`X7OY+3~$%;dyOG7>1cwH9k(1~UNDRfb{DqJNsr=>SZJW-uGUK-Wt<*>qN0te8F+ zG?Z1-K*p?ZY}VHISt%R6I78_lXg}71VdD3=JlE4@`ivb=u?W(dVd3jmUZAFx9-6*VYEN`qQSt%PIp4n;&ruRX?bXz5Zc>L46W=AH<$T2>b z0L(PNVATHrj*+yyY#6pubacZmAYpM$L8rk9h!JnvQ zd~Uh}4`2WobTChV$z|)9@jtk2>f{(?zo0rcO~@3M73P@HkI-J8iZ_61t(GO5fI&~F z_Z&(z?Eppl6TRn9>~uDtKL331m0F$4n2}?pY&%y;*YgLeqX>C8Mz>Njz}Qyd*Hmgf z4#!Xrb7H1BnCOe=gA(DDEZ%cM1Bu@A>QppQj`dt@PXoh3h78l8yOvLeKQbnZndL0H zF^ZVa)0}cjG;PDKt^c}Ggkz?_1*L2z*%S+116m>gYl~Cn|0&rTUI3tZ3`;_>YJfg#XNb1DIClVK5%k zcqP&MVCrk+f^*@>01So9U^^Xib}1V<3T6mka$fmhRW%ejmlqYqLipQv9DvyXnB=dc zKA5FYU?2_#6Jz1HKn?5ua3&-f0z)CwzO~B%mNMbA%oPR6um#Tm zm;xM=w&F_*sN_ik=$&gY5zH z=$Xysxi{xH7Y>FTlgs8-%SBT#%U)R(b-N75+_tH`Tollc87DB;0H!Uatgp8v#X>m_ z>O0Lb$e50FFyFlnFg*Kt2Xlyl$!g_Q*^xXCrOZG@bMD->bS#k(g#9?c;Lez9Gsk7ic5Sg~gxR@9q#zTO zEY@2^#5olWIuXlCWmPGJAq^~~I2efd2UKJVbr<~tUhJFryeFqQ+3eaZF-xdVZEFR0 z(chl;Ip;kmC(@d5jHi@6q0>s}tfl2g4_B}k1MAD1o6oR@Ie-6lNA6so0hpZ=nLvKw zV6vej1`<8gV457{7?iSD)?=~&!wtiimZ_9AaS6uC@~hjm+e=GJ%e6a?A1*%L+o7v_Kezy%oE^{d+(|CwXqhRfl>O#FK=>8OUw;4_xgb;$?U-{B z&-@9@up9f_yyPD0&$z%br8jWQ^76{B#T(1t@2;)RL0#xxckAVyh3-5Y0|889I;>%M z#v~C8cg$2Qy)r0Udg?U&IT@gtDc$|>{r&q7AMV~=TU%Y7y9dp4b+=aM)=Ae1jN2_s z1>A+XPhe!r>S=og11b_g)K zq9|GbFwteqNH9?L%6U=AO5noLpkQ5XAmTX~p$9TSY}FA6pPozy3)2SVDghO(XRO) zL@7%kn0~M9%&6fqDw&=#`YE9q3X`Sz1*22&8yu|4tRvSI+m~a2Ccs>JLnt0TJ;p+h$t_ z7}M9xe=1tXBomCUnZJqBg*WCAVj{OJrM{sVzQI^VmDJ?5(5@?{ZiDMad^3M2$&T?i zTt@Wwbcn#Tm{pkSe!(;qA)6V{bRwrWI%9BD*75%^{M{zzFfdKUqq;K`uvN`g{DLtp z)lrkNuz9#(@GuI@er=IGnp7}D9kZbbQQo(B4f0#cRFNexS=9vtBl;+1>Ho5n`DtQO z^joGQ1xyHxu?H{!p?Q1EtcLNQtNL2OOvlsqpU60!80SdPTwi&i++cYwa5<4b71??W zRj#HOH`irq0hqRJ_$*~r%=6tIHKi1@T(#OW@TQJvVx}o$;u)2lDbLPUD&_LDpaVR+ zY5Rp>&@@xITrNcA7|YE99xwP}NtWdgOtaN$@tLI7f^_QW7)D!@+oq|;nlT54fvH>( zT!3Y#XL9-7eBwn@hO*Iskue-=dOu)NskS%JDXZ$b8vU9-u*9+xv*2)X?INJ;)BxAW zUNTakCT7Cd{L`^rA0u%t9=qg+$b&|L1Q+PknC&Y&$0wNaWQ-*2BDq*H8RsP<)CZFn zp4kDWk{!o=WhiCw?Djw5#yGMeb=y&(KKlU|j^WwZ^r{jYfA6xUdp+A$v-aoiitCc{h(Dg5^YNh65O21ED zD@%5N4H$4~ZRe9E)kei)ai-$H>>l8m)lMo0riCfU9D1`jR6#&Aj!n6-vXUlK{#!E6 zU>uk&zPNNqC{(>taH*PuoT`99w~R>@GHfcx6`2VYi@;TA&^EumwAykvHvqdWFt6Qa zF-C4U#;9(3ok1u=PuFq8q|Ju@S|DSka!E4Oe*mT=nbrTiR+5VY)7s8W((XjZPjSf^@8=b7E=tqw!=MwX;}YiIQ#o0miF) z3S`tY3*8*^>c>it@1-h)|9SnR1+%-wowM!&U*aD)vtN3)mVIR z8BIpRZf~mS0xX<1&kI#5MHr_7V619Oi`D)IUn>8^Y*^abO5=Y_F4iq8nwg3x6U`+8 z7i#DyIh-@s3-z>HkSUqPm0~7fvkzi7sHgaC|3B@T@@?(7bW9#NjS4VX((83-FB4$7 z9F4oZOo=e`0y|0&2wa8J4DNwfuUj_F4L%{Su@FrCgD0ln@zrI!OWC%(vO^rS1Ux2V zIE|3z0xrYxm|R9^Wzud5^<+%{kX)FOIQx437H}-MMpn|8h7Z&#@+jC!C ze&k{Qot?E{OdW<8B9?}1``vzIEM@{W+{dKTPSP}krnC7pOR`WTX^bXD&C60&cDEh{$F4}|A*=bfmE!$?6q94|B)} zqVbKB){nZjGE2sph_0?KM*VcAl$xf~oMZr|l*w2!2sNiB4|h>SPEGrxBDnz+S__}^ zCr-#a-BJFj9_zPUG?p#({CCL(a3Fy30yY<+e|-%!Ak!w9wojv>(`>4@UOSp8f7nSg zbT^eyX~Id)(16(-j$vcfwA#0kyWV$sn=BXKz+8yMKIcVbuU&zLU~_vqOC}VzzyKF# zE|2u$(%oJeiC8eG$n+d?B^+N}TwhM2F6;vq!-j@nXfkYLqEzGwM*`wlN)f|@PN%U5 zY9+WJ6xuJRGnxyrup@#ez6T-Z!3={ro!kvuiD^fbfxloENH4)k1w4ory}?VfNxzP0R3+Y7K*ARn2h zZO_Gp%NJJzGC?r!!!9lJ+rpx9fT0M66vkq^;;O*HmZ&h|bQ5?xC& zBAD_PLz^*)K1zVGL-vD@TE(DB3n%l8K+CFy0|A1mm;}My-)cX`zG0~c{Q00gH~r7W zGUI1tQ=s7-K>sq4@Wzy0cP3QQrjl%>jVbR4=&a7zEW!%4RSVWc0ScJ0;7AaM1Iv5J0E~UCt{E>oB(t;LE2Gf31VIoh5qK+u zzut^&NXCE}cSf0*r?XhbQ#Ld`y}8Mn#(%$gf9*R5z-V!4>}VC8#bz-s{e)nS(7>>f z8Q>jC(<@IG;aCi4TD4fpd(_)6Es13G@!x)_d{_Hq^`N1b zhD+0je_;-b3YCBT>_^bKpf_-N-A?-c@bc>U)kKdC01Ub`GN^4bx@>i5?8KreqcddiNIc6LCB zbPB840L*O*{^~(I`Ti>9x-np(SbYhSLPR@JDrcuf!je!PXL^oi9O(h*L~{umo2)Cd zYG@sR$`X^4JN1v-_785(&(FWWgv^UGGN5Er9y;T0xx~p8BVE1!{yR$|B(QnIqPaJg ze*=S#WT4XT{`l#~amFVc=*EUjRIZd%R6fgDpM^3AP(14nxkiInIDDBac7G~Mt7Mv$ zhly)I_KC&v*hN^<$??hY?!ENY4&j*Z@b_q}(u z-ZLT_+6`J8XEdc3W6)we5nuiE%h%t2bJ5F<(S+JiWSpC~WM%*adyy6m>H&pOc})tWb#GPS3f|S4A`9W;N#|`rh$dP z^o!yLPBwq4BUFeiVwSPFFvj>dcW3X~R1}7BRH{}71qHvrNktGGE@=`9jcwujHB?%Q zv}&9}aS$9D`VY7WE_HE9ZK0%eC_;r$0c*~dm7bO4s z{YB`(mk0k!7mwHJz_GJ}&3=**o-t1{;1axi31DEEf%3}6}PM2Dl5m3{Np3c~1uH@ocqo4aNPpD8mrMK&5Hcmc`MTWAF`3)3L`qm@&!h{yXyT z^b2ME%rYanTMKiWFb&aMpQl+!fju#jAs4VBmwf*2I9x&?dLKH$WNdy@eE?JIVxi8T zl4RmoCMuK>OcHIE;pJj=V^W}bHZcE0Ois?^z=CAhvQ%8|jHt8CaBVr?G-C;d5?rph zO|^PCrcR$AnZ1(C&Xmg#$=t|}Jul2n3pAUB**oKaf<*IVerg6a8L+Sjn8@GDu>jN3 zr#0Gvk;`Mig*%U1T8a!xGs9RG-d(0+=ow6t4b4m}m*%D`L{nYJ4?Kiz{%*Tq$vppX z;{G@`$@cpwV+TfY!86KQ+b=@LK;~e_KE|@RP0Y@KPzJHVe`Dm~>;lkifK6p(8E8;> zA(Y`YgT1oD_s53%1OwdanNY5+V`PG9hSjl+>Bw1H1~oGs6XS7<9TA&jx$qwGY<8hA zSKX{sCM$Exc~r&Rd4Wa!W5ajvjPwaA8FiDNje?WeKPV4J5EQ6BSG?qo}!Q66| zGG7R2Q@3Upe-(;z)#~*0O0lr8_&h&;Lt>FPUUhU_doYY-?T}$)mSHSo_K&x5sWW&F zW}g~>gAC9NVOoq+l#d|HQ;ubmDz(&3WR}w2aqYk;E|dF_wu3#@{jO zoDvYB)OoU^iHwOcq?p{$Wtc^B*z&1tqh$l88*j1sqASz#sh>JWFi0{+*?$)m7Ro55 z7XTp>-1SjcvJd7xidiF~?7@&s_cam8T&Ep$ghQDQ5z88HVkT>nZsEJ8q#oJ(!l7px z14GB~xJ{=WbmZva!w1d4P~j*1sm6*3WeN;LD3^5~jA+z1A36gk%(?A8-lr%$wF71`081E z0ljqM>eYvDtE-=Xu6|p8>o`V^=@jj<#vixX#WJj3YCMw^w;G=fgu^axKBaB#cvR; zQ>)JSs!3OA5>1y?AOjgg$7IVXkIGYykzOn*CYp_rak_qhibvl+y^01S+L$}>5bPeY zBE^-$H#DyJh2nY@-0x!92*7dtXP(vS@qf)R_D+Q2bL{;bOSZMl$4AQw9_>l#43Xr>&q zhmJ9S4(7fl1S5Jfq2=N)&e^B5sa74Y zR46l@^lxG?;h~I%v9!!u!cKu3e|tf0h6g`iwJXWA>Su#gNfy5_`xyhw=MXS0o?{&5 z7(KwWjbzlIC-qy#GLB*FW0@+K+oM+&oMV>bfLV+;=+}C)HI}_i$I-guk)Drf{p8;h zJReht1EyeAlTmB_-586$j1Fb3UX}gf?%Y+|2%<1-#SW z(Y64lDbPoVOUtGWh)>JX7_+Z8B3tebW@0f;feB+w)r*)(j5!WVGIqhtDKOSz42qa9 z_NtvO$G$8ClW8$64vdB1kDjtof!QF&)Jh7*7s+Xq4HzzC<8*mwHC>J@=D>jmCW60F zsSKJZN@ZNJ>_-ElyO?B*sZ|zC{|Mq5|JMYU4q04=X0oZ&Vsydml{;NlH_Ez45LfpP z2rxae9Roedrm!CaFu7dRYZw@!FG%r+p5mR)tY0A^iREl zbdql3e)%fKz6>=?Xi0i9=$AH^N7)v#GCN?JUQxn|bS9WHeK1?DFqpHi7<9^v`(#|L zthTcs^Vus*pS%dn1uVw2@IRub{V?-q*js*74rAwoK$2^ z#gNQ5a3RSoxfM+4w_*v?p%Gcd)-q86=nF(GFLj2Js|R%#rY;Jlb7d}6mnF8D9blxZ zFq%o1+a`y4!OoKy)9iNgLX<*`sXCarysUK@<9y6vfv!93nm^FYwu8&lO4P!UA6Mqe z;L38;9c{1~^t+;|VQ+)Y&1E3Ex}W}Oq`I4~>yj^=#oBE$wK8`w<4H{Y>q}?f`?r%& zqyBkQDE(aaZX)$Ahkmw!Bp z>BK?ocj={a?#PcCjNfY8lP`pPKY9AQ@G%UL_vz@em6Ijbdzi$(KY98`L8dJZ=*{ax zVg++=$2-P;Z&^FV!b_%Jcfgk^F36i&jPF?&49WKm31)LO&5=w8oc;@@4qiV&$FLU-Hgh*J&6EJPIZw{r0 z_kPyN$z7u577Mar7 zr?SdO^32yJCNMo+8x(1Y7Ryc2_&HjfEp{9^7Kx}^o{G|o@;(Ws2bT~r34^f}fFv}) zF+AGI&_Gz49oL3&@Kx4e38!+gP?csXp%ESHP`l%|hlk1mG~zIW<6EX{LLv#v6|i*X zvAo}m@q-H+WPI3|EM-k&!9%p=U~NE^4Xs5*A9~K|q?KA7b%v zwo|1M$KqjSX=70(gQoFiG^7lf!JuNZSPw2N%aZ+Sxw8U50;3H_lzvsgvf!`Wvj#D6tJr5IopXB|CC-6$Kv1g2lq6htmB9vl}#+sHa{1 z>I3jxy`CW=fs^hUSF5wPzYfyy81wasKqQ>aia*vl3sYtb7!;<2=7QjqHy zH)_u;^^Y}3`hmaw3oyis!lkNQgt12fW|wk*k=y}9#AsY~#K}sthpYoa>kzE-;S?%%okFB@X7oZ>-8zpI>F9_p9H?F-_uNF8s!mn5z$u zGt;}(7BG2S$G~OB!x&#bJfiW=QK=6W#050^x-WF)GF3sS{=EL%NFYV|w>F}HE~Dlkuc*yXM} z>_TdFbVB|C156gc=qBSFnEC&Mndz?>xT%a=Sr3Nvl5vn=LMwAlb;&@RzX33Fkj>v5 z7^qe@vjB#$vNE2^EbKAlusudGnl>v#0+U$)1Gr%J#ge^S7{I zIu|`wT6&YJ@;DgHnapX$Or>OY+uU@;#LSU?dG>Mq$-q)?L7wVSgD!KAMLzv59VGsmp+o2gPbfe6Jint^T!19*WTIf zs0{;AI0@k5BIRPyULYhcXjQ2fmD;@j!<92WC_91b`hkAF7Hr->a$yA+J3 zU1!8>i@%SVEX!5|#-^CUH!+)5{}eN>0?ZCzir-!4pJ5qokh`B*Jaz=WSe z-8wnBz0{oyr~mh%ZuPb%a{-1Sv*q+(cgXxfz&KbeKivBxWX?6uGRGcV5HQ6*@i!$f zY3n~!ykfre;T|wvA|{1Q<=Zlsb0ilAOzCI{=05+2({ZR#M{wCqxRgEjXY+sB$_)MQ zp*mf@UM*fWTL;@Q;-Zi-GO<7Xx(xN``2BPZ0bDF#ynx|%`il(E#JKONH%o8GEKiP` zkg3Wt8g!%r47})2Z?;BBH5$TdRLUIem@N7royDYPA0zgZcmA+2Xp9Vl#@HP8?iB$g zVc|mA4Vl)`cr|0tfK4dPWa3~SVEZ8bC@u(8=r~dh%^l2DLAMJcyb~?=2mZvTzz~U}mv}}gDF>sLnP2HSp zp}G&dEgz*Dq1>?eix*)T(bT1!i7PO=qK$CMrPD4$(se$A<&g|BsY{6xvj>mLFx9|J zywQnex%kn+R%^I9q-s$%%UsjZRBeq_))_dRT%IDP!2wxSaa0>M zl6mHzHO+o*3%`b=k#$jl$HsT4%!Q6XB)gGsE~MBA=}va^2nw@g=0D}8EQiImLcw**TgI00000NkvXXu0mjfcbhyg literal 48357 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 JS4*0N{2wq<5N-ef diff --git a/plugins/woocommerce/assets/images/task_list/sales-section-illustration.png b/plugins/woocommerce/assets/images/task_list/sales-section-illustration.png index 816e87a7f595d7efcbbde1b7d0716c1050a14ea1..133800215bc9a736177a58c1e3600872850275e7 100644 GIT binary patch literal 29679 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 Date: Wed, 13 Apr 2022 10:16:33 -0500 Subject: [PATCH 316/386] Update PR template and post-merge comment for changelogger --- .github/PULL_REQUEST_TEMPLATE.md | 5 +---- .github/workflows/scripts/add-post-merge-comment.php | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b5140fcd7f0..dd08989d3c3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -25,13 +25,10 @@ Closes # . * [ ] Have you added an explanation of what your changes do and why you'd like us to include them? * [ ] Have you written new tests for your changes, as applicable? * [ ] Have you successfully run tests with your changes locally? +* [ ] Have you created a changelog file by running `pnpm nx affected --target=changelog`? -### Changelog entry - -> Enter a summary of all changes on this Pull Request. This will appear in the changelog if accepted. - ### FOR PR REVIEWER ONLY: * [ ] I have reviewed that everything is sanitized/escaped appropriately for any SQL or XSS injection possibilities. I made sure Linting is not ignored or disabled. diff --git a/.github/workflows/scripts/add-post-merge-comment.php b/.github/workflows/scripts/add-post-merge-comment.php index ba2e13baf2d..afd6ec803f4 100644 --- a/.github/workflows/scripts/add-post-merge-comment.php +++ b/.github/workflows/scripts/add-post-merge-comment.php @@ -57,7 +57,6 @@ echo "The pull request was merged by: $merger_user_name\n"; $comment_body = "Hi @$merger_user_name, thanks for merging this pull request. Please take a look at these follow-up tasks you may need to perform: -- [ ] Add the `release: add changelog` label - [ ] Add the `release: add testing instructions` label"; $add_comment_mutation = " From f15e38f55e7500b68a2e16c9546726c769e4640c Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 13 Apr 2022 16:01:31 -0300 Subject: [PATCH 317/386] Fix minor css issues with complete task list item --- .../js/experimental/src/experimental-list/task-item/index.tsx | 2 +- .../experimental/src/experimental-list/task-item/style.scss | 4 ++-- plugins/woocommerce-admin/client/tasks/tasks.scss | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/js/experimental/src/experimental-list/task-item/index.tsx b/packages/js/experimental/src/experimental-list/task-item/index.tsx index 8aa2864d296..59b8ecd2ffd 100644 --- a/packages/js/experimental/src/experimental-list/task-item/index.tsx +++ b/packages/js/experimental/src/experimental-list/task-item/index.tsx @@ -130,7 +130,7 @@ export const TaskItem: React.FC< TaskItemProps > = ( { }, [ expanded ] ); const className = classnames( 'woocommerce-task-list__item', { - complete: completed, + 'is-complete': completed, expanded: isTaskExpanded, 'level-2': level === 2 && ! completed, 'level-1': level === 1 && ! completed, diff --git a/packages/js/experimental/src/experimental-list/task-item/style.scss b/packages/js/experimental/src/experimental-list/task-item/style.scss index f7fb5eb81df..7a95dc775f0 100644 --- a/packages/js/experimental/src/experimental-list/task-item/style.scss +++ b/packages/js/experimental/src/experimental-list/task-item/style.scss @@ -121,7 +121,7 @@ $task-alert-yellow: #f0b849; left: 5px; } - &.complete { + &.is-complete { .woocommerce-task__icon { background-color: var(--wp-admin-theme-color); } @@ -136,7 +136,7 @@ $task-alert-yellow: #f0b849; } } - &:not(.complete) { + &:not(.is-complete) { .woocommerce-task__icon { border: 1px solid $gray-100; background: $white; diff --git a/plugins/woocommerce-admin/client/tasks/tasks.scss b/plugins/woocommerce-admin/client/tasks/tasks.scss index 07719e15b8a..1ba8c76f427 100644 --- a/plugins/woocommerce-admin/client/tasks/tasks.scss +++ b/plugins/woocommerce-admin/client/tasks/tasks.scss @@ -236,7 +236,7 @@ } .woocommerce-task-list__setup_experiment_1 { - .woocommerce-experimental-list .woocommerce-experimental-list__item.complete { + .woocommerce-experimental-list .woocommerce-experimental-list__item.is-complete { text-decoration: line-through; .woocommerce-task-list__item-title { From 3b3fb00d046ecaf2f87de8cd08dc704422ec00f5 Mon Sep 17 00:00:00 2001 From: Joel T Date: Wed, 13 Apr 2022 12:01:51 -0700 Subject: [PATCH 318/386] Updating changelog --- plugins/woocommerce/changelog/update-32509_deasync_issue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/changelog/update-32509_deasync_issue b/plugins/woocommerce/changelog/update-32509_deasync_issue index c3fabe4aef0..5e972dddd67 100644 --- a/plugins/woocommerce/changelog/update-32509_deasync_issue +++ b/plugins/woocommerce/changelog/update-32509_deasync_issue @@ -1,5 +1,5 @@ Significance: patch Type: update -Comment: Simply updating a patch version of the deasync package +Updating deasync package to resolve install script issue with Linux From 59adcb26b84acbee279f70e563fcdb209ce495ef Mon Sep 17 00:00:00 2001 From: ratulhasan Date: Thu, 21 Oct 2021 20:43:40 +0600 Subject: [PATCH 319/386] Fix Uncaught TypeError: Unsupported operand types: string * float --- .../woocommerce/includes/admin/class-wc-admin-post-types.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php index 2eaef024a14..511c07faea7 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php @@ -928,7 +928,7 @@ class WC_Admin_Post_Types { return false; } - $old_price = $product->{"get_{$price_type}_price"}(); + $old_price = doubleval( $product->{"get_{$price_type}_price"}() ); $price_changed = false; $change_price = absint( $request_data[ "change_{$price_type}_price" ] ); From 3b4168dcbccc0fc91745382b68fe8f95fc30a65d Mon Sep 17 00:00:00 2001 From: ratulhasan Date: Fri, 22 Oct 2021 10:56:11 +0600 Subject: [PATCH 320/386] Fix Uncaught TypeError: Unsupported operand types: string * float --- .../woocommerce/includes/admin/class-wc-admin-post-types.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php index 511c07faea7..5b16daafb19 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php @@ -928,7 +928,7 @@ class WC_Admin_Post_Types { return false; } - $old_price = doubleval( $product->{"get_{$price_type}_price"}() ); + $old_price = ( $product->{"get_{$price_type}_price"}() === '' ) ? 0 : $product->{"get_{$price_type}_price"}(); $price_changed = false; $change_price = absint( $request_data[ "change_{$price_type}_price" ] ); From 12719616d9c17e1b55fad538ae08b5729754c498 Mon Sep 17 00:00:00 2001 From: ratulhasan Date: Fri, 22 Oct 2021 11:01:39 +0600 Subject: [PATCH 321/386] empty check --- .../woocommerce/includes/admin/class-wc-admin-post-types.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php index 5b16daafb19..4749aaa6023 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php @@ -928,7 +928,7 @@ class WC_Admin_Post_Types { return false; } - $old_price = ( $product->{"get_{$price_type}_price"}() === '' ) ? 0 : $product->{"get_{$price_type}_price"}(); + $old_price = empty( $product->{"get_{$price_type}_price"}() ) ? 0 : $product->{"get_{$price_type}_price"}(); $price_changed = false; $change_price = absint( $request_data[ "change_{$price_type}_price" ] ); From f593a2435c9a7c0e89d246f6f0dd2aa27e7bd553 Mon Sep 17 00:00:00 2001 From: barryhughes <3594411+barryhughes@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:46:52 -0700 Subject: [PATCH 322/386] Use a cast to float instead of ternary. --- plugins/woocommerce/changelog/fix-unsupported-operand-types | 4 ++++ .../woocommerce/includes/admin/class-wc-admin-post-types.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/fix-unsupported-operand-types diff --git a/plugins/woocommerce/changelog/fix-unsupported-operand-types b/plugins/woocommerce/changelog/fix-unsupported-operand-types new file mode 100644 index 00000000000..ce5ecc73871 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-unsupported-operand-types @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Avoid unsupported operand type errors from within `WC_Admin_Post_Types::set_new_price()`. diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php index 4749aaa6023..2a5ca1281f0 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php @@ -928,7 +928,7 @@ class WC_Admin_Post_Types { return false; } - $old_price = empty( $product->{"get_{$price_type}_price"}() ) ? 0 : $product->{"get_{$price_type}_price"}(); + $old_price = (float) $product->{"get_{$price_type}_price"}(); $price_changed = false; $change_price = absint( $request_data[ "change_{$price_type}_price" ] ); From bc9ce569b0b610b1e150ca133d8e27a4c6d3d30f Mon Sep 17 00:00:00 2001 From: Joel T Date: Thu, 7 Apr 2022 12:39:47 -0700 Subject: [PATCH 323/386] Adding Products screens to those that will not show tasks reminder bar --- .../client/tasks/reminder-bar/reminder-bar.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/reminder-bar/reminder-bar.tsx b/plugins/woocommerce-admin/client/tasks/reminder-bar/reminder-bar.tsx index 6eb2e071b0b..2e5fe88efb1 100644 --- a/plugins/woocommerce-admin/client/tasks/reminder-bar/reminder-bar.tsx +++ b/plugins/woocommerce-admin/client/tasks/reminder-bar/reminder-bar.tsx @@ -27,7 +27,7 @@ type ReminderBarProps = { }; type ReminderTextProps = { - remainingCount: number; + remainingCount: number | null; }; const REMINDER_BAR_HIDDEN_OPTION = 'woocommerce_task_list_reminder_bar_hidden'; @@ -125,7 +125,14 @@ export const TasksReminderBar: React.FC< ReminderBarProps > = ( { taskListComplete || reminderBarHidden || completedTasksCount === 0 || - [ 'Home', 'Shipping', 'Tax', 'Payments' ].includes( pageTitle ); + [ + 'Home', + 'Shipping', + 'Tax', + 'Payments', + 'Edit Product', + 'Add New', + ].includes( pageTitle ); useEffect( () => { updateBodyMargin(); From 932571c839e4c090ec02a894ad1f4b817e38762e Mon Sep 17 00:00:00 2001 From: Joel T Date: Thu, 7 Apr 2022 12:43:17 -0700 Subject: [PATCH 324/386] Adding changelog --- .../changelog/fix-32428_reminder_bar_invalid_screens | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-32428_reminder_bar_invalid_screens diff --git a/plugins/woocommerce/changelog/fix-32428_reminder_bar_invalid_screens b/plugins/woocommerce/changelog/fix-32428_reminder_bar_invalid_screens new file mode 100644 index 00000000000..7043ac660e4 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-32428_reminder_bar_invalid_screens @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fixing bug in which tasks reminder bar was displayed on product screens From 7ca042d270fa3515e57ac2a26fe2629f1c6b6c80 Mon Sep 17 00:00:00 2001 From: ratulhasan Date: Thu, 14 Apr 2022 02:47:16 +0600 Subject: [PATCH 325/386] Use a cast to float instead of ternary. --- .../woocommerce/includes/admin/class-wc-admin-post-types.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php index 4749aaa6023..2a5ca1281f0 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php @@ -928,7 +928,7 @@ class WC_Admin_Post_Types { return false; } - $old_price = empty( $product->{"get_{$price_type}_price"}() ) ? 0 : $product->{"get_{$price_type}_price"}(); + $old_price = (float) $product->{"get_{$price_type}_price"}(); $price_changed = false; $change_price = absint( $request_data[ "change_{$price_type}_price" ] ); From 0ec373734f0af6388b812b3550a0c1a9d83f621d Mon Sep 17 00:00:00 2001 From: Joel T Date: Wed, 13 Apr 2022 16:24:55 -0700 Subject: [PATCH 326/386] Replacing condition depending on pageTitle due to translation issues --- .../client/tasks/reminder-bar/reminder-bar.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/reminder-bar/reminder-bar.tsx b/plugins/woocommerce-admin/client/tasks/reminder-bar/reminder-bar.tsx index 2e5fe88efb1..ea8ccd4c771 100644 --- a/plugins/woocommerce-admin/client/tasks/reminder-bar/reminder-bar.tsx +++ b/plugins/woocommerce-admin/client/tasks/reminder-bar/reminder-bar.tsx @@ -14,6 +14,7 @@ import { getAdminLink } from '@woocommerce/settings'; import { close as closeIcon } from '@wordpress/icons'; import interpolateComponents from '@automattic/interpolate-components'; import { useEffect } from '@wordpress/element'; +import { getQuery } from '@woocommerce/navigation'; /** * Internal dependencies @@ -66,7 +67,6 @@ const ReminderText: React.FC< ReminderTextProps > = ( { remainingCount } ) => { export const TasksReminderBar: React.FC< ReminderBarProps > = ( { taskListId = 'setup_experiment_1', - pageTitle, updateBodyMargin, } ) => { const { updateOptions } = useDispatch( OPTIONS_STORE_NAME ); @@ -119,20 +119,18 @@ export const TasksReminderBar: React.FC< ReminderBarProps > = ( { }; } ); + const isHomescreen = + getQuery().page && getQuery().page === 'wc-admin' && ! getQuery().path; + const isActiveTaskPage = Boolean( getQuery().wc_onboarding_active_task ); + const hideReminderBar = loading || taskListHidden || taskListComplete || reminderBarHidden || completedTasksCount === 0 || - [ - 'Home', - 'Shipping', - 'Tax', - 'Payments', - 'Edit Product', - 'Add New', - ].includes( pageTitle ); + isHomescreen || + isActiveTaskPage; useEffect( () => { updateBodyMargin(); From 1375f000f6830c05b9f5fee5bc5f61f7f5b74854 Mon Sep 17 00:00:00 2001 From: moon Date: Tue, 12 Apr 2022 09:00:16 -0700 Subject: [PATCH 327/386] Use WC_PLUGIN_FILE to hook deactivation -- WC_ADMIN_PLUGIN_FILE is no longer available. --- .../woocommerce/src/Internal/Admin/Notes/OrderMilestones.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Internal/Admin/Notes/OrderMilestones.php b/plugins/woocommerce/src/Internal/Admin/Notes/OrderMilestones.php index f3423fd43a6..e6a9bd351d4 100644 --- a/plugins/woocommerce/src/Internal/Admin/Notes/OrderMilestones.php +++ b/plugins/woocommerce/src/Internal/Admin/Notes/OrderMilestones.php @@ -83,7 +83,7 @@ class OrderMilestones { $this->allowed_statuses = apply_filters( 'woocommerce_admin_order_milestone_statuses', $this->allowed_statuses ); add_action( 'woocommerce_after_register_post_type', array( $this, 'init' ) ); - register_deactivation_hook( WC_ADMIN_PLUGIN_FILE, array( $this, 'clear_scheduled_event' ) ); + register_deactivation_hook( WC_PLUGIN_FILE, array( $this, 'clear_scheduled_event' ) ); } /** From 1d290fe21babce4f56c7bbd851a0051a5b1ae9c4 Mon Sep 17 00:00:00 2001 From: moon Date: Tue, 12 Apr 2022 09:14:10 -0700 Subject: [PATCH 328/386] Remove use of DeactivatePlugin -- this note is no longer needed since WCA is now embeded into the core --- .../react-admin/wc-admin-update-functions.php | 17 ++- .../src/Admin/Composer/Package.php | 41 ------- .../src/Admin/Notes/DeprecatedNotes.php | 21 ---- .../Internal/Admin/Notes/DeactivatePlugin.php | 111 ------------------ 4 files changed, 11 insertions(+), 179 deletions(-) delete mode 100644 plugins/woocommerce/src/Internal/Admin/Notes/DeactivatePlugin.php diff --git a/plugins/woocommerce/includes/react-admin/wc-admin-update-functions.php b/plugins/woocommerce/includes/react-admin/wc-admin-update-functions.php index c4d8bf11cd7..c9f2a689192 100644 --- a/plugins/woocommerce/includes/react-admin/wc-admin-update-functions.php +++ b/plugins/woocommerce/includes/react-admin/wc-admin-update-functions.php @@ -10,7 +10,6 @@ use \Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskLists; use \Automattic\WooCommerce\Admin\Notes\Notes; use \Automattic\WooCommerce\Internal\Admin\Notes\UnsecuredReportFiles; -use \Automattic\WooCommerce\Internal\Admin\Notes\DeactivatePlugin; use \Automattic\WooCommerce\Admin\ReportExporter; /** @@ -77,12 +76,18 @@ function wc_admin_update_130_remove_dismiss_action_from_tracking_opt_in_note() { } /** - * Change the deactivate plugin note type to 'info'. - */ -function wc_admin_update_140_change_deactivate_plugin_note_type() { - global $wpdb; - $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}wc_admin_notes SET type = 'info' WHERE name = %s", DeactivatePlugin::NOTE_NAME ) ); + * Update DB Version. + */ +function wc_admin_update_130_db_version() { + Installer::update_db_version( '1.3.0' ); +} + +/** + * Update DB Version. + */ +function wc_admin_update_140_db_version() { + Installer::update_db_version( '1.4.0' ); } /** diff --git a/plugins/woocommerce/src/Admin/Composer/Package.php b/plugins/woocommerce/src/Admin/Composer/Package.php index 5d27cb01d2f..a4ed70bac7f 100644 --- a/plugins/woocommerce/src/Admin/Composer/Package.php +++ b/plugins/woocommerce/src/Admin/Composer/Package.php @@ -11,7 +11,6 @@ namespace Automattic\WooCommerce\Admin\Composer; defined( 'ABSPATH' ) || exit; -use Automattic\WooCommerce\Internal\Admin\Notes\DeactivatePlugin; use Automattic\WooCommerce\Admin\Notes\Notes; use Automattic\WooCommerce\Admin\Notes\NotesUnavailableException; use Automattic\WooCommerce\Internal\Admin\FeaturePlugin; @@ -51,13 +50,6 @@ class Package { // Avoid double initialization when the feature plugin is in use. if ( defined( 'WC_ADMIN_VERSION_NUMBER' ) ) { self::$active_version = WC_ADMIN_VERSION_NUMBER; - - // Check version after WooCommerce is initialized. - add_action( 'woocommerce_init', array( __CLASS__, 'check_outdated_wca_plugin' ) ); - - // Register a deactivation hook for the feature plugin. - register_deactivation_hook( WC_ADMIN_PLUGIN_FILE, array( __CLASS__, 'on_deactivation' ) ); - return; } @@ -112,39 +104,6 @@ class Package { return dirname( __DIR__ ); } - /** - * Add deactivation hook for versions of the plugin that don't have the deactivation note. - */ - public static function on_deactivation() { - if ( ! self::is_notes_initialized() ) { - return; - } - - $update_version = new DeactivatePlugin(); - $update_version::delete_note(); - } - - /** - * Checks if embedded WCA version is newer than standalone WCA - * and adds/removes DeactivatePlugin note as necessary. - */ - public static function check_outdated_wca_plugin() { - - if ( ! self::is_notes_initialized() ) { - return; - } - - $update_version = new DeactivatePlugin(); - - if ( version_compare( WC_ADMIN_VERSION_NUMBER, self::VERSION, '<' ) ) { - if ( method_exists( $update_version, 'possibly_add_note' ) ) { - $update_version::possibly_add_note(); - } - } else { - $update_version::delete_note(); - } - } - /** * Checks if notes have been initialized. */ diff --git a/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php b/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php index 972c60cce13..6eafd2debb3 100644 --- a/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php +++ b/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php @@ -118,27 +118,6 @@ class WC_Admin_Notes_Customize_Store_With_Blocks extends DeprecatedClassFacade { protected static $deprecated_in_version = '1.7.0'; } -/** - * WC_Admin_Notes_Deactivate_Plugin. - * - * @deprecated since 1.7.0, use DeactivatePlugin - */ -class WC_Admin_Notes_Deactivate_Plugin extends DeprecatedClassFacade { - /** - * The name of the non-deprecated class that this facade covers. - * - * @var string - */ - protected static $facade_over_classname = 'Automattic\WooCommerce\Internal\Admin\Notes\DeactivatePlugin'; - - /** - * The version that this class was deprecated in. - * - * @var string - */ - protected static $deprecated_in_version = '1.7.0'; -} - /** * WC_Admin_Notes_Edit_Products_On_The_Move. * diff --git a/plugins/woocommerce/src/Internal/Admin/Notes/DeactivatePlugin.php b/plugins/woocommerce/src/Internal/Admin/Notes/DeactivatePlugin.php deleted file mode 100644 index cbd8663e70a..00000000000 --- a/plugins/woocommerce/src/Internal/Admin/Notes/DeactivatePlugin.php +++ /dev/null @@ -1,111 +0,0 @@ -set_title( __( 'Deactivate old WooCommerce Admin version', 'woocommerce' ) ); - $note->set_content( __( 'Your current version of WooCommerce Admin is outdated and a newer version is included with WooCommerce. We recommend deactivating the plugin and using the stable version included with WooCommerce.', 'woocommerce' ) ); - $note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); - $note->set_name( self::NOTE_NAME ); - $note->set_content_data( (object) array() ); - $note->set_source( 'woocommerce-admin' ); - $note->add_action( - 'deactivate-feature-plugin', - __( 'Deactivate', 'woocommerce' ), - wc_admin_url( '&action=deactivate-feature-plugin' ), - Note::E_WC_ADMIN_NOTE_UNACTIONED - ); - $note->add_nonce_to_action( 'deactivate-feature-plugin', 'deactivate-plugin_' . WC_ADMIN_PLUGIN_FILE, '' ); - return $note; - } - - /** - * Delete the note if the version is higher than the included. - */ - public static function delete_note() { - Notes::delete_notes_with_name( self::NOTE_NAME ); - } - - /** - * Deactivate feature plugin. - */ - public function deactivate_feature_plugin() { - if ( - ! isset( $_GET['page'] ) || - 'wc-admin' !== $_GET['page'] || - ! isset( $_GET['action'] ) || - 'deactivate-feature-plugin' !== $_GET['action'] || - ! defined( 'WC_ADMIN_PLUGIN_FILE' ) - ) { - return; - } - - $note = self::get_note(); - $action = $note->get_action( 'deactivate-feature-plugin' ); - - // Preserve compatability with notes populated before nonce implementation. - if ( ! isset( $_GET['_wpnonce'] ) && ( ! $action || ! isset( $action->nonce_action ) ) ) { - self::deactivate_redirect( wp_create_nonce( 'deactivate-plugin_' . WC_ADMIN_PLUGIN_FILE ) ); - return; - } - - $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : ''; - - if ( ! wp_verify_nonce( $nonce, 'deactivate-plugin_' . WC_ADMIN_PLUGIN_FILE ) ) { - return; - } - - self::deactivate_redirect( $nonce ); - } - - /** - * Deactivation redirect - * - * @param string $nonce The nonce. - */ - public static function deactivate_redirect( $nonce ) { - - $deactivate_url = admin_url( 'plugins.php?action=deactivate&plugin=' . rawurlencode( WC_ADMIN_PLUGIN_FILE ) . '&plugin_status=all&paged=1&_wpnonce=' . $nonce ); - wp_safe_redirect( $deactivate_url ); - - exit; - } -} From 24cdec7bd2ef4c4f7eec7bda1446f9141466038d Mon Sep 17 00:00:00 2001 From: moon Date: Tue, 12 Apr 2022 09:32:58 -0700 Subject: [PATCH 329/386] Ignore PascalCase format error --- .../src/Admin/Notes/DeprecatedNotes.php | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php b/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php index 6eafd2debb3..5d01d4efa5e 100644 --- a/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php +++ b/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php @@ -17,7 +17,7 @@ use Automattic\WooCommerce\Admin\DeprecatedClassFacade; * * @deprecated since 1.7.0, use Note */ -class WC_Admin_Note extends DeprecatedClassFacade { +class WC_Admin_Note extends DeprecatedClassFacade { // phpcs:ignore // These constants must be redeclared as to not break plugins that use them. const E_WC_ADMIN_NOTE_ERROR = Note::E_WC_ADMIN_NOTE_ERROR; const E_WC_ADMIN_NOTE_WARNING = Note::E_WC_ADMIN_NOTE_WARNING; @@ -60,7 +60,7 @@ class WC_Admin_Note extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use Notes */ -class WC_Admin_Notes extends DeprecatedClassFacade { +class WC_Admin_Notes extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -81,7 +81,7 @@ class WC_Admin_Notes extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use CouponPageMoved */ -class WC_Admin_Notes_Coupon_Page_Moved extends DeprecatedClassFacade { +class WC_Admin_Notes_Coupon_Page_Moved extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -102,7 +102,7 @@ class WC_Admin_Notes_Coupon_Page_Moved extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use CustomizeStoreWithBlocks */ -class WC_Admin_Notes_Customize_Store_With_Blocks extends DeprecatedClassFacade { +class WC_Admin_Notes_Customize_Store_With_Blocks extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -123,7 +123,7 @@ class WC_Admin_Notes_Customize_Store_With_Blocks extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use EditProductsOnTheMove */ -class WC_Admin_Notes_Edit_Products_On_The_Move extends DeprecatedClassFacade { +class WC_Admin_Notes_Edit_Products_On_The_Move extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -144,7 +144,7 @@ class WC_Admin_Notes_Edit_Products_On_The_Move extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use EUVATNumber */ -class WC_Admin_Notes_EU_VAT_Number extends DeprecatedClassFacade { +class WC_Admin_Notes_EU_VAT_Number extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -165,7 +165,7 @@ class WC_Admin_Notes_EU_VAT_Number extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use FacebookMarketingExpert */ -class WC_Admin_Notes_Facebook_Marketing_Expert extends DeprecatedClassFacade { +class WC_Admin_Notes_Facebook_Marketing_Expert extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -186,7 +186,7 @@ class WC_Admin_Notes_Facebook_Marketing_Expert extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use FirstProduct */ -class WC_Admin_Notes_First_Product extends DeprecatedClassFacade { +class WC_Admin_Notes_First_Product extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -207,7 +207,7 @@ class WC_Admin_Notes_First_Product extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use GivingFeedbackNotes */ -class WC_Admin_Notes_Giving_Feedback_Notes extends DeprecatedClassFacade { +class WC_Admin_Notes_Giving_Feedback_Notes extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -228,7 +228,7 @@ class WC_Admin_Notes_Giving_Feedback_Notes extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use InsightFirstSale */ -class WC_Admin_Notes_Insight_First_Sale extends DeprecatedClassFacade { +class WC_Admin_Notes_Insight_First_Sale extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -249,7 +249,7 @@ class WC_Admin_Notes_Insight_First_Sale extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use InstallJPAndWCSPlugins */ -class WC_Admin_Notes_Install_JP_And_WCS_Plugins extends DeprecatedClassFacade { +class WC_Admin_Notes_Install_JP_And_WCS_Plugins extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -270,7 +270,7 @@ class WC_Admin_Notes_Install_JP_And_WCS_Plugins extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use LaunchChecklist */ -class WC_Admin_Notes_Launch_Checklist extends DeprecatedClassFacade { +class WC_Admin_Notes_Launch_Checklist extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -291,7 +291,7 @@ class WC_Admin_Notes_Launch_Checklist extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use MigrateFromShopify */ -class WC_Admin_Notes_Migrate_From_Shopify extends DeprecatedClassFacade { +class WC_Admin_Notes_Migrate_From_Shopify extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -312,7 +312,7 @@ class WC_Admin_Notes_Migrate_From_Shopify extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use MobileApp */ -class WC_Admin_Notes_Mobile_App extends DeprecatedClassFacade { +class WC_Admin_Notes_Mobile_App extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -333,7 +333,7 @@ class WC_Admin_Notes_Mobile_App extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use NewSalesRecord */ -class WC_Admin_Notes_New_Sales_Record extends DeprecatedClassFacade { +class WC_Admin_Notes_New_Sales_Record extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -354,7 +354,7 @@ class WC_Admin_Notes_New_Sales_Record extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use OnboardingEmailMarketing */ -class WC_Admin_Notes_Onboarding_Email_Marketing extends DeprecatedClassFacade { +class WC_Admin_Notes_Onboarding_Email_Marketing extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -375,7 +375,7 @@ class WC_Admin_Notes_Onboarding_Email_Marketing extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use OnboardingPayments */ -class WC_Admin_Notes_Onboarding_Payments extends DeprecatedClassFacade { +class WC_Admin_Notes_Onboarding_Payments extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -396,7 +396,7 @@ class WC_Admin_Notes_Onboarding_Payments extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use OnlineClothingStore */ -class WC_Admin_Notes_Online_Clothing_Store extends DeprecatedClassFacade { +class WC_Admin_Notes_Online_Clothing_Store extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -417,7 +417,7 @@ class WC_Admin_Notes_Online_Clothing_Store extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use OrderMilestones */ -class WC_Admin_Notes_Order_Milestones extends DeprecatedClassFacade { +class WC_Admin_Notes_Order_Milestones extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -438,7 +438,7 @@ class WC_Admin_Notes_Order_Milestones extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use PerformanceOnMobile */ -class WC_Admin_Notes_Performance_On_Mobile extends DeprecatedClassFacade { +class WC_Admin_Notes_Performance_On_Mobile extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -459,7 +459,7 @@ class WC_Admin_Notes_Performance_On_Mobile extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use PersonalizeStore */ -class WC_Admin_Notes_Personalize_Store extends DeprecatedClassFacade { +class WC_Admin_Notes_Personalize_Store extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -480,7 +480,7 @@ class WC_Admin_Notes_Personalize_Store extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use RealTimeOrderAlerts */ -class WC_Admin_Notes_Real_Time_Order_Alerts extends DeprecatedClassFacade { +class WC_Admin_Notes_Real_Time_Order_Alerts extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -501,7 +501,7 @@ class WC_Admin_Notes_Real_Time_Order_Alerts extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use SellingOnlineCourses */ -class WC_Admin_Notes_Selling_Online_Courses extends DeprecatedClassFacade { +class WC_Admin_Notes_Selling_Online_Courses extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -522,7 +522,7 @@ class WC_Admin_Notes_Selling_Online_Courses extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use SetUpAdditionalPaymentTypes */ -class WC_Admin_Notes_Set_Up_Additional_Payment_Types extends DeprecatedClassFacade { +class WC_Admin_Notes_Set_Up_Additional_Payment_Types extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -543,7 +543,7 @@ class WC_Admin_Notes_Set_Up_Additional_Payment_Types extends DeprecatedClassFaca * * @deprecated since 1.7.0, use TestCheckout */ -class WC_Admin_Notes_Test_Checkout extends DeprecatedClassFacade { +class WC_Admin_Notes_Test_Checkout extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -564,7 +564,7 @@ class WC_Admin_Notes_Test_Checkout extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use TrackingOptIn */ -class WC_Admin_Notes_Tracking_Opt_In extends DeprecatedClassFacade { +class WC_Admin_Notes_Tracking_Opt_In extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -585,7 +585,7 @@ class WC_Admin_Notes_Tracking_Opt_In extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use WooSubscriptionsNotes */ -class WC_Admin_Notes_Woo_Subscriptions_Notes extends DeprecatedClassFacade { +class WC_Admin_Notes_Woo_Subscriptions_Notes extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -606,7 +606,7 @@ class WC_Admin_Notes_Woo_Subscriptions_Notes extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use WooCommercePayments */ -class WC_Admin_Notes_WooCommerce_Payments extends DeprecatedClassFacade { +class WC_Admin_Notes_WooCommerce_Payments extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * @@ -627,7 +627,7 @@ class WC_Admin_Notes_WooCommerce_Payments extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use WooCommerceSubscriptions */ -class WC_Admin_Notes_WooCommerce_Subscriptions extends DeprecatedClassFacade { +class WC_Admin_Notes_WooCommerce_Subscriptions extends DeprecatedClassFacade { // phpcs:ignore /** * The name of the non-deprecated class that this facade covers. * From ffdd8cb34ad0ef7399546d3fd20c74de6a99135b Mon Sep 17 00:00:00 2001 From: moon Date: Tue, 12 Apr 2022 09:34:57 -0700 Subject: [PATCH 330/386] Add changelog --- .../changelog/update-32522-remove-deactivation-hooks-from-wca | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/update-32522-remove-deactivation-hooks-from-wca diff --git a/plugins/woocommerce/changelog/update-32522-remove-deactivation-hooks-from-wca b/plugins/woocommerce/changelog/update-32522-remove-deactivation-hooks-from-wca new file mode 100644 index 00000000000..a34843d86a6 --- /dev/null +++ b/plugins/woocommerce/changelog/update-32522-remove-deactivation-hooks-from-wca @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update WCA deactivation hooks to work with WC deactvation. From 73a629275591e2918eb63cf8acef56a7b88c42de Mon Sep 17 00:00:00 2001 From: moon Date: Tue, 12 Apr 2022 09:49:49 -0700 Subject: [PATCH 331/386] Remove wc_admin_update_140_change_deactivate_plugin_note_type callback --- .../src/Internal/Admin/Install.php | 590 ++++++++++++++++++ 1 file changed, 590 insertions(+) create mode 100644 plugins/woocommerce/src/Internal/Admin/Install.php diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php new file mode 100644 index 00000000000..c5ac3c6793a --- /dev/null +++ b/plugins/woocommerce/src/Internal/Admin/Install.php @@ -0,0 +1,590 @@ + array( + 'wc_admin_update_0201_order_status_index', + 'wc_admin_update_0201_db_version', + ), + '0.23.0' => array( + 'wc_admin_update_0230_rename_gross_total', + 'wc_admin_update_0230_db_version', + ), + '0.25.1' => array( + 'wc_admin_update_0251_remove_unsnooze_action', + 'wc_admin_update_0251_db_version', + ), + '1.1.0' => array( + 'wc_admin_update_110_remove_facebook_note', + 'wc_admin_update_110_db_version', + ), + '1.3.0' => array( + 'wc_admin_update_130_remove_dismiss_action_from_tracking_opt_in_note', + 'wc_admin_update_130_db_version', + ), + '1.4.0' => array( + 'wc_admin_update_140_db_version', + ), + '1.6.0' => array( + 'wc_admin_update_160_remove_facebook_note', + 'wc_admin_update_160_db_version', + ), + '1.7.0' => array( + 'wc_admin_update_170_homescreen_layout', + 'wc_admin_update_170_db_version', + ), + '2.7.0' => array( + 'wc_admin_update_270_delete_report_downloads', + 'wc_admin_update_270_db_version', + ), + '2.7.1' => array( + 'wc_admin_update_271_update_task_list_options', + 'wc_admin_update_271_db_version', + ), + '2.8.0' => array( + 'wc_admin_update_280_order_status', + 'wc_admin_update_280_db_version', + ), + '2.9.0' => array( + 'wc_admin_update_290_update_apperance_task_option', + 'wc_admin_update_290_delete_default_homepage_layout_option', + 'wc_admin_update_290_db_version', + ), + '3.0.0' => array( + 'wc_admin_update_300_update_is_read_from_last_read', + 'wc_admin_update_300_db_version', + ), + '3.4.0' => array( + 'wc_admin_update_340_remove_is_primary_from_note_action', + 'wc_admin_update_340_db_version', + ), + ); + + /** + * Migrated option names mapping. New => old. + * + * @var array + */ + protected static $migrated_options = array( + 'woocommerce_onboarding_profile' => 'wc_onboarding_profile', + 'woocommerce_admin_install_timestamp' => 'wc_admin_install_timestamp', + 'woocommerce_onboarding_opt_in' => 'wc_onboarding_opt_in', + 'woocommerce_admin_import_stats' => 'wc_admin_import_stats', + 'woocommerce_admin_version' => 'wc_admin_version', + 'woocommerce_admin_last_orders_milestone' => 'wc_admin_last_orders_milestone', + 'woocommerce_admin-wc-helper-last-refresh' => 'wc-admin-wc-helper-last-refresh', + 'woocommerce_admin_report_export_status' => 'wc_admin_report_export_status', + 'woocommerce_task_list_complete' => 'woocommerce_task_list_complete', + 'woocommerce_task_list_hidden' => 'woocommerce_task_list_hidden', + 'woocommerce_extended_task_list_complete' => 'woocommerce_extended_task_list_complete', + 'woocommerce_extended_task_list_hidden' => 'woocommerce_extended_task_list_hidden', + ); + + /** + * Hook in tabs. + */ + public static function init() { + if ( ( is_admin() && ! wp_doing_ajax() ) || wp_doing_cron() || defined( 'WP_CLI' ) ) { + add_action( 'init', array( __CLASS__, 'check_version' ), 5 ); + } + add_filter( 'wpmu_drop_tables', array( __CLASS__, 'wpmu_drop_tables' ) ); + + // Add wc-admin report tables to list of WooCommerce tables. + add_filter( 'woocommerce_install_get_tables', array( __CLASS__, 'add_tables' ) ); + } + + /** + * Migrate option values to their new keys/names. + */ + public static function migrate_options() { + wc_maybe_define_constant( 'WC_ADMIN_MIGRATING_OPTIONS', true ); + + foreach ( self::$migrated_options as $new_option => $old_option ) { + $old_option_value = get_option( $old_option, false ); + + // Continue if no option value was previously set. + if ( false === $old_option_value ) { + continue; + } + + if ( '1' === $old_option_value ) { + $old_option_value = 'yes'; + } elseif ( '0' === $old_option_value ) { + $old_option_value = 'no'; + } + + update_option( $new_option, $old_option_value ); + if ( $new_option !== $old_option ) { + delete_option( $old_option ); + } + } + } + + /** + * Check WC Admin version and run the updater is required. + * + * This check is done on all requests and runs if the versions do not match. + */ + public static function check_version() { + if ( defined( 'IFRAME_REQUEST' ) ) { + return; + } + + $version_option = get_option( self::VERSION_OPTION ); + $requires_update = version_compare( get_option( self::VERSION_OPTION ), WC_ADMIN_VERSION_NUMBER, '<' ); + + /* + * When included as part of Core, no `on_activation` hook as been called + * so there is no version in options. Make sure install gets called in this + * case as well as a regular version update + */ + if ( ! $version_option || $requires_update ) { + self::install(); + /** + * WooCommerce Admin has been installed or updated. + */ + do_action( 'woocommerce_admin_updated' ); + + if ( ! $version_option ) { + /** + * WooCommerce Admin has been installed. + */ + do_action( 'woocommerce_admin_newly_installed' ); + } + + if ( $requires_update ) { + /** + * An existing installation of WooCommerce Admin has been + * updated. + */ + do_action( 'woocommerce_admin_updated_existing' ); + } + } + + /* + * Add the version option if none is found, as would be the case when + * initialized via Core for the first time. + */ + if ( ! $version_option ) { + add_option( self::VERSION_OPTION, WC_ADMIN_VERSION_NUMBER ); + } + } + + /** + * Install WC Admin. + */ + public static function install() { + if ( ! is_blog_installed() ) { + return; + } + + // Check if we are not already running this routine. + if ( self::is_installing() ) { + return; + } + + // If we made it till here nothing is running yet, lets set the transient now. + set_transient( 'wc_admin_installing', 'yes', MINUTE_IN_SECONDS * 10 ); + + self::migrate_options(); + self::create_tables(); + self::create_events(); + self::delete_obsolete_notes(); + self::maybe_update_db_version(); + + delete_transient( 'wc_admin_installing' ); + + // Use add_option() here to avoid overwriting this value with each + // plugin version update. We base plugin age off of this value. + add_option( 'woocommerce_admin_install_timestamp', time() ); + do_action( 'woocommerce_admin_installed' ); + } + + /** + * Check if the installer is installing. + * + * @return bool + */ + public static function is_installing() { + return 'yes' === get_transient( 'wc_admin_installing' ); + } + + /** + * Get database schema. + * + * @return string + */ + protected static function get_schema() { + global $wpdb; + + $collate = $wpdb->has_cap( 'collation' ) ? $wpdb->get_charset_collate() : ''; + + // Max DB index length. See wp_get_db_schema(). + $max_index_length = 191; + + $tables = " + CREATE TABLE {$wpdb->prefix}wc_order_stats ( + order_id bigint(20) unsigned NOT NULL, + parent_id bigint(20) unsigned DEFAULT 0 NOT NULL, + date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, + date_created_gmt datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, + num_items_sold int(11) DEFAULT 0 NOT NULL, + total_sales double DEFAULT 0 NOT NULL, + tax_total double DEFAULT 0 NOT NULL, + shipping_total double DEFAULT 0 NOT NULL, + net_total double DEFAULT 0 NOT NULL, + returning_customer boolean DEFAULT NULL, + status varchar(200) NOT NULL, + customer_id BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (order_id), + KEY date_created (date_created), + KEY customer_id (customer_id), + KEY status (status({$max_index_length})) + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_order_product_lookup ( + order_item_id BIGINT UNSIGNED NOT NULL, + order_id BIGINT UNSIGNED NOT NULL, + product_id BIGINT UNSIGNED NOT NULL, + variation_id BIGINT UNSIGNED NOT NULL, + customer_id BIGINT UNSIGNED NULL, + date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, + product_qty INT NOT NULL, + product_net_revenue double DEFAULT 0 NOT NULL, + product_gross_revenue double DEFAULT 0 NOT NULL, + coupon_amount double DEFAULT 0 NOT NULL, + tax_amount double DEFAULT 0 NOT NULL, + shipping_amount double DEFAULT 0 NOT NULL, + shipping_tax_amount double DEFAULT 0 NOT NULL, + PRIMARY KEY (order_item_id), + KEY order_id (order_id), + KEY product_id (product_id), + KEY customer_id (customer_id), + KEY date_created (date_created) + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_order_tax_lookup ( + order_id BIGINT UNSIGNED NOT NULL, + tax_rate_id BIGINT UNSIGNED NOT NULL, + date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, + shipping_tax double DEFAULT 0 NOT NULL, + order_tax double DEFAULT 0 NOT NULL, + total_tax double DEFAULT 0 NOT NULL, + PRIMARY KEY (order_id, tax_rate_id), + KEY tax_rate_id (tax_rate_id), + KEY date_created (date_created) + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_order_coupon_lookup ( + order_id BIGINT UNSIGNED NOT NULL, + coupon_id BIGINT NOT NULL, + date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, + discount_amount double DEFAULT 0 NOT NULL, + PRIMARY KEY (order_id, coupon_id), + KEY coupon_id (coupon_id), + KEY date_created (date_created) + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_admin_notes ( + note_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + name varchar(255) NOT NULL, + type varchar(20) NOT NULL, + locale varchar(20) NOT NULL, + title longtext NOT NULL, + content longtext NOT NULL, + content_data longtext NULL default null, + status varchar(200) NOT NULL, + source varchar(200) NOT NULL, + date_created datetime NOT NULL default '0000-00-00 00:00:00', + date_reminder datetime NULL default null, + is_snoozable boolean DEFAULT 0 NOT NULL, + layout varchar(20) DEFAULT '' NOT NULL, + image varchar(200) NULL DEFAULT NULL, + is_deleted boolean DEFAULT 0 NOT NULL, + is_read boolean DEFAULT 0 NOT NULL, + icon varchar(200) NOT NULL default 'info', + PRIMARY KEY (note_id) + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_admin_note_actions ( + action_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + note_id BIGINT UNSIGNED NOT NULL, + name varchar(255) NOT NULL, + label varchar(255) NOT NULL, + query longtext NOT NULL, + status varchar(255) NOT NULL, + actioned_text varchar(255) NOT NULL, + nonce_action varchar(255) NULL DEFAULT NULL, + nonce_name varchar(255) NULL DEFAULT NULL, + PRIMARY KEY (action_id), + KEY note_id (note_id) + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_customer_lookup ( + customer_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + user_id BIGINT UNSIGNED DEFAULT NULL, + username varchar(60) DEFAULT '' NOT NULL, + first_name varchar(255) NOT NULL, + last_name varchar(255) NOT NULL, + email varchar(100) NULL default NULL, + date_last_active timestamp NULL default null, + date_registered timestamp NULL default null, + country char(2) DEFAULT '' NOT NULL, + postcode varchar(20) DEFAULT '' NOT NULL, + city varchar(100) DEFAULT '' NOT NULL, + state varchar(100) DEFAULT '' NOT NULL, + PRIMARY KEY (customer_id), + UNIQUE KEY user_id (user_id), + KEY email (email) + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_category_lookup ( + category_tree_id BIGINT UNSIGNED NOT NULL, + category_id BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (category_tree_id,category_id) + ) $collate; + "; + + return $tables; + } + + /** + * Create database tables. + */ + public static function create_tables() { + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + + dbDelta( self::get_schema() ); + } + + /** + * Return a list of tables. Used to make sure all WC Admin tables are dropped + * when uninstalling the plugin in a single site or multi site environment. + * + * @return array WC tables. + */ + public static function get_tables() { + global $wpdb; + + return array( + "{$wpdb->prefix}wc_order_stats", + "{$wpdb->prefix}wc_order_product_lookup", + "{$wpdb->prefix}wc_order_tax_lookup", + "{$wpdb->prefix}wc_order_coupon_lookup", + "{$wpdb->prefix}wc_admin_notes", + "{$wpdb->prefix}wc_admin_note_actions", + "{$wpdb->prefix}wc_customer_lookup", + "{$wpdb->prefix}wc_category_lookup", + ); + } + + /** + * Adds new tables. + * + * @param array $wc_tables List of WooCommerce tables. + * @return array + */ + public static function add_tables( $wc_tables ) { + return array_merge( + $wc_tables, + self::get_tables() + ); + } + + /** + * Uninstall tables when MU blog is deleted. + * + * @param array $tables List of tables that will be deleted by WP. + * + * @return string[] + */ + public static function wpmu_drop_tables( $tables ) { + return array_merge( $tables, self::get_tables() ); + } + + /** + * Get list of DB update callbacks. + * + * @return array + */ + public static function get_db_update_callbacks() { + return self::$db_updates; + } + + /** + * Is a DB update needed? + * + * @return boolean + */ + public static function needs_db_update() { + $current_db_version = get_option( self::VERSION_OPTION, null ); + $updates = self::get_db_update_callbacks(); + $update_versions = array_keys( $updates ); + usort( $update_versions, 'version_compare' ); + + return ! is_null( $current_db_version ) && version_compare( $current_db_version, end( $update_versions ), '<' ); + } + + /** + * See if we need to show or run database updates during install. + */ + private static function maybe_update_db_version() { + if ( self::needs_db_update() ) { + self::update(); + } else { + self::update_db_version(); + } + } + + /** + * Push all needed DB updates to the queue for processing. + */ + private static function update() { + $current_db_version = get_option( self::VERSION_OPTION ); + $loop = 0; + + foreach ( self::get_db_update_callbacks() as $version => $update_callbacks ) { + if ( version_compare( $current_db_version, $version, '<' ) ) { + $completed_version_updates = 0; + foreach ( $update_callbacks as $update_callback ) { + $pending_jobs = WC()->queue()->search( + array( + 'per_page' => 1, + 'hook' => 'woocommerce_run_update_callback', + 'search' => wp_json_encode( array( $update_callback ) ), + 'group' => 'woocommerce-db-updates', + 'status' => 'pending', + ) + ); + + $complete_jobs = WC()->queue()->search( + array( + 'per_page' => 1, + 'hook' => 'woocommerce_run_update_callback', + 'search' => wp_json_encode( array( $update_callback ) ), + 'group' => 'woocommerce-db-updates', + 'status' => 'complete', + ) + ); + + $completed_version_updates += count( $complete_jobs ); + + if ( empty( $pending_jobs ) && empty( $complete_jobs ) ) { + WC()->queue()->schedule_single( + time() + $loop, + 'woocommerce_run_update_callback', + array( $update_callback ), + 'woocommerce-db-updates' + ); + Cache::invalidate(); + } + + $loop++; + + } + + // Users have experienced concurrency issues where all update callbacks + // have run but the version option hasn't been updated. If all the updates + // for a version are complete, update the version option to reflect that. + // See: https:// github.com/woocommerce/woocommerce-admin/issues/5058. + if ( count( $update_callbacks ) === $completed_version_updates ) { + self::update_db_version( $version ); + } + } + } + } + + /** + * Update WC Admin version to current. + * + * @param string|null $version New WooCommerce Admin DB version or null. + */ + public static function update_db_version( $version = null ) { + update_option( self::VERSION_OPTION, is_null( $version ) ? WC_ADMIN_VERSION_NUMBER : $version ); + } + + /** + * Schedule cron events. + */ + public static function create_events() { + if ( ! wp_next_scheduled( 'wc_admin_daily' ) ) { + wp_schedule_event( time(), 'daily', 'wc_admin_daily' ); + } + // Note: this is potentially redundant when the core package exists. + wp_schedule_single_event( time() + 10, 'generate_category_lookup_table' ); + } + + /** + * Delete obsolete notes. + */ + protected static function delete_obsolete_notes() { + $obsolete_notes_names = array( + 'wc-admin-welcome-note', + 'wc-admin-store-notice-setting-moved', + 'wc-admin-store-notice-giving-feedback', + 'wc-admin-learn-more-about-product-settings', + 'wc-admin-onboarding-profiler-reminder', + 'wc-admin-historical-data', + 'wc-admin-review-shipping-settings', + 'wc-admin-home-screen-feedback', + 'wc-admin-effortless-payments-by-mollie', + 'wc-admin-google-ads-and-marketing', + 'wc-admin-marketing-intro', + 'wc-admin-draw-attention', + 'wc-admin-need-some-inspiration', + 'wc-admin-choose-niche', + 'wc-admin-start-dropshipping-business', + 'wc-admin-filter-by-product-variations-in-reports', + 'wc-admin-learn-more-about-variable-products', + 'wc-admin-getting-started-ecommerce-webinar', + 'wc-admin-navigation-feedback', + 'wc-admin-navigation-feedback-follow-up', + ); + + $additional_obsolete_notes_names = apply_filters( + 'woocommerce_admin_obsolete_notes_names', + array() + ); + + if ( is_array( $additional_obsolete_notes_names ) ) { + $obsolete_notes_names = array_merge( + $obsolete_notes_names, + $additional_obsolete_notes_names + ); + } + + Notes::delete_notes_with_name( $obsolete_notes_names ); + } + + /** + * Drop WooCommerce Admin tables. + * + * @return void + */ + public static function drop_tables() { + global $wpdb; + + $tables = self::get_tables(); + + foreach ( $tables as $table ) { + /* phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared */ + $wpdb->query( "DROP TABLE IF EXISTS {$table}" ); + /* phpcs:enable */ + } + } +} From 1a978411ebcf0f71a794182156bb23a54e8d677f Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 13 Apr 2022 06:25:45 -0700 Subject: [PATCH 332/386] Add phpcs ignore rules --- phpcs.xml | 12 ++++ .../src/Admin/Notes/DeprecatedNotes.php | 58 +++++++++---------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/phpcs.xml b/phpcs.xml index 74e638dd71a..70bf2d649ca 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -128,4 +128,16 @@ src/Internal/Admin/ src/Admin/ + + + + src/Internal/Admin/ + src/Admin/ + + + + + src/Internal/Admin/ + src/Admin/ + diff --git a/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php b/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php index 5d01d4efa5e..6eafd2debb3 100644 --- a/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php +++ b/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php @@ -17,7 +17,7 @@ use Automattic\WooCommerce\Admin\DeprecatedClassFacade; * * @deprecated since 1.7.0, use Note */ -class WC_Admin_Note extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Note extends DeprecatedClassFacade { // These constants must be redeclared as to not break plugins that use them. const E_WC_ADMIN_NOTE_ERROR = Note::E_WC_ADMIN_NOTE_ERROR; const E_WC_ADMIN_NOTE_WARNING = Note::E_WC_ADMIN_NOTE_WARNING; @@ -60,7 +60,7 @@ class WC_Admin_Note extends DeprecatedClassFacade { // phpcs:ignore * * @deprecated since 1.7.0, use Notes */ -class WC_Admin_Notes extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -81,7 +81,7 @@ class WC_Admin_Notes extends DeprecatedClassFacade { // phpcs:ignore * * @deprecated since 1.7.0, use CouponPageMoved */ -class WC_Admin_Notes_Coupon_Page_Moved extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Coupon_Page_Moved extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -102,7 +102,7 @@ class WC_Admin_Notes_Coupon_Page_Moved extends DeprecatedClassFacade { // phpcs: * * @deprecated since 1.7.0, use CustomizeStoreWithBlocks */ -class WC_Admin_Notes_Customize_Store_With_Blocks extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Customize_Store_With_Blocks extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -123,7 +123,7 @@ class WC_Admin_Notes_Customize_Store_With_Blocks extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use EditProductsOnTheMove */ -class WC_Admin_Notes_Edit_Products_On_The_Move extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Edit_Products_On_The_Move extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -144,7 +144,7 @@ class WC_Admin_Notes_Edit_Products_On_The_Move extends DeprecatedClassFacade { / * * @deprecated since 1.7.0, use EUVATNumber */ -class WC_Admin_Notes_EU_VAT_Number extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_EU_VAT_Number extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -165,7 +165,7 @@ class WC_Admin_Notes_EU_VAT_Number extends DeprecatedClassFacade { // phpcs:igno * * @deprecated since 1.7.0, use FacebookMarketingExpert */ -class WC_Admin_Notes_Facebook_Marketing_Expert extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Facebook_Marketing_Expert extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -186,7 +186,7 @@ class WC_Admin_Notes_Facebook_Marketing_Expert extends DeprecatedClassFacade { / * * @deprecated since 1.7.0, use FirstProduct */ -class WC_Admin_Notes_First_Product extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_First_Product extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -207,7 +207,7 @@ class WC_Admin_Notes_First_Product extends DeprecatedClassFacade { // phpcs:igno * * @deprecated since 1.7.0, use GivingFeedbackNotes */ -class WC_Admin_Notes_Giving_Feedback_Notes extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Giving_Feedback_Notes extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -228,7 +228,7 @@ class WC_Admin_Notes_Giving_Feedback_Notes extends DeprecatedClassFacade { // ph * * @deprecated since 1.7.0, use InsightFirstSale */ -class WC_Admin_Notes_Insight_First_Sale extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Insight_First_Sale extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -249,7 +249,7 @@ class WC_Admin_Notes_Insight_First_Sale extends DeprecatedClassFacade { // phpcs * * @deprecated since 1.7.0, use InstallJPAndWCSPlugins */ -class WC_Admin_Notes_Install_JP_And_WCS_Plugins extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Install_JP_And_WCS_Plugins extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -270,7 +270,7 @@ class WC_Admin_Notes_Install_JP_And_WCS_Plugins extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use LaunchChecklist */ -class WC_Admin_Notes_Launch_Checklist extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Launch_Checklist extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -291,7 +291,7 @@ class WC_Admin_Notes_Launch_Checklist extends DeprecatedClassFacade { // phpcs:i * * @deprecated since 1.7.0, use MigrateFromShopify */ -class WC_Admin_Notes_Migrate_From_Shopify extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Migrate_From_Shopify extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -312,7 +312,7 @@ class WC_Admin_Notes_Migrate_From_Shopify extends DeprecatedClassFacade { // php * * @deprecated since 1.7.0, use MobileApp */ -class WC_Admin_Notes_Mobile_App extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Mobile_App extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -333,7 +333,7 @@ class WC_Admin_Notes_Mobile_App extends DeprecatedClassFacade { // phpcs:ignore * * @deprecated since 1.7.0, use NewSalesRecord */ -class WC_Admin_Notes_New_Sales_Record extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_New_Sales_Record extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -354,7 +354,7 @@ class WC_Admin_Notes_New_Sales_Record extends DeprecatedClassFacade { // phpcs:i * * @deprecated since 1.7.0, use OnboardingEmailMarketing */ -class WC_Admin_Notes_Onboarding_Email_Marketing extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Onboarding_Email_Marketing extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -375,7 +375,7 @@ class WC_Admin_Notes_Onboarding_Email_Marketing extends DeprecatedClassFacade { * * @deprecated since 1.7.0, use OnboardingPayments */ -class WC_Admin_Notes_Onboarding_Payments extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Onboarding_Payments extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -396,7 +396,7 @@ class WC_Admin_Notes_Onboarding_Payments extends DeprecatedClassFacade { // phpc * * @deprecated since 1.7.0, use OnlineClothingStore */ -class WC_Admin_Notes_Online_Clothing_Store extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Online_Clothing_Store extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -417,7 +417,7 @@ class WC_Admin_Notes_Online_Clothing_Store extends DeprecatedClassFacade { // ph * * @deprecated since 1.7.0, use OrderMilestones */ -class WC_Admin_Notes_Order_Milestones extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Order_Milestones extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -438,7 +438,7 @@ class WC_Admin_Notes_Order_Milestones extends DeprecatedClassFacade { // phpcs:i * * @deprecated since 1.7.0, use PerformanceOnMobile */ -class WC_Admin_Notes_Performance_On_Mobile extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Performance_On_Mobile extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -459,7 +459,7 @@ class WC_Admin_Notes_Performance_On_Mobile extends DeprecatedClassFacade { // ph * * @deprecated since 1.7.0, use PersonalizeStore */ -class WC_Admin_Notes_Personalize_Store extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Personalize_Store extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -480,7 +480,7 @@ class WC_Admin_Notes_Personalize_Store extends DeprecatedClassFacade { // phpcs: * * @deprecated since 1.7.0, use RealTimeOrderAlerts */ -class WC_Admin_Notes_Real_Time_Order_Alerts extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Real_Time_Order_Alerts extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -501,7 +501,7 @@ class WC_Admin_Notes_Real_Time_Order_Alerts extends DeprecatedClassFacade { // p * * @deprecated since 1.7.0, use SellingOnlineCourses */ -class WC_Admin_Notes_Selling_Online_Courses extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Selling_Online_Courses extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -522,7 +522,7 @@ class WC_Admin_Notes_Selling_Online_Courses extends DeprecatedClassFacade { // p * * @deprecated since 1.7.0, use SetUpAdditionalPaymentTypes */ -class WC_Admin_Notes_Set_Up_Additional_Payment_Types extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Set_Up_Additional_Payment_Types extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -543,7 +543,7 @@ class WC_Admin_Notes_Set_Up_Additional_Payment_Types extends DeprecatedClassFaca * * @deprecated since 1.7.0, use TestCheckout */ -class WC_Admin_Notes_Test_Checkout extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Test_Checkout extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -564,7 +564,7 @@ class WC_Admin_Notes_Test_Checkout extends DeprecatedClassFacade { // phpcs:igno * * @deprecated since 1.7.0, use TrackingOptIn */ -class WC_Admin_Notes_Tracking_Opt_In extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Tracking_Opt_In extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -585,7 +585,7 @@ class WC_Admin_Notes_Tracking_Opt_In extends DeprecatedClassFacade { // phpcs:ig * * @deprecated since 1.7.0, use WooSubscriptionsNotes */ -class WC_Admin_Notes_Woo_Subscriptions_Notes extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_Woo_Subscriptions_Notes extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -606,7 +606,7 @@ class WC_Admin_Notes_Woo_Subscriptions_Notes extends DeprecatedClassFacade { // * * @deprecated since 1.7.0, use WooCommercePayments */ -class WC_Admin_Notes_WooCommerce_Payments extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_WooCommerce_Payments extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * @@ -627,7 +627,7 @@ class WC_Admin_Notes_WooCommerce_Payments extends DeprecatedClassFacade { // php * * @deprecated since 1.7.0, use WooCommerceSubscriptions */ -class WC_Admin_Notes_WooCommerce_Subscriptions extends DeprecatedClassFacade { // phpcs:ignore +class WC_Admin_Notes_WooCommerce_Subscriptions extends DeprecatedClassFacade { /** * The name of the non-deprecated class that this facade covers. * From c80aba8450c49a39d35db725c6a6ead5cc0a3444 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 13 Apr 2022 16:33:16 -0700 Subject: [PATCH 333/386] Remove install.php --- .../src/Internal/Admin/Install.php | 590 ------------------ 1 file changed, 590 deletions(-) delete mode 100644 plugins/woocommerce/src/Internal/Admin/Install.php diff --git a/plugins/woocommerce/src/Internal/Admin/Install.php b/plugins/woocommerce/src/Internal/Admin/Install.php deleted file mode 100644 index c5ac3c6793a..00000000000 --- a/plugins/woocommerce/src/Internal/Admin/Install.php +++ /dev/null @@ -1,590 +0,0 @@ - array( - 'wc_admin_update_0201_order_status_index', - 'wc_admin_update_0201_db_version', - ), - '0.23.0' => array( - 'wc_admin_update_0230_rename_gross_total', - 'wc_admin_update_0230_db_version', - ), - '0.25.1' => array( - 'wc_admin_update_0251_remove_unsnooze_action', - 'wc_admin_update_0251_db_version', - ), - '1.1.0' => array( - 'wc_admin_update_110_remove_facebook_note', - 'wc_admin_update_110_db_version', - ), - '1.3.0' => array( - 'wc_admin_update_130_remove_dismiss_action_from_tracking_opt_in_note', - 'wc_admin_update_130_db_version', - ), - '1.4.0' => array( - 'wc_admin_update_140_db_version', - ), - '1.6.0' => array( - 'wc_admin_update_160_remove_facebook_note', - 'wc_admin_update_160_db_version', - ), - '1.7.0' => array( - 'wc_admin_update_170_homescreen_layout', - 'wc_admin_update_170_db_version', - ), - '2.7.0' => array( - 'wc_admin_update_270_delete_report_downloads', - 'wc_admin_update_270_db_version', - ), - '2.7.1' => array( - 'wc_admin_update_271_update_task_list_options', - 'wc_admin_update_271_db_version', - ), - '2.8.0' => array( - 'wc_admin_update_280_order_status', - 'wc_admin_update_280_db_version', - ), - '2.9.0' => array( - 'wc_admin_update_290_update_apperance_task_option', - 'wc_admin_update_290_delete_default_homepage_layout_option', - 'wc_admin_update_290_db_version', - ), - '3.0.0' => array( - 'wc_admin_update_300_update_is_read_from_last_read', - 'wc_admin_update_300_db_version', - ), - '3.4.0' => array( - 'wc_admin_update_340_remove_is_primary_from_note_action', - 'wc_admin_update_340_db_version', - ), - ); - - /** - * Migrated option names mapping. New => old. - * - * @var array - */ - protected static $migrated_options = array( - 'woocommerce_onboarding_profile' => 'wc_onboarding_profile', - 'woocommerce_admin_install_timestamp' => 'wc_admin_install_timestamp', - 'woocommerce_onboarding_opt_in' => 'wc_onboarding_opt_in', - 'woocommerce_admin_import_stats' => 'wc_admin_import_stats', - 'woocommerce_admin_version' => 'wc_admin_version', - 'woocommerce_admin_last_orders_milestone' => 'wc_admin_last_orders_milestone', - 'woocommerce_admin-wc-helper-last-refresh' => 'wc-admin-wc-helper-last-refresh', - 'woocommerce_admin_report_export_status' => 'wc_admin_report_export_status', - 'woocommerce_task_list_complete' => 'woocommerce_task_list_complete', - 'woocommerce_task_list_hidden' => 'woocommerce_task_list_hidden', - 'woocommerce_extended_task_list_complete' => 'woocommerce_extended_task_list_complete', - 'woocommerce_extended_task_list_hidden' => 'woocommerce_extended_task_list_hidden', - ); - - /** - * Hook in tabs. - */ - public static function init() { - if ( ( is_admin() && ! wp_doing_ajax() ) || wp_doing_cron() || defined( 'WP_CLI' ) ) { - add_action( 'init', array( __CLASS__, 'check_version' ), 5 ); - } - add_filter( 'wpmu_drop_tables', array( __CLASS__, 'wpmu_drop_tables' ) ); - - // Add wc-admin report tables to list of WooCommerce tables. - add_filter( 'woocommerce_install_get_tables', array( __CLASS__, 'add_tables' ) ); - } - - /** - * Migrate option values to their new keys/names. - */ - public static function migrate_options() { - wc_maybe_define_constant( 'WC_ADMIN_MIGRATING_OPTIONS', true ); - - foreach ( self::$migrated_options as $new_option => $old_option ) { - $old_option_value = get_option( $old_option, false ); - - // Continue if no option value was previously set. - if ( false === $old_option_value ) { - continue; - } - - if ( '1' === $old_option_value ) { - $old_option_value = 'yes'; - } elseif ( '0' === $old_option_value ) { - $old_option_value = 'no'; - } - - update_option( $new_option, $old_option_value ); - if ( $new_option !== $old_option ) { - delete_option( $old_option ); - } - } - } - - /** - * Check WC Admin version and run the updater is required. - * - * This check is done on all requests and runs if the versions do not match. - */ - public static function check_version() { - if ( defined( 'IFRAME_REQUEST' ) ) { - return; - } - - $version_option = get_option( self::VERSION_OPTION ); - $requires_update = version_compare( get_option( self::VERSION_OPTION ), WC_ADMIN_VERSION_NUMBER, '<' ); - - /* - * When included as part of Core, no `on_activation` hook as been called - * so there is no version in options. Make sure install gets called in this - * case as well as a regular version update - */ - if ( ! $version_option || $requires_update ) { - self::install(); - /** - * WooCommerce Admin has been installed or updated. - */ - do_action( 'woocommerce_admin_updated' ); - - if ( ! $version_option ) { - /** - * WooCommerce Admin has been installed. - */ - do_action( 'woocommerce_admin_newly_installed' ); - } - - if ( $requires_update ) { - /** - * An existing installation of WooCommerce Admin has been - * updated. - */ - do_action( 'woocommerce_admin_updated_existing' ); - } - } - - /* - * Add the version option if none is found, as would be the case when - * initialized via Core for the first time. - */ - if ( ! $version_option ) { - add_option( self::VERSION_OPTION, WC_ADMIN_VERSION_NUMBER ); - } - } - - /** - * Install WC Admin. - */ - public static function install() { - if ( ! is_blog_installed() ) { - return; - } - - // Check if we are not already running this routine. - if ( self::is_installing() ) { - return; - } - - // If we made it till here nothing is running yet, lets set the transient now. - set_transient( 'wc_admin_installing', 'yes', MINUTE_IN_SECONDS * 10 ); - - self::migrate_options(); - self::create_tables(); - self::create_events(); - self::delete_obsolete_notes(); - self::maybe_update_db_version(); - - delete_transient( 'wc_admin_installing' ); - - // Use add_option() here to avoid overwriting this value with each - // plugin version update. We base plugin age off of this value. - add_option( 'woocommerce_admin_install_timestamp', time() ); - do_action( 'woocommerce_admin_installed' ); - } - - /** - * Check if the installer is installing. - * - * @return bool - */ - public static function is_installing() { - return 'yes' === get_transient( 'wc_admin_installing' ); - } - - /** - * Get database schema. - * - * @return string - */ - protected static function get_schema() { - global $wpdb; - - $collate = $wpdb->has_cap( 'collation' ) ? $wpdb->get_charset_collate() : ''; - - // Max DB index length. See wp_get_db_schema(). - $max_index_length = 191; - - $tables = " - CREATE TABLE {$wpdb->prefix}wc_order_stats ( - order_id bigint(20) unsigned NOT NULL, - parent_id bigint(20) unsigned DEFAULT 0 NOT NULL, - date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, - date_created_gmt datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, - num_items_sold int(11) DEFAULT 0 NOT NULL, - total_sales double DEFAULT 0 NOT NULL, - tax_total double DEFAULT 0 NOT NULL, - shipping_total double DEFAULT 0 NOT NULL, - net_total double DEFAULT 0 NOT NULL, - returning_customer boolean DEFAULT NULL, - status varchar(200) NOT NULL, - customer_id BIGINT UNSIGNED NOT NULL, - PRIMARY KEY (order_id), - KEY date_created (date_created), - KEY customer_id (customer_id), - KEY status (status({$max_index_length})) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_order_product_lookup ( - order_item_id BIGINT UNSIGNED NOT NULL, - order_id BIGINT UNSIGNED NOT NULL, - product_id BIGINT UNSIGNED NOT NULL, - variation_id BIGINT UNSIGNED NOT NULL, - customer_id BIGINT UNSIGNED NULL, - date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, - product_qty INT NOT NULL, - product_net_revenue double DEFAULT 0 NOT NULL, - product_gross_revenue double DEFAULT 0 NOT NULL, - coupon_amount double DEFAULT 0 NOT NULL, - tax_amount double DEFAULT 0 NOT NULL, - shipping_amount double DEFAULT 0 NOT NULL, - shipping_tax_amount double DEFAULT 0 NOT NULL, - PRIMARY KEY (order_item_id), - KEY order_id (order_id), - KEY product_id (product_id), - KEY customer_id (customer_id), - KEY date_created (date_created) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_order_tax_lookup ( - order_id BIGINT UNSIGNED NOT NULL, - tax_rate_id BIGINT UNSIGNED NOT NULL, - date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, - shipping_tax double DEFAULT 0 NOT NULL, - order_tax double DEFAULT 0 NOT NULL, - total_tax double DEFAULT 0 NOT NULL, - PRIMARY KEY (order_id, tax_rate_id), - KEY tax_rate_id (tax_rate_id), - KEY date_created (date_created) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_order_coupon_lookup ( - order_id BIGINT UNSIGNED NOT NULL, - coupon_id BIGINT NOT NULL, - date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, - discount_amount double DEFAULT 0 NOT NULL, - PRIMARY KEY (order_id, coupon_id), - KEY coupon_id (coupon_id), - KEY date_created (date_created) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_admin_notes ( - note_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - name varchar(255) NOT NULL, - type varchar(20) NOT NULL, - locale varchar(20) NOT NULL, - title longtext NOT NULL, - content longtext NOT NULL, - content_data longtext NULL default null, - status varchar(200) NOT NULL, - source varchar(200) NOT NULL, - date_created datetime NOT NULL default '0000-00-00 00:00:00', - date_reminder datetime NULL default null, - is_snoozable boolean DEFAULT 0 NOT NULL, - layout varchar(20) DEFAULT '' NOT NULL, - image varchar(200) NULL DEFAULT NULL, - is_deleted boolean DEFAULT 0 NOT NULL, - is_read boolean DEFAULT 0 NOT NULL, - icon varchar(200) NOT NULL default 'info', - PRIMARY KEY (note_id) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_admin_note_actions ( - action_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - note_id BIGINT UNSIGNED NOT NULL, - name varchar(255) NOT NULL, - label varchar(255) NOT NULL, - query longtext NOT NULL, - status varchar(255) NOT NULL, - actioned_text varchar(255) NOT NULL, - nonce_action varchar(255) NULL DEFAULT NULL, - nonce_name varchar(255) NULL DEFAULT NULL, - PRIMARY KEY (action_id), - KEY note_id (note_id) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_customer_lookup ( - customer_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - user_id BIGINT UNSIGNED DEFAULT NULL, - username varchar(60) DEFAULT '' NOT NULL, - first_name varchar(255) NOT NULL, - last_name varchar(255) NOT NULL, - email varchar(100) NULL default NULL, - date_last_active timestamp NULL default null, - date_registered timestamp NULL default null, - country char(2) DEFAULT '' NOT NULL, - postcode varchar(20) DEFAULT '' NOT NULL, - city varchar(100) DEFAULT '' NOT NULL, - state varchar(100) DEFAULT '' NOT NULL, - PRIMARY KEY (customer_id), - UNIQUE KEY user_id (user_id), - KEY email (email) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_category_lookup ( - category_tree_id BIGINT UNSIGNED NOT NULL, - category_id BIGINT UNSIGNED NOT NULL, - PRIMARY KEY (category_tree_id,category_id) - ) $collate; - "; - - return $tables; - } - - /** - * Create database tables. - */ - public static function create_tables() { - require_once ABSPATH . 'wp-admin/includes/upgrade.php'; - - dbDelta( self::get_schema() ); - } - - /** - * Return a list of tables. Used to make sure all WC Admin tables are dropped - * when uninstalling the plugin in a single site or multi site environment. - * - * @return array WC tables. - */ - public static function get_tables() { - global $wpdb; - - return array( - "{$wpdb->prefix}wc_order_stats", - "{$wpdb->prefix}wc_order_product_lookup", - "{$wpdb->prefix}wc_order_tax_lookup", - "{$wpdb->prefix}wc_order_coupon_lookup", - "{$wpdb->prefix}wc_admin_notes", - "{$wpdb->prefix}wc_admin_note_actions", - "{$wpdb->prefix}wc_customer_lookup", - "{$wpdb->prefix}wc_category_lookup", - ); - } - - /** - * Adds new tables. - * - * @param array $wc_tables List of WooCommerce tables. - * @return array - */ - public static function add_tables( $wc_tables ) { - return array_merge( - $wc_tables, - self::get_tables() - ); - } - - /** - * Uninstall tables when MU blog is deleted. - * - * @param array $tables List of tables that will be deleted by WP. - * - * @return string[] - */ - public static function wpmu_drop_tables( $tables ) { - return array_merge( $tables, self::get_tables() ); - } - - /** - * Get list of DB update callbacks. - * - * @return array - */ - public static function get_db_update_callbacks() { - return self::$db_updates; - } - - /** - * Is a DB update needed? - * - * @return boolean - */ - public static function needs_db_update() { - $current_db_version = get_option( self::VERSION_OPTION, null ); - $updates = self::get_db_update_callbacks(); - $update_versions = array_keys( $updates ); - usort( $update_versions, 'version_compare' ); - - return ! is_null( $current_db_version ) && version_compare( $current_db_version, end( $update_versions ), '<' ); - } - - /** - * See if we need to show or run database updates during install. - */ - private static function maybe_update_db_version() { - if ( self::needs_db_update() ) { - self::update(); - } else { - self::update_db_version(); - } - } - - /** - * Push all needed DB updates to the queue for processing. - */ - private static function update() { - $current_db_version = get_option( self::VERSION_OPTION ); - $loop = 0; - - foreach ( self::get_db_update_callbacks() as $version => $update_callbacks ) { - if ( version_compare( $current_db_version, $version, '<' ) ) { - $completed_version_updates = 0; - foreach ( $update_callbacks as $update_callback ) { - $pending_jobs = WC()->queue()->search( - array( - 'per_page' => 1, - 'hook' => 'woocommerce_run_update_callback', - 'search' => wp_json_encode( array( $update_callback ) ), - 'group' => 'woocommerce-db-updates', - 'status' => 'pending', - ) - ); - - $complete_jobs = WC()->queue()->search( - array( - 'per_page' => 1, - 'hook' => 'woocommerce_run_update_callback', - 'search' => wp_json_encode( array( $update_callback ) ), - 'group' => 'woocommerce-db-updates', - 'status' => 'complete', - ) - ); - - $completed_version_updates += count( $complete_jobs ); - - if ( empty( $pending_jobs ) && empty( $complete_jobs ) ) { - WC()->queue()->schedule_single( - time() + $loop, - 'woocommerce_run_update_callback', - array( $update_callback ), - 'woocommerce-db-updates' - ); - Cache::invalidate(); - } - - $loop++; - - } - - // Users have experienced concurrency issues where all update callbacks - // have run but the version option hasn't been updated. If all the updates - // for a version are complete, update the version option to reflect that. - // See: https:// github.com/woocommerce/woocommerce-admin/issues/5058. - if ( count( $update_callbacks ) === $completed_version_updates ) { - self::update_db_version( $version ); - } - } - } - } - - /** - * Update WC Admin version to current. - * - * @param string|null $version New WooCommerce Admin DB version or null. - */ - public static function update_db_version( $version = null ) { - update_option( self::VERSION_OPTION, is_null( $version ) ? WC_ADMIN_VERSION_NUMBER : $version ); - } - - /** - * Schedule cron events. - */ - public static function create_events() { - if ( ! wp_next_scheduled( 'wc_admin_daily' ) ) { - wp_schedule_event( time(), 'daily', 'wc_admin_daily' ); - } - // Note: this is potentially redundant when the core package exists. - wp_schedule_single_event( time() + 10, 'generate_category_lookup_table' ); - } - - /** - * Delete obsolete notes. - */ - protected static function delete_obsolete_notes() { - $obsolete_notes_names = array( - 'wc-admin-welcome-note', - 'wc-admin-store-notice-setting-moved', - 'wc-admin-store-notice-giving-feedback', - 'wc-admin-learn-more-about-product-settings', - 'wc-admin-onboarding-profiler-reminder', - 'wc-admin-historical-data', - 'wc-admin-review-shipping-settings', - 'wc-admin-home-screen-feedback', - 'wc-admin-effortless-payments-by-mollie', - 'wc-admin-google-ads-and-marketing', - 'wc-admin-marketing-intro', - 'wc-admin-draw-attention', - 'wc-admin-need-some-inspiration', - 'wc-admin-choose-niche', - 'wc-admin-start-dropshipping-business', - 'wc-admin-filter-by-product-variations-in-reports', - 'wc-admin-learn-more-about-variable-products', - 'wc-admin-getting-started-ecommerce-webinar', - 'wc-admin-navigation-feedback', - 'wc-admin-navigation-feedback-follow-up', - ); - - $additional_obsolete_notes_names = apply_filters( - 'woocommerce_admin_obsolete_notes_names', - array() - ); - - if ( is_array( $additional_obsolete_notes_names ) ) { - $obsolete_notes_names = array_merge( - $obsolete_notes_names, - $additional_obsolete_notes_names - ); - } - - Notes::delete_notes_with_name( $obsolete_notes_names ); - } - - /** - * Drop WooCommerce Admin tables. - * - * @return void - */ - public static function drop_tables() { - global $wpdb; - - $tables = self::get_tables(); - - foreach ( $tables as $table ) { - /* phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared */ - $wpdb->query( "DROP TABLE IF EXISTS {$table}" ); - /* phpcs:enable */ - } - } -} From 09a85d9cb47a6b59bb40c966f6f38cb151073eef Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 13 Apr 2022 16:53:33 -0700 Subject: [PATCH 334/386] Remove wc_admin_update_140_change_deactivate_plugin_note_type -- no longer needed --- plugins/woocommerce/includes/class-wc-install.php | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 0d3fd7ec0d6..d19c54df066 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -168,7 +168,6 @@ class WC_Install { 'wc_update_440_insert_attribute_terms_for_variable_products', 'wc_admin_update_110_remove_facebook_note', 'wc_admin_update_130_remove_dismiss_action_from_tracking_opt_in_note', - 'wc_admin_update_140_change_deactivate_plugin_note_type', 'wc_update_440_db_version', ), '4.5.0' => array( From e7ce043e94feff1bee038ca68e7ad1898da847d8 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 14 Apr 2022 07:58:56 +0800 Subject: [PATCH 335/386] Update gateway countries --- .../DefaultPaymentGateways.php | 82 ++----------------- 1 file changed, 5 insertions(+), 77 deletions(-) diff --git a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php index 221e0fc06a5..da84a6c4afb 100644 --- a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php +++ b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php @@ -43,55 +43,11 @@ class DefaultPaymentGateways { 'is_visible' => array( // https://stripe.com/global. self::get_rules_for_countries( - array( - 'AU', - 'AT', - 'BE', - 'BG', - 'BR', - 'CA', - 'CY', - 'CZ', - 'DK', - 'EE', - 'FI', - 'FR', - 'DE', - 'GR', - 'HK', - 'IN', - 'IE', - 'IT', - 'JP', - 'LV', - 'LT', - 'LU', - 'MY', - 'MT', - 'MX', - 'NL', - 'NZ', - 'NO', - 'PL', - 'PT', - 'RO', - 'SG', - 'SK', - 'SI', - 'ES', - 'SE', - 'CH', - 'GB', - 'US', - 'PR', - 'HU', - 'SL', - 'ID', - ) + array( 'AU', 'AT', 'BE', 'BG', 'BR', 'CA', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HK', 'IN', 'IE', 'IT', 'JP', 'LV', 'LT', 'LU', 'MY', 'MT', 'MX', 'NL', 'NZ', 'NO', 'PL', 'PT', 'RO', 'SG', 'SK', 'SI', 'ES', 'SE', 'CH', 'GB', 'US', 'PR', 'HU', 'SL', 'ID', 'MY', 'SI', 'PR' ) ), self::get_rules_for_cbd( false ), ), - 'category_other' => array( 'US', 'CA', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DK', 'EE', 'ES', 'FI', 'FR', 'DE', 'GB', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SL', 'SE', 'MX', 'BR', 'AU', 'NZ', 'HK', 'JP', 'SG', 'ID', 'IN' ), + 'category_other' => array( 'AU', 'AT', 'BE', 'BG', 'BR', 'CA', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HK', 'IN', 'IE', 'IT', 'JP', 'LV', 'LT', 'LU', 'MY', 'MT', 'MX', 'NL', 'NZ', 'NO', 'PL', 'PT', 'RO', 'SG', 'SK', 'SI', 'ES', 'SE', 'CH', 'GB', 'US', 'PR', 'HU', 'SL', 'ID', 'MY', 'SI', 'PR' ), 'category_additional' => array(), 'recommendation_priority' => 3, ), @@ -129,28 +85,12 @@ class DefaultPaymentGateways { 'plugins' => array( 'klarna-payments-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( - array( - 'DK', - 'DE', - 'AT', - 'NL', - 'CH', - 'BE', - 'SP', - 'PL', - 'FR', - 'IT', - 'GB', - 'ES', - 'FI', - 'NO', - 'SE', - ) + array( 'US', 'CA', 'DK', 'DE', 'AT', 'NL', 'CH', 'BE', 'SP', 'PL', 'FR', 'IT', 'GB', 'ES', 'FI', 'NO', 'SE', 'ES', 'FI', 'NO', 'SE' ) ), self::get_rules_for_cbd( false ), ), 'category_other' => array(), - 'category_additional' => array( 'AT', 'BE', 'CH', 'DK', 'ES', 'FI', 'FR', 'DE', 'GB', 'IT', 'NL', 'NO', 'PL', 'SE' ), + 'category_additional' => array( 'US', 'CA', 'DK', 'DE', 'AT', 'NL', 'CH', 'BE', 'SP', 'PL', 'FR', 'IT', 'GB', 'ES', 'FI', 'NO', 'SE', 'ES', 'FI', 'NO', 'SE' ), ), array( 'id' => 'mollie_wc_gateway_banktransfer', @@ -160,19 +100,7 @@ class DefaultPaymentGateways { 'plugins' => array( 'mollie-payments-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( - array( - 'FR', - 'DE', - 'GB', - 'AT', - 'CH', - 'ES', - 'IT', - 'PL', - 'FI', - 'NL', - 'BE', - ) + array( 'FR', 'DE', 'GB', 'AT', 'CH', 'ES', 'IT', 'PL', 'FI', 'NL', 'BE' ) ), ), 'category_other' => array( 'FR', 'DE', 'GB', 'AT', 'CH', 'ES', 'IT', 'PL', 'FI', 'NL', 'BE' ), From be1342420c154fabcd3efa09182fefbc368f5fff Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 14 Apr 2022 08:09:24 +0800 Subject: [PATCH 336/386] Add more timeout threshold for e2e --- packages/js/admin-e2e-tests/src/specs/tasks/payment.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts index 27052d002fc..0434d53e171 100644 --- a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts +++ b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts @@ -63,7 +63,7 @@ const testAdminPaymentSetupTask = () => { iban: '12 3456 7890', swiftCode: 'ABBA', } ); - await waitForTimeout( 1000 ); + await waitForTimeout( 1500 ); expect( await settings.paymentMethodIsEnabled( 'bacs' ) ).toBe( true ); @@ -79,7 +79,7 @@ const testAdminPaymentSetupTask = () => { await paymentsSetup.isDisplayed(); await paymentsSetup.showOtherPaymentMethods(); await paymentsSetup.enableCashOnDelivery(); - await waitForTimeout( 1000 ); + await waitForTimeout( 1500 ); expect( await settings.paymentMethodIsEnabled( 'cod' ) ).toBe( true ); From f514c81074953c9d74af253b551c969e1cf842b4 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 13 Apr 2022 17:11:44 -0700 Subject: [PATCH 337/386] Fix broken tests --- .../tests/legacy/unit-tests/woocommerce-admin/install.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php index 5bc2da088bc..62e771dbc92 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/install.php @@ -125,8 +125,8 @@ class WC_Admin_Tests_Install extends WP_UnitTestCase { public function db_update_version_provider() { return array( // [DB Update version string, # of expected pending jobs] - array( '3.9.0', 34 ), - array( '4.0.0', 27 ), + array( '3.9.0', 33 ), + array( '4.0.0', 26 ), array( '4.4.0', 22 ), array( '4.5.0', 20 ), array( '5.0.0', 16 ), From 197c613f079bffc4322234bf7ae29e97361d7d65 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 14 Apr 2022 08:12:19 +0800 Subject: [PATCH 338/386] Add changelog --- .../changelog/update-32132-payment-logic-in-task-and-settings | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/update-32132-payment-logic-in-task-and-settings diff --git a/plugins/woocommerce/changelog/update-32132-payment-logic-in-task-and-settings b/plugins/woocommerce/changelog/update-32132-payment-logic-in-task-and-settings new file mode 100644 index 00000000000..ba445758a66 --- /dev/null +++ b/plugins/woocommerce/changelog/update-32132-payment-logic-in-task-and-settings @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Update payment gateway logic in payment task From c81d74978168cd1ae1fa33b06efa1bfec7bee16e Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 14 Apr 2022 08:37:53 +0800 Subject: [PATCH 339/386] Fix cases when gateway is disabled but has been set up --- .../fills/PaymentGatewaySuggestions/components/Action.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Action.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Action.js index 9a4c6b3bb31..3b3c4ec66f2 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Action.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Action.js @@ -118,6 +118,10 @@ export const Action = ( { } if ( ! needsSetup ) { + if ( ! isEnabled ) { + return ; + } + return ; } From 39c57c802f8c0fb4eb61cdae2b54d68d844852bf Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 14 Apr 2022 08:38:42 +0800 Subject: [PATCH 340/386] Fix tests --- .../PaymentGatewaySuggestions/components/List/test/list.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/test/list.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/test/list.js index 6bd6e4e07e0..6c7331eec61 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/test/list.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/test/list.js @@ -138,7 +138,7 @@ describe( 'PaymentGatewaySuggestions > List', () => { expect( queryByText( 'Recommended' ) ).not.toBeInTheDocument(); } ); - it( 'should display Manage button if not enabled and does have setup', () => { + it( 'should display Manage button if enabled and does have setup', () => { const props = { ...defaultProps, paymentGateways: [ @@ -180,6 +180,7 @@ describe( 'PaymentGatewaySuggestions > List', () => { ...mockGateway, plugins: [ 'nope' ], needsSetup: false, + enabled: true, }, ], }; From adbc1013123a88dad0ad265e88b3f838908e7c4f Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 14 Apr 2022 08:39:55 +0800 Subject: [PATCH 341/386] Update logic to only show additional gateways when other is installed and wcpay isnt eligible --- .../tasks/fills/PaymentGatewaySuggestions/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js index c7422657572..53997144cee 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/index.js @@ -214,7 +214,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { return false; }, [ countryCode, paymentGateways ] ); - const isEligibleWCPay = + const isWCPaySupported = Array.from( paymentGateways.values() ).findIndex( ( gateway ) => { return ( gateway.plugins?.length === 1 && @@ -252,9 +252,6 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { offline.push( gateway ); } else if ( gateway.enabled ) { // Enabled gateways should be ignored. - } else if ( ! isEligibleWCPay ) { - // When WCPay-ineligible, just show all gateways. - additional.push( gateway ); } else if ( isWCPayOrOtherCategoryDoneSetup ) { // If WCPay or "other" gateway is enabled in an WCPay-eligible country, only // allow to list "additional" gateways. @@ -266,6 +263,9 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { ) { additional.push( gateway ); } + } else if ( ! isWCPaySupported ) { + // When WCPay-ineligible, just show all gateways. + additional.push( gateway ); } else if ( gateway.category_other && gateway.category_other.indexOf( countryCode ) !== -1 @@ -280,7 +280,7 @@ export const PaymentGatewaySuggestions = ( { onComplete, query } ) => { ), [ countryCode, - isEligibleWCPay, + isWCPaySupported, isWCPayOrOtherCategoryDoneSetup, paymentGateways, ] From 0fb4b5d9f5ecd3d6095cd72db7ea54e980883719 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 14 Apr 2022 09:33:26 +0800 Subject: [PATCH 342/386] Update e2e --- packages/js/admin-e2e-tests/CHANGELOG.md | 2 ++ packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts | 1 + packages/js/admin-e2e-tests/src/specs/tasks/payment.ts | 6 ++++++ 3 files changed, 9 insertions(+) diff --git a/packages/js/admin-e2e-tests/CHANGELOG.md b/packages/js/admin-e2e-tests/CHANGELOG.md index db24c246ffd..463e840c3e8 100644 --- a/packages/js/admin-e2e-tests/CHANGELOG.md +++ b/packages/js/admin-e2e-tests/CHANGELOG.md @@ -2,6 +2,8 @@ - Update test for payment task. #32467 +- Increase timeout threshold for payment task. #32605 + # 1.0.0 - Add returned type annotations and remove unused vars. #8020 diff --git a/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts b/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts index 1dae7adc683..f2fbe95d01b 100644 --- a/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts +++ b/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts @@ -32,6 +32,7 @@ export class PaymentsSetup extends BasePage { `${ selector }[aria-expanded=false]` ); await toggleButton?.click(); + await waitForElementByText( 'h2', 'Offline payment methods' ); } async goToPaymentMethodSetup( diff --git a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts index 0434d53e171..ba4b081fe91 100644 --- a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts +++ b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts @@ -54,6 +54,9 @@ const testAdminPaymentSetupTask = () => { it( 'Saving valid bank account transfer details enables the payment method', async () => { await paymentsSetup.showOtherPaymentMethods(); + await takeScreenshotFor( + 'Payment setup task show other payment methods' + ); await paymentsSetup.goToPaymentMethodSetup( 'bacs' ); await bankTransferSetup.saveAccountDetails( { accountNumber: '1234', @@ -78,6 +81,9 @@ const testAdminPaymentSetupTask = () => { await homeScreen.clickOnTaskList( 'Set up payments' ); await paymentsSetup.isDisplayed(); await paymentsSetup.showOtherPaymentMethods(); + await takeScreenshotFor( + 'Payment setup task show other payment methods' + ); await paymentsSetup.enableCashOnDelivery(); await waitForTimeout( 1500 ); expect( await settings.paymentMethodIsEnabled( 'cod' ) ).toBe( From 54ea7c9cbeb924329919076444e3c84eef8e383e Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Thu, 14 Apr 2022 09:39:31 +0800 Subject: [PATCH 343/386] Revert previous image attribute to preserve old images and new icons are replace with new attribute --- .../components/List/Item.js | 4 +- .../DefaultPaymentGateways.php | 48 +++++++++++++------ 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/Item.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/Item.js index 2c4ba03a8b9..bb327ace13e 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/Item.js +++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/List/Item.js @@ -15,7 +15,7 @@ import './List.scss'; export const Item = ( { isRecommended, markConfigured, paymentGateway } ) => { const { - image, + image_72x72: image72x72, content, id, plugins = [], @@ -58,7 +58,7 @@ export const Item = ( { isRecommended, markConfigured, paymentGateway } ) => { className={ classes } > - { + {