├── Api ├── CmsEntityConverterManagerInterface.php ├── CmsEntityGeneratorManagerInterface.php ├── ContentExportInterface.php ├── ContentImportInterface.php ├── ContentVersionManagementInterface.php ├── ContentVersionRepositoryInterface.php ├── Data │ └── ContentVersionInterface.php └── StoreManagementInterface.php ├── Block └── Adminhtml │ ├── Cms │ ├── Block │ │ └── Edit │ │ │ └── History.php │ ├── CmsAbstract.php │ └── Page │ │ └── Edit │ │ └── History.php │ └── Import │ └── Button │ └── Import.php ├── CHANGELOG.md ├── Console └── Command │ ├── CMSUpgradeCommand.php │ ├── ClearCMSHistoryCommand.php │ └── ImportContent.php ├── Controller └── Adminhtml │ ├── AbstractMassExport.php │ ├── Block │ └── MassExport.php │ ├── History │ ├── Apply.php │ └── View.php │ ├── Import │ ├── Import.php │ ├── Index.php │ └── Upload.php │ └── Page │ └── MassExport.php ├── Cron └── DeleteBackups.php ├── Exception └── InvalidXmlImportFilesException.php ├── File ├── FileManager.php └── FileManagerInterface.php ├── LICENSE.txt ├── Model ├── BackupManager.php ├── Config.php ├── Config │ ├── App │ │ └── FileResolver.php │ ├── Block │ │ ├── Converter.php │ │ ├── Reader.php │ │ └── SchemaLocator.php │ ├── ConverterAbstract.php │ ├── Cron │ │ └── SaveValue.php │ ├── Page │ │ ├── Converter.php │ │ ├── Reader.php │ │ └── SchemaLocator.php │ ├── ReaderAbstract.php │ └── SchemaLocatorAbstract.php ├── Content │ ├── Converter │ │ ├── AbstractConverter.php │ │ ├── Block │ │ │ └── Converter.php │ │ ├── CmsEntityConverterInterface.php │ │ ├── CmsEntityConverterManager.php │ │ └── Page │ │ │ └── Converter.php │ ├── Export.php │ ├── Generator │ │ ├── CmsEntityGeneratorInterface.php │ │ ├── CmsEntityGeneratorManager.php │ │ ├── Json │ │ │ └── Generator.php │ │ └── Xml │ │ │ └── Generator.php │ └── Import.php ├── ContentVersion.php ├── ContentVersionManagement.php ├── ContentVersionRepository.php ├── Dir │ └── Reader.php ├── EntityManagement.php ├── OptionSource │ ├── CmsMode.php │ ├── MediaMode.php │ ├── Methods.php │ └── Periods.php ├── ResourceModel │ ├── ContentVersion.php │ └── ContentVersion │ │ └── Collection.php ├── Service │ ├── ClearCMSHistory.php │ ├── GetCmsEntityItems.php │ └── GetContentVersions.php └── StoreManagement.php ├── Observer ├── CmsSaveBefore.php └── DeleteContentVersion.php ├── README.md ├── Setup └── Recurring.php ├── Test └── Unit │ ├── Block │ └── Adminhtml │ │ └── Cms │ │ └── CmsAbstractTest.php │ ├── Controller │ └── Adminhtml │ │ ├── Block │ │ └── MassExportTest.php │ │ ├── History │ │ └── ViewTest.php │ │ ├── Import │ │ ├── ImportTest.php │ │ ├── IndexTest.php │ │ └── UploadTest.php │ │ └── Page │ │ └── MassExportTest.php │ ├── Model │ ├── BackupManagerTest.php │ ├── Config │ │ ├── App │ │ │ └── FileResolverTest.php │ │ └── ConverterAbstractTest.php │ ├── ConfigTest.php │ ├── ContentVersionManagementTest.php │ ├── ContentVersionRepositoryTest.php │ ├── Service │ │ └── ClearCMSHistoryTest.php │ └── StoreManagementTest.php │ └── Observer │ └── CmsSaveBeforeTest.php ├── Ui └── DataProvider │ ├── HistoryView.php │ └── Import.php ├── composer.json ├── etc ├── acl.xml ├── adminhtml │ ├── events.xml │ ├── menu.xml │ ├── routes.xml │ └── system.xml ├── cms_block_data.xsd ├── cms_page_data.xsd ├── config.xml ├── crontab.xml ├── db_schema.xml ├── di.xml ├── module.xml └── od_cms │ ├── cms_block_data.xml.sample │ ├── cms_page_data.xml.sample │ └── cms_page_data_no-route.xml.sample ├── registration.php └── view └── adminhtml ├── layout ├── cmscontent_history_view.xml └── cmscontent_import_index.xml ├── templates ├── cms │ └── backup │ │ └── history.phtml └── history │ └── view.phtml └── ui_component ├── cms_block_form.xml ├── cms_block_listing.xml ├── cms_page_form.xml ├── cms_page_listing.xml ├── cmscontent_import_form.xml └── overdose_cmscontent_historyview_form.xml /Api/CmsEntityConverterManagerInterface.php: -------------------------------------------------------------------------------- 1 | pageRepository = $pageRepository; 61 | $this->backupManager = $backupManager; 62 | $this->blockRepository = $blockRepository; 63 | $this->backendUrl = $backendUrl; 64 | } 65 | 66 | /** 67 | * Prepare backups list 68 | * 69 | * @return array 70 | * @throws LocalizedException 71 | */ 72 | public function getBackups(): array 73 | { 74 | $id = $this->getRequest()->getParam($this->urlParamId); 75 | $cmsObject = $this->getCmsObject($id); 76 | 77 | return $this->backupManager->getBackupsByCmsEntity($this->bcType, $cmsObject); 78 | } 79 | 80 | /** 81 | * Retrieve url for viewing of backup content 82 | * 83 | * @param $backup 84 | * @return string 85 | */ 86 | public function getBackupUrl($backup) 87 | { 88 | return $this->backendUrl->getUrl( 89 | 'cmscontent/history/view', 90 | [ 91 | 'bc_type' => $this->bcType, 92 | 'bc_identifier' => $backup['identifier'], 93 | 'item' => $backup['name'], 94 | 'store_id' => $backup['store_id'] 95 | ] 96 | ); 97 | } 98 | 99 | /** 100 | * Retrieve url for viewing of backup content 101 | * 102 | * @param $backup 103 | * @return string 104 | */ 105 | public function getBackupApplyUrl($backup) 106 | { 107 | return $this->backendUrl->getUrl( 108 | 'cmscontent/history/apply', 109 | [ 110 | 'item_id' => $this->getRequest()->getParam($this->urlParamId), 111 | 'bc_type' => $this->bcType, 112 | 'bc_identifier' => $backup['identifier'], 113 | 'item' => $backup['name'], 114 | 'store_id' => $backup['store_id'] 115 | ] 116 | ); 117 | } 118 | 119 | /** 120 | * Retrieve cms block or cms page by identifier 121 | * 122 | * @param $id 123 | * 124 | * @return BlockInterface|PageInterface|null 125 | */ 126 | public function getCmsObject($id) 127 | { 128 | try { 129 | if ($this->bcType == BackupManager::TYPE_CMS_BLOCK) { 130 | return $this->blockRepository->getById($id); 131 | } else if ($this->bcType == BackupManager::TYPE_CMS_PAGE) { 132 | return $this->pageRepository->getById($id); 133 | } 134 | 135 | return null; 136 | } catch (LocalizedException $e) { 137 | $this->_logger->critical(__('Something went wrong while getting cms block or page %1', $id)); 138 | } catch (\Exception $e) { 139 | $this->_logger->critical(__('Something went wrong with block or page %1', $id)); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Block/Adminhtml/Cms/Page/Edit/History.php: -------------------------------------------------------------------------------- 1 | __('Import'), 18 | 'class' => 'action-secondary', 19 | 'sort_order' => 20, 20 | 'on_click' => '', 21 | 'data_attribute' => [ 22 | 'mage-init' => [ 23 | 'buttonAdapter' => [ 24 | 'actions' => [ 25 | [ 26 | 'targetName' => 'cmscontent_import_form.cmscontent_import_form', 27 | 'actionName' => 'save', 28 | 'params' => [ 29 | false 30 | ] 31 | ] 32 | ] 33 | ] 34 | ] 35 | ], 36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [2.0.5] - 25-04-2024 8 | ### Fix 9 | - Fix return type for console commands. 10 | - Fix rivate variable from parent class (`Model\Dir\Reader`). 11 | 12 | ## [2.0.4] - 17-07-2023 13 | ### Fix 14 | - Fix the issue with the argument type in `Console\Command\ImportContent`. 15 | 16 | ## [2.0.3] - 09-01-2023 17 | ### Changed 18 | - Use the Core module for configuration tab. 19 | 20 | ## [2.0.2] - 31-10-2022 21 | ### Added 22 | - Unit tests for Block, Model and Controller classes. 23 | 24 | ### Changed 25 | - Made minor change in code to fix php8.1 compatibility issue. 26 | - Updated return type of function. 27 | 28 | ### Removed 29 | - Removed leftover docblock params for a function.q 30 | 31 | ## [2.0.1] - 30-10-2022 32 | ### Added 33 | - Backup preview now show title and identifier. 34 | - Added Apply button with confirm popup to recover page builder content and save block/page content. 35 | ### Changed 36 | - Backup preview now in json format. 37 | - Updated notes for "Clear history" feature. (Make it more clear). 38 | - Enable module by default. 39 | - Some BE refactoring. 40 | ### Removed 41 | - Removed menu item "View history" and config for this. 42 | 43 | ## [2.0.0] - 26-06-2022 44 | ### Added 45 | - Feature "clear history" 46 | -- Automatic deleting an old history files by crone 47 | -- CLI command for clearing history files 48 | - Feature "separate files support" (you can import/export to/from separate xml/json files). 49 | - Simple Unit Test for some thin places. 50 | 51 | ### Changed 52 | - Refactored code. 53 | - Position of system configs. 54 | - Import form moved to UI instead of direct blocks. 55 | - Support store ID. 56 | 57 | ### Fixed 58 | - Using system configs. 59 | 60 | ## [1.1.0] - 10-06-2022 61 | ### Added 62 | - Possibility to import/export CMS pages or blocks. 63 | - Automatic deleting an old history backups. 64 | - Refactored code. 65 | 66 | -------------------------------------------------------------------------------- /Console/Command/CMSUpgradeCommand.php: -------------------------------------------------------------------------------- 1 | contentVersionManagement = $contentVersionManagement; 42 | } 43 | 44 | /** 45 | * @inheritdoc 46 | */ 47 | protected function configure() 48 | { 49 | $this->setName('od:cms:upgrade') 50 | ->setDescription('CMS page/blocks upgrade configuration') 51 | ->setDefinition([ 52 | new InputOption( 53 | 'type', 54 | '-t', 55 | InputOption::VALUE_REQUIRED, 56 | 'CMS-type to upgrade: [block|blocks|page|pages]' 57 | ), 58 | new InputOption( 59 | 'identifier', 60 | '-i', 61 | InputOption::VALUE_REQUIRED, 62 | 'Comma-separated Identifiers of Block/Page to upgrade' 63 | ), 64 | 65 | ]); 66 | 67 | parent::configure(); 68 | } 69 | 70 | /** 71 | * @inheritdoc 72 | */ 73 | protected function execute(InputInterface $input, OutputInterface $output) 74 | { 75 | $identifiers = []; 76 | $cmsIdentifier = $input->getOption(self::OPTION_CMS_IDENTIFIER); 77 | if (!empty($cmsIdentifier)) { 78 | $identifiers = explode(',', $cmsIdentifier); 79 | } 80 | 81 | $cmsType = $input->getOption(self::OPTION_CMS_TYPE); 82 | if (empty($cmsType) && !empty($cmsIdentifier)) { 83 | throw new \InvalidArgumentException('CMS Entity type OPTION [--type] missed'); 84 | } 85 | 86 | if (!empty($cmsType)) { 87 | switch ($cmsType) { 88 | case 'block': 89 | case 'blocks': 90 | $this->contentVersionManagement->processBlocks($identifiers); 91 | break; 92 | case 'page': 93 | case 'pages': 94 | $this->contentVersionManagement->processPages($identifiers); 95 | break; 96 | default: 97 | throw new \InvalidArgumentException($cmsType . ' is incorrect CMS entity type'); 98 | } 99 | } else { 100 | $this->contentVersionManagement->processAll(); 101 | } 102 | $output->writeln('Upgrade Completed!'); 103 | 104 | return 0; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Console/Command/ClearCMSHistoryCommand.php: -------------------------------------------------------------------------------- 1 | clearCMSHistory = $clearCMSHistory; 31 | } 32 | 33 | /** 34 | * @inheritdoc 35 | */ 36 | protected function configure(): void 37 | { 38 | $this->setName('od:cms:history-clear') 39 | ->setDescription('Delete old CMS history files.') 40 | ->setDefinition([ 41 | new InputOption( 42 | 'type', 43 | '-t', 44 | InputOption::VALUE_REQUIRED, 45 | 'CMS-type to upgrade: [block|blocks|page|pages]' 46 | ) 47 | ]); 48 | 49 | parent::configure(); 50 | } 51 | 52 | /** 53 | * @inheritdoc 54 | */ 55 | protected function execute(InputInterface $input, OutputInterface $output) 56 | { 57 | $cmsType = $input->getOption('type'); 58 | if (empty($cmsType)) { 59 | $output->writeln('CMS Entity type OPTION [--type] missed!'); 60 | return 0; 61 | } 62 | 63 | try { 64 | switch ($cmsType) { 65 | case 'block': 66 | case 'blocks': 67 | $count = $this->clearCMSHistory->execute(Config::TYPE_BLOCK); 68 | break; 69 | case 'page': 70 | case 'pages': 71 | $count = $this->clearCMSHistory->execute(Config::TYPE_PAGE); 72 | break; 73 | default: 74 | throw new \InvalidArgumentException($cmsType . ' is incorrect CMS entity type'); 75 | } 76 | } catch (\Exception $e) { 77 | $output->writeln($e->getMessage()); 78 | return 0; 79 | } 80 | 81 | if ($count > 0) { 82 | $output->writeln("" . __('%1 history files was deleted!', $count)->render() . ""); 83 | } else { 84 | $output->writeln('All backups are actual!'); 85 | } 86 | return 0; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Console/Command/ImportContent.php: -------------------------------------------------------------------------------- 1 | contentImport = $contentImport; 29 | parent::__construct(); 30 | } 31 | 32 | /** 33 | * @inheritdoc 34 | */ 35 | protected function configure() 36 | { 37 | $this->setName('od:cms:import'); 38 | $this->setDescription('Import CMS zip file'); 39 | $this->addArgument('zipfile', InputArgument::REQUIRED, __('Zip file containing CMS information')->render()); 40 | 41 | parent::configure(); 42 | } 43 | 44 | /** 45 | * @inheritdoc 46 | */ 47 | protected function execute(InputInterface $input, OutputInterface $output) 48 | { 49 | $zipFile = $input->getArgument('zipfile'); 50 | if ($this->contentImport->importContentFromZipFile($zipFile, false) == 0) { 51 | $output->writeln('Archive is empty.'); 52 | 53 | return; 54 | } 55 | $output->writeln('Done.'); 56 | return 0; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Controller/Adminhtml/AbstractMassExport.php: -------------------------------------------------------------------------------- 1 | fileFactory->create( 58 | $fileName, 59 | [ 60 | 'type' => 'filename', 61 | 'value' => $this->returnZipFile($convertedBlocks, $fileName, $type), 62 | 'rm' => true, 63 | ], 64 | DirectoryList::VAR_DIR, 65 | 'application/zip' 66 | ); 67 | } 68 | 69 | /** 70 | * Return zip file 71 | * 72 | * @param array $convertedBlocks 73 | * @param string $fileName 74 | * @param string $type 75 | * 76 | * @return string 77 | * @throws FileSystemException|LocalizedException 78 | */ 79 | protected function returnZipFile(array $convertedBlocks, string $fileName, string $type): string 80 | { 81 | $fileType = (string)$this->getRequest()->getParam('type', 'json'); 82 | $isSplit = (bool)$this->getRequest()->getParam('split', false); 83 | 84 | return $this->contentExport->createZipFile( 85 | $convertedBlocks, 86 | $type, 87 | $fileType, 88 | $fileName, 89 | $isSplit 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Block/MassExport.php: -------------------------------------------------------------------------------- 1 | filter = $filter; 52 | $this->collectionFactory = $collectionFactory; 53 | $this->contentExport = $contentExport; 54 | $this->fileFactory = $fileFactory; 55 | $this->dateTime = $dateTime; 56 | $this->cmsEntityConverterManager = $cmsEntityConverterManager; 57 | 58 | parent::__construct($context); 59 | } 60 | 61 | /** 62 | * Make mass export 63 | * 64 | * @return ResponseInterface 65 | * @throws LocalizedException 66 | */ 67 | public function execute(): ResponseInterface 68 | { 69 | $blocks = $this->filter->getCollection($this->collectionFactory->create())->getItems(); 70 | 71 | $fileName = sprintf('cms_block_%s.zip', $this->dateTime->date('Ymd_His')); 72 | 73 | $convertedBlocks = $this->cmsEntityConverterManager 74 | ->getConverter(CmsEntityConverterManagerInterface::BLOCK_ENTITY_CODE) 75 | ->convertToArray($blocks); 76 | 77 | return $this->formFile( 78 | $fileName, 79 | $convertedBlocks, 80 | CmsEntityConverterManagerInterface::BLOCK_ENTITY_CODE 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Controller/Adminhtml/History/Apply.php: -------------------------------------------------------------------------------- 1 | importExportInterface = $importExportInterface; 75 | $this->redirectFactory = $redirectFactory; 76 | $this->pageRepository = $pageRepository; 77 | $this->blockRepository = $blockRepository; 78 | $this->backupManager = $backupManager; 79 | $this->file = $file; 80 | $this->jsonFormatter = $jsonFormatter; 81 | 82 | parent::__construct($context); 83 | } 84 | 85 | /** 86 | * @return Redirect 87 | */ 88 | public function execute() 89 | { 90 | $identifier = $this->getRequest()->getParam('bc_identifier'); 91 | $itemId = $this->getRequest()->getParam('item_id'); 92 | $file = $this->getRequest()->getParam('item'); 93 | $cmsType = $this->getRequest()->getParam('bc_type'); 94 | $storeId = $this->getRequest()->getParam('store_id'); 95 | try { 96 | if (!is_null($storeId)) { 97 | $path = $this->backupManager->getBackupPathByStoreId($cmsType, $identifier, (int)$storeId) . DIRECTORY_SEPARATOR . $file; 98 | } else { 99 | $path = $this->backupManager->getBackupPath($cmsType, $identifier) . DIRECTORY_SEPARATOR . $file; 100 | } 101 | $backupFile = $this->file->readData($path); 102 | $jsonBackup = $this->jsonFormatter->decode($backupFile, true); 103 | if ($cmsType == BackupManager::TYPE_CMS_BLOCK) { 104 | $cmsObject = $this->blockRepository->getById($itemId); 105 | $cmsObject->setContent($jsonBackup['content']); 106 | $this->blockRepository->save($cmsObject); 107 | } else if ($cmsType == BackupManager::TYPE_CMS_PAGE) { 108 | $cmsObject = $this->pageRepository->getById($itemId); 109 | $cmsObject->setContent($jsonBackup['content']); 110 | $this->pageRepository->save($cmsObject); 111 | } 112 | $this->messageManager->addSuccessMessage(__('Successfully changed content with backup file %1', $file)); 113 | } catch (\Exception $e) { 114 | $this->messageManager->addErrorMessage(__('Something went wrong with saving block or page %1', $file)); 115 | } 116 | $resultRedirect = $this->redirectFactory->create(); 117 | $resultRedirect->setUrl($this->_redirect->getRefererUrl()); 118 | return $resultRedirect; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Controller/Adminhtml/History/View.php: -------------------------------------------------------------------------------- 1 | resultFactory->create(ResultFactory::TYPE_PAGE); 23 | 24 | $resultPage->getConfig()->getTitle()->prepend(__('Backup Preview')); 25 | 26 | return $resultPage; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Import/Import.php: -------------------------------------------------------------------------------- 1 | importExportInterface = $importExportInterface; 43 | $this->redirectFactory = $redirectFactory; 44 | 45 | parent::__construct($context); 46 | } 47 | 48 | /** 49 | * @return Redirect 50 | */ 51 | public function execute(): Redirect 52 | { 53 | try { 54 | $cmsMode = $this->getRequest()->getParam('cms_import_mode'); 55 | $mediaMode = $this->getRequest()->getParam('media_import_mode'); 56 | $upload = $this->getRequest()->getParam('upload'); 57 | 58 | $count = $this->importExportInterface 59 | ->setCmsModeOption($cmsMode) 60 | ->setMediaModeOption($mediaMode) 61 | ->importContentFromZipFile( 62 | $upload[0]['path'] . DIRECTORY_SEPARATOR . $upload[0]['file'], 63 | true 64 | ); 65 | 66 | $this->messageManager->addSuccessMessage( 67 | __('A total of %1 item(s) have been imported/updated.', $count) 68 | ); 69 | } catch (\Exception $e) { 70 | $this->messageManager->addExceptionMessage($e); 71 | } 72 | 73 | $resultRedirect = $this->redirectFactory->create(); 74 | return $resultRedirect->setPath('*/*/index'); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Import/Index.php: -------------------------------------------------------------------------------- 1 | resultFactory->create(ResultFactory::TYPE_PAGE); 27 | 28 | $resultPage->setActiveMenu('Overdose_CMSContent::import') 29 | ->addBreadcrumb(__('CMS'), __('CMS')); 30 | 31 | $resultPage->addBreadcrumb(__('Import CMS'), __('Import CMS')); 32 | 33 | $resultPage->getConfig()->getTitle()->prepend(__('CMS')); 34 | $resultPage->getConfig()->getTitle()->prepend(__('CMS Import')); 35 | 36 | return $resultPage; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Import/Upload.php: -------------------------------------------------------------------------------- 1 | config = $config; 50 | $this->resultJsonFactory = $resultJsonFactory; 51 | $this->fileUploaderFactory = $fileUploaderFactory; 52 | $this->messageManager = $messageManager; 53 | } 54 | 55 | /** 56 | * @return ResultInterface 57 | */ 58 | public function execute(): ResultInterface 59 | { 60 | $result = $this->upload(); 61 | $response = $this->resultJsonFactory->create(); 62 | $response->setData($result); 63 | 64 | return $response; 65 | } 66 | 67 | /** 68 | * Upload file 69 | * 70 | * @return array 71 | */ 72 | private function upload(): array 73 | { 74 | $result = []; 75 | $uploader = $this->fileUploaderFactory->create(['fileId' => 'upload']); 76 | $uploader->setAllowedExtensions(['zip']); 77 | $uploader->setAllowRenameFiles(true); 78 | 79 | try { 80 | $result = $uploader->save($this->config->getBackupsDir() . DIRECTORY_SEPARATOR . 'import'); 81 | } catch (\Exception $e) { 82 | $this->messageManager->addErrorMessage($e->getMessage()); 83 | } 84 | return $result; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Page/MassExport.php: -------------------------------------------------------------------------------- 1 | filter = $filter; 52 | $this->collectionFactory = $collectionFactory; 53 | $this->contentExport = $contentExport; 54 | $this->fileFactory = $fileFactory; 55 | $this->dateTime = $dateTime; 56 | $this->cmsEntityConverterManager = $cmsEntityConverterManager; 57 | 58 | parent::__construct($context); 59 | } 60 | 61 | /** 62 | * Make mass export 63 | * 64 | * @return ResponseInterface 65 | * @throws LocalizedException 66 | */ 67 | public function execute(): ResponseInterface 68 | { 69 | $pages = $this->filter->getCollection($this->collectionFactory->create())->getItems(); 70 | 71 | $fileName = sprintf('cms_page_%s.zip', $this->dateTime->date('Ymd_His')); 72 | 73 | $convertedPages = $this->cmsEntityConverterManager 74 | ->getConverter(CmsEntityConverterManagerInterface::PAGE_ENTITY_CODE) 75 | ->convertToArray($pages); 76 | 77 | return $this->formFile( 78 | $fileName, 79 | $convertedPages, 80 | CmsEntityConverterManagerInterface::PAGE_ENTITY_CODE 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Cron/DeleteBackups.php: -------------------------------------------------------------------------------- 1 | clearCMSHistory = $clearCMSHistory; 32 | $this->config = $config; 33 | } 34 | 35 | /** 36 | * Process getting and deleting old CMS files 37 | * 38 | * @return void 39 | * @throws FileSystemException 40 | */ 41 | public function execute(): void 42 | { 43 | if ($this->config->isCronEnabled()) { 44 | return; 45 | } 46 | $this->clearCMSHistory->execute(Config::TYPE_BLOCK); 47 | $this->clearCMSHistory->execute(Config::TYPE_PAGE); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Exception/InvalidXmlImportFilesException.php: -------------------------------------------------------------------------------- 1 | fileIo = $fileIo; 43 | $this->filesystem = $filesystem; 44 | $this->logger = $logger; 45 | } 46 | 47 | /** 48 | * @inheritDoc 49 | */ 50 | public function writeData( 51 | string $path, 52 | string $fileName, 53 | string $content, 54 | string $fileExtension = self::FILE_EXTENSION 55 | ): FileManagerInterface { 56 | if (!$path || !$fileName) { 57 | return $this; 58 | } 59 | 60 | try { 61 | $path = $this->getFolder($path); 62 | $this->fileIo->open(['path' => $path]); 63 | $this->fileIo->write($fileName . $fileExtension, $content, 0666); 64 | } catch (\Exception $e) { 65 | $this->logger->critical(__('Something went wrong while saving file')); 66 | } 67 | return $this; 68 | } 69 | 70 | /** 71 | * @inheritDoc 72 | */ 73 | public function readData($filename) 74 | { 75 | return $this->fileIo->read($filename); 76 | } 77 | 78 | /** 79 | * Get folder 80 | * 81 | * @param string $path 82 | * 83 | * @return string; 84 | */ 85 | public function getFolder(string $path): string 86 | { 87 | try { 88 | $this->fileIo->checkAndCreateFolder($path, 0775); 89 | 90 | return $path; 91 | } catch (\Exception $e) { 92 | $this->logger->critical(__('Something went wrong while saving file')); 93 | 94 | return ''; 95 | } 96 | } 97 | 98 | /** 99 | * Get media file path 100 | * 101 | * @param string $mediaFile 102 | * @param bool $write = false 103 | * 104 | * @return string 105 | * @throws FileSystemException 106 | */ 107 | public function getMediaPath(string $mediaFile, bool $write = false): ?string 108 | { 109 | if ($write) { 110 | $mediaDir = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); 111 | } else { 112 | $mediaDir = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA); 113 | } 114 | return $mediaDir->getAbsolutePath($mediaFile); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /File/FileManagerInterface.php: -------------------------------------------------------------------------------- 1 | scopeConfig = $scopeConfig; 75 | $this->directoryList = $directoryList; 76 | } 77 | 78 | /** 79 | * Check is functionality enabled 80 | * 81 | * @return bool 82 | */ 83 | public function isEnabled(): bool 84 | { 85 | return (bool)$this->scopeConfig->getValue(self::XML_PATH_IS_ENABLED, ScopeInterface::SCOPE_WEBSITE); 86 | } 87 | 88 | /** 89 | * Check is delete by cron enabled 90 | * 91 | * @return bool 92 | */ 93 | public function isCronEnabled(): bool 94 | { 95 | return (bool)$this->scopeConfig->getValue(self::XML_PATH_IS_CRON_ENABLED, ScopeInterface::SCOPE_STORE); 96 | } 97 | 98 | /** 99 | * Get method type 100 | * 101 | * @return string 102 | */ 103 | public function getMethodType(): string 104 | { 105 | if (!$result = $this->scopeConfig->getValue(self::XML_PATH_DELETE_METHOD_TYPE, ScopeInterface::SCOPE_STORE)) { 106 | return self::PERIOD; 107 | } 108 | return $result; 109 | } 110 | 111 | /** 112 | * Get period type 113 | * 114 | * @return int 115 | */ 116 | public function getPeriodType(): int 117 | { 118 | if (!$result = $this->scopeConfig->getValue( 119 | self::XML_PATH_OLDER_THAN_PERIOD_TYPE, 120 | ScopeInterface::SCOPE_STORE 121 | )) { 122 | return self::MONTH; 123 | } 124 | return (int)$result; 125 | } 126 | 127 | /** 128 | * Get period number 129 | * 130 | * @return int 131 | */ 132 | public function getPeriodNumber(): int 133 | { 134 | if (!$result = $this->scopeConfig->getValue( 135 | self::XML_PATH_OLDER_THAN_PERIOD_NUMBER, ScopeInterface::SCOPE_STORE 136 | )) { 137 | return 5; 138 | } 139 | return (int)$result; 140 | } 141 | 142 | /** 143 | * @return bool 144 | */ 145 | public function isLogsEnabled(): bool 146 | { 147 | return (bool)$this->scopeConfig->getValue(self::XML_PATH_LOGS, ScopeInterface::SCOPE_STORE); 148 | } 149 | 150 | /** 151 | * Get full path to directory with backups 152 | * 153 | * @return string 154 | */ 155 | public function getBackupsDir(): string 156 | { 157 | try { 158 | return $this->directoryList->getPath(DirectoryList::VAR_DIR) . DIRECTORY_SEPARATOR . self::CMS_DIR; 159 | } catch (\Exception $e) { 160 | return ''; 161 | } 162 | } 163 | 164 | /** 165 | * Form export path 166 | * 167 | * @return string 168 | */ 169 | public function getExportPath(): string 170 | { 171 | try { 172 | return $this->directoryList->getPath(DirectoryList::VAR_DIR) 173 | . DIRECTORY_SEPARATOR 174 | . self::CMS_DIR 175 | . DIRECTORY_SEPARATOR 176 | . self::EXPORT_PATH; 177 | } catch (\Exception $e) { 178 | return ''; 179 | } 180 | } 181 | 182 | /** 183 | * Get Extract Path 184 | * 185 | * @return string 186 | */ 187 | public function getExtractPath(): string 188 | { 189 | try { 190 | return $this->directoryList->getPath(DirectoryList::VAR_DIR) 191 | . DIRECTORY_SEPARATOR 192 | . self::CMS_DIR 193 | . DIRECTORY_SEPARATOR 194 | . self::EXTRACT_PATH; 195 | } catch (\Exception $e) { 196 | return ''; 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /Model/Config/App/FileResolver.php: -------------------------------------------------------------------------------- 1 | iteratorFactory = $iteratorFactory; 45 | $this->filesystem = $filesystem; 46 | $this->moduleReader = $moduleReader; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function get($filename, $scope) 53 | { 54 | switch ($scope) { 55 | case 'primary': 56 | $directory = $this->filesystem->getDirectoryRead(DirectoryList::CONFIG); 57 | $absolutePaths = []; 58 | foreach ($directory->search('{' . $filename . ',*/' . $filename . '}') as $path) { 59 | $absolutePaths[] = $directory->getAbsolutePath($path); 60 | } 61 | $iterator = $this->iteratorFactory->create($absolutePaths); 62 | break; 63 | case 'global': 64 | $iterator = $this->moduleReader->getConfigurationFiles($filename); 65 | break; 66 | default: 67 | $iterator = $this->moduleReader->getConfigurationFiles($scope . '/' . $filename); 68 | break; 69 | } 70 | return $iterator; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Model/Config/Block/Converter.php: -------------------------------------------------------------------------------- 1 | '', 18 | '/config/cms_blocks/cms_block' => 'identifier', 19 | '/config/cms_blocks/cms_block/attribute' => 'code', 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /Model/Config/Block/SchemaLocator.php: -------------------------------------------------------------------------------- 1 | getElementsByTagName($this->itemsNode); 30 | 31 | /** @var \DOMElement $item */ 32 | foreach ($items as $item) { 33 | $resultArray = []; 34 | 35 | /** @var \DOMNodeList $children */ 36 | $children = $item->getElementsByTagName($this->childNode); 37 | /** @var \DOMElement $child */ 38 | foreach ($children as $child) { 39 | $childData = []; 40 | if (!$identifier = $child->getAttribute('identifier')) { 41 | throw new \InvalidArgumentException( 42 | __('Attribute "identifier" of "%1" does not exist', $this->childNode) 43 | ); 44 | } 45 | 46 | $childData['identifier'] = $identifier; 47 | 48 | /** @var \DOMNodeList $cmsAttributes */ 49 | $cmsAttributes = $child->getElementsByTagName('attribute'); 50 | /** \DOMElement $cmsAttribute */ 51 | foreach ($cmsAttributes as $cmsAttribute) { 52 | $childData[$cmsAttribute->getAttribute('code')] = $cmsAttribute->textContent; 53 | } 54 | 55 | $storeIdsString = empty($childData['store_ids']) 56 | ? '0' : str_replace(',', '_', $childData['store_ids']); 57 | $childIndex = $identifier . '_' . $storeIdsString; 58 | $resultArray[$childIndex] = $childData; 59 | } 60 | 61 | //IF need array wrapper cms_blocks/cms_pages use $output[$this->itemsNode] = $resultArray; 62 | $output = array_merge($output, $resultArray); 63 | } 64 | 65 | return $output; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Model/Config/Cron/SaveValue.php: -------------------------------------------------------------------------------- 1 | configInterface = $configInterface; 47 | } 48 | 49 | /** 50 | * @return SaveValue 51 | * @throws \Exception 52 | */ 53 | public function afterSave(): SaveValue 54 | { 55 | $time = $this->getData(Config::CRON_ARRAY_PATH_TIME_VALUE); 56 | $frequency = $this->getData(Config::CRON_ARRAY_PATH_FREQUENCY_VALUE); 57 | 58 | $cronExprArray = [ 59 | (int)$time[1], //Minute 60 | (int)$time[0], //Hour 61 | $frequency == Frequency::CRON_MONTHLY ? '1' : '*', //Day of the Month 62 | '*', //Month of the Year 63 | $frequency == Frequency::CRON_WEEKLY ? '1' : '*', //Day of the Week 64 | ]; 65 | 66 | $cronExprString = implode(' ', $cronExprArray); 67 | 68 | try { 69 | $this->configInterface->saveConfig( 70 | Config::CRON_STRING_PATH, 71 | $cronExprString, 72 | $this->getScope(), 73 | $this->getScopeId() 74 | ); 75 | } catch (\Exception $e) { 76 | throw new \Exception(__('We can\'t save the cron expression.')); 77 | } 78 | 79 | return parent::afterSave(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Model/Config/Page/Converter.php: -------------------------------------------------------------------------------- 1 | '', 18 | '/config/cms_pages/cms_page' => 'identifier', 19 | '/config/cms_pages/cms_page/attribute' => 'code', 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /Model/Config/Page/SchemaLocator.php: -------------------------------------------------------------------------------- 1 | logger = $logger; 58 | 59 | parent::__construct( 60 | $fileResolver, 61 | $converter, 62 | $schemaLocator, 63 | $validationState, 64 | static::FILE_NAME, 65 | $idAttributes, 66 | $domDocumentClass, 67 | $defaultScope 68 | ); 69 | } 70 | 71 | /** 72 | * Load configuration for primary and global scopes 73 | * 74 | * @param null $scope 75 | * @return array 76 | * @throws LocalizedException 77 | */ 78 | public function read($scope = null) 79 | { 80 | $fileListGlobal = $this->_fileResolver->get($this->_fileName, self::SCOPE_GLOBAL); 81 | $fileListGlobal = count($fileListGlobal) ? $fileListGlobal->toArray() : []; 82 | $fileListPrimary = $this->_fileResolver->get($this->_fileName, self::SCOPE_PRIMARY); 83 | $fileListPrimary = count($fileListPrimary) ? $fileListPrimary->toArray() : []; 84 | 85 | $configGlobal = $this->_readFiles($fileListGlobal); 86 | $configPrimary = $this->_readFiles($fileListPrimary); 87 | 88 | if ($duplicateItems = array_intersect_key($configGlobal, $configPrimary)) { 89 | $listOfImportFiles = array_merge(array_keys($fileListGlobal), array_keys($fileListPrimary)); 90 | $this->handleDuplicationError($duplicateItems, $listOfImportFiles); 91 | } 92 | 93 | return array_merge($configGlobal, $configPrimary); 94 | } 95 | 96 | /** 97 | * @param string $file 98 | * @return array 99 | * @throws LocalizedException 100 | */ 101 | public function readFromFile(string $file) 102 | { 103 | return $this->_readFiles([$file => file_get_contents($file)]); 104 | } 105 | 106 | /** 107 | * @param array $duplicateItems 108 | * @param array $listOfImportFiles 109 | * @return mixed 110 | * @throws InvalidXmlImportFilesException 111 | */ 112 | private function handleDuplicationError(array $duplicateItems, array $listOfImportFiles) 113 | { 114 | $entityName = $this->_fileName === BlockReader::FILE_NAME ? 'block' : 'page'; 115 | $duplicates = ''; 116 | foreach ($duplicateItems as $item) { 117 | $duplicates .= sprintf( 118 | "%s identifier \"%s\" for the store id - %s, ", 119 | $entityName, 120 | $item[ContentVersionInterface::IDENTIFIER], 121 | $item[ContentVersionInterface::STORE_IDS] ?: '0' 122 | ); 123 | } 124 | $errorMessage = __( 125 | "CMS import failed. You have duplicate %1s. " 126 | . 'Please check your import files. List of duplicates - %2.', 127 | $entityName, 128 | $duplicates 129 | ); 130 | $this->logger->critical(__( 131 | $errorMessage . "\n List of import files for the duplication check - %1", 132 | implode(', ', $listOfImportFiles) 133 | )); 134 | 135 | throw new InvalidXmlImportFilesException($errorMessage); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Model/Config/SchemaLocatorAbstract.php: -------------------------------------------------------------------------------- 1 | schema = $moduleReader->getModuleDir(Dir::MODULE_ETC_DIR, 'Overdose_CMSContent') 36 | . DIRECTORY_SEPARATOR . $this->schemaFile; 37 | $this->perFileSchema = $moduleReader->getModuleDir(Dir::MODULE_ETC_DIR, 'Overdose_CMSContent') 38 | . DIRECTORY_SEPARATOR . $this->schemaFile; 39 | } 40 | 41 | /** 42 | * @inheritDoc 43 | */ 44 | public function getSchema(): ?string 45 | { 46 | return $this->schema; 47 | } 48 | 49 | /** 50 | * @inheritDoc 51 | */ 52 | public function getPerFileSchema(): ?string 53 | { 54 | return $this->perFileSchema; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Model/Content/Converter/AbstractConverter.php: -------------------------------------------------------------------------------- 1 | storeRepositoryInterface = $storeRepositoryInterface; 33 | $this->blockRepositoryInterface = $blockRepositoryInterface; 34 | } 35 | 36 | /** 37 | * Get media attachments from content 38 | * 39 | * @param $content 40 | * 41 | * @return array 42 | */ 43 | protected function getMediaAttachments($content): array 44 | { 45 | $result = []; 46 | if (preg_match_all('/\{\{media.+?url\s*=\s*("|")(.+?)("|").*?\}\}/', $content, $matches)) { 47 | $result += $matches[2]; 48 | } 49 | 50 | if (preg_match_all('/{{media.+?url\s*=\s*(?!"|")(.+?)}}/', $content, $matches)) { 51 | $result += $matches[1]; 52 | } 53 | 54 | return $result; 55 | } 56 | 57 | /** 58 | * @param string $content 59 | * 60 | * @return array 61 | * @throws LocalizedException 62 | */ 63 | protected function saveBlockByIdent(string $content): array 64 | { 65 | $references = []; 66 | 67 | $pattern = '/{{widget.+?block_id\s*=\s*("|")(\d+?)("|").*?}}/'; 68 | 69 | if (preg_match_all($pattern, $content, $matches)) { 70 | foreach ($matches[2] as $blockId) { 71 | $block = $this->blockRepositoryInterface->getById($blockId); 72 | $references[$blockId] = $block->getIdentifier(); 73 | } 74 | } 75 | 76 | return $references; 77 | } 78 | 79 | /** 80 | * Get store codes 81 | * 82 | * @param array $storeIds 83 | * 84 | * @return array 85 | * @throws NoSuchEntityException 86 | */ 87 | public function getStoreCodes(array $storeIds): array 88 | { 89 | $return = []; 90 | foreach ($storeIds as $storeId) { 91 | $return[] = $this->storeRepositoryInterface->getById($storeId)->getCode(); 92 | } 93 | return $return; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Model/Content/Converter/Block/Converter.php: -------------------------------------------------------------------------------- 1 | convertBlockToArray($cmsEntity); 25 | $blocks[$this->getBlockKey($cmsEntity)] = $blockInfo; 26 | $media = array_merge($media, $blockInfo['media']); 27 | } 28 | 29 | return [ 30 | self::BLOCK_ENTITY_CODE => $blocks, 31 | 'media' => $media, 32 | ]; 33 | } 34 | 35 | /** 36 | * Convert CMS block to array 37 | * 38 | * @param BlockInterface $blockInterface 39 | * 40 | * @return array 41 | * @throws LocalizedException 42 | */ 43 | private function convertBlockToArray(BlockInterface $blockInterface): array 44 | { 45 | // Extract attachments 46 | $media = $this->getMediaAttachments($blockInterface->getContent()); 47 | 48 | return [ 49 | 'cms' => [ 50 | BlockInterface::IDENTIFIER => $blockInterface->getIdentifier(), 51 | BlockInterface::TITLE => $blockInterface->getTitle(), 52 | BlockInterface::CONTENT => $blockInterface->getContent(), 53 | BlockInterface::IS_ACTIVE => (string)$blockInterface->isActive(), 54 | ], 55 | 'stores' => $this->getStoreCodes($blockInterface->getStores()), 56 | 'media' => $media, 57 | 'block_references' => $this->saveBlockByIdent($blockInterface->getContent()) 58 | ]; 59 | } 60 | 61 | /** 62 | * Get block unique key 63 | * 64 | * @param BlockInterface $blockInterface 65 | * 66 | * @return string 67 | * @throws NoSuchEntityException 68 | */ 69 | private function getBlockKey(BlockInterface $blockInterface): string 70 | { 71 | $keys = $this->getStoreCodes($blockInterface->getStores()); 72 | $keys[] = $blockInterface->getIdentifier(); 73 | 74 | return implode(':', $keys); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Model/Content/Converter/CmsEntityConverterInterface.php: -------------------------------------------------------------------------------- 1 | converters = $converters; 24 | } 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | public function getConverter(string $type): CmsEntityConverterInterface 30 | { 31 | if (isset($this->converters[$type])) { 32 | return $this->converters[$type]; 33 | } 34 | throw new LocalizedException(__("Can't find converter")); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Model/Content/Converter/Page/Converter.php: -------------------------------------------------------------------------------- 1 | convertPageToArray($pageInterface); 25 | $pages[$this->getPageKey($pageInterface)] = $pageInfo; 26 | $media = array_merge($media, $pageInfo['media']); 27 | } 28 | 29 | return [ 30 | self::PAGE_ENTITY_CODE => $pages, 31 | 'media' => $media, 32 | ]; 33 | } 34 | 35 | /** 36 | * Return CMS page to array 37 | * 38 | * @param PageInterface $pageInterface 39 | * 40 | * @return array 41 | * @throws LocalizedException 42 | */ 43 | public function convertPageToArray(PageInterface $pageInterface): array 44 | { 45 | // Extract attachments 46 | $media = $this->getMediaAttachments($pageInterface->getContent()); 47 | 48 | return [ 49 | 'cms' => [ 50 | PageInterface::IDENTIFIER => $pageInterface->getIdentifier(), 51 | PageInterface::TITLE => $pageInterface->getTitle(), 52 | PageInterface::PAGE_LAYOUT => $pageInterface->getPageLayout(), 53 | PageInterface::META_KEYWORDS => $pageInterface->getMetaKeywords(), 54 | PageInterface::META_DESCRIPTION => $pageInterface->getMetaDescription(), 55 | PageInterface::CONTENT_HEADING => $pageInterface->getContentHeading(), 56 | PageInterface::CONTENT => $pageInterface->getContent(), 57 | PageInterface::SORT_ORDER => $pageInterface->getSortOrder(), 58 | PageInterface::LAYOUT_UPDATE_XML => $pageInterface->getLayoutUpdateXml(), 59 | PageInterface::CUSTOM_THEME => $pageInterface->getCustomTheme(), 60 | PageInterface::CUSTOM_ROOT_TEMPLATE => $pageInterface->getCustomRootTemplate(), 61 | PageInterface::CUSTOM_LAYOUT_UPDATE_XML => $pageInterface->getCustomLayoutUpdateXml(), 62 | PageInterface::CUSTOM_THEME_FROM => $pageInterface->getCustomThemeFrom(), 63 | PageInterface::CUSTOM_THEME_TO => $pageInterface->getCustomThemeTo(), 64 | PageInterface::IS_ACTIVE => (string)$pageInterface->isActive(), 65 | ], 66 | 'stores' => $this->getStoreCodes($pageInterface->getStores()), 67 | 'media' => $media, 68 | 'block_references' => $this->saveBlockByIdent($pageInterface->getContent()), 69 | ]; 70 | } 71 | 72 | /** 73 | * Get page unique key 74 | * 75 | * @param PageInterface $pageInterface 76 | * 77 | * @return string 78 | * @throws NoSuchEntityException 79 | */ 80 | private function getPageKey(PageInterface $pageInterface): string 81 | { 82 | $keys = $this->getStoreCodes($pageInterface->getStores()); 83 | $keys[] = $pageInterface->getIdentifier(); 84 | 85 | return implode(':', $keys); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Model/Content/Export.php: -------------------------------------------------------------------------------- 1 | cmsEntityGeneratorManager = $cmsEntityGeneratorManager; 53 | $this->config = $config; 54 | $this->file = $file; 55 | $this->fileManager = $fileManager; 56 | } 57 | 58 | /** 59 | * @inheridoc 60 | */ 61 | public function createZipFile( 62 | array $convertedEntities, 63 | string $entityType, 64 | string $type, 65 | string $fileName, 66 | bool $split 67 | ): string { 68 | $exportPath = $this->fileManager->getFolder($this->config->getExportPath()); 69 | 70 | $relativeZipFile = Config::CMS_DIR . DIRECTORY_SEPARATOR 71 | . Config::EXPORT_PATH . DIRECTORY_SEPARATOR 72 | . $fileName; 73 | 74 | $zipArchive = $this->putContentToZip( 75 | $convertedEntities, 76 | $entityType, 77 | $exportPath . '/' . $fileName, 78 | $type, 79 | $split 80 | ); 81 | 82 | // Add media files 83 | foreach ($convertedEntities['media'] as $mediaFile) { 84 | //Strip Quotes if any 85 | $mediaFile = str_replace(['"',""","'"], '', $mediaFile); 86 | $absMediaPath = $this->fileManager->getMediaPath($mediaFile); 87 | if ($this->file->fileExists($absMediaPath, true)) { 88 | $zipArchive->addFile($absMediaPath, self::MEDIA_ARCHIVE_PATH . '/' . $mediaFile); 89 | } 90 | } 91 | 92 | $zipArchive->close(); 93 | 94 | // Clear export path 95 | $this->file->rm($relativeZipFile); 96 | 97 | return $relativeZipFile; 98 | } 99 | 100 | /** 101 | * @param array $contentArray 102 | * @param string $cmsEntityCode 103 | * @param string $zipFileName 104 | * @param string $type 105 | * @param bool $split 106 | * @return ZipArchive 107 | * @throws LocalizedException 108 | */ 109 | private function putContentToZip( 110 | array $contentArray, 111 | string $cmsEntityCode, 112 | string $zipFileName, 113 | string $type, 114 | bool $split 115 | ): ZipArchive { 116 | $zipArchive = new ZipArchive(); 117 | $zipArchive->open($zipFileName, ZipArchive::CREATE); 118 | $generator = $this->cmsEntityGeneratorManager->getGenerator($type); 119 | 120 | if ($split) { 121 | foreach ($contentArray[$cmsEntityCode] as $key => $content) { 122 | $payload = $generator->generate( 123 | [ 124 | $cmsEntityCode => [$key => $content] 125 | ] 126 | ); 127 | 128 | $zipArchive->addFromString( 129 | sprintf( 130 | '%s_%s_%s.%s', 131 | self::FILENAME, 132 | $this->prepareEntityPartName($cmsEntityCode), 133 | $key, 134 | $type 135 | ), 136 | $payload 137 | ); 138 | } 139 | } else { 140 | $fullContent = []; 141 | foreach ($contentArray[$cmsEntityCode] as $key => $content) { 142 | $entityContent = [ 143 | $cmsEntityCode => [$key => $content] 144 | ]; 145 | $fullContent = array_merge_recursive($fullContent, $entityContent); 146 | } 147 | $payload = $generator->generate($fullContent); 148 | $zipArchive->addFromString( 149 | sprintf('%s_%s.%s', self::FILENAME, $this->prepareEntityPartName($cmsEntityCode), $type), 150 | $payload 151 | ); 152 | } 153 | 154 | return $zipArchive; 155 | } 156 | 157 | /** 158 | * Prepare entity part name 159 | * 160 | * @param string $cmsEntityCode 161 | * 162 | * @return string 163 | */ 164 | private function prepareEntityPartName(string $cmsEntityCode): string 165 | { 166 | return substr($cmsEntityCode, 0, strlen($cmsEntityCode) - 1) . '_data'; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /Model/Content/Generator/CmsEntityGeneratorInterface.php: -------------------------------------------------------------------------------- 1 | self::PAGE_SCHEMA_NAME, 16 | CmsEntityConverterInterface::BLOCK_ENTITY_CODE => self::BLOCK_SCHEMA_NAME 17 | ]; 18 | 19 | const MAIN_ENTITY_NODE_NAME = 'cms'; 20 | const STORES_ENTITY_NODE_NAME = 'stores'; 21 | 22 | /** 23 | * Call generate action 24 | * 25 | * @param array $data 26 | * 27 | * @return string 28 | */ 29 | public function generate(array $data): string; 30 | } 31 | -------------------------------------------------------------------------------- /Model/Content/Generator/CmsEntityGeneratorManager.php: -------------------------------------------------------------------------------- 1 | generators = $generators; 24 | } 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | public function getGenerator($type): CmsEntityGeneratorInterface 30 | { 31 | if (isset($this->generators[$type])) { 32 | return $this->generators[$type]; 33 | } 34 | throw new LocalizedException(__("Can't find generator")); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Model/Content/Generator/Json/Generator.php: -------------------------------------------------------------------------------- 1 | serializerInterface = $serializerInterface; 24 | } 25 | 26 | /** 27 | * @param array $data 28 | * 29 | * @return string 30 | */ 31 | public function generate(array $data): string 32 | { 33 | return $this->serializerInterface->serialize($data); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Model/ContentVersion.php: -------------------------------------------------------------------------------- 1 | _init(ResourceContentVersion::class); 19 | } 20 | 21 | /** 22 | * @inheritdoc 23 | */ 24 | public function getType(): ?string 25 | { 26 | return $this->getData(self::TYPE); 27 | } 28 | 29 | /** 30 | * @inheritdoc 31 | */ 32 | public function setType(string $type): ContentVersionInterface 33 | { 34 | return $this->setData(self::TYPE, $type); 35 | } 36 | 37 | /** 38 | * @inheritdoc 39 | */ 40 | public function getIdentifier(): ?string 41 | { 42 | return $this->getData(self::IDENTIFIER); 43 | } 44 | 45 | /** 46 | * @inheritdoc 47 | */ 48 | public function setIdentifier(string $identifier): ContentVersionInterface 49 | { 50 | return $this->setData(self::IDENTIFIER, $identifier); 51 | } 52 | 53 | /** 54 | * @inheritdoc 55 | */ 56 | public function getVersion(): ?string 57 | { 58 | return $this->getData(self::VERSION); 59 | } 60 | 61 | /** 62 | * @inheritdoc 63 | */ 64 | public function setVersion(string $version): ContentVersionInterface 65 | { 66 | return $this->setData(self::VERSION, $version); 67 | } 68 | 69 | /** 70 | * @inheritdoc 71 | */ 72 | public function getStoreIds(): ?string 73 | { 74 | return $this->getData(self::STORE_IDS); 75 | } 76 | 77 | /** 78 | * @inheritdoc 79 | */ 80 | public function setStoreIds(string $storeIds): ContentVersionInterface 81 | { 82 | return $this->setData(self::STORE_IDS, $storeIds); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Model/ContentVersionRepository.php: -------------------------------------------------------------------------------- 1 | resource = $resource; 63 | $this->contentVersionFactory = $contentVersionFactory; 64 | $this->contentVersionCollectionFactory = $contentVersionCollectionFactory; 65 | $this->searchResultsFactory = $searchResultsFactory; 66 | $this->collectionProcessor = $collectionProcessor; 67 | } 68 | 69 | /** 70 | * @inheritdoc 71 | */ 72 | public function save(ContentVersionInterface $contentVersion): ContentVersionInterface 73 | { 74 | try { 75 | $this->resource->save($contentVersion); 76 | 77 | return $this->get($contentVersion->getId()); 78 | } catch (\Exception $exception) { 79 | throw new CouldNotSaveException( 80 | __('Could not save the contentVersion: %1', $exception->getMessage()) 81 | ); 82 | } 83 | } 84 | 85 | /** 86 | * @inheritdoc 87 | */ 88 | public function get(string $id): ContentVersionInterface 89 | { 90 | $contentVersion = $this->contentVersionFactory->create(); 91 | $this->resource->load($contentVersion, $id); 92 | if (!$contentVersion->getId()) { 93 | throw new NoSuchEntityException(__('content_version with id "%1" does not exist.', $id)); 94 | } 95 | return $contentVersion; 96 | } 97 | 98 | /** 99 | * @inheritdoc 100 | */ 101 | public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface 102 | { 103 | $collection = $this->contentVersionCollectionFactory->create(); 104 | 105 | $this->collectionProcessor->process($searchCriteria, $collection); 106 | 107 | $searchResult = $this->searchResultsFactory->create(); 108 | $searchResult->setItems($collection->getItems()); 109 | $searchResult->setTotalCount($collection->getSize()); 110 | $searchResult->setSearchCriteria($searchCriteria); 111 | 112 | return $searchResult; 113 | } 114 | 115 | /** 116 | * @inheritdoc 117 | */ 118 | public function delete(ContentVersionInterface $contentVersion): bool 119 | { 120 | try { 121 | $contentVersionModel = $this->contentVersionFactory->create(); 122 | $this->resource->load($contentVersionModel, $contentVersion->getId()); 123 | $this->resource->delete($contentVersionModel); 124 | } catch (\Exception $exception) { 125 | throw new CouldNotDeleteException(__( 126 | 'Could not delete the content_version: %1', 127 | $exception->getMessage() 128 | )); 129 | } 130 | return true; 131 | } 132 | 133 | /** 134 | * @inheritdoc 135 | */ 136 | public function deleteById($id): bool 137 | { 138 | return $this->delete($this->get($id)); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Model/Dir/Reader.php: -------------------------------------------------------------------------------- 1 | getFilesIterator($filename, Dir::MODULE_ETC_DIR); 27 | } 28 | 29 | /** 30 | * @inheridoc 31 | */ 32 | private function getFilesIterator($filename, $subDir = '') 33 | { 34 | if (!isset($this->fileIterators[$subDir][$filename])) { 35 | $this->fileIterators[$subDir][$filename] = $this->fileIteratorFactory->create( 36 | $this->getFiles($filename, $subDir) 37 | ); 38 | } 39 | return $this->fileIterators[$subDir][$filename]; 40 | } 41 | 42 | /** 43 | * @inheridoc 44 | */ 45 | private function getFiles($filename, $subDir = '') 46 | { 47 | $result = []; 48 | foreach ($this->modulesList->getNames() as $moduleName) { 49 | try { 50 | $moduleSubDir = $this->getModuleDir($subDir, $moduleName); 51 | } catch (\InvalidArgumentException $e) { 52 | continue; 53 | } 54 | $file = $moduleSubDir . '/' . $filename; 55 | $directoryRead = $this->readFactory->create($moduleSubDir); 56 | $path = $directoryRead->getRelativePath($file); 57 | if ($directoryRead->isExist($path)) { 58 | $result[] = $file; 59 | } 60 | // === Overdose - the override part BEGIN === 61 | if ($filename === BlockReader::FILE_NAME || $filename === PageReader::FILE_NAME) { 62 | $pattern = substr($filename, 0, strlen($filename) - 4); 63 | $dir = $moduleSubDir . '/' . ReaderAbstract::OD_CONFIG_DIR_NAME; 64 | $directoryRead = $this->readFactory->create($dir); 65 | $path = $directoryRead->getRelativePath($dir); 66 | if ($directoryRead->isExist($path)) { 67 | $odFiles = array_filter($directoryRead->read($path), function ($file) use ($pattern) { 68 | return strpos($file, $pattern) === 0 && substr($file, -4) === '.xml'; 69 | }); 70 | array_walk($odFiles, function (&$file) use ($dir) { 71 | $file = $dir . '/' . $file; 72 | }); 73 | $result = array_merge($odFiles, $result); 74 | } 75 | } 76 | // === Overdose - the override part END === 77 | } 78 | return $result; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Model/EntityManagement.php: -------------------------------------------------------------------------------- 1 | repositoryList = $repositoryList; 41 | $this->factoryList = $factoryList; 42 | } 43 | 44 | /** 45 | * Get repository 46 | * 47 | * @return BlockRepositoryInterface|PageRepositoryInterface 48 | * @throws LocalizedException 49 | */ 50 | public function getRepository(string $type) 51 | { 52 | if (isset($this->repositoryList[$type])) { 53 | return $this->repositoryList[$type]; 54 | } 55 | throw new LocalizedException(__('Expected entity instance not found.')); 56 | } 57 | 58 | /** 59 | * Get repository 60 | * 61 | * @return BlockInterface|PageInterface 62 | * @throws LocalizedException 63 | */ 64 | public function getFactory(string $type) 65 | { 66 | if (isset($this->factoryList[$type])) { 67 | return $this->factoryList[$type]->create(); 68 | } 69 | throw new LocalizedException(__('Expected entity instance not found.')); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Model/OptionSource/CmsMode.php: -------------------------------------------------------------------------------- 1 | __('Overwrite existing'), 22 | 'value' => ContentImportInterface::OD_CMS_MODE_UPDATE 23 | ], 24 | [ 25 | 'label' => __('Skip existing'), 26 | 'value' => ContentImportInterface::OD_CMS_MODE_SKIP 27 | ] 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Model/OptionSource/MediaMode.php: -------------------------------------------------------------------------------- 1 | __('Do not import'), 22 | 'value' => ContentImportInterface::OD_MEDIA_MODE_NONE 23 | ], 24 | [ 25 | 'label' => __('Overwrite existing'), 26 | 'value' => ContentImportInterface::OD_MEDIA_MODE_UPDATE 27 | ], 28 | [ 29 | 'label' => __('Skip existing'), 30 | 'value' => ContentImportInterface::OD_MEDIA_MODE_SKIP 31 | ] 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Model/OptionSource/Methods.php: -------------------------------------------------------------------------------- 1 | __('By Periods'), 20 | 'value' => Config::PERIOD 21 | ], 22 | [ 23 | 'label' => __('Older Than'), 24 | 'value' => Config::OLDER_THAN 25 | ] 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Model/OptionSource/Periods.php: -------------------------------------------------------------------------------- 1 | __('Days'), 20 | 'value' => Config::DAY 21 | ], 22 | [ 23 | 'label' => __('Weeks'), 24 | 'value' => Config::WEEK 25 | ], 26 | [ 27 | 'label' => __('Months'), 28 | 'value' => Config::MONTH 29 | ], 30 | [ 31 | 'label' => __('Years'), 32 | 'value' => Config::YEAR 33 | ] 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Model/ResourceModel/ContentVersion.php: -------------------------------------------------------------------------------- 1 | _init('od_cmscontent_version', 'id'); 21 | } 22 | 23 | /** 24 | * @inheritDoc 25 | */ 26 | protected function _beforeSave(AbstractModel $object) 27 | { 28 | if (empty($storeIds = $object->getData(ContentVersionInterface::STORE_IDS))) { 29 | $storeIds = '0'; 30 | } else { 31 | $storeIdsArray = explode(',', $storeIds); 32 | if (in_array('0', $storeIdsArray)) { 33 | $storeIds = '0'; 34 | } 35 | } 36 | 37 | $object->setData(ContentVersionInterface::STORE_IDS, $storeIds); 38 | 39 | return parent::_beforeSave($object); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Model/ResourceModel/ContentVersion/Collection.php: -------------------------------------------------------------------------------- 1 | _init(ContentVersion::class, ResourceContentVersion::class); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Model/Service/GetCmsEntityItems.php: -------------------------------------------------------------------------------- 1 | searchCriteriaBuilder = $searchCriteriaBuilder; 54 | $this->filterBuilder = $filterBuilder; 55 | $this->filterGroupBuilder = $filterGroupBuilder; 56 | $this->entityManagement = $entityManagement; 57 | } 58 | 59 | /** 60 | * Get Cms items 61 | * 62 | * /** 63 | * @param string $type 64 | * @param string $identifier 65 | * @param array $storeIds 66 | * 67 | * @return array 68 | * @throws LocalizedException 69 | */ 70 | public function execute(string $type, string $identifier, array $storeIds): array 71 | { 72 | $repository = $this->getCmsRepository($type); 73 | 74 | $searchCriteria = $this->prepareSearchCriteria($identifier, $storeIds); 75 | 76 | return $repository->getList($searchCriteria)->getItems(); 77 | } 78 | 79 | /** 80 | * Retrieve repository for cms-block or cms-page 81 | * 82 | * @param string $type 83 | * 84 | * @return BlockRepositoryInterface|PageRepositoryInterface 85 | * @throws LocalizedException 86 | */ 87 | private function getCmsRepository(string $type) 88 | { 89 | return $this->entityManagement->getRepository($type); 90 | } 91 | 92 | /** 93 | * Prepare search criteria 94 | * 95 | * @param string $identifier 96 | * @param array $storeIds 97 | * 98 | * @return SearchCriteria 99 | */ 100 | private function prepareSearchCriteria(string $identifier, array $storeIds): SearchCriteria 101 | { 102 | $filtersGroups = []; 103 | 104 | $filterIdentifier = $this->filterBuilder 105 | ->setField('identifier') 106 | ->setConditionType('eq') 107 | ->setValue($identifier) 108 | ->create(); 109 | $filterGroup1 = $this->filterGroupBuilder 110 | ->setFilters([$filterIdentifier]) 111 | ->create(); 112 | $filtersGroups[] = $filterGroup1; 113 | 114 | $filterStore = $this->filterBuilder 115 | ->setField('store_id') 116 | ->setConditionType('in') 117 | ->setValue($storeIds) 118 | ->create(); 119 | $filterGroup2 = $this->filterGroupBuilder 120 | ->setFilters([$filterStore]) 121 | ->create(); 122 | $filtersGroups[] = $filterGroup2; 123 | 124 | return $this->searchCriteriaBuilder 125 | ->setFilterGroups($filtersGroups) 126 | ->create(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Model/Service/GetContentVersions.php: -------------------------------------------------------------------------------- 1 | searchCriteriaBuilder = $searchCriteriaBuilder; 62 | $this->filterBuilder = $filterBuilder; 63 | $this->filterGroupBuilder = $filterGroupBuilder; 64 | $this->contentVersionRepository = $contentVersionRepository; 65 | $this->logger = $logger; 66 | } 67 | 68 | /** 69 | * Retrieves all CMS content records from DB based on type (0 - blocks, 1-pages), filtered by identifiers 70 | * 71 | * @param int $type 72 | * @param array $filterIds 73 | * 74 | * @return array 75 | */ 76 | public function execute(int $type, array $filterIds): array 77 | { 78 | $result = []; 79 | 80 | try { 81 | $searchCriteria = $this->prepareSearchCriteria($type, $filterIds); 82 | 83 | $searchResult = $this->contentVersionRepository->getList($searchCriteria)->getItems(); 84 | 85 | /** @var ContentVersion $item */ 86 | foreach ($searchResult as $item) { 87 | $storePart = str_replace(',', '_', $item->getStoreIds()); 88 | $key = $item->getIdentifier() . '_' . $storePart; 89 | $result[$key] = $item; 90 | } 91 | return $result; 92 | } catch (LocalizedException $e) { 93 | $this->logger->critical(__('Something went wrong during getting content versions')); 94 | } 95 | return $result; 96 | } 97 | 98 | /** 99 | * Prepare search criteria 100 | * 101 | * @param int $type 102 | * @param array $filterIds 103 | * 104 | * @return SearchCriteria 105 | */ 106 | private function prepareSearchCriteria(int $type, array $filterIds): SearchCriteria 107 | { 108 | $filtersGroups = []; 109 | 110 | $filterType = $this->filterBuilder 111 | ->setField(ContentVersionInterface::TYPE) 112 | ->setConditionType('eq') 113 | ->setValue($type) 114 | ->create(); 115 | $filterGroup1 = $this->filterGroupBuilder 116 | ->setFilters([$filterType]) 117 | ->create(); 118 | $filtersGroups[] = $filterGroup1; 119 | 120 | if (count($filterIds)) { 121 | $filterId = $this->filterBuilder 122 | ->setField(ContentVersionInterface::IDENTIFIER) 123 | ->setConditionType('in') 124 | ->setValue(implode(",", $filterIds)) 125 | ->create(); 126 | $filterGroup2 = $this->filterGroupBuilder 127 | ->setFilters([$filterId]) 128 | ->create(); 129 | $filtersGroups[] = $filterGroup2; 130 | } 131 | 132 | return $this->searchCriteriaBuilder 133 | ->setFilterGroups($filtersGroups) 134 | ->create(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Model/StoreManagement.php: -------------------------------------------------------------------------------- 1 | storeRepositoryInterface = $storeRepositoryInterface; 24 | } 25 | 26 | /** 27 | * Get store ids by codes 28 | * @param array $storeCodes 29 | * @return array 30 | */ 31 | public function getStoreIdsByCodes(array $storeCodes): array 32 | { 33 | $storeCodes = $this->filterStoresByStoreCodes($storeCodes); 34 | $storeIds = []; 35 | foreach ($storeCodes as $storeCode) { 36 | if ($storeCode == 'admin') { 37 | $storeIds[] = 0; 38 | } else { 39 | try { 40 | $store = $this->storeRepositoryInterface->get($storeCode); 41 | if ($store && $store->getId()) { 42 | $storeIds[] = $store->getId(); 43 | } 44 | } catch (NoSuchEntityException $exception) { 45 | continue; 46 | } 47 | } 48 | } 49 | return $storeIds; 50 | } 51 | 52 | /** 53 | * Filter stores passed to import by existed 54 | * 55 | * @param array $storeCodes 56 | * 57 | * @return array 58 | */ 59 | public function filterStoresByStoreCodes(array $storeCodes): array 60 | { 61 | $filteredStores = []; 62 | $allStores = $this->storeRepositoryInterface->getList(); 63 | foreach ($storeCodes as $storeCode) { 64 | if (array_key_exists($storeCode, $allStores)) { 65 | $filteredStores[] = $storeCode; 66 | } 67 | } 68 | return $filteredStores; 69 | } 70 | 71 | /** 72 | * Filter stores passed to import by existed 73 | * 74 | * @param array $storeIds 75 | * 76 | * @return array 77 | */ 78 | public function filterStoresByStoreIds(array $storeIds): array 79 | { 80 | $filteredStores = []; 81 | $allStores = $this->storeRepositoryInterface->getList(); 82 | foreach ($allStores as $store) { 83 | if (in_array($store->getId(), $storeIds)) { 84 | $filteredStores[] = $store->getId(); 85 | } 86 | } 87 | return $filteredStores; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Observer/CmsSaveBefore.php: -------------------------------------------------------------------------------- 1 | BackupManager::TYPE_CMS_BLOCK, 20 | 'cms_page_save_before' => BackupManager::TYPE_CMS_PAGE, 21 | ]; 22 | 23 | /** 24 | * Add keys to check if data was changed 25 | * 26 | * @var array 27 | */ 28 | private $keysToCheck = [ 29 | 'identifier', 30 | 'title', 31 | 'content', 32 | 'content_heading', 33 | ]; 34 | 35 | /** 36 | * @var BackupManager 37 | */ 38 | private $backupManager; 39 | 40 | /** 41 | * CmsSaveBefore constructor. 42 | * 43 | * @param BackupManager $backupManager 44 | */ 45 | public function __construct( 46 | BackupManager $backupManager 47 | ) { 48 | $this->backupManager = $backupManager; 49 | } 50 | 51 | /** 52 | * @inheritDoc 53 | */ 54 | public function execute(Observer $observer) 55 | { 56 | $eventName = $observer->getEvent()->getName(); 57 | if (empty($this->eventsTypeMap[$eventName])) { 58 | return; 59 | } 60 | 61 | $cmsObject = $observer->getEvent()->getData('data_object'); 62 | if ($this->hasImportantDataChanges($cmsObject)) { 63 | $this->backupManager->createBackup($this->eventsTypeMap[$eventName], $cmsObject); 64 | } 65 | } 66 | 67 | /** 68 | * Check if cms object was changed 69 | * 70 | * @param $cmsObject 71 | * 72 | * @return bool 73 | */ 74 | private function hasImportantDataChanges($cmsObject): bool 75 | { 76 | foreach ($this->keysToCheck as $key) { 77 | if ($cmsObject->getOrigData() && $cmsObject->getData($key) !== $cmsObject->getOrigData($key)) { 78 | return true; 79 | } 80 | } 81 | return false; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Observer/DeleteContentVersion.php: -------------------------------------------------------------------------------- 1 | ContentVersionInterface::TYPE_PAGE, 23 | 'cms_block_delete_commit_after' => ContentVersionInterface::TYPE_BLOCK, 24 | ]; 25 | 26 | /** 27 | * @var ContentVersionManagementInterface 28 | */ 29 | private $contentVersionManagement; 30 | 31 | /** 32 | * @param ContentVersionManagementInterface $contentVersionManagement 33 | */ 34 | public function __construct( 35 | ContentVersionManagementInterface $contentVersionManagement 36 | ) { 37 | $this->contentVersionManagement = $contentVersionManagement; 38 | } 39 | 40 | /** 41 | * @inheritDoc 42 | */ 43 | public function execute(Observer $observer): void 44 | { 45 | $eventName = $observer->getEvent()->getName(); 46 | if (!array_key_exists($eventName, $this->eventsTypeMap)) { 47 | return; 48 | } 49 | /** @var PageInterface|BlockInterface $cmsModel */ 50 | $cmsModel = $observer->getEvent()->getData('data_object'); 51 | $this->contentVersionManagement->deleteContentVersion( 52 | $cmsModel->getIdentifier(), 53 | $this->eventsTypeMap[$eventName], 54 | $cmsModel->getStores() 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Setup/Recurring.php: -------------------------------------------------------------------------------- 1 | contentVersionManagement = $contentVersionManagement; 25 | } 26 | 27 | /** 28 | * @inheritDoc 29 | */ 30 | public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) 31 | { 32 | $this->contentVersionManagement->processAll(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Test/Unit/Controller/Adminhtml/History/ViewTest.php: -------------------------------------------------------------------------------- 1 | resultFactoryMock = $this->getMockBuilder(ResultFactory::class) 45 | ->disableOriginalConstructor() 46 | ->getMock(); 47 | $this->pageMock = $this->getMockBuilder(Page::class) 48 | ->disableOriginalConstructor() 49 | ->getMock(); 50 | $this->pageConfigMock = $this->getMockBuilder(Config::class) 51 | ->disableOriginalConstructor() 52 | ->getMock(); 53 | $this->pageTitleMock = $this->getMockBuilder(Title::class) 54 | ->disableOriginalConstructor() 55 | ->getMock(); 56 | $this->contextMock = $this->getMockBuilder(Context::class) 57 | ->disableOriginalConstructor() 58 | ->getMock(); 59 | } 60 | 61 | public function testExecute() 62 | { 63 | $this->resultFactoryMock->expects($this->once()) 64 | ->method('create') 65 | ->with(ResultFactory::TYPE_PAGE) 66 | ->willReturn($this->pageMock); 67 | $this->pageMock->expects($this->once()) 68 | ->method('getConfig') 69 | ->willReturn($this->pageConfigMock); 70 | $this->pageConfigMock->expects($this->once()) 71 | ->method('getTitle') 72 | ->willReturn($this->pageTitleMock); 73 | $this->pageTitleMock->expects($this->once()) 74 | ->method('prepend') 75 | ->with('Backup Preview'); 76 | $this->contextMock->expects($this->once()) 77 | ->method('getResultFactory') 78 | ->willReturn($this->resultFactoryMock); 79 | 80 | $controller = new View($this->contextMock); 81 | $this->assertSame($this->pageMock, $controller->execute()); 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Test/Unit/Controller/Adminhtml/Import/ImportTest.php: -------------------------------------------------------------------------------- 1 | requestMock = $this->getMockBuilder(RequestInterface::class) 50 | ->disableOriginalConstructor() 51 | ->getMockForAbstractClass(); 52 | $this->importExportMock = $this->getMockBuilder(ContentImportInterface::class) 53 | ->disableOriginalConstructor() 54 | ->getMockForAbstractClass(); 55 | $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class) 56 | ->disableOriginalConstructor() 57 | ->getMockForAbstractClass(); 58 | $this->redirectFactoryMock = $this->getMockBuilder(RedirectFactory::class) 59 | ->disableOriginalConstructor() 60 | ->getMock(); 61 | $this->redirectMock = $this->getMockBuilder(Redirect::class) 62 | ->disableOriginalConstructor() 63 | ->getMock(); 64 | $this->contextMock = $this->getMockBuilder(Context::class) 65 | ->disableOriginalConstructor() 66 | ->getMock(); 67 | } 68 | 69 | public function testExecute() 70 | { 71 | $upload[] = [ 72 | 'file' => 'filename', 73 | 'path' => 'temppath' 74 | ]; 75 | $cmsMode = ContentImportInterface::OD_CMS_MODE_UPDATE; 76 | $mediaMode = ContentImportInterface::OD_MEDIA_MODE_NONE; 77 | 78 | $this->requestMock->expects($this->atLeast(3)) 79 | ->method('getParam') 80 | ->withConsecutive(['cms_import_mode'], ['media_import_mode'], ['upload']) 81 | ->willReturnOnConsecutiveCalls( 82 | $cmsMode, 83 | $mediaMode, 84 | $upload 85 | ); 86 | 87 | $this->importExportMock->expects($this->once()) 88 | ->method('setCmsModeOption') 89 | ->with($cmsMode) 90 | ->willReturn($this->importExportMock); 91 | 92 | $this->importExportMock->expects($this->once()) 93 | ->method('setMediaModeOption') 94 | ->with($mediaMode) 95 | ->willReturn($this->importExportMock); 96 | 97 | $this->importExportMock->expects($this->once()) 98 | ->method('importContentFromZipFile') 99 | ->with($upload[0]['path'] . DIRECTORY_SEPARATOR . $upload[0]['file'], true) 100 | ->willReturn(1); 101 | 102 | $this->messageManagerMock->expects($this->once()) 103 | ->method('addSuccessMessage'); 104 | 105 | $this->redirectFactoryMock->expects($this->once()) 106 | ->method('create') 107 | ->willReturn($this->redirectMock); 108 | 109 | $this->redirectMock->expects($this->once()) 110 | ->method('setPath') 111 | ->with('*/*/index') 112 | ->willReturnSelf(); 113 | 114 | $this->contextMock->expects($this->once()) 115 | ->method('getRequest') 116 | ->willReturn($this->requestMock); 117 | 118 | $this->contextMock->expects($this->once()) 119 | ->method('getMessageManager') 120 | ->willReturn($this->messageManagerMock); 121 | 122 | $controller = new Import( 123 | $this->contextMock, 124 | $this->importExportMock, 125 | $this->redirectFactoryMock 126 | ); 127 | 128 | $this->assertSame($this->redirectMock, $controller->execute()); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Test/Unit/Controller/Adminhtml/Import/IndexTest.php: -------------------------------------------------------------------------------- 1 | resultFactoryMock = $this->getMockBuilder(ResultFactory::class) 45 | ->disableOriginalConstructor() 46 | ->getMock(); 47 | $this->resultPageMock = $this->getMockBuilder(Page::class) 48 | ->disableOriginalConstructor() 49 | ->getMock(); 50 | $this->pageConfigMock = $this->getMockBuilder(Config::class) 51 | ->disableOriginalConstructor() 52 | ->getMock(); 53 | $this->pageTitleMock = $this->getMockBuilder(Title::class) 54 | ->disableOriginalConstructor() 55 | ->getMock(); 56 | $this->contextMock = $this->getMockBuilder(Context::class) 57 | ->disableOriginalConstructor() 58 | ->getMock(); 59 | } 60 | 61 | /** 62 | * @return void 63 | */ 64 | public function testExecute() 65 | { 66 | $this->resultFactoryMock->expects($this->once()) 67 | ->method('create') 68 | ->with(ResultFactory::TYPE_PAGE) 69 | ->willReturn($this->resultPageMock); 70 | 71 | $this->resultPageMock->expects($this->once()) 72 | ->method('setActiveMenu') 73 | ->with('Overdose_CMSContent::import') 74 | ->willReturnSelf(); 75 | 76 | $this->resultPageMock->expects($this->atLeast(2)) 77 | ->method('addBreadcrumb') 78 | ->withConsecutive(['CMS', 'CMS'], ['Import CMS', 'Import CMS']) 79 | ->willReturnSelf(); 80 | 81 | $this->resultPageMock->expects($this->atLeast(2)) 82 | ->method('getConfig') 83 | ->willReturn($this->pageConfigMock); 84 | 85 | $this->pageConfigMock->expects($this->atLeast(2)) 86 | ->method('getTitle') 87 | ->willReturn($this->pageTitleMock); 88 | 89 | $this->pageTitleMock->expects($this->atLeast(2)) 90 | ->method('prepend') 91 | ->withConsecutive(['CMS'], ['CMS Import']); 92 | 93 | $this->contextMock->expects($this->once()) 94 | ->method('getResultFactory') 95 | ->willReturn($this->resultFactoryMock); 96 | 97 | $controller = new Index($this->contextMock); 98 | 99 | $this->assertSame($this->resultPageMock, $controller->execute()); 100 | 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Test/Unit/Controller/Adminhtml/Import/UploadTest.php: -------------------------------------------------------------------------------- 1 | resultJsonFactoryMock = $this->getMockBuilder(JsonFactory::class) 50 | ->disableOriginalConstructor() 51 | ->getMock(); 52 | $this->resultJsonMock = $this->getMockBuilder(Json::class) 53 | ->disableOriginalConstructor() 54 | ->getMock(); 55 | $this->configMock = $this->getMockBuilder(Config::class) 56 | ->disableOriginalConstructor() 57 | ->getMock(); 58 | $this->uploaderFactoryMock = $this->getMockBuilder(UploaderFactory::class) 59 | ->disableOriginalConstructor() 60 | ->getMock(); 61 | $this->uploaderMock = $this->getMockBuilder(Uploader::class) 62 | ->disableOriginalConstructor() 63 | ->getMock(); 64 | $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class) 65 | ->disableOriginalConstructor() 66 | ->getMockForAbstractClass(); 67 | } 68 | 69 | /** 70 | * @return void 71 | */ 72 | public function testExecute() 73 | { 74 | $backupDir = ''; 75 | 76 | $this->resultJsonFactoryMock->expects($this->once()) 77 | ->method('create') 78 | ->willReturn($this->resultJsonMock); 79 | 80 | $this->resultJsonMock->expects($this->once()) 81 | ->method('setData') 82 | ->with([]) 83 | ->willReturnSelf(); 84 | 85 | $this->uploaderFactoryMock->expects($this->once()) 86 | ->method('create') 87 | ->with(['fileId' => 'upload']) 88 | ->willReturn($this->uploaderMock); 89 | 90 | $this->uploaderMock->expects($this->once()) 91 | ->method('setAllowedExtensions') 92 | ->with(['zip']) 93 | ->willReturnSelf(); 94 | 95 | $this->uploaderMock->expects($this->once()) 96 | ->method('setAllowRenameFiles') 97 | ->with(true) 98 | ->willReturnSelf(); 99 | 100 | $this->uploaderMock->expects($this->once()) 101 | ->method('save') 102 | ->with($backupDir . DIRECTORY_SEPARATOR . 'import') 103 | ->willReturn([]); 104 | 105 | $this->configMock->expects($this->once()) 106 | ->method('getBackupsDir') 107 | ->willReturn($backupDir); 108 | 109 | $controller = new Upload( 110 | $this->configMock, 111 | $this->resultJsonFactoryMock, 112 | $this->uploaderFactoryMock, 113 | $this->messageManagerMock 114 | ); 115 | 116 | $this->assertSame($this->resultJsonMock, $controller->execute()); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Test/Unit/Model/BackupManagerTest.php: -------------------------------------------------------------------------------- 1 | configMock = $this->getMockBuilder(Config::class) 55 | ->disableOriginalConstructor() 56 | ->getMock(); 57 | $this->fileMock = $this->getMockBuilder(FileManagerInterface::class) 58 | ->disableOriginalConstructor() 59 | ->getMock(); 60 | $this->fileDriverMock = $this->getMockBuilder(File::class) 61 | ->disableOriginalConstructor() 62 | ->getMock(); 63 | $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) 64 | ->disableOriginalConstructor() 65 | ->getMock(); 66 | $this->cmsBlockModelMock = $this->getMockBuilder(Block::class) 67 | ->disableOriginalConstructor() 68 | ->getMock(); 69 | $this->cmsPageModelMock = $this->getMockBuilder(Page::class) 70 | ->disableOriginalConstructor() 71 | ->getMock(); 72 | $this->json = new Json(); 73 | 74 | $this->model = new BackupManager( 75 | $this->fileDriverMock, 76 | $this->fileMock, 77 | $this->configMock, 78 | $this->json, 79 | $this->loggerMock 80 | ); 81 | } 82 | 83 | /** 84 | * @return void 85 | */ 86 | public function testCreateBackupWithBlock() 87 | { 88 | $this->configMock->expects($this->once()) 89 | ->method('isEnabled') 90 | ->willReturn(true); 91 | 92 | $this->fileMock->expects($this->once()) 93 | ->method('writeData'); 94 | 95 | $this->cmsBlockModelMock->expects($this->atLeastOnce()) 96 | ->method('getStores') 97 | ->willReturn([0, 1]); 98 | 99 | $this->cmsBlockModelMock->expects($this->atLeastOnce()) 100 | ->method('getIdentifier') 101 | ->willReturn('1'); 102 | 103 | $this->cmsBlockModelMock->expects($this->atLeastOnce()) 104 | ->method('getOrigData') 105 | ->withConsecutive(['title'], ['content']) 106 | ->willReturnOnConsecutiveCalls('Lorem Ipsum Dolor Sit Amet.'); 107 | 108 | $this->model->setCmsObject($this->cmsBlockModelMock); 109 | $this->assertSame( 110 | $this->model, 111 | $this->model->createBackup('cms_block', $this->cmsBlockModelMock) 112 | ); 113 | } 114 | 115 | /** 116 | * @return void 117 | */ 118 | public function testCreateBackupWithPage() 119 | { 120 | $this->configMock->expects($this->once()) 121 | ->method('isEnabled') 122 | ->willReturn(true); 123 | 124 | $this->fileMock->expects($this->once()) 125 | ->method('writeData'); 126 | 127 | $this->cmsPageModelMock->expects($this->atLeastOnce()) 128 | ->method('getStores') 129 | ->willReturn([0, 1]); 130 | 131 | $this->cmsPageModelMock->expects($this->atLeastOnce()) 132 | ->method('getIdentifier') 133 | ->willReturn('1'); 134 | 135 | $this->cmsPageModelMock->expects($this->atLeastOnce()) 136 | ->method('getOrigData') 137 | ->withConsecutive(['title'], ['content']) 138 | ->willReturnOnConsecutiveCalls('Lorem Ipsum Dolor Sit Amet.'); 139 | 140 | $this->model->setCmsObject($this->cmsPageModelMock); 141 | $this->assertSame( 142 | $this->model, 143 | $this->model->createBackup('cms_page', $this->cmsPageModelMock) 144 | ); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /Test/Unit/Model/Config/App/FileResolverTest.php: -------------------------------------------------------------------------------- 1 | readerMock = $this->getMockBuilder(Reader::class) 47 | ->disableOriginalConstructor() 48 | ->getMock(); 49 | 50 | $this->fileSystemMock = $this->getMockBuilder(Filesystem::class) 51 | ->disableOriginalConstructor() 52 | ->getMock(); 53 | 54 | $this->iteratorFactoryMock = $this->getMockBuilder(FileIteratorFactory::class) 55 | ->onlyMethods(['create']) 56 | ->disableOriginalConstructor() 57 | ->getMock(); 58 | 59 | $this->readInterfaceMock = $this->getMockBuilder(ReadInterface::class) 60 | ->disableOriginalConstructor() 61 | ->getMock(); 62 | 63 | $this->fileIteratorMock = $this->getMockBuilder(FileIterator::class) 64 | ->disableOriginalConstructor() 65 | ->getMock(); 66 | 67 | $this->sutObject = new FileResolver( 68 | $this->readerMock, 69 | $this->fileSystemMock, 70 | $this->iteratorFactoryMock 71 | ); 72 | } 73 | 74 | /** 75 | * @dataProvider dataProviderForGet 76 | * @param string $filename 77 | * @param string $scope 78 | */ 79 | public function testGet($filename, $scope) 80 | { 81 | if ($scope == 'primary') { 82 | $this->fileSystemMock->expects($this->once()) 83 | ->method('getDirectoryRead') 84 | ->willReturn($this->readInterfaceMock); 85 | 86 | $this->readInterfaceMock->expects($this->once()) 87 | ->method('search') 88 | ->willReturn(['subFileOne', 'subFileTwo']); 89 | 90 | $this->readInterfaceMock->expects($this->exactly(2)) 91 | ->method('getAbsolutePath') 92 | ->willReturnOnConsecutiveCalls(['absPathOne', 'absPathTwo']); 93 | 94 | $this->iteratorFactoryMock->expects($this->once()) 95 | ->method('create') 96 | ->willReturn($this->fileIteratorMock); 97 | } else { 98 | $this->readerMock->expects($this->once()) 99 | ->method('getConfigurationFiles') 100 | ->willReturn($this->fileIteratorMock); 101 | } 102 | 103 | $this->assertSame($this->fileIteratorMock, $this->sutObject->get($filename, $scope)); 104 | } 105 | 106 | /** 107 | * Data provider for get method. 108 | * @return string[][] 109 | */ 110 | public function dataProviderForGet(): array 111 | { 112 | return [ 113 | 'case_1_no_scope' => ['fileOne', ''], 114 | 'case_2_primary_scope' => ['fileTwo', 'primary'], 115 | 'case_3_global_scope' => ['fileThree', 'global'] 116 | ]; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Test/Unit/Model/Config/ConverterAbstractTest.php: -------------------------------------------------------------------------------- 1 | converterObject = new Converter(); 22 | } 23 | 24 | /** 25 | * @dataProvider dataProviderForConvert 26 | */ 27 | public function testConvert($xmlContent) 28 | { 29 | $domDocument = new DOMDocument(); 30 | $domDocument->loadXML($xmlContent); 31 | 32 | $result = $this->converterObject->convert($domDocument); 33 | $expected = [ 34 | 'some-identifier_0' => [ 35 | 'identifier' => 'some-identifier', 36 | 'title' => 'Some block title', 37 | 'content' => '
Content here
', 38 | 'version' => '1.0.0', 39 | 'is_active' => '1', 40 | 'store_ids' => '' 41 | ], 42 | 'some-identifier-2_1_2_3' => [ 43 | 'identifier' => 'some-identifier-2', 44 | 'title' => 'Some block title 2', 45 | 'content' => '
Content here 2
', 46 | 'version' => '1.0.1', 47 | 'is_active' => '1', 48 | 'store_ids' => '1,2,3' 49 | ] 50 | ]; 51 | 52 | $this->assertSame($expected, $result); 53 | } 54 | 55 | /** 56 | * data provider for convert function. The xmlContent value is hardcoded and taken from sample cms_block_data.xml 57 | * file present in etc/od_cms directory. 58 | * @return string[][] 59 | */ 60 | public function dataProviderForConvert(): array 61 | { 62 | $xmlContent = 'Some block titleContent here]]>1.0.01Some block title 2Content here 2]]>1.0.111,2,3'; 63 | return [[$xmlContent]]; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Test/Unit/Model/ConfigTest.php: -------------------------------------------------------------------------------- 1 | scopeConfig = $this->createMock(ScopeConfigInterface::class); 31 | $this->directoryList = $this->createMock(DirectoryList::class); 32 | 33 | $this->config = $this->createMock(Config::class); 34 | } 35 | 36 | /** 37 | * Test getBackupsDir 38 | * 39 | * @return void 40 | */ 41 | public function testGetBackupsDir() 42 | { 43 | $this->directoryList->expects($this->once()) 44 | ->method('getPath') 45 | ->willReturn('var'); 46 | 47 | $config = new Config( 48 | $this->scopeConfig, 49 | $this->directoryList 50 | ); 51 | 52 | $result = $config->getBackupsDir(); 53 | 54 | $expected = 'var' . DIRECTORY_SEPARATOR . Config::CMS_DIR; 55 | 56 | $this->assertEquals($expected, $result); 57 | } 58 | 59 | /** 60 | * Test getExportPath 61 | * 62 | * @return void 63 | */ 64 | public function testGetExportPath() 65 | { 66 | $this->directoryList->expects($this->once()) 67 | ->method('getPath') 68 | ->willReturn('var'); 69 | 70 | $config = new Config( 71 | $this->scopeConfig, 72 | $this->directoryList 73 | ); 74 | 75 | $result = $config->getExportPath(); 76 | 77 | $expected = 'var' . DIRECTORY_SEPARATOR . Config::CMS_DIR . DIRECTORY_SEPARATOR . Config::EXPORT_PATH; 78 | 79 | $this->assertEquals($expected, $result); 80 | } 81 | 82 | /** 83 | * Test getExtractPath 84 | * 85 | * @return void 86 | */ 87 | public function testGetExtractPath() 88 | { 89 | $this->directoryList->expects($this->once()) 90 | ->method('getPath') 91 | ->willReturn('var'); 92 | 93 | $config = new Config( 94 | $this->scopeConfig, 95 | $this->directoryList 96 | ); 97 | 98 | $result = $config->getExtractPath(); 99 | 100 | $expected = 'var' . DIRECTORY_SEPARATOR . Config::CMS_DIR . DIRECTORY_SEPARATOR . Config::EXTRACT_PATH; 101 | 102 | $this->assertEquals($expected, $result); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Test/Unit/Model/Service/ClearCMSHistoryTest.php: -------------------------------------------------------------------------------- 1 | fileMock = $this->getMockBuilder(File::class) 56 | ->disableOriginalConstructor() 57 | ->getMock(); 58 | $this->dateTimeMock = $this->getMockBuilder(DateTime::class) 59 | ->disableOriginalConstructor() 60 | ->getMock(); 61 | $this->configMock = $this->getMockBuilder(Config::class) 62 | ->disableOriginalConstructor() 63 | ->getMock(); 64 | $this->loggerInterfaceMock = $this->getMockBuilder(LoggerInterface::class) 65 | ->disableOriginalConstructor() 66 | ->getMock(); 67 | 68 | $this->serviceModel = new ClearCMSHistory( 69 | $this->dateTimeMock, 70 | $this->configMock, 71 | $this->loggerInterfaceMock, 72 | $this->fileMock 73 | ); 74 | } 75 | 76 | /** 77 | * Test clear action by providing two files in mock objects and expecting count of two returned in result. 78 | * @return void 79 | * @throws \Magento\Framework\Exception\FileSystemException 80 | */ 81 | public function testExecute() 82 | { 83 | $outerDirs = ['parentOne', 'parentTwo']; 84 | $files = ['fileOne', 'fileTwo']; 85 | 86 | $this->fileMock->expects($this->atLeastOnce()) 87 | ->method('readDirectory') 88 | ->willReturnOnConsecutiveCalls($outerDirs, $files, $files, $files, $files); 89 | 90 | $this->fileMock->expects($this->atLeastOnce()) 91 | ->method('isFile') 92 | ->willReturn(true); 93 | 94 | $this->configMock->expects($this->atLeastOnce()) 95 | ->method('getBackupsDir') 96 | ->willReturn(''); 97 | 98 | $this->configMock->expects($this->atLeastOnce()) 99 | ->method('getMethodType') 100 | ->willReturn(Config::PERIOD); 101 | 102 | $this->dateTimeMock->expects($this->atLeastOnce()) 103 | ->method('gmtTimestamp') 104 | ->willReturn(1659355018); 105 | 106 | $this->assertEquals(2, $this->serviceModel->execute(Config::TYPE_BLOCK)); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Test/Unit/Model/StoreManagementTest.php: -------------------------------------------------------------------------------- 1 | storeRepositoryMock = $this->getMockBuilder(StoreRepositoryInterface::class) 34 | ->disableOriginalConstructor() 35 | ->getMock(); 36 | $this->storeInterfaceMock = $this->getMockBuilder(StoreInterface::class) 37 | ->disableOriginalConstructor() 38 | ->getMock(); 39 | $this->model = new StoreManagement( 40 | $this->storeRepositoryMock 41 | ); 42 | } 43 | 44 | /** 45 | * @return array 46 | */ 47 | public function dataProviderGetStoreIdsByCodes(): array 48 | { 49 | return [ 50 | 'store_not_found_case' => [ 51 | 'storeCodes' => ['us'], 52 | 'expectedResult' => [] 53 | ], 54 | 'store_found_case' => [ 55 | 'storeCodes' => ['nz'], 56 | 'expectedResult' => [1] 57 | ], 58 | 'admin_case' => [ 59 | 'storeCodes' => ['admin'], 60 | 'expectedResult' => [0] 61 | ], 62 | 'multi_store_case' => [ 63 | 'storeCodes' => ['admin', 'nz', 'us', 'au'], 64 | 'expectedResult' => [0, 1, 2] 65 | ] 66 | ]; 67 | } 68 | 69 | /** 70 | * @dataProvider dataProviderGetStoreIdsByCodes 71 | * @return void 72 | */ 73 | public function testGetStoreIdsByCodes($storeCodes, $expectedResult) 74 | { 75 | $this->storeInterfaceMock 76 | ->method('getId') 77 | ->willReturn(1); 78 | 79 | $auStore = $this->getMockBuilder(StoreInterface::class) 80 | ->disableOriginalConstructor() 81 | ->getMock(); 82 | $auStore->method('getId') 83 | ->willReturn(2); 84 | 85 | $this->storeRepositoryMock 86 | ->method('get') 87 | ->withConsecutive(['nz'], ['au']) 88 | ->willReturnOnConsecutiveCalls($this->storeInterfaceMock, $auStore); 89 | 90 | $this->storeRepositoryMock->expects($this->atLeastOnce()) 91 | ->method('getList') 92 | ->willReturn(['admin' => 'adminStore', 'nz' => 'nzStore', 'au' => 'auStore']); 93 | 94 | $this->assertEquals($expectedResult, $this->model->getStoreIdsByCodes($storeCodes)); 95 | } 96 | 97 | /** 98 | * @return array 99 | */ 100 | public function dataProviderFilterStoresByStoreCodes(): array 101 | { 102 | return [ 103 | 'store_not_found_case' => [ 104 | 'storeCodes' => ['pk'], 105 | 'expectedResult' => [] 106 | ], 107 | 'store_found_case' => [ 108 | 'storeCodes' => ['nz'], 109 | 'expectedResult' => ['nz'] 110 | ], 111 | 'admin_case' => [ 112 | 'storeCodes' => ['admin'], 113 | 'expectedResult' => ['admin'] 114 | ], 115 | 'multi_store_case' => [ 116 | 'storeCodes' => ['admin', 'au', 'nz', 'ua'], 117 | 'expectedResult' => ['admin', 'nz'] 118 | ] 119 | ]; 120 | } 121 | 122 | /** 123 | * @dataProvider dataProviderFilterStoresByStoreCodes 124 | * @return void 125 | */ 126 | public function testFilterStoresByStoreCodes($storeCodes, $expectedResult) 127 | { 128 | $this->storeRepositoryMock->expects($this->once()) 129 | ->method('getList') 130 | ->willReturn(['admin' => 'adminStore', 'nz' => 'nzStore']); 131 | 132 | $this->assertSame($expectedResult, $this->model->filterStoresByStoreCodes($storeCodes)); 133 | } 134 | 135 | /** 136 | * @return array 137 | */ 138 | public function dataProviderFilterStoresByStoreIds(): array 139 | { 140 | return [ 141 | 'store_not_found_case' => [ 142 | 'storeIds' => [77], 143 | 'expectedResult' => [] 144 | ], 145 | 'store_found_case' => [ 146 | 'storeIds' => [1], 147 | 'expectedResult' => [1] 148 | ], 149 | 'admin_case' => [ 150 | 'storeIds' => [0], 151 | 'expectedResult' => [0] 152 | ], 153 | 'multi_store_case' => [ 154 | 'storeIds' => [0, 1, 2, 77], 155 | 'expectedResult' => [0, 1, 2] 156 | ] 157 | ]; 158 | } 159 | 160 | /** 161 | * @dataProvider dataProviderFilterStoresByStoreIds 162 | * @return void 163 | */ 164 | public function testFilterStoresByStoreIds($storeIds, $expectedResult) 165 | { 166 | $adminStore = $this->getMockBuilder(StoreInterface::class) 167 | ->disableOriginalConstructor() 168 | ->getMock(); 169 | $adminStore->expects($this->atLeastOnce()) 170 | ->method('getId') 171 | ->willReturn(0); 172 | 173 | $nzStore = $this->getMockBuilder(StoreInterface::class) 174 | ->disableOriginalConstructor() 175 | ->getMock(); 176 | $nzStore->expects($this->atLeastOnce()) 177 | ->method('getId') 178 | ->willReturn(1); 179 | 180 | $auStore = $this->getMockBuilder(StoreInterface::class) 181 | ->disableOriginalConstructor() 182 | ->getMock(); 183 | $auStore->expects($this->atLeastOnce()) 184 | ->method('getId') 185 | ->willReturn(2); 186 | 187 | $this->storeRepositoryMock->expects($this->atLeastOnce()) 188 | ->method('getList') 189 | ->willReturn(['admin' => $adminStore, 'nz' => $nzStore, 'au' => $auStore]); 190 | 191 | $this->assertSame($expectedResult, $this->model->filterStoresByStoreIds($storeIds)); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /Test/Unit/Observer/CmsSaveBeforeTest.php: -------------------------------------------------------------------------------- 1 | cmsSaveBefore = $this->getMockBuilder(CmsSaveBefore::class) 28 | ->disableOriginalConstructor(true) 29 | ->getMock(); 30 | } 31 | 32 | /** 33 | * Test function: HasImportantDataChanges 34 | * 35 | * @param $cmsEntity 36 | * @param bool $expected 37 | * @dataProvider formHasImportantDataChangesProvider 38 | * 39 | * @return void 40 | * @throws \ReflectionException 41 | */ 42 | public function testHasImportantDataChanges($cmsEntity, bool $expected) 43 | { 44 | $result = $this->invokeMethod( 45 | $this->cmsSaveBefore, 46 | 'hasImportantDataChanges', 47 | [ 48 | $cmsEntity 49 | ] 50 | ); 51 | 52 | $this->assertEquals($expected, $result); 53 | } 54 | 55 | /** 56 | * Provider testHasImportantDataChanges 57 | * 58 | * @return array 59 | */ 60 | public function formHasImportantDataChangesProvider(): array 61 | { 62 | $testCases = []; 63 | 64 | $dataModel = $this->getMockBuilder(AbstractModel::class) 65 | ->disableOriginalConstructor() 66 | ->getMockForAbstractClass(); 67 | 68 | $dataModelCase1 = clone $dataModel; 69 | $dataModelCase1->setData([ 70 | 'identifier' => 'no-route', 71 | 'title' => 'no-route', 72 | 'content' => 'no-route', 73 | 'content_heading' => 'no-route', 74 | ]); 75 | $dataModelCase1->setOrigData('identifier', 'no-route'); 76 | $dataModelCase1->setOrigData('title', 'no-route'); 77 | $dataModelCase1->setOrigData('content', 'no-route'); 78 | $dataModelCase1->setOrigData('content_heading', 'no-route'); 79 | 80 | $testCases['case_1_true'] = [ 81 | 'cmsEntity' => $dataModelCase1, 82 | 'expected' => false 83 | ]; 84 | 85 | $dataModelCase2 = clone $dataModel; 86 | $dataModelCase2->setData([ 87 | 'identifier' => 'no-route2', 88 | 'title' => 'no-route', 89 | 'content' => 'no-route', 90 | 'content_heading' => 'no-route', 91 | ]); 92 | $dataModelCase2->setOrigData('identifier', 'no-route'); 93 | $dataModelCase2->setOrigData('title', 'no-route'); 94 | $dataModelCase2->setOrigData('content', 'no-route'); 95 | $dataModelCase2->setOrigData('content_heading', 'no-route'); 96 | 97 | $testCases['case_2_false'] = [ 98 | 'cmsEntity' => $dataModelCase2, 99 | 'expected' => true 100 | ]; 101 | 102 | $dataModelCase3 = clone $dataModel; 103 | $dataModelCase3->setData([ 104 | 'identifier' => 'no-route2', 105 | 'title' => 'no-route', 106 | 'content' => 'no-route', 107 | 'content_heading' => 'no-route', 108 | ]); 109 | 110 | $testCases['case_3_false'] = [ 111 | 'cmsEntity' => $dataModelCase3, 112 | 'expected' => false 113 | ]; 114 | 115 | return $testCases; 116 | } 117 | 118 | /** 119 | * Invoke private method 120 | * 121 | * @param $object 122 | * @param $methodName 123 | * @param array $parameters 124 | * 125 | * @return mixed 126 | * @throws \ReflectionException 127 | */ 128 | protected function invokeMethod(&$object, $methodName, array $parameters = []) 129 | { 130 | $reflection = new \ReflectionClass(get_class($object)); 131 | $method = $reflection->getMethod($methodName); 132 | $method->setAccessible(true); 133 | 134 | return $method->invokeArgs($object, $parameters); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Ui/DataProvider/HistoryView.php: -------------------------------------------------------------------------------- 1 | backupManager = $backupManager; 68 | $this->jsonFormatter = $jsonFormatter; 69 | $this->file = $file; 70 | $this->request = $request; 71 | } 72 | 73 | /** 74 | * Get data 75 | * 76 | * @return array 77 | */ 78 | public function getData(): array 79 | { 80 | $data = []; 81 | if ($backupItemIdentifier = $this->request->getParam('bc_identifier')) { 82 | $parsedArray = $this->jsonFormatter->decode($this->getBackupContent(), true); 83 | $data[$backupItemIdentifier] = [ 84 | 'identifier' => $parsedArray['identifier'] ?? '', 85 | 'title' => $parsedArray['title'] ?? '', 86 | 'content' => $parsedArray['content'] ?? '' 87 | ]; 88 | } 89 | 90 | return $data; 91 | } 92 | 93 | /** 94 | * Retrieve content from backup 95 | * 96 | * @return mixed 97 | */ 98 | public function getBackupContent() 99 | { 100 | $itemIdentifier = $this->request->getParam('bc_identifier'); 101 | $itemName = $this->request->getParam('item'); 102 | $itemType = $this->request->getParam('bc_type'); 103 | $storeId = $this->request->getParam('store_id'); 104 | 105 | if (!is_null($storeId)) { 106 | $path = $this->backupManager->getBackupPathByStoreId($itemType, $itemIdentifier, (int)$storeId) 107 | . DIRECTORY_SEPARATOR . $itemName; 108 | } else { 109 | $path = $this->backupManager->getBackupPath($itemType, $itemIdentifier) 110 | . DIRECTORY_SEPARATOR . $itemName; 111 | } 112 | 113 | return $this->file->readData($path); 114 | } 115 | 116 | /** 117 | * @inheritDoc 118 | */ 119 | public function addFilter(Filter $filter) 120 | { 121 | return $this; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Ui/DataProvider/Import.php: -------------------------------------------------------------------------------- 1 | =102.0", 14 | "overdose/module-core": "*" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "Overdose\\CMSContent\\": "" 19 | }, 20 | "files": [ 21 | "registration.php" 22 | ] 23 | }, 24 | "version": "2.0.5" 25 | } 26 | -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /etc/adminhtml/events.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 | 14 | 15 | 16 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /etc/adminhtml/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /etc/adminhtml/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | od_core 7 | Overdose_CMSContent::config_overdose_cmscontent 8 | 9 | 10 | 11 | 12 | 13 | Magento\Config\Model\Config\Source\Yesno 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | In case enabled: old backups will be deleted by cron according to selected deleting method. 24 | Magento\Config\Model\Config\Source\Yesno 25 | 26 | 27 | 28 | Overdose\CMSContent\Model\OptionSource\Methods 29 | By Periods: Left one file per a period (weeks, months and years):
31 | - One history item per year for older than year.
32 | - One history item per month for older than month but yonger 12m.
33 | - One history item per week for older than week but yonger one month.
34 | Files younger than one week, excluded.

35 |

Older Than: Delete files older than 'Period' and it number.

36 | ]]>
37 |
38 | 39 | 40 | Overdose\CMSContent\Model\OptionSource\Periods 41 | 42 | older_than 43 | 44 | 45 | 46 | 47 | 48 | older_than 49 | 50 | 51 | 52 | 1 53 | 54 |
55 | 56 | 57 | 58 | 59 | Magento\Cron\Model\Config\Source\Frequency 60 | Overdose\CMSContent\Model\Config\Cron\SaveValue 61 | 62 | 63 | 64 | 67 | 68 | 69 | 1 70 | 71 | 72 |
73 | 74 | 75 | 76 | 77 | 78 | Magento\Config\Model\Config\Source\Yesno 79 | 80 | 81 | 1 82 | 83 | 84 |
85 |
86 |
87 | -------------------------------------------------------------------------------- /etc/cms_block_data.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /etc/cms_page_data.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0 7 | 8 | 9 | 10 | 0 11 | older_than 12 | 5 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /etc/crontab.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | crontab/default/jobs/cms_content_delete_backups/schedule/cron_expr 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /etc/db_schema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /etc/od_cms/cms_block_data.xml.sample: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Some block title 6 | Content here ]]> 7 | 1.0.0 8 | 1 9 | 10 | 11 | 12 | Some block title 2 13 | Content here 2]]> 14 | 1.0.1 15 | 1 16 | 1,2,3 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /etc/od_cms/cms_page_data.xml.sample: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Some title 6 | Content here ]]> 7 | 1.0.0 8 | 1 9 | 10 | 11 | 12 | Some title 2 13 | Some content heading 2 14 | Content here 2]]> 15 | 2columns-left 16 | 1.0.1 17 | 1 18 | 1,2,3 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /etc/od_cms/cms_page_data_no-route.xml.sample: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | no-route 6 | 404 Not Found 7 | 2columns-right 8 | Page keywords 9 | Page description 10 | Whoops, our bad... 11 | 12 |
The page you requested was not found, and we have a fine guess why.
13 |
14 |
    15 |
  • If you typed the URL directly, please make sure the spelling is correct.
  • 16 |
  • If you clicked on a link to get here, the link is outdated.
  • 17 |
18 | 19 |
20 |
What can you do?
21 |
Have no fear, help is near! There are many ways you can get back on track with Magento Store.
22 |
23 |
    24 |
  • Go back to the previous page.
  • 25 |
  • Use the search bar at the top of the page to search for your products.
  • 26 |
  • Follow these links to get you back on track!
    Store Home | My Account
27 | ]]>
28 | 0 29 | 30 | 31 | 32 | 33 | 34 | 35 | 1 36 | 0 37 | 1.0.1 38 |
39 |
40 |
41 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /view/adminhtml/layout/cmscontent_import_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /view/adminhtml/templates/cms/backup/history.phtml: -------------------------------------------------------------------------------- 1 | 2 | getBackups(); ?> 3 |
4 | 7 |
8 |
9 | 10 |
11 | 12 |
13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 |
21 | 22 |
23 | 27 | 50 | 51 | -------------------------------------------------------------------------------- /view/adminhtml/templates/history/view.phtml: -------------------------------------------------------------------------------- 1 | 6 | getBackupContent(); ?> 7 | -------------------------------------------------------------------------------- /view/adminhtml/ui_component/cms_block_form.xml: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | 7 | 8 | 9 | 10 | 11 | true 12 | 13 | 14 | 15 | 18 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /view/adminhtml/ui_component/cms_block_listing.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | json_export 9 | Export to JSON file 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | json_split_export 18 | Export to split JSON files 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | xml_export 27 | Export to XML file 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | xml_split_export 36 | Export to split XML files 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /view/adminhtml/ui_component/cms_page_form.xml: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | true 13 | 14 | 15 | 16 | 19 | 20 |
21 |
22 | -------------------------------------------------------------------------------- /view/adminhtml/ui_component/cms_page_listing.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | export_json 9 | Export JSON 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | export_json_split 18 | Export as split JSON files 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | export_xml 27 | Export XML 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | export_xml_split 36 | Export as split XML files 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /view/adminhtml/ui_component/cmscontent_import_form.xml: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | cmscontent_import_form.cmscontent_import_form_data_source 6 | 7 | CMS Content Import 8 | templates/form/collapsible 9 | 10 | 11 | 12 | cmscontent_import_form 13 | data 14 | 15 | cmscontent_import_form.cmscontent_import_form_data_source 16 | 17 | 18 |