Skip to content

Commit 1ccd0d4

Browse files
authored
Merge pull request #10 from rocketweb/IP-314_store_support
IP-314 Expand the CMS import / export module to cover store blocks/pages
2 parents 3bda7c7 + eb1919b commit 1ccd0d4

File tree

3 files changed

+113
-25
lines changed

3 files changed

+113
-25
lines changed

Console/Command/ImportCmsData.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class ImportCmsData extends \Symfony\Component\Console\Command\Command
2828
private const INPUT_TYPE_VALUES = ['block', 'page', 'all'];
2929
private const INPUT_KEY_IDENTIFIER = 'identifier';
3030
private const INPUT_KEY_IMPORT_ALL = 'importAll';
31+
private const INPUT_KEY_STORE = 'store';
3132
private \RocketWeb\CmsImportExport\Model\Service\ImportCmsDataService $importCmsDataService;
3233

3334
public function __construct(
@@ -60,6 +61,12 @@ protected function configure()
6061
'a',
6162
InputOption::VALUE_NONE,
6263
'Flag to import all files'
64+
),
65+
new InputOption(
66+
self::INPUT_KEY_STORE,
67+
's',
68+
InputOption::VALUE_OPTIONAL,
69+
'Specific Store Code'
6370
)
6471
]);
6572
parent::configure();
@@ -88,7 +95,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
8895
$identifiers = explode(',', $identifiers);
8996
}
9097

91-
$this->importCmsDataService->execute($types, $identifiers, $importAll);
98+
$storeCode = empty($input->getOption(self::INPUT_KEY_STORE)) ?
99+
null :
100+
$input->getOption(self::INPUT_KEY_STORE);
101+
102+
$this->importCmsDataService->execute($types, $identifiers, $importAll, $storeCode);
92103

93104
return 0;
94105
}

Model/Service/ImportCmsDataService.php

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
namespace RocketWeb\CmsImportExport\Model\Service;
1919

20+
use Magento\Cms\Api\Data\BlockInterface;
21+
use Magento\Cms\Api\Data\PageInterface;
2022
use Magento\Framework\App\Filesystem\DirectoryList;
2123

2224
class ImportCmsDataService
@@ -39,7 +41,9 @@ public function __construct(
3941
\Magento\Framework\Filesystem\DirectoryList $directoryList,
4042
\Magento\Framework\Filesystem $filesystem,
4143
\Magento\Framework\Serialize\SerializerInterface $serializer,
42-
\Magento\Store\Api\StoreRepositoryInterface $storeRepository
44+
\Magento\Store\Api\StoreRepositoryInterface $storeRepository,
45+
private readonly \Magento\Cms\Api\GetBlockByIdentifierInterface $getBlockByIdentifier,
46+
private readonly \Magento\Cms\Api\GetPageByIdentifierInterface $getPageByIdentifier
4347
) {
4448
$this->pageRepository = $pageRepository;
4549
$this->blockRepository = $blockRepository;
@@ -51,7 +55,7 @@ public function __construct(
5155
$this->storeRepository = $storeRepository;
5256
}
5357

54-
public function execute(array $types, ?array $identifiers, bool $importAll)
58+
public function execute(array $types, ?array $identifiers, bool $importAll, ?string $storeCode = null)
5559
{
5660
$workingDirPath = 'sync_cms_data';
5761

@@ -70,9 +74,9 @@ public function execute(array $types, ?array $identifiers, bool $importAll)
7074
}
7175

7276
if ($type == 'block') {
73-
$this->importBlocks($typeDirPath, $identifiers);
77+
$this->importBlocks($typeDirPath, $identifiers, $storeCode);
7478
} else if ($type == 'page') {
75-
$this->importPages($typeDirPath, $identifiers);
79+
$this->importPages($typeDirPath, $identifiers, $storeCode);
7680
}
7781
}
7882
}
@@ -97,7 +101,7 @@ private function getStoreIds($storeCodes): array
97101
return $storeIds;
98102
}
99103

100-
private function importBlocks(string $dirPath, ?array $identifiers): void
104+
private function importBlocks(string $dirPath, ?array $identifiers, ?string $storeCode = null): void
101105
{
102106
$filePaths = $this->directoryRead->read($this->varPath . $dirPath);
103107
foreach ($filePaths as $filePath) {
@@ -112,12 +116,17 @@ private function importBlocks(string $dirPath, ?array $identifiers): void
112116
// If we have a list of items, we skip if its not in the list
113117
continue;
114118
}
115-
116-
try {
117-
$block = $this->blockRepository->getById($identifier);
118-
} catch (\Magento\Framework\Exception\NoSuchEntityException $exception) {
119-
$block = $this->blockFactory->create();
119+
if ($storeCode !== null && ($this->getStoreCode($filePath) !== $storeCode)) {
120+
// Skip identifiers not assigned to specific store when storeCode parameter is set
121+
echo sprintf(
122+
'Skipping identifier %s because requested update only for store %s %s',
123+
$identifier,
124+
$storeCode,
125+
PHP_EOL
126+
);
127+
continue;
120128
}
129+
121130
$content = $this->directoryRead->readFile($filePath);
122131
$jsonData = $this->directoryRead->readFile(str_replace('.html', '.json', $filePath));
123132
$jsonData = $this->serializer->unserialize($jsonData);
@@ -128,6 +137,13 @@ private function importBlocks(string $dirPath, ?array $identifiers): void
128137
'is_active' => $block->isActive()
129138
];*/
130139
$storeIds = $this->getStoreIds($jsonData['stores']);
140+
try {
141+
$block = $this->getBlockByIdentifier->execute($identifier, (int)reset($storeIds));
142+
$this->validateStoreAssociation($filePath, $block, $storeIds, 'Block');
143+
} catch (\Magento\Framework\Exception\NoSuchEntityException $exception) {
144+
$block = $this->blockFactory->create();
145+
}
146+
131147
$block->setTitle($jsonData['title']);
132148
$block->setContent($content);
133149
$block->setIdentifier($jsonData['identifier']);
@@ -145,7 +161,7 @@ private function importBlocks(string $dirPath, ?array $identifiers): void
145161
}
146162
}
147163

148-
private function importPages(string $dirPath, ?array $identifiers): void
164+
private function importPages(string $dirPath, ?array $identifiers, ?string $storeCode = null): void
149165
{
150166
$filePaths = $this->directoryRead->read($this->varPath . $dirPath);
151167
foreach ($filePaths as $filePath) {
@@ -155,23 +171,35 @@ private function importPages(string $dirPath, ?array $identifiers): void
155171
}
156172
$identifier = str_replace($dirPath, '', $filePath);
157173
$identifier = str_replace('.html', '', $identifier);
158-
$identifier = substr_replace($identifier, '', strpos($identifier, '---'));
174+
$identifier = substr_replace($identifier, '', strrpos($identifier, '---'));
159175
$identifier = str_replace('---', '/', $identifier);
160176
$identifier = str_replace('_html', '.html', $identifier);
161177
if ($identifiers !== null && !in_array($identifier, $identifiers)) {
162178
// If we have a list of items, we skip if its not in the list
163179
continue;
164180
}
165181

166-
try {
167-
$page = $this->pageRepository->getById($identifier);
168-
} catch (\Magento\Framework\Exception\NoSuchEntityException $exception) {
169-
$page = $this->pageFactory->create();
182+
if ($storeCode !== null && ($this->getStoreCode($filePath) !== $storeCode)) {
183+
// Skip identifiers not assigned to specific store when storeCode parameter is set
184+
echo sprintf(
185+
'Skipping identifier %s because requested update only for store %s %s',
186+
$identifier,
187+
$storeCode,
188+
PHP_EOL
189+
);
190+
continue;
170191
}
192+
171193
$content = $this->directoryRead->readFile($filePath);
172194
$jsonData = $this->directoryRead->readFile(str_replace('.html', '.json', $filePath));
173195
$jsonData = $this->serializer->unserialize($jsonData);
174196
$storeIds = $this->getStoreIds($jsonData['stores']);
197+
try {
198+
$page = $this->getPageByIdentifier->execute($identifier, (int)reset($storeIds));
199+
$this->validateStoreAssociation($filePath, $page, $storeIds, 'Page');
200+
} catch (\Magento\Framework\Exception\NoSuchEntityException $exception) {
201+
$page = $this->pageFactory->create();
202+
}
175203
/*$jsonContent = [
176204
'title' => $page->getTitle(),
177205
'is_active' => $page->isActive(),
@@ -193,8 +221,54 @@ private function importPages(string $dirPath, ?array $identifiers): void
193221
try {
194222
$this->pageRepository->save($page);
195223
} catch (\Exception $exception) {
196-
echo $exception->getMessage() . ' | Block ID: ' . $identifier . "\n";
224+
echo $exception->getMessage() . ' | Page ID: ' . $identifier . "\n";
225+
}
226+
}
227+
}
228+
229+
/**
230+
* We are validating here is store association is correct
231+
* string $filePath - HTML filename, may contain either store code or _all_
232+
* BlockInterface | PageInterface $entity - either block or page if already exists
233+
* array $storeIds - array of stores to associate from JSON file
234+
* string $entityType - either "block" or "page", for accurate messaging
235+
*
236+
* We load store by store code specified in $filePath
237+
* Further we validate it against the data we have in JSON and if currently existing block/page
238+
*/
239+
private function validateStoreAssociation(
240+
string $filePath,
241+
BlockInterface | PageInterface $entity,
242+
array $storeIds,
243+
string $entityType
244+
) : void {
245+
$exceptionMessage = sprintf('%s with path %s has inconsistent store data', $entityType, $filePath);
246+
if (count($storeIds) > 1) {
247+
throw new \LogicException($exceptionMessage);
248+
}
249+
$storeCode = $this->getStoreCode($filePath);
250+
$storeId = (int)reset($storeIds);
251+
$currentStoreIds = $entity->getStoreId();
252+
if ($storeCode === '_all_') {
253+
if ($storeId !== 0 || count($currentStoreIds) > 1 || (int)reset($currentStoreIds) !== 0) {
254+
throw new \LogicException($exceptionMessage);
197255
}
256+
return ;
257+
}
258+
$store = $this->storeRepository->get($storeId);
259+
if ($store->getCode() !== $storeCode) {
260+
throw new \LogicException($exceptionMessage);
261+
}
262+
263+
if (array_diff($currentStoreIds, $storeIds) !== []) {
264+
throw new \LogicException($exceptionMessage);
198265
}
199266
}
267+
268+
private function getStoreCode(string $filePath) : string
269+
{
270+
$storeCode = str_replace('.html', '', $filePath);
271+
$storeCode = substr($storeCode, strrpos($storeCode, '---') + 3);
272+
return $storeCode;
273+
}
200274
}

README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,18 +80,21 @@ Import cms pages/blocks from var/sync_cms_data
8080
Options:
8181
-t, --type=TYPE Which type are we importing - block/page/all
8282
-i, --identifier[=IDENTIFIER] identifier to process (one or CSV list)
83+
-a, --importAll Flag to import all files
84+
-s, --store[STORE_CODE] Store code to process only pages/blocks specific to this store
8385
```
8486

8587
This command works by using files in `var/sync_cms_data/cms/` path. As you can see from the options, we need to define:
86-
- type - which can be CMS block, CMS page or both - **required**
88+
- type - which can be CMS block or CMS page - **required**
8789
- identifier - either a CMS block or CMS page identifier - **optional**
88-
90+
There are optional parameters:
91+
- importAll - when identifiers not specified we'll import all blocks or pages
92+
- store - store code (like default) to import block(s)/pages(s) only for specific store
8993
With the combination of these two, we can **import**:
90-
- all CMS content (using --type=all)
91-
- all CMS pages (using --type=page)
92-
- all CMS blocks (using --type=block)
93-
- specific CMS page or pages (using --type=page --identifier=about-us.html,homepage-new)
94+
- all CMS pages (using --type=page and importAll)
95+
- all CMS blocks (using --type=block and importAll)
96+
- specific CMS page or pages (using --type=page --identifier=about-us,homepage-new)
9497
- specific CMS block or blocks (using --type=block --identifier=who-are-we,homepage-carousel)
95-
98+
- specific CMS page by store (using --type=page --identifier=about-us-default --store=default)
9699
Once you execute the command, the content will be created/updated in Magento Admin.
97100
By executing `php bin/magento cache:flush` you should be able to see the updated CMS content on frontend also!

0 commit comments

Comments
 (0)