Skip to content

Commit

Permalink
Merge branch 'v4' into develop
Browse files Browse the repository at this point in the history
# Conflicts:
#	CHANGELOG.md
#	README.md
  • Loading branch information
nfourtythree committed Apr 3, 2024
2 parents ec8b257 + 60637b7 commit e99fd5c
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 22 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

- Shopify now requires Craft CMS 5.0.0-beta.10 or later.

## 4.1.0 - 2024-04-03

- Added support for syncing variant meta fields. ([#99](https://github.com/craftcms/shopify/issues/99))
- Added the `syncProductMetafields` and `syncVariantMetafields` config settings, which can be enabled to sync meta fields.
- Added `craft\shopify\models\Settings::$syncProductMetafields`.
- Added `craft\shopify\models\Settings::$syncVariantMetafields`.

## 4.0.0 - 2023-11-02

> [!IMPORTANT]
Expand Down
43 changes: 32 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,16 @@ Products from your Shopify store are represented in Craft as product [elements](

### Synchronization

Once the plugin has been configured, you can perform an initial synchronization of all products via the **Shopify Sync** utility.
Once the plugin has been configured, you can perform an initial synchronization of all products via the command line.

> [!NOTE]
> Larger stores with 100+ products should perform the initial synchronization via the command line instead:
>
> ```sh
> php craft shopify/sync/products
> ```
```sh
php craft shopify/sync/products
```

The [`syncProductMetafields` and `syncVariantMetafields` settings](#settings) govern what data is synchronized via this process. Going forward, your products will be automatically kept in sync via [webhooks](#set-up-webhooks).

Going forward, your products will be automatically kept in sync via [webhooks](#set-up-webhooks).
> [!NOTE]
> Smaller stores with only a few products can perform synchronization via the **Shopify Sync** utility.

### Native Attributes

Expand Down Expand Up @@ -422,7 +422,8 @@ You can get an array of variant objects for a product by calling [`product.getVa
Unlike products, variants in Craft…
- …are represented exactly as [the API](https://shopify.dev/api/admin-rest/2023-10/resources/product-variant#resource-object) returns them;
- …are represented as [the API](https://shopify.dev/api/admin-rest/2023-10/resources/product-variant#resource-object) returns them;
- …the `metafields` property is accessible in addition to the API’s returned properties;
- …use Shopify’s convention of underscores in property names instead of exposing [camel-cased equivalents](#native-attributes);
- …are plain associative arrays;
- …have no methods of their own;
Expand All @@ -436,7 +437,9 @@ Once you have a reference to a variant, you can output its properties:
```
> **Note**
> The built-in [`currency`](https://craftcms.com/docs/5.x/dev/filters.html#currency) Twig filter is a great way to format money values.
> The built-in [`currency`](https://craftcms.com/docs/4.x/dev/filters.html#currency) Twig filter is a great way to format money values.
>
> The `metafields` property will only be populated if the `syncVariantMetafields` setting is enabled.
### Using Options
Expand Down Expand Up @@ -707,6 +710,24 @@ Relationships defined with the _Shopify Products_ field use stable element IDs u
## Going Further
### Settings
The following settings can be controlled by creating a `shopify.php` file in your `config/` directory.
| Setting | Type | Default | Description |
|-------------------------|--------|---------|-------------|
| `apiKey` | `string` | — | Shopify API key. |
| `apiSecretKey` | `string` | — | Shopify API secret key. |
| `accessToken` | `string` | — | Shopify API access token. |
| `hostName` | `string` | — | Shopify [host name](#store-hostname). |
| `uriFormat` | `string` | — | Product element URI format. |
| `template` | `string` | — | Product element template path. |
| `syncProductMetafields` | `bool` | `true` | Whether product metafields should be included when syncing products. This adds an extra API request per product. |
| `syncVariantMetafields` | `bool` | `false` | Whether variant metafields should be included when syncing products. This adds an extra API request per variant. |
> [!NOTE]
> Setting `apiKey`, `apiSecretKey`, `accessToken`, and `hostName` via `shopify.php` will override Project Config values set via the control panel during [app setup](#create-a-shopify-app). You can still reference environment values from the config file with `craft\helpers\App::env()`.
### Events
#### `craft\shopify\services\Products::EVENT_BEFORE_SYNCHRONIZE_PRODUCT`
Expand Down Expand Up @@ -777,4 +798,4 @@ return [
## Rate Limiting
The Shopify API implements [rate limiting rules](https://shopify.dev/docs/api/usage/rate-limits) the plugin makes its best effort to avoid hitting these limits.
The Shopify API implements [rate limiting rules](https://shopify.dev/docs/api/usage/rate-limits) the plugin makes its best effort to avoid hitting these limits.
2 changes: 1 addition & 1 deletion src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class Plugin extends BasePlugin
/**
* @var string
*/
public string $schemaVersion = '4.0.6'; // For some reason the 2.2+ version of the plugin was at 4.0 schema version
public string $schemaVersion = '4.0.7';

/**
* @inheritdoc
Expand Down
1 change: 1 addition & 0 deletions src/jobs/UpdateProductMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
/**
* Updates the metadata for a Shopify product.
*
* @TODO remove in next major version
* @deprecated 4.0.0 No longer used internally due to the use of `Retry-After` headers in the Shopify API.
*/
class UpdateProductMetadata extends BaseJob
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace craft\shopify\migrations;

use craft\db\Migration;
use craft\db\Query;

/**
* m240402_105857_add_metafields_property_to_variants migration.
*/
class m240402_105857_add_metafields_property_to_variants extends Migration
{
/**
* @inheritdoc
*/
public function safeUp(): bool
{
$productRows = (new Query())
->select(['shopifyId', 'variants'])
->from('{{%shopify_productdata}}')
->all();

foreach ($productRows as $product) {
$variants = json_decode($product['variants'], true);
foreach ($variants as &$variant) {
if (isset($variant['metafields'])) {
continue;
}

$variant['metafields'] = [];
}

$this->update('{{%shopify_productdata}}', ['variants' => json_encode($variants)], ['shopifyId' => $product['shopifyId']]);
}

return true;
}

/**
* @inheritdoc
*/
public function safeDown(): bool
{
echo "m240402_105857_add_metafields_property_to_variants cannot be reverted.\n";
return false;
}
}
16 changes: 16 additions & 0 deletions src/models/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@ class Settings extends Model
public string $template = '';
private mixed $_productFieldLayout;

/**
* Whether product metafields should be included when syncing products. This adds an extra API request per product.
*
* @var bool
* @since 4.1.0
*/
public bool $syncProductMetafields = true;

/**
* Whether variant metafields should be included when syncing products. This adds an extra API request per variant.
*
* @var bool
* @since 4.1.0
*/
public bool $syncVariantMetafields = false;

public function rules(): array
{
return [
Expand Down
31 changes: 30 additions & 1 deletion src/services/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,41 @@ public function getProductIdByInventoryItemId($id): ?int
* @return ShopifyMetafield[]
*/
public function getMetafieldsByProductId(int $id): array
{
if (!Plugin::getInstance()->getSettings()->syncProductMetafields) {
return [];
}

return $this->getMetafieldsByIdAndOwnerResource($id, 'product');
}

/**
* @param int $id
* @return ShopifyMetafield[]
* @since 4.1.0
*/
public function getMetafieldsByVariantId(int $id): array
{
if (!Plugin::getInstance()->getSettings()->syncVariantMetafields) {
return [];
}

return $this->getMetafieldsByIdAndOwnerResource($id, 'variants');
}

/**
* @param int $id
* @param string $ownerResource
* @return ShopifyMetafield[]
* @since 4.1.0
*/
public function getMetafieldsByIdAndOwnerResource(int $id, string $ownerResource): array
{
/** @var ShopifyMetafield[] $metafields */
$metafields = $this->getAll(ShopifyMetafield::class, [
'metafield' => [
'owner_id' => $id,
'owner_resource' => 'product',
'owner_resource' => $ownerResource,
],
]);

Expand Down
34 changes: 25 additions & 9 deletions src/services/Products.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,27 @@ class Products extends Component
*/
public const EVENT_BEFORE_SYNCHRONIZE_PRODUCT = 'beforeSynchronizeProduct';

/**
* @param ShopifyProduct $product
* @return void
* @throws \yii\base\InvalidConfigException
* @since 4.1.0
*/
private function _updateProduct(ShopifyProduct $product): void
{
$api = Plugin::getInstance()->getApi();

$variants = $api->getVariantsByProductId($product->id);
$productMetafields = $api->getMetafieldsByProductId($product->id);

foreach ($variants as &$variant) {
$variantMetafields = $api->getMetafieldsByVariantId($variant['id']);
$variant['metafields'] = $variantMetafields;
}

$this->createOrUpdateProduct($product, $productMetafields, $variants);
}

/**
* @return void
* @throws \Throwable
Expand All @@ -64,9 +85,7 @@ public function syncAllProducts(): void
$products = $api->getAllProducts();

foreach ($products as $product) {
$variants = $api->getVariantsByProductId($product->id);
$metafields = $api->getMetafieldsByProductId($product->id);
$this->createOrUpdateProduct($product, $metafields, $variants);
$this->_updateProduct($product);
}

// Remove any products that are no longer in Shopify just in case.
Expand All @@ -88,10 +107,8 @@ public function syncProductByShopifyId($id): void
$api = Plugin::getInstance()->getApi();

$product = $api->getProductByShopifyId($id);
$metaFields = $api->getMetafieldsByProductId($id);
$variants = $api->getVariantsByProductId($id);

$this->createOrUpdateProduct($product, $metaFields, $variants);
$this->_updateProduct($product);
}

/**
Expand All @@ -105,9 +122,8 @@ public function syncProductByInventoryItemId($id): void

if ($productId = $api->getProductIdByInventoryItemId($id)) {
$product = $api->getProductByShopifyId($productId);
$metaFields = $api->getMetafieldsByProductId($product->id);
$variants = $api->getVariantsByProductId($product->id);
$this->createOrUpdateProduct($product, $metaFields, $variants);

$this->_updateProduct($product);
}
}

Expand Down

0 comments on commit e99fd5c

Please sign in to comment.