diff --git a/CHANGELOG.md b/CHANGELOG.md index 302a875..ec0a7b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,10 @@ ## 4.0.0 - WIP - Shopify now requires `shopify/shopify-api` 5.2.0 or later. -- Shopify now requires custom apps to be created with event version set to `2023-10`. - -## Unreleased - -- Added an `async` flag to the `craft/shopify/sync` command. -- Added an `async` param to the sync utility. +- Shopify now the related custom app to be created with event version set to `2023-10`. +- Syncing meta fields is no longer performed in a job. +- Deprecated the `craft\shopify\jobs\UpdateProductMetadata` job. - Fixed a bug where the template routing setting would not save. -- Added `craft\shopify\helpers\Api`. -- Added `craft\shopify\jobs\UpdateProductVariants`. ## 3.2.0 - 2023-06-12 diff --git a/src/console/controllers/SyncController.php b/src/console/controllers/SyncController.php index 90ebf44..cc144e5 100644 --- a/src/console/controllers/SyncController.php +++ b/src/console/controllers/SyncController.php @@ -9,6 +9,7 @@ use craft\console\Controller; use craft\helpers\Console; +use craft\shopify\elements\Product; use craft\shopify\Plugin; use yii\console\ExitCode; @@ -23,22 +24,6 @@ class SyncController extends Controller /** @var string $defaultAction */ public $defaultAction = 'products'; - /** - * @var bool Whether to sync product and other associated data in the queue. - * @since 3.3.0 - */ - public bool $async = true; - - /** - * @inheritdoc - */ - public function options($actionID): array - { - $options = parent::options($actionID); - $options[] = 'async'; - return $options; - } - /** * Sync all Shopify data. */ @@ -60,7 +45,11 @@ public function actionProducts(): int private function _syncProducts(): void { $this->stdout('Syncing Shopify products…' . PHP_EOL . PHP_EOL, Console::FG_GREEN); - Plugin::getInstance()->getProducts()->syncAllProducts($this->async); - $this->stdout('Finished' . PHP_EOL . PHP_EOL, Console::FG_GREEN); + // start timer + $start = microtime(true); + Plugin::getInstance()->getProducts()->syncAllProducts(); + // end timer + $time = microtime(true) - $start; + $this->stdout('Finished syncing ' . Product::find()->count() . ' product(s) in ' . round($time, 2) . 's' . PHP_EOL . PHP_EOL, Console::FG_GREEN); } } diff --git a/src/controllers/ProductsController.php b/src/controllers/ProductsController.php index 1234e33..6a71635 100644 --- a/src/controllers/ProductsController.php +++ b/src/controllers/ProductsController.php @@ -45,9 +45,7 @@ public function actionProductIndex(): Response */ public function actionSync(): Response { - $async = (bool)Craft::$app->getRequest()->getParam('async', false); - - Plugin::getInstance()->getProducts()->syncAllProducts($async); + Plugin::getInstance()->getProducts()->syncAllProducts(); return $this->asSuccess(Craft::t('shopify', 'Products successfully synced')); } diff --git a/src/helpers/Api.php b/src/helpers/Api.php deleted file mode 100644 index d35af24..0000000 --- a/src/helpers/Api.php +++ /dev/null @@ -1,31 +0,0 @@ - - * @since 3.3.0 - */ -class Api -{ - /** - * @return void - */ - public static function rateLimit(): void - { - if (!Plugin::getInstance()->getSettings()->rateLimitRequests) { - return; - } - - sleep(Plugin::getInstance()->getSettings()->rateLimitSeconds); - } -} diff --git a/src/jobs/UpdateProductMetadata.php b/src/jobs/UpdateProductMetadata.php index 71de108..e768e6e 100644 --- a/src/jobs/UpdateProductMetadata.php +++ b/src/jobs/UpdateProductMetadata.php @@ -4,13 +4,14 @@ use craft\queue\BaseJob; use craft\shopify\elements\Product; -use craft\shopify\helpers\Api as ApiHelper; use craft\shopify\helpers\Metafields as MetafieldsHelper; use craft\shopify\Plugin; use craft\shopify\records\ProductData as ProductDataRecord; /** * Updates the metadata for a Shopify product. + * + * @deprecated 4.0.0 No longer used internally due to the use of `Retry-After` headers in the Shopify API. */ class UpdateProductMetadata extends BaseJob { @@ -31,7 +32,6 @@ public function execute($queue): void $productData = ProductDataRecord::find()->where(['shopifyId' => $this->shopifyProductId])->one(); $productData->metaFields = $metaFields; $productData->save(); - ApiHelper::rateLimit(); // Avoid rate limiting } } diff --git a/src/jobs/UpdateProductVariants.php b/src/jobs/UpdateProductVariants.php deleted file mode 100644 index 75cd125..0000000 --- a/src/jobs/UpdateProductVariants.php +++ /dev/null @@ -1,47 +0,0 @@ -getApi(); - - /** @var Product|null $product */ - $product = Product::find()->shopifyId($this->shopifyProductId)->one(); - - if ($product) { - $variants = $api->getVariantsByProductId($this->shopifyProductId); - $product->setVariants($variants); - /** @var ProductDataRecord $productData */ - $productData = ProductDataRecord::find()->where(['shopifyId' => $this->shopifyProductId])->one(); - $productData->variants = $variants; - $productData->save(); - ApiHelper::rateLimit(); // Avoid rate limiting - } - } - - /** - * @inheritdoc - */ - protected function defaultDescription(): ?string - { - return null; - } -} diff --git a/src/models/Settings.php b/src/models/Settings.php index f5d2fc9..b032d91 100644 --- a/src/models/Settings.php +++ b/src/models/Settings.php @@ -28,23 +28,10 @@ class Settings extends Model public string $template = ''; private mixed $_productFieldLayout; - /** - * @var bool Whether to rate limit requests to Shopify - * @since 3.3.0 - */ - public bool $rateLimitRequests = true; - - /** - * @var int The number of seconds to wait between requests - * @since 3.3.0 - */ - public int $rateLimitSeconds = 1; - public function rules(): array { return [ [['apiSecretKey', 'apiKey', 'accessToken', 'hostName'], 'required'], - [['rateLimitSeconds', 'rateLimitRequests'], 'safe'], ]; } @@ -60,8 +47,6 @@ public function attributeLabels(): array 'hostName' => Craft::t('shopify', 'Shopify Host Name'), 'uriFormat' => Craft::t('shopify', 'Product URI format'), 'template' => Craft::t('shopify', 'Product Template'), - 'rateLimitRequests' => Craft::t('shopify', 'Rate Limit Requests'), - 'rateLimitSeconds' => Craft::t('shopify', 'Rate Limit Seconds'), ]; } diff --git a/src/services/Api.php b/src/services/Api.php index 43a56df..367d6bc 100644 --- a/src/services/Api.php +++ b/src/services/Api.php @@ -11,7 +11,6 @@ use craft\base\Component; use craft\helpers\App; use craft\log\MonologTarget; -use craft\shopify\helpers\Api as ApiHelper; use craft\shopify\Plugin; use Shopify\Auth\FileSessionStorage; use Shopify\Auth\Session; @@ -92,15 +91,19 @@ public function getProductIdByInventoryItemId($id): ?int * Retrieves "metafields" for the provided Shopify product ID. * * @param int $id Shopify Product ID + * @return ShopifyMetafield[] */ public function getMetafieldsByProductId(int $id): array { - return $this->getAll(ShopifyMetafield::class, [ + /** @var ShopifyMetafield[] $metafields */ + $metafields = $this->getAll(ShopifyMetafield::class, [ 'metafield' => [ 'owner_id' => $id, 'owner_resource' => 'product', ], ]); + + return $metafields; } /** @@ -147,7 +150,6 @@ public function getAll(string $type, array $params = []): array [], $type::$NEXT_PAGE_QUERY ?: $params, )); - ApiHelper::rateLimit(); // Avoid rate limiting } while ($type::$NEXT_PAGE_QUERY); return $resources; @@ -196,7 +198,7 @@ public function getSession(): ?Session sessionStorage: new FileSessionStorage(Craft::$app->getPath()->getStoragePath() . DIRECTORY_SEPARATOR . 'shopify_api_sessions'), apiVersion: self::SHOPIFY_API_VERSION, isEmbeddedApp: false, - logger: $webLogTarget->getLogger() + logger: $webLogTarget->getLogger(), ); $hostName = App::parseEnv($pluginSettings->hostName); diff --git a/src/services/Products.php b/src/services/Products.php index e3f8146..d3c9ffe 100644 --- a/src/services/Products.php +++ b/src/services/Products.php @@ -11,10 +11,7 @@ use craft\shopify\elements\Product; use craft\shopify\elements\Product as ProductElement; use craft\shopify\events\ShopifyProductSyncEvent; -use craft\shopify\helpers\Api as ApiHelper; use craft\shopify\helpers\Metafields as MetafieldsHelper; -use craft\shopify\jobs\UpdateProductMetadata; -use craft\shopify\jobs\UpdateProductVariants; use craft\shopify\Plugin; use craft\shopify\records\ProductData as ProductDataRecord; use Shopify\Rest\Admin2023_10\Metafield as ShopifyMetafield; @@ -61,32 +58,15 @@ class Products extends Component * @throws \Throwable * @throws \yii\base\InvalidConfigException */ - public function syncAllProducts(bool $asynchronous = true): void + public function syncAllProducts(): void { $api = Plugin::getInstance()->getApi(); $products = $api->getAllProducts(); foreach ($products as $product) { - if ($asynchronous) { - $this->createOrUpdateProduct($product); - Craft::$app->getQueue()->push(new UpdateProductMetadata([ - 'description' => Craft::t('shopify', 'Updating product metafields for “{title}”', [ - 'title' => $product->title, - ]), - 'shopifyProductId' => $product->id, - ])); - Craft::$app->getQueue()->push(new UpdateProductVariants([ - 'description' => Craft::t('shopify', 'Updating product variants for “{title}”', [ - 'title' => $product->title, - ]), - 'shopifyProductId' => $product->id, - ])); - } else { - $metaFields = $api->getMetafieldsByProductId($product->id); - ApiHelper::rateLimit(); - $variants = $api->getVariantsByProductId($product->id); - $this->createOrUpdateProduct($product, $metaFields, $variants); - } + $variants = $api->getVariantsByProductId($product->id); + $metafields = $api->getMetafieldsByProductId($product->id); + $this->createOrUpdateProduct($product, $metafields, $variants); } // Remove any products that are no longer in Shopify just in case. @@ -126,7 +106,8 @@ public function syncProductByInventoryItemId($id): void if ($productId = $api->getProductIdByInventoryItemId($id)) { $product = $api->getProductByShopifyId($productId); $metaFields = $api->getMetafieldsByProductId($product->id); - $this->createOrUpdateProduct($product, $metaFields); + $variants = $api->getVariantsByProductId($product->id); + $this->createOrUpdateProduct($product, $metaFields, $variants); } } @@ -161,7 +142,6 @@ public function createOrUpdateProduct(ShopifyProduct $product, array $metafields 'updatedAt' => $product->updated_at, 'variants' => $variants ?? $product->variants, 'vendor' => $product->vendor, - // This one is unusual, because we’re merging two different Shopify API resources: 'metaFields' => $metaFields, ]; diff --git a/src/templates/utilities/_sync.twig b/src/templates/utilities/_sync.twig index c3c41d0..527c7be 100644 --- a/src/templates/utilities/_sync.twig +++ b/src/templates/utilities/_sync.twig @@ -5,15 +5,6 @@ {{ actionInput('') }} {{ csrfInput() }} - {{ forms.lightswitchField({ - label: "Use queue for syncing related objects like meta fields."|t('shopify'), - warning: 'If disabled timeouts may occur, or API rate limits may be hit.'|t('commerce'), - id: 'async', - name: 'async', - value: 1, - on: true - }) }} - diff --git a/src/translations/en/shopify.php b/src/translations/en/shopify.php index 3f4cd6b..55d5e2c 100644 --- a/src/translations/en/shopify.php +++ b/src/translations/en/shopify.php @@ -16,8 +16,6 @@ return [ 'Product Template' => 'Product Template', 'Product URI format' => 'Product URI format', - 'Rate Limit Requests' => 'Rate Limit Requests', - 'Rate Limit Seconds' => 'Rate Limit Seconds', 'Shopify API Key' => 'Shopify API Key', 'Shopify API Secret Key' => 'Shopify API Secret Key', 'Shopify Access Token' => 'Shopify Access Token',