Skip to content

Commit dbbe793

Browse files
committed
Ensure any metafield data removed on Shopify is removed in Statamic
1 parent ad3788c commit dbbe793

2 files changed

Lines changed: 134 additions & 18 deletions

File tree

src/Jobs/ImportSingleProductJob.php

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -237,15 +237,7 @@ public function handle()
237237

238238
// meta fields
239239
try {
240-
$metafields = collect(Arr::get($this->data, 'metafields.edges', []))->map(fn ($metafield) => $metafield['node'] ?? [])->filter()->all();
241-
242-
if ($metafields) {
243-
$metafields = $this->parseMetafields($metafields, 'product');
244-
245-
if ($metafields) {
246-
$entry->merge($metafields);
247-
}
248-
}
240+
$this->syncMetafields($entry, Arr::get($this->data, 'metafields.edges', []), 'product');
249241
} catch (\Throwable $e) {
250242
Log::error('Could not retrieve metafields for product '.$this->data['id']);
251243
Log::error($e->getMessage());
@@ -619,15 +611,7 @@ private function importVariants(array $returnedVariants, string $product_slug, G
619611
$entry->merge($data);
620612

621613
try {
622-
$metafields = collect(Arr::get($variant, 'metafields.edges', []))->map(fn ($metafield) => $metafield['node'] ?? [])->filter()->all();
623-
624-
if ($metafields) {
625-
$metafields = $this->parseMetafields($metafields, 'product-variant');
626-
627-
if ($metafields) {
628-
$entry->merge($metafields);
629-
}
630-
}
614+
$this->syncMetafields($entry, Arr::get($variant, 'metafields.edges', []), 'product-variant');
631615
} catch (\Throwable $e) {
632616
Log::error('Could not retrieve metafields for variant '.$this->data['id']);
633617
}
@@ -717,6 +701,27 @@ public function failed(Throwable $exception): void
717701
ProductImportFailed::dispatch($this->productId, $this->storeHandle, $exception);
718702
}
719703

704+
/**
705+
* Sync metafields onto an entry, clearing any keys that were previously set
706+
* but are no longer returned by Shopify (i.e. the metafield was deleted).
707+
*/
708+
private function syncMetafields(\Statamic\Contracts\Entries\Entry $entry, array $edges, string $context): void
709+
{
710+
$raw = collect($edges)->map(fn ($m) => $m['node'] ?? [])->filter()->all();
711+
$parsed = $this->parseMetafields($raw, $context);
712+
713+
$previousKeys = $entry->get('shopify_metafield_keys', []);
714+
foreach (array_diff($previousKeys, array_keys($parsed)) as $staleKey) {
715+
$entry->set($staleKey, null);
716+
}
717+
718+
if ($parsed) {
719+
$entry->merge($parsed);
720+
}
721+
722+
$entry->set('shopify_metafield_keys', array_keys($parsed));
723+
}
724+
720725
/**
721726
* Update the purchase history for this item
722727
*/

tests/Unit/ImportSingleProductJobTest.php

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,117 @@ public function updates_changed_handle()
241241
$this->assertSame($entry->slug(), 'product-new');
242242
}
243243

244+
#[Test]
245+
public function clears_product_metafields_removed_in_shopify()
246+
{
247+
Facades\Collection::make(config('shopify.collection_handle', 'products'))->save();
248+
Facades\Taxonomy::make()->handle('collections')->save();
249+
Facades\Taxonomy::make()->handle('tags')->save();
250+
Facades\Taxonomy::make()->handle('type')->save();
251+
Facades\Taxonomy::make()->handle('vendor')->save();
252+
253+
$jsonWithMetafield = $this->getProductJson();
254+
$jsonWithoutMetafield = str_replace(
255+
'"edges": [
256+
{
257+
"node": {
258+
"id" : 1,
259+
"key": "some_metafield",
260+
"value": "this is a value"
261+
}
262+
}
263+
]',
264+
'"edges": []',
265+
$jsonWithMetafield
266+
);
267+
268+
$this->mock(Graphql::class, function (MockInterface $mock) use ($jsonWithMetafield, $jsonWithoutMetafield) {
269+
$mock
270+
->shouldReceive('query')
271+
->andReturn(
272+
new HttpResponse(status: 200, body: $jsonWithMetafield),
273+
new HttpResponse(status: 200, body: $jsonWithoutMetafield)
274+
);
275+
});
276+
277+
Jobs\ImportSingleProductJob::dispatch(1072481042);
278+
279+
$entry = Facades\Entry::whereCollection(config('shopify.collection_handle', 'products'))->first();
280+
$this->assertSame($entry->get('some_metafield'), 'this is a value');
281+
282+
Jobs\ImportSingleProductJob::dispatch(1072481042);
283+
284+
$entry = Facades\Entry::whereCollection(config('shopify.collection_handle', 'products'))->first();
285+
$this->assertNull($entry->get('some_metafield'));
286+
$this->assertSame([], $entry->get('shopify_metafield_keys'));
287+
}
288+
289+
#[Test]
290+
public function clears_variant_metafields_removed_in_shopify()
291+
{
292+
Facades\Collection::make(config('shopify.collection_handle', 'products'))->save();
293+
Facades\Taxonomy::make()->handle('collections')->save();
294+
Facades\Taxonomy::make()->handle('tags')->save();
295+
Facades\Taxonomy::make()->handle('type')->save();
296+
Facades\Taxonomy::make()->handle('vendor')->save();
297+
298+
$jsonWithVariantMetafield = str_replace(
299+
'"metafields": {
300+
"edges": []
301+
}',
302+
'"metafields": {
303+
"edges": [
304+
{
305+
"node": {
306+
"id": 2,
307+
"key": "variant_metafield",
308+
"value": "variant value"
309+
}
310+
}
311+
]
312+
}',
313+
$this->getProductJson()
314+
);
315+
316+
$jsonWithoutVariantMetafield = str_replace(
317+
'"metafields": {
318+
"edges": [
319+
{
320+
"node": {
321+
"id": 2,
322+
"key": "variant_metafield",
323+
"value": "variant value"
324+
}
325+
}
326+
]
327+
}',
328+
'"metafields": {
329+
"edges": []
330+
}',
331+
$jsonWithVariantMetafield
332+
);
333+
334+
$this->mock(Graphql::class, function (MockInterface $mock) use ($jsonWithVariantMetafield, $jsonWithoutVariantMetafield) {
335+
$mock
336+
->shouldReceive('query')
337+
->andReturn(
338+
new HttpResponse(status: 200, body: $jsonWithVariantMetafield),
339+
new HttpResponse(status: 200, body: $jsonWithoutVariantMetafield)
340+
);
341+
});
342+
343+
Jobs\ImportSingleProductJob::dispatch(1072481042);
344+
345+
$variant = Facades\Entry::whereCollection('variants')->first();
346+
$this->assertSame($variant->get('variant_metafield'), 'variant value');
347+
348+
Jobs\ImportSingleProductJob::dispatch(1072481042);
349+
350+
$variant = Facades\Entry::whereCollection('variants')->first();
351+
$this->assertNull($variant->get('variant_metafield'));
352+
$this->assertSame([], $variant->get('shopify_metafield_keys'));
353+
}
354+
244355
private function getProductJson(): string
245356
{
246357
return '{

0 commit comments

Comments
 (0)