-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[GTIN] Add WP CLI command for migration #2662
base: add/support-core-gtin-field
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
<?php | ||
declare( strict_types=1 ); | ||
|
||
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; | ||
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductRepository; | ||
use Exception; | ||
use WP_CLI; | ||
|
||
defined( 'ABSPATH' ) || exit; | ||
|
||
/** | ||
* Class WP_CLI_GTIN_Migration | ||
* Creates a set of utility commands in WP CLI for GTIN Migration | ||
* | ||
* @package Automattic\WooCommerce\GoogleListingsAndAds\Utility | ||
* | ||
* @since x.x.x | ||
*/ | ||
class WPCLIMigrationGTIN implements Service, Registerable, Conditional { | ||
|
||
|
||
/** @var AttributeManager */ | ||
public AttributeManager $attribute_manager; | ||
|
||
/** @var ProductRepository */ | ||
public ProductRepository $product_repository; | ||
|
||
/** | ||
* Constructor | ||
* | ||
* @param ProductRepository $product_repository | ||
* @param AttributeManager $attribute_manager | ||
*/ | ||
public function __construct( ProductRepository $product_repository, AttributeManager $attribute_manager ) { | ||
$this->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() ) { | ||
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; | ||
} | ||
|
||
// 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 ) { | ||
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 ) ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Small nitpick, but I would have used |
||
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 ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we combine this error with the product ID, so we can know what to fix? Right now I'm getting this as output:
Which leaves me having to manually figure out which product I should fix. |
||
} | ||
} 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() ) ); | ||
} | ||
} | ||
|
||
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; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how we can setup a environment without WP_CLI, but based on the doc here it sounds like it might not always be available. And since we aren't doing any conditional checks it seems like it would fatal here.
Should we make this class implement
Conditional
and add ais_needed
function to check if WP_CLI is available?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tweaked here 173824b