Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources/lang/en/messages.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
'option_title_price_nostock' => ':title - :price (Out of Stock)',
'option_title_nostock' => ':title (Out of Stock)',
'out_of_stock' => 'Out of Stock',
'reimport_product' => 'Reimport from Shopify',
];
48 changes: 48 additions & 0 deletions src/Actions/ReimportProduct.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace StatamicRadPack\Shopify\Actions;

use Statamic\Actions\Action;
use Statamic\Contracts\Entries\Entry;
use StatamicRadPack\Shopify\Jobs\ImportSingleProductJob;
use StatamicRadPack\Shopify\Support\StoreConfig;

class ReimportProduct extends Action
{
public string $icon = 'arrow-down';

public static function title()
{
return __('shopify::messages.reimport_product');
}

public function visibleTo($item)
{
return $this->context['view'] === 'form'
&& $item instanceof Entry
&& $item->collectionHandle() === config('shopify.collection_handle', 'products');
}

public function visibleToBulk($items)
{
return false;
}

public function authorize($user, $item)
{
return $user->can('access shopify');
}

public function run($entries, $values)
{
$entries->each(function (Entry $entry) {
$storeHandle = null;

if (StoreConfig::isMultiStore() && StoreConfig::getMode() === 'localized') {
$storeHandle = StoreConfig::findBySite($entry->locale())['handle'] ?? null;
}

ImportSingleProductJob::dispatch((int) $entry->get('product_id'), [], $storeHandle);
});
}
}
4 changes: 4 additions & 0 deletions src/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ class ServiceProvider extends AddonServiceProvider
Commands\ShopifyWebhooksRegister::class,
];

protected $actions = [
Actions\ReimportProduct::class,
];

protected $fieldtypes = [
Fieldtypes\Variants::class,
Fieldtypes\DisabledText::class,
Expand Down
13 changes: 13 additions & 0 deletions src/Support/StoreConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ public static function findByHandle(string $handle): ?array
return array_merge(['handle' => $handle], $stores[$handle]);
}

public static function findBySite(string $siteHandle): ?array
{
$stores = config('shopify.multi_store.stores', []);

foreach ($stores as $handle => $store) {
if (($store['site'] ?? null) === $siteHandle) {
return array_merge(['handle' => $handle], $store);
}
}

return null;
}

public static function findByDomain(string $domain): ?array
{
$stores = config('shopify.multi_store.stores', []);
Expand Down
163 changes: 163 additions & 0 deletions tests/Unit/ReimportProductActionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php

namespace StatamicRadPack\Shopify\Tests\Unit;

use Illuminate\Support\Facades\Queue;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Facades;
use StatamicRadPack\Shopify\Actions\ReimportProduct;
use StatamicRadPack\Shopify\Jobs\ImportSingleProductJob;
use StatamicRadPack\Shopify\Tests\TestCase;

class ReimportProductActionTest extends TestCase
{
private function makeProductEntry(int $productId = 123, string $locale = 'default')
{
$entry = Facades\Entry::make()
->collection(config('shopify.collection_handle', 'products'))
->locale($locale)
->slug('test-product')
->data(['product_id' => $productId]);

$entry->save();

return $entry;
}

private function makeVariantEntry()
{
$entry = Facades\Entry::make()
->collection('variants')
->slug('test-variant')
->data(['product_id' => 999]);

$entry->save();

return $entry;
}

private function actionWithContext(string $view = 'form'): ReimportProduct
{
return (new ReimportProduct)->context(['view' => $view]);
}

#[Test]
public function is_visible_on_product_entry_form_view()
{
$entry = $this->makeProductEntry();

$this->assertTrue($this->actionWithContext('form')->visibleTo($entry));
}

#[Test]
public function is_not_visible_on_list_view()
{
$entry = $this->makeProductEntry();

$this->assertFalse($this->actionWithContext('list')->visibleTo($entry));
}

#[Test]
public function is_not_visible_on_non_product_entry()
{
$entry = $this->makeVariantEntry();

$this->assertFalse($this->actionWithContext('form')->visibleTo($entry));
}

#[Test]
public function is_not_visible_in_bulk()
{
$this->assertFalse($this->actionWithContext()->visibleToBulk(collect()));
}

#[Test]
public function dispatches_import_job_with_product_id()
{
Queue::fake();

$entry = $this->makeProductEntry(productId: 456);

$this->actionWithContext()->run(collect([$entry]), []);

Queue::assertPushed(ImportSingleProductJob::class, function ($job) {
return $job->productId === 456 && $job->storeHandle === null;
});
}

#[Test]
public function dispatches_job_without_store_handle_in_single_store_mode()
{
Queue::fake();

config(['shopify.multi_store.enabled' => false]);

$entry = $this->makeProductEntry(productId: 789);

$this->actionWithContext()->run(collect([$entry]), []);

Queue::assertPushed(ImportSingleProductJob::class, function ($job) {
return $job->productId === 789 && $job->storeHandle === null;
});
}

#[Test]
public function dispatches_job_with_store_handle_in_localized_multi_store_mode()
{
Queue::fake();

Facades\Site::setSites([
'en' => ['url' => '/', 'locale' => 'en_US'],
'fr' => ['url' => '/fr/', 'locale' => 'fr_FR'],
]);

Facades\Collection::find(config('shopify.collection_handle', 'products'))->sites(['en', 'fr'])->save();

config(['shopify.multi_store' => [
'enabled' => true,
'mode' => 'localized',
'primary_store' => 'uk',
'stores' => [
'uk' => ['url' => 'uk.myshopify.com', 'admin_token' => 'tok', 'site' => 'en'],
'fr' => ['url' => 'fr.myshopify.com', 'admin_token' => 'tok', 'site' => 'fr'],
],
]]);

$entry = Facades\Entry::make()
->collection(config('shopify.collection_handle', 'products'))
->locale('fr')
->slug('test-product-fr')
->data(['product_id' => 101]);

$entry->save();

$this->actionWithContext()->run(collect([$entry]), []);

Queue::assertPushed(ImportSingleProductJob::class, function ($job) {
return $job->productId === 101 && $job->storeHandle === 'fr';
});
}

#[Test]
public function dispatches_job_without_store_handle_in_unified_multi_store_mode()
{
Queue::fake();

config(['shopify.multi_store' => [
'enabled' => true,
'mode' => 'unified',
'primary_store' => 'uk',
'stores' => [
'uk' => ['url' => 'uk.myshopify.com', 'admin_token' => 'tok', 'site' => 'en'],
],
]]);

$entry = $this->makeProductEntry(productId: 202);

$this->actionWithContext()->run(collect([$entry]), []);

Queue::assertPushed(ImportSingleProductJob::class, function ($job) {
return $job->productId === 202 && $job->storeHandle === null;
});
}
}
Loading