diff --git a/Console/Command/ImportCmsData.php b/Console/Command/ImportCmsData.php index db412f6..030a3e9 100644 --- a/Console/Command/ImportCmsData.php +++ b/Console/Command/ImportCmsData.php @@ -28,6 +28,7 @@ class ImportCmsData extends \Symfony\Component\Console\Command\Command private const INPUT_TYPE_VALUES = ['block', 'page', 'all']; private const INPUT_KEY_IDENTIFIER = 'identifier'; private const INPUT_KEY_IMPORT_ALL = 'importAll'; + private const INPUT_KEY_STORE = 'store'; private \RocketWeb\CmsImportExport\Model\Service\ImportCmsDataService $importCmsDataService; public function __construct( @@ -60,6 +61,12 @@ protected function configure() 'a', InputOption::VALUE_NONE, 'Flag to import all files' + ), + new InputOption( + self::INPUT_KEY_STORE, + 's', + InputOption::VALUE_OPTIONAL, + 'Specific Store Code' ) ]); parent::configure(); @@ -88,7 +95,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int $identifiers = explode(',', $identifiers); } - $this->importCmsDataService->execute($types, $identifiers, $importAll); + $storeCode = empty($input->getOption(self::INPUT_KEY_STORE)) ? + null : + $input->getOption(self::INPUT_KEY_STORE); + + $this->importCmsDataService->execute($types, $identifiers, $importAll, $storeCode); return 0; } diff --git a/Model/Service/ImportCmsDataService.php b/Model/Service/ImportCmsDataService.php index 95c2419..311fb44 100644 --- a/Model/Service/ImportCmsDataService.php +++ b/Model/Service/ImportCmsDataService.php @@ -17,6 +17,8 @@ namespace RocketWeb\CmsImportExport\Model\Service; +use Magento\Cms\Api\Data\BlockInterface; +use Magento\Cms\Api\Data\PageInterface; use Magento\Framework\App\Filesystem\DirectoryList; class ImportCmsDataService @@ -39,7 +41,9 @@ public function __construct( \Magento\Framework\Filesystem\DirectoryList $directoryList, \Magento\Framework\Filesystem $filesystem, \Magento\Framework\Serialize\SerializerInterface $serializer, - \Magento\Store\Api\StoreRepositoryInterface $storeRepository + \Magento\Store\Api\StoreRepositoryInterface $storeRepository, + private readonly \Magento\Cms\Api\GetBlockByIdentifierInterface $getBlockByIdentifier, + private readonly \Magento\Cms\Api\GetPageByIdentifierInterface $getPageByIdentifier ) { $this->pageRepository = $pageRepository; $this->blockRepository = $blockRepository; @@ -51,7 +55,7 @@ public function __construct( $this->storeRepository = $storeRepository; } - public function execute(array $types, ?array $identifiers, bool $importAll) + public function execute(array $types, ?array $identifiers, bool $importAll, ?string $storeCode = null) { $workingDirPath = 'sync_cms_data'; @@ -70,9 +74,9 @@ public function execute(array $types, ?array $identifiers, bool $importAll) } if ($type == 'block') { - $this->importBlocks($typeDirPath, $identifiers); + $this->importBlocks($typeDirPath, $identifiers, $storeCode); } else if ($type == 'page') { - $this->importPages($typeDirPath, $identifiers); + $this->importPages($typeDirPath, $identifiers, $storeCode); } } } @@ -97,7 +101,7 @@ private function getStoreIds($storeCodes): array return $storeIds; } - private function importBlocks(string $dirPath, ?array $identifiers): void + private function importBlocks(string $dirPath, ?array $identifiers, ?string $storeCode = null): void { $filePaths = $this->directoryRead->read($this->varPath . $dirPath); foreach ($filePaths as $filePath) { @@ -112,12 +116,17 @@ private function importBlocks(string $dirPath, ?array $identifiers): void // If we have a list of items, we skip if its not in the list continue; } - - try { - $block = $this->blockRepository->getById($identifier); - } catch (\Magento\Framework\Exception\NoSuchEntityException $exception) { - $block = $this->blockFactory->create(); + if ($storeCode !== null && ($this->getStoreCode($filePath) !== $storeCode)) { + // Skip identifiers not assigned to specific store when storeCode parameter is set + echo sprintf( + 'Skipping identifier %s because requested update only for store %s %s', + $identifier, + $storeCode, + PHP_EOL + ); + continue; } + $content = $this->directoryRead->readFile($filePath); $jsonData = $this->directoryRead->readFile(str_replace('.html', '.json', $filePath)); $jsonData = $this->serializer->unserialize($jsonData); @@ -128,6 +137,13 @@ private function importBlocks(string $dirPath, ?array $identifiers): void 'is_active' => $block->isActive() ];*/ $storeIds = $this->getStoreIds($jsonData['stores']); + try { + $block = $this->getBlockByIdentifier->execute($identifier, (int)reset($storeIds)); + $this->validateStoreAssociation($filePath, $block, $storeIds, 'Block'); + } catch (\Magento\Framework\Exception\NoSuchEntityException $exception) { + $block = $this->blockFactory->create(); + } + $block->setTitle($jsonData['title']); $block->setContent($content); $block->setIdentifier($jsonData['identifier']); @@ -145,7 +161,7 @@ private function importBlocks(string $dirPath, ?array $identifiers): void } } - private function importPages(string $dirPath, ?array $identifiers): void + private function importPages(string $dirPath, ?array $identifiers, ?string $storeCode = null): void { $filePaths = $this->directoryRead->read($this->varPath . $dirPath); foreach ($filePaths as $filePath) { @@ -155,7 +171,7 @@ private function importPages(string $dirPath, ?array $identifiers): void } $identifier = str_replace($dirPath, '', $filePath); $identifier = str_replace('.html', '', $identifier); - $identifier = substr_replace($identifier, '', strpos($identifier, '---')); + $identifier = substr_replace($identifier, '', strrpos($identifier, '---')); $identifier = str_replace('---', '/', $identifier); $identifier = str_replace('_html', '.html', $identifier); if ($identifiers !== null && !in_array($identifier, $identifiers)) { @@ -163,15 +179,27 @@ private function importPages(string $dirPath, ?array $identifiers): void continue; } - try { - $page = $this->pageRepository->getById($identifier); - } catch (\Magento\Framework\Exception\NoSuchEntityException $exception) { - $page = $this->pageFactory->create(); + if ($storeCode !== null && ($this->getStoreCode($filePath) !== $storeCode)) { + // Skip identifiers not assigned to specific store when storeCode parameter is set + echo sprintf( + 'Skipping identifier %s because requested update only for store %s %s', + $identifier, + $storeCode, + PHP_EOL + ); + continue; } + $content = $this->directoryRead->readFile($filePath); $jsonData = $this->directoryRead->readFile(str_replace('.html', '.json', $filePath)); $jsonData = $this->serializer->unserialize($jsonData); $storeIds = $this->getStoreIds($jsonData['stores']); + try { + $page = $this->getPageByIdentifier->execute($identifier, (int)reset($storeIds)); + $this->validateStoreAssociation($filePath, $page, $storeIds, 'Page'); + } catch (\Magento\Framework\Exception\NoSuchEntityException $exception) { + $page = $this->pageFactory->create(); + } /*$jsonContent = [ 'title' => $page->getTitle(), 'is_active' => $page->isActive(), @@ -193,8 +221,54 @@ private function importPages(string $dirPath, ?array $identifiers): void try { $this->pageRepository->save($page); } catch (\Exception $exception) { - echo $exception->getMessage() . ' | Block ID: ' . $identifier . "\n"; + echo $exception->getMessage() . ' | Page ID: ' . $identifier . "\n"; + } + } + } + + /** + * We are validating here is store association is correct + * string $filePath - HTML filename, may contain either store code or _all_ + * BlockInterface | PageInterface $entity - either block or page if already exists + * array $storeIds - array of stores to associate from JSON file + * string $entityType - either "block" or "page", for accurate messaging + * + * We load store by store code specified in $filePath + * Further we validate it against the data we have in JSON and if currently existing block/page + */ + private function validateStoreAssociation( + string $filePath, + BlockInterface | PageInterface $entity, + array $storeIds, + string $entityType + ) : void { + $exceptionMessage = sprintf('%s with path %s has inconsistent store data', $entityType, $filePath); + if (count($storeIds) > 1) { + throw new \LogicException($exceptionMessage); + } + $storeCode = $this->getStoreCode($filePath); + $storeId = (int)reset($storeIds); + $currentStoreIds = $entity->getStoreId(); + if ($storeCode === '_all_') { + if ($storeId !== 0 || count($currentStoreIds) > 1 || (int)reset($currentStoreIds) !== 0) { + throw new \LogicException($exceptionMessage); } + return ; + } + $store = $this->storeRepository->get($storeId); + if ($store->getCode() !== $storeCode) { + throw new \LogicException($exceptionMessage); + } + + if (array_diff($currentStoreIds, $storeIds) !== []) { + throw new \LogicException($exceptionMessage); } } + + private function getStoreCode(string $filePath) : string + { + $storeCode = str_replace('.html', '', $filePath); + $storeCode = substr($storeCode, strrpos($storeCode, '---') + 3); + return $storeCode; + } } diff --git a/README.md b/README.md index 8da06b8..c487fd0 100644 --- a/README.md +++ b/README.md @@ -80,18 +80,21 @@ Import cms pages/blocks from var/sync_cms_data Options: -t, --type=TYPE Which type are we importing - block/page/all -i, --identifier[=IDENTIFIER] identifier to process (one or CSV list) +-a, --importAll Flag to import all files +-s, --store[STORE_CODE] Store code to process only pages/blocks specific to this store ``` This command works by using files in `var/sync_cms_data/cms/` path. As you can see from the options, we need to define: -- type - which can be CMS block, CMS page or both - **required** +- type - which can be CMS block or CMS page - **required** - identifier - either a CMS block or CMS page identifier - **optional** - +There are optional parameters: +- importAll - when identifiers not specified we'll import all blocks or pages +- store - store code (like default) to import block(s)/pages(s) only for specific store With the combination of these two, we can **import**: -- all CMS content (using --type=all) -- all CMS pages (using --type=page) -- all CMS blocks (using --type=block) -- specific CMS page or pages (using --type=page --identifier=about-us.html,homepage-new) +- all CMS pages (using --type=page and importAll) +- all CMS blocks (using --type=block and importAll) +- specific CMS page or pages (using --type=page --identifier=about-us,homepage-new) - specific CMS block or blocks (using --type=block --identifier=who-are-we,homepage-carousel) - +- specific CMS page by store (using --type=page --identifier=about-us-default --store=default) Once you execute the command, the content will be created/updated in Magento Admin. By executing `php bin/magento cache:flush` you should be able to see the updated CMS content on frontend also!