From d719907ffa97d205ac3a2ceff8b2257f391c9842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20P=C3=A9rez=20Pellicer?= <5908855+puntope@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:27:14 -0600 Subject: [PATCH 1/6] Add WP CLI command for GTIN Migration --- .../CoreServiceProvider.php | 5 + src/Utility/WPCLIMigrationGTIN.php | 187 ++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 src/Utility/WPCLIMigrationGTIN.php diff --git a/src/Internal/DependencyManagement/CoreServiceProvider.php b/src/Internal/DependencyManagement/CoreServiceProvider.php index e717221143..c60eff49a8 100644 --- a/src/Internal/DependencyManagement/CoreServiceProvider.php +++ b/src/Internal/DependencyManagement/CoreServiceProvider.php @@ -126,6 +126,7 @@ use Automattic\WooCommerce\GoogleListingsAndAds\Utility\DateTimeUtility; use Automattic\WooCommerce\GoogleListingsAndAds\Utility\ImageUtility; use Automattic\WooCommerce\GoogleListingsAndAds\Utility\ISOUtility; +use Automattic\WooCommerce\GoogleListingsAndAds\Utility\WPCLIMigrationGTIN; use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\League\ISO3166\ISO3166DataProvider; use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\Psr\Container\ContainerInterface; use Automattic\WooCommerce\GoogleListingsAndAds\View\PHPViewFactory; @@ -216,6 +217,7 @@ class CoreServiceProvider extends AbstractServiceProvider { AttributeMapping::class => true, MarketingChannelRegistrar::class => true, OAuthService::class => true, + WPCLIMigrationGTIN::class => true, ]; /** @@ -431,5 +433,8 @@ function ( ...$arguments ) { $this->share_with_tags( GLAChannel::class, MerchantCenterService::class, AdsCampaign::class, Ads::class, MerchantStatuses::class, ProductSyncStats::class ); $this->share_with_tags( MarketingChannelRegistrar::class, GLAChannel::class, WC::class ); } + + // ClI Classes + $this->share_with_tags( WPCLIMigrationGTIN::class, ProductRepository::class, AttributeManager::class ); } } diff --git a/src/Utility/WPCLIMigrationGTIN.php b/src/Utility/WPCLIMigrationGTIN.php new file mode 100644 index 0000000000..882dff4201 --- /dev/null +++ b/src/Utility/WPCLIMigrationGTIN.php @@ -0,0 +1,187 @@ +product_repository = $product_repository; + $this->attribute_manager = $attribute_manager; + } + + /** + * Register service and initialize hooks. + */ + public function register(): void { + WP_CLI::add_hook( 'after_wp_load', [ $this, 'register_commands' ] ); + } + + /** + * Register the commands + */ + public function register_commands(): void { + WP_CLI::add_command( 'wc g4wc gtin-migration start', [ $this, 'gtin_migration_start' ] ); + } + + /** + * Starts the GTIN migration in batches + */ + public function gtin_migration_start(): void { + $batch_size = $this->get_batch_size(); + $num_products = $this->get_total_products_count(); + WP_CLI::log( sprintf( 'Starting GTIN migration for %s products in the store.', $num_products ) ); + $progress = WP_CLI\Utils\make_progress_bar( 'GTIN Migration', $num_products / $batch_size ); + $processed = 0; + $batch_number = 1; + $start_time = microtime( true ); + + // First batch + $items = $this->get_items( $batch_number ); + $processed += $this->process_items( $items ); + $progress->tick(); + + // Next batches + while ( ! empty( $items ) ) { + ++$batch_number; + $items = $this->get_items( $batch_number ); + $processed += $this->process_items( $items ); + $progress->tick(); + } + + $progress->finish(); + $total_time = microtime( true ) - $start_time; + + // Issue a warning if nothing is migrated. + if ( ! $processed ) { + WP_CLI::warning( __( 'No GTIN were migrated.', 'google-listings-and-ads' ) ); + return; + } + + WP_CLI::success( + sprintf( + /* Translators: %1$d is the number of migrated GTINS and %2$d is the execution time in seconds. */ + _n( + '%1$d GTIN was migrated in %2$d seconds.', + '%1$d GTIN were migrated in %2$d seconds.', + $processed, + 'google-listings-and-ads' + ), + $processed, + $total_time + ) + ); + } + + /** + * Get total of products in the store to be migrated. + * + * @return int The total number of products. + */ + private function get_total_products_count(): int { + $args = [ + 'status' => 'publish', + 'return' => 'ids', + 'type' => [ 'simple', 'variation' ], + ]; + + return count( $this->product_repository->find_ids( $args ) ); + } + + + /** + * Get the items for the current batch + * + * @param int $batch_number + * @return int[] Array of WooCommerce product IDs + */ + private function get_items( int $batch_number ): array { + return $this->product_repository->find_all_product_ids( $this->get_batch_size(), $this->get_query_offset( $batch_number ) ); + } + + /** + * Get the query offset based on a given batch number and the specified batch size. + * + * @param int $batch_number + * + * @return int + */ + protected function get_query_offset( int $batch_number ): int { + return $this->get_batch_size() * ( $batch_number - 1 ); + } + + /** + * Get the batch size. By default, 100. + * + * @return int The batch size. + */ + private function get_batch_size(): int { + return apply_filters( 'woocommerce_gla_batched_cli_size', 100 ); + } + + /** + * Process batch items. + * + * @param int[] $items A single batch of WooCommerce product IDs from the get_batch() method. + * @return int The number of items processed. + */ + protected function process_items( array $items ): int { + // todo: refactor avoiding duplication with MigrateGTIN Job after https://github.com/woocommerce/google-listings-and-ads/pull/2656 gets merged. + // update the product core GTIN using G4W GTIN + $products = $this->product_repository->find_by_ids( $items ); + $processed = 0; + + foreach ( $products as $product ) { + // void if core GTIN is already set. + if ( $product->get_global_unique_id() ) { + continue; + } + + // process variations + if ( $product instanceof \WC_Product_Variable ) { + $variations = $product->get_children(); + $processed += $this->process_items( $variations ); + continue; + } + + $gtin = $this->attribute_manager->get_value( $product, 'gtin' ); + if ( $gtin ) { + $product->set_global_unique_id( $gtin ); + $product->save(); + WP_CLI::success( sprintf( 'GTIN [ %s ] has been migrated for Product ID: %s - %s', $gtin, $product->get_id(), $product->get_name() ) ); + ++$processed; + } + } + + return $processed; + } +} From cb6b1ae3cc7f0fd319e07b7313cc3b9af8eb7a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20P=C3=A9rez=20Pellicer?= <5908855+puntope@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:33:00 -0600 Subject: [PATCH 2/6] PHPCS --- src/Internal/DependencyManagement/CoreServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Internal/DependencyManagement/CoreServiceProvider.php b/src/Internal/DependencyManagement/CoreServiceProvider.php index c60eff49a8..d18c76e69c 100644 --- a/src/Internal/DependencyManagement/CoreServiceProvider.php +++ b/src/Internal/DependencyManagement/CoreServiceProvider.php @@ -217,7 +217,7 @@ class CoreServiceProvider extends AbstractServiceProvider { AttributeMapping::class => true, MarketingChannelRegistrar::class => true, OAuthService::class => true, - WPCLIMigrationGTIN::class => true, + WPCLIMigrationGTIN::class => true, ]; /** From e9f21a0b5b8081d0b979136d0024115108ec6e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20P=C3=A9rez=20Pellicer?= <5908855+puntope@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:52:27 +0100 Subject: [PATCH 3/6] Use debug to some verbose messages --- src/Utility/WPCLIMigrationGTIN.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utility/WPCLIMigrationGTIN.php b/src/Utility/WPCLIMigrationGTIN.php index 882dff4201..b91aea9ede 100644 --- a/src/Utility/WPCLIMigrationGTIN.php +++ b/src/Utility/WPCLIMigrationGTIN.php @@ -177,7 +177,7 @@ protected function process_items( array $items ): int { if ( $gtin ) { $product->set_global_unique_id( $gtin ); $product->save(); - WP_CLI::success( sprintf( 'GTIN [ %s ] has been migrated for Product ID: %s - %s', $gtin, $product->get_id(), $product->get_name() ) ); + WP_CLI::debug( sprintf( 'GTIN [ %s ] has been migrated for Product ID: %s - %s', $gtin, $product->get_id(), $product->get_name() ) ); ++$processed; } } From 173824b0497177db38028d23d175b8624dce9ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20P=C3=A9rez=20Pellicer?= <5908855+puntope@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:57:43 +0100 Subject: [PATCH 4/6] Add is_needed logic --- src/Utility/WPCLIMigrationGTIN.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Utility/WPCLIMigrationGTIN.php b/src/Utility/WPCLIMigrationGTIN.php index b91aea9ede..1df9951f4b 100644 --- a/src/Utility/WPCLIMigrationGTIN.php +++ b/src/Utility/WPCLIMigrationGTIN.php @@ -3,6 +3,7 @@ namespace Automattic\WooCommerce\GoogleListingsAndAds\Utility; +use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Conditional; use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Registerable; use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service; use Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes\AttributeManager; @@ -19,7 +20,7 @@ * * @since x.x.x */ -class WPCLIMigrationGTIN implements Service, Registerable { +class WPCLIMigrationGTIN implements Service, Registerable, Conditional { /** @var AttributeManager */ @@ -184,4 +185,13 @@ protected function process_items( array $items ): int { return $processed; } + + /** + * Check if this Service is needed. + * @see https://make.wordpress.org/cli/handbook/guides/commands-cookbook/#include-in-a-plugin-or-theme + * @return bool + */ + public static function is_needed(): bool { + return defined( 'WP_CLI' ) && WP_CLI; + } } From 31097f66d8923280d7f786f50a41bbb624165d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20P=C3=A9rez=20Pellicer?= <5908855+puntope@users.noreply.github.com> Date: Wed, 6 Nov 2024 17:59:17 +0100 Subject: [PATCH 5/6] Handle errors --- src/Utility/WPCLIMigrationGTIN.php | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/Utility/WPCLIMigrationGTIN.php b/src/Utility/WPCLIMigrationGTIN.php index 1df9951f4b..18438f96b6 100644 --- a/src/Utility/WPCLIMigrationGTIN.php +++ b/src/Utility/WPCLIMigrationGTIN.php @@ -8,6 +8,7 @@ use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service; use Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes\AttributeManager; use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductRepository; +use Exception; use WP_CLI; defined( 'ABSPATH' ) || exit; @@ -164,6 +165,7 @@ protected function process_items( array $items ): int { foreach ( $products as $product ) { // void if core GTIN is already set. if ( $product->get_global_unique_id() ) { + WP_CLI::debug( sprintf( 'GTIN has been skipped for Product ID: %s - %s. GTIN was found in Product Inventory tab.', $product->get_id(), $product->get_name() ) ); continue; } @@ -175,11 +177,23 @@ protected function process_items( array $items ): int { } $gtin = $this->attribute_manager->get_value( $product, 'gtin' ); - if ( $gtin ) { - $product->set_global_unique_id( $gtin ); - $product->save(); - WP_CLI::debug( sprintf( 'GTIN [ %s ] has been migrated for Product ID: %s - %s', $gtin, $product->get_id(), $product->get_name() ) ); - ++$processed; + if ( ! $gtin ) { + WP_CLI::debug( sprintf( 'GTIN has been skipped for Product ID: %s - %s. No GTIN was found', $product->get_id(), $product->get_name() ) ); + continue; + } + + $gtin = str_replace( "-", "", $gtin ); + if ( is_numeric( $gtin ) ) { + try { + $product->set_global_unique_id( $gtin ); + $product->save(); + WP_CLI::debug( sprintf( 'GTIN [ %s ] has been migrated for Product ID: %s - %s', $gtin, $product->get_id(), $product->get_name() ) ); + ++$processed; + } catch ( Exception $e ) { + WP_CLI::error( $e->getMessage(), false ); + } + } else { + WP_CLI::debug( sprintf( 'GTIN [ %s ] has been skipped for Product ID: %s - %s. Invalid GTIN was found.', $gtin, $product->get_id(), $product->get_name() ) ); } } From 476adf70b9c009ed389c8d7c872a9abe1cc91139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20P=C3=A9rez=20Pellicer?= <5908855+puntope@users.noreply.github.com> Date: Wed, 6 Nov 2024 18:07:22 +0100 Subject: [PATCH 6/6] PHPCS --- src/Utility/WPCLIMigrationGTIN.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Utility/WPCLIMigrationGTIN.php b/src/Utility/WPCLIMigrationGTIN.php index 18438f96b6..e5e5e11983 100644 --- a/src/Utility/WPCLIMigrationGTIN.php +++ b/src/Utility/WPCLIMigrationGTIN.php @@ -182,7 +182,7 @@ protected function process_items( array $items ): int { continue; } - $gtin = str_replace( "-", "", $gtin ); + $gtin = str_replace( '-', '', $gtin ); if ( is_numeric( $gtin ) ) { try { $product->set_global_unique_id( $gtin ); @@ -202,6 +202,7 @@ protected function process_items( array $items ): int { /** * Check if this Service is needed. + * * @see https://make.wordpress.org/cli/handbook/guides/commands-cookbook/#include-in-a-plugin-or-theme * @return bool */