|
1 | 1 | <?php
|
2 | 2 |
|
| 3 | +declare(strict_types=1); |
| 4 | + |
3 | 5 | namespace Osiset\ShopifyApp\Actions;
|
4 | 6 |
|
| 7 | +use Illuminate\Support\Facades\Cache; |
| 8 | +use Illuminate\Support\Str; |
5 | 9 | use Osiset\ShopifyApp\Contracts\Queries\Shop as IShopQuery;
|
| 10 | +use Osiset\ShopifyApp\Contracts\ShopModel; |
6 | 11 | use Osiset\ShopifyApp\Objects\Enums\ThemeSupportLevel;
|
7 | 12 | use Osiset\ShopifyApp\Objects\Values\ShopId;
|
8 |
| -use Osiset\ShopifyApp\Services\ThemeHelper; |
| 13 | +use Osiset\ShopifyApp\Util; |
9 | 14 |
|
10 |
| -/** |
11 |
| - * Activates a plan for a shop. |
12 |
| - */ |
13 |
| -class VerifyThemeSupport |
| 15 | +final class VerifyThemeSupport |
14 | 16 | {
|
15 |
| - /** |
16 |
| - * Querier for shops. |
17 |
| - * |
18 |
| - * @var IShopQuery |
19 |
| - */ |
20 |
| - protected $shopQuery; |
| 17 | + private const ASSET_FILE_NAMES = ['templates/product.json', 'templates/collection.json', 'templates/index.json']; |
21 | 18 |
|
22 |
| - /** |
23 |
| - * Theme helper. |
24 |
| - * |
25 |
| - * @var ThemeHelper |
26 |
| - */ |
27 |
| - protected $themeHelper; |
| 19 | + private const MAIN_ROLE = 'main'; |
| 20 | + |
| 21 | + private string $cacheInterval; |
| 22 | + |
| 23 | + private int $cacheDuration; |
28 | 24 |
|
29 |
| - /** |
30 |
| - * Setup. |
31 |
| - * |
32 |
| - * @param IShopQuery $shopQuery The querier for shops. |
33 |
| - * @param ThemeHelper $themeHelper Theme helper. |
34 |
| - * |
35 |
| - * @return void |
36 |
| - */ |
37 | 25 | public function __construct(
|
38 |
| - IShopQuery $shopQuery, |
39 |
| - ThemeHelper $themeHelper |
| 26 | + private IShopQuery $shopQuery, |
| 27 | + private FetchMainTheme $fetchMainTheme, |
| 28 | + private FetchThemeAssets $fetchThemeAssets, |
40 | 29 | ) {
|
41 |
| - $this->shopQuery = $shopQuery; |
42 |
| - $this->themeHelper = $themeHelper; |
| 30 | + $this->cacheInterval = (string) Str::of(Util::getShopifyConfig('theme_support.cache_interval')) |
| 31 | + ->plural() |
| 32 | + ->ucfirst() |
| 33 | + ->start('add'); |
| 34 | + |
| 35 | + $this->cacheDuration = (int) Util::getShopifyConfig('theme_support.cache_duration'); |
| 36 | + } |
| 37 | + |
| 38 | + public function __invoke(ShopId $shopId): int |
| 39 | + { |
| 40 | + $shop = $this->shopQuery->getById($shopId); |
| 41 | + |
| 42 | + /** @var array{id: string, name: string} */ |
| 43 | + $mainTheme = Cache::remember( |
| 44 | + "mainTheme.{$shop->getId()->toNative()}", |
| 45 | + now()->{$this->cacheInterval}($this->cacheDuration), |
| 46 | + fn () => $this->fetchMainTheme->handle($shop) |
| 47 | + ); |
| 48 | + |
| 49 | + if (isset($mainTheme['id'])) { |
| 50 | + /** @var array<int, array{filename: string, content: string}> */ |
| 51 | + $assets = Cache::remember( |
| 52 | + "assets.{$mainTheme['id']}.{$shop->getId()->toNative()}", |
| 53 | + now()->{$this->cacheInterval}($this->cacheDuration), |
| 54 | + fn () => $this->fetchThemeAssets->handle( |
| 55 | + shop: $shop, |
| 56 | + mainThemeId: $mainTheme['id'], |
| 57 | + filenames: self::ASSET_FILE_NAMES |
| 58 | + ) |
| 59 | + ); |
| 60 | + $templateMainSections = $this->mainSections( |
| 61 | + shop: $shop, |
| 62 | + mainTheme: $mainTheme, |
| 63 | + assets: $assets |
| 64 | + ); |
| 65 | + $sectionsWithAppBlock = $this->sectionsWithAppBlock($templateMainSections); |
| 66 | + |
| 67 | + $hasTemplates = count($assets) > 0; |
| 68 | + $allTemplatesHasRightType = count($assets) === count($sectionsWithAppBlock); |
| 69 | + $hasTemplatesCountWithRightType = count($sectionsWithAppBlock) > 0; |
| 70 | + |
| 71 | + return match (true) { |
| 72 | + $hasTemplates && $allTemplatesHasRightType => ThemeSupportLevel::FULL, |
| 73 | + $hasTemplatesCountWithRightType => ThemeSupportLevel::PARTIAL, |
| 74 | + default => ThemeSupportLevel::UNSUPPORTED |
| 75 | + }; |
| 76 | + } |
| 77 | + |
| 78 | + return ThemeSupportLevel::UNSUPPORTED; |
43 | 79 | }
|
44 | 80 |
|
45 | 81 | /**
|
46 |
| - * Execution. |
| 82 | + * @template T |
| 83 | + * @template Z |
47 | 84 | *
|
48 |
| - * @param ShopId $shopId The shop ID. |
49 |
| - * |
50 |
| - * @return int |
| 85 | + * @param Z $mainTheme |
| 86 | + * @param T $assets |
| 87 | + * @return T |
51 | 88 | */
|
52 |
| - public function __invoke(ShopId $shopId): int |
| 89 | + private function mainSections(ShopModel $shop, array $mainTheme, array $assets): array |
53 | 90 | {
|
54 |
| - $this->themeHelper->extractStoreMainTheme($shopId); |
| 91 | + $filenamesForMainSections = array_filter( |
| 92 | + array_map(function ($asset) { |
| 93 | + $assetContent = json_decode($asset['content'], true); |
55 | 94 |
|
56 |
| - if ($this->themeHelper->themeIsReady()) { |
57 |
| - $templateJSONFiles = $this->themeHelper->templateJSONFiles(); |
58 |
| - $templateMainSections = $this->themeHelper->mainSections($templateJSONFiles); |
59 |
| - $sectionsWithAppBlock = $this->themeHelper->sectionsWithAppBlock($templateMainSections); |
| 95 | + $mainAsset = array_filter($assetContent['sections'], function ($value, $key) { |
| 96 | + return $key == self::MAIN_ROLE || str_starts_with($value['type'], self::MAIN_ROLE); |
| 97 | + }, ARRAY_FILTER_USE_BOTH); |
60 | 98 |
|
61 |
| - $hasTemplates = count($templateJSONFiles) > 0; |
62 |
| - $allTemplatesHasRightType = count($templateJSONFiles) === count($sectionsWithAppBlock); |
63 |
| - $templatesСountWithRightType = count($sectionsWithAppBlock); |
| 99 | + if ($mainAsset) { |
| 100 | + return 'sections/' . end($mainAsset)['type'] . '.liquid'; |
| 101 | + } |
| 102 | + }, $assets) |
| 103 | + ); |
64 | 104 |
|
65 |
| - switch (true) { |
66 |
| - case $hasTemplates && $allTemplatesHasRightType: |
67 |
| - return ThemeSupportLevel::FULL; |
| 105 | + return Cache::remember( |
| 106 | + "mainSections.{$mainTheme['id']}." . sha1(implode('|', $filenamesForMainSections)), |
| 107 | + now()->{$this->cacheInterval}($this->cacheDuration), |
| 108 | + fn () => $this->fetchThemeAssets->handle( |
| 109 | + shop: $shop, |
| 110 | + mainThemeId: $mainTheme['id'], |
| 111 | + filenames: [...$filenamesForMainSections] |
| 112 | + ) |
| 113 | + ); |
| 114 | + } |
| 115 | + |
| 116 | + /** |
| 117 | + * @template T |
| 118 | + * |
| 119 | + * @param T $templateMainSections |
| 120 | + * @return T |
| 121 | + */ |
| 122 | + private function sectionsWithAppBlock(array $templateMainSections): array |
| 123 | + { |
| 124 | + return array_filter(array_map(function ($file) { |
| 125 | + $acceptsAppBlock = false; |
68 | 126 |
|
69 |
| - case $templatesСountWithRightType: |
70 |
| - return ThemeSupportLevel::PARTIAL; |
| 127 | + preg_match('/\{\%-?\s+schema\s+-?\%\}([\s\S]*?)\{\%-?\s+endschema\s+-?\%\}/m', $file['content'], $matches); |
| 128 | + $schema = json_decode($matches[1] ?? '{}', true); |
71 | 129 |
|
72 |
| - default: |
73 |
| - return ThemeSupportLevel::UNSUPPORTED; |
| 130 | + if ($schema && isset($schema['blocks'])) { |
| 131 | + $acceptsAppBlock = in_array('@app', array_column($schema['blocks'], 'type')); |
74 | 132 | }
|
75 |
| - } |
76 | 133 |
|
77 |
| - return ThemeSupportLevel::UNSUPPORTED; |
| 134 | + return $acceptsAppBlock ? $file : null; |
| 135 | + }, $templateMainSections)); |
78 | 136 | }
|
79 | 137 | }
|
0 commit comments