├── Block
└── Adminhtml
│ └── Button
│ ├── Back.php
│ ├── Delete.php
│ ├── Reset.php
│ └── Save.php
├── Console
└── Command
│ └── Deploy.php
├── Controller
└── Adminhtml
│ ├── Delete.php
│ ├── Edit.php
│ ├── Heartbeat
│ └── Index.php
│ ├── Index.php
│ ├── InlineEdit.php
│ ├── MassDelete.php
│ ├── NewAction.php
│ ├── Save.php
│ └── Upload.php
├── Generator
├── ListRepo.php
├── NameMatcher.php
├── Repo.php
├── UiCollectionProvider.php
└── UiManager.php
├── LICENSE.txt
├── Model
├── FileChecker.php
├── FileInfo.php
├── ResourceModel
│ ├── AbstractModel.php
│ ├── Collection
│ │ ├── AbstractCollection.php
│ │ └── StoreAwareAbstractCollection.php
│ ├── Relation
│ │ └── Store
│ │ │ ├── ReadHandler.php
│ │ │ └── SaveHandler.php
│ ├── Store.php
│ └── StoreAwareAbstractModel.php
└── Uploader.php
├── README.md
├── Source
├── Catalog
│ ├── ProductAttribute.php
│ ├── ProductAttributeOptions.php
│ └── ProductAttributeSet.php
├── Options.php
└── StoreView.php
├── Test
└── Unit
│ ├── Block
│ └── Adminhtml
│ │ └── Button
│ │ ├── BackTest.php
│ │ ├── DeleteTest.php
│ │ ├── ResetTest.php
│ │ └── SaveTest.php
│ ├── Console
│ └── Command
│ │ └── DeployTest.php
│ ├── Controller
│ ├── DeleteTest.php
│ ├── EditTest.php
│ ├── Heartbeat
│ │ └── IndexTest.php
│ ├── IndexTest.php
│ ├── InlineEditTest.php
│ ├── MassDeleteTest.php
│ ├── NewActionTest.php
│ ├── SaveTest.php
│ └── UploadTest.php
│ ├── Generator
│ ├── ListRepoTest.php
│ ├── NameMatcherTest.php
│ ├── RepoTest.php
│ ├── UiCollectionProviderTest.php
│ └── UiManagerTest.php
│ ├── Model
│ ├── FileCheckerTest.php
│ ├── FileInfoTest.php
│ ├── ResourceModel
│ │ ├── AbstractModelTest.php
│ │ ├── Collection
│ │ │ ├── AbstractCollectionTest.php
│ │ │ └── StoreAwareAbstractCollectionTest.php
│ │ ├── Relation
│ │ │ └── Store
│ │ │ │ ├── ReadHandlerTest.php
│ │ │ │ └── SaveHandlerTest.php
│ │ ├── StoreAwareAbstractModelTest.php
│ │ └── StoreTest.php
│ └── UploaderTest.php
│ ├── Source
│ ├── Catalog
│ │ ├── ProductAttributeOptionsTest.php
│ │ ├── ProductAttributeSetTest.php
│ │ └── ProductAttributeTest.php
│ ├── OptionsTest.php
│ └── StoreViewTest.php
│ ├── Ui
│ ├── Component
│ │ └── Listing
│ │ │ ├── ActionsColumnTest.php
│ │ │ └── ImageTest.php
│ ├── EntityUiConfigTest.php
│ ├── Form
│ │ ├── DataModifier
│ │ │ ├── CompositeDataModifierTest.php
│ │ │ ├── DataModifierTest.php
│ │ │ ├── MultiselectTest.php
│ │ │ ├── NullModifierTest.php
│ │ │ └── UploadTest.php
│ │ └── DataProviderTest.php
│ └── SaveDataProcessor
│ │ ├── CompositeProcessorTest.php
│ │ ├── DateTest.php
│ │ ├── DynamicRowsTest.php
│ │ ├── MultiselectTest.php
│ │ ├── NullProcessorTest.php
│ │ └── UploadTest.php
│ └── ViewModel
│ ├── Formatter
│ ├── DateTest.php
│ ├── FileTest.php
│ ├── ImageTest.php
│ ├── OptionTest.php
│ ├── TextTest.php
│ └── WysiwygTest.php
│ ├── FormatterTest.php
│ └── HeartbeatTest.php
├── Ui
├── CollectionProviderInterface.php
├── Component
│ └── Listing
│ │ ├── ActionsColumn.php
│ │ └── Image.php
├── EntityUiConfig.php
├── EntityUiManagerInterface.php
├── Form
│ ├── DataModifier
│ │ ├── CompositeDataModifier.php
│ │ ├── DynamicRows.php
│ │ ├── Multiselect.php
│ │ ├── NullModifier.php
│ │ └── Upload.php
│ ├── DataModifierInterface.php
│ └── DataProvider.php
├── SaveDataProcessor
│ ├── CompositeProcessor.php
│ ├── Date.php
│ ├── DynamicRows.php
│ ├── Multiselect.php
│ ├── NullProcessor.php
│ └── Upload.php
└── SaveDataProcessorInterface.php
├── ViewModel
├── Formatter.php
├── Formatter
│ ├── Date.php
│ ├── File.php
│ ├── FormatterInterface.php
│ ├── Image.php
│ ├── Options.php
│ ├── Text.php
│ └── Wysiwyg.php
└── Heartbeat.php
├── composer.json
├── etc
├── adminhtml
│ └── routes.xml
├── crud
│ └── di.xml
├── di.xml
└── module.xml
├── i18n
└── en_US.csv
├── registration.php
└── view
└── adminhtml
├── templates
└── heartbeat.phtml
└── web
├── js
└── heartbeat.js
└── template
└── preview.html
/Block/Adminhtml/Button/Back.php:
--------------------------------------------------------------------------------
1 | url = $url;
47 | $this->uiConfig = $uiConfig;
48 | }
49 |
50 | /**
51 | * @return array
52 | */
53 | public function getButtonData()
54 | {
55 | return [
56 | 'label' => $this->getLabel(),
57 | 'on_click' => sprintf("location.href = '%s';", $this->url->getUrl('*/*/')),
58 | 'class' => 'back',
59 | 'sort_order' => 10
60 | ];
61 | }
62 |
63 | /**
64 | * @return string
65 | */
66 | private function getLabel()
67 | {
68 | return $this->uiConfig ? $this->uiConfig->getBackLabel() : __('Back')->render();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Block/Adminhtml/Button/Delete.php:
--------------------------------------------------------------------------------
1 | request = $request;
64 | $this->entityUiManager = $entityUiManager;
65 | $this->uiConfig = $uiConfig;
66 | $this->url = $url;
67 | }
68 |
69 | /**
70 | * @inheritDoc
71 | */
72 | public function getButtonData()
73 | {
74 | $data = [];
75 | if ($this->getEntityId()) {
76 | $data = [
77 | 'label' => $this->uiConfig->getDeleteLabel(),
78 | 'class' => 'delete',
79 | 'on_click' => 'deleteConfirm(\'' .
80 | $this->uiConfig->getDeleteMessage() . '\', \'' . $this->getDeleteUrl() . '\', {"data": {}})',
81 | 'sort_order' => 20,
82 | ];
83 | }
84 | return $data;
85 | }
86 |
87 | /**
88 | * @return int|null
89 | */
90 | private function getEntityId(): ?int
91 | {
92 | try {
93 | return $this->entityUiManager->get(
94 | (int)$this->request->getParam($this->uiConfig->getRequestParamName(), 0)
95 | )->getId();
96 | } catch (NoSuchEntityException $e) {
97 | return null;
98 | }
99 | }
100 |
101 | /**
102 | * @return string
103 | */
104 | private function getDeleteUrl(): string
105 | {
106 | return $this->url->getUrl(
107 | '*/*/delete',
108 | [$this->uiConfig->getRequestParamName() => $this->getEntityId()]
109 | );
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Block/Adminhtml/Button/Reset.php:
--------------------------------------------------------------------------------
1 | __('Reset'),
37 | 'class' => 'reset',
38 | 'on_click' => 'location.reload();',
39 | 'sort_order' => 30
40 | ];
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Console/Command/Deploy.php:
--------------------------------------------------------------------------------
1 | reader = $reader;
58 | $this->ioFile = $ioFile;
59 | $this->directoryList = $directoryList;
60 | parent::__construct($name);
61 | }
62 |
63 | /**
64 | * configure command
65 | */
66 | protected function configure()
67 | {
68 | $this->setName('umc:crud:deploy');
69 | $this->setDescription('Copies the required di.xml file for Umc_Crud module to app/etc');
70 | $this->addOption("force", "-f", InputOption::VALUE_NONE, "Force deploy config");
71 | }
72 |
73 | /**
74 | * @param InputInterface $input
75 | * @param OutputInterface $output
76 | * @return int|null|void
77 | * @throws \Magento\Framework\Exception\FileSystemException
78 | */
79 | protected function execute(InputInterface $input, OutputInterface $output)
80 | {
81 | if ($output->isVerbose()) {
82 | $output->writeln('Copying CRUD generators config to root etc folder');
83 | }
84 | $source = $this->reader->getModuleDir('etc', 'Umc_Crud') . '/' . self::DI_FOLDER . '/di.xml';
85 | $destination = $this->directoryList->getPath('etc') . '/crud';
86 | $destinationFile = $destination . '/di.xml';
87 |
88 | $isForce = $input->getOption('force');
89 | if (!$isForce && $this->ioFile->fileExists($destinationFile)) {
90 | $output->writeln($destinationFile . " already exists. Use -f to overwrite it.");
91 | } else {
92 | $this->ioFile->checkAndCreateFolder($destination);
93 | $this->ioFile->cp($source, $destinationFile);
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/Delete.php:
--------------------------------------------------------------------------------
1 | uiConfig = $uiConfig;
53 | $this->uiManager = $uiManager;
54 | parent::__construct($context);
55 | }
56 |
57 | /**
58 | * @return \Magento\Framework\Controller\Result\Redirect
59 | */
60 | public function execute()
61 | {
62 | $paramName = $this->uiConfig->getRequestParamName();
63 | /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */
64 | $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
65 | $id = (int)$this->getRequest()->getParam($paramName);
66 | if ($id) {
67 | try {
68 | $this->uiManager->delete($id);
69 | $this->messageManager->addSuccessMessage($this->uiConfig->getDeleteSuccessMessage());
70 | $resultRedirect->setPath('*/*/');
71 | return $resultRedirect;
72 | } catch (NoSuchEntityException $e) {
73 | $this->messageManager->addErrorMessage($this->uiConfig->getDeleteMissingEntityMessage());
74 | $resultRedirect->setPath('*/*/');
75 | return $resultRedirect;
76 | } catch (LocalizedException $e) {
77 | $this->messageManager->addErrorMessage($e->getMessage());
78 | $resultRedirect->setPath('*/*/edit', [$paramName => $id]);
79 | return $resultRedirect;
80 | } catch (\Exception $e) {
81 | $this->messageManager->addErrorMessage($this->uiConfig->getGeneralDeleteErrorMessage());
82 | $resultRedirect->setPath('*/*/edit', [$paramName => $id]);
83 | return $resultRedirect;
84 | }
85 | }
86 | $this->messageManager->addErrorMessage($this->uiConfig->getDeleteMissingEntityMessage());
87 | $resultRedirect->setPath('*/*/');
88 | return $resultRedirect;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/Edit.php:
--------------------------------------------------------------------------------
1 | entityUiManager = $entityUiManager;
51 | $this->uiConfig = $uiConfig;
52 | parent::__construct($context);
53 | }
54 |
55 | /**
56 | * @return \Magento\Backend\Model\View\Result\Page
57 | */
58 | public function execute()
59 | {
60 | $id = (int)$this->getRequest()->getParam($this->uiConfig->getRequestParamName());
61 | $entity = $this->entityUiManager->get($id);
62 | /** @var \Magento\Backend\Model\View\Result\Page $resultPage */
63 | $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE);
64 | $activeMenu = $this->uiConfig->getMenuItem();
65 | if ($activeMenu) {
66 | $resultPage->setActiveMenu($activeMenu);
67 | }
68 | $resultPage->getConfig()->getTitle()->prepend($this->uiConfig->getListPageTitle());
69 | if (!$entity->getId()) {
70 | $resultPage->getConfig()->getTitle()->prepend($this->uiConfig->getNewLabel());
71 | } else {
72 | $resultPage->getConfig()->getTitle()->prepend($entity->getData($this->uiConfig->getNameAttribute()));
73 | }
74 | return $resultPage;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/Heartbeat/Index.php:
--------------------------------------------------------------------------------
1 | resultFactory->create(ResultFactory::TYPE_JSON);
38 | $response->setData([]);
39 | return $response;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/Index.php:
--------------------------------------------------------------------------------
1 | uiConfig = $uiConfig;
46 | }
47 |
48 | /**
49 | * @return \Magento\Backend\Model\View\Result\Page
50 | */
51 | public function execute()
52 | {
53 | /** @var \Magento\Backend\Model\View\Result\Page $resultPage */
54 | $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE);
55 | $listMenuItem = $this->uiConfig->getMenuItem();
56 | if ($listMenuItem) {
57 | $resultPage->setActiveMenu($listMenuItem);
58 | }
59 | $pageTitle = $this->uiConfig->getListPageTitle();
60 | if ($pageTitle) {
61 | $resultPage->getConfig()->getTitle()->prepend($pageTitle);
62 | }
63 | return $resultPage;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/InlineEdit.php:
--------------------------------------------------------------------------------
1 | dataProcessor = $dataProcessor;
53 | $this->entityUiManager = $entityUiManager;
54 | parent::__construct($context);
55 | }
56 |
57 | /**
58 | * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\Result\Json
59 | * |\Magento\Framework\Controller\ResultInterface
60 | * @throws \Magento\Framework\Exception\NoSuchEntityException
61 | */
62 | public function execute()
63 | {
64 | /** @var \Magento\Framework\Controller\Result\Json $resultJson */
65 | $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON);
66 | $error = false;
67 | $messages = [];
68 |
69 | if ($this->getRequest()->getParam('isAjax')) {
70 | $postItems = $this->getRequest()->getParam('items', []);
71 | if (!count($postItems)) {
72 | $messages[] = __('Please correct the data sent.');
73 | $error = true;
74 | } else {
75 | foreach (array_keys($postItems) as $id) {
76 | try {
77 | $entity = $this->entityUiManager->get($id);
78 | $newData = $this->dataProcessor->modifyData($postItems[$id]);
79 | // phpcs:disable Magento2.Performance.ForeachArrayMerge
80 | $entity->setData(array_merge($entity->getData(), $newData));
81 | // phpcs:enable
82 | $this->entityUiManager->save($entity);
83 | } catch (\Exception $e) {
84 | $messages[] = '[' . __('Error') . ': ' . $id . '] ' . $e->getMessage();
85 | $error = true;
86 | }
87 | }
88 | }
89 | }
90 | $resultJson->setData([
91 | 'messages' => $messages,
92 | 'error' => $error
93 | ]);
94 | return $resultJson;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/MassDelete.php:
--------------------------------------------------------------------------------
1 | filter = $filter;
69 | $this->collectionProvider = $collectionProvider;
70 | $this->uiConfig = $uiConfig;
71 | $this->uiManager = $uiManager;
72 | parent::__construct($context);
73 | }
74 |
75 | /**
76 | * execute action
77 | *
78 | * @return \Magento\Framework\Controller\Result\Redirect
79 | */
80 | public function execute()
81 | {
82 | try {
83 | $collection = $this->filter->getCollection($this->collectionProvider->getCollection());
84 | $collectionSize = $collection->getSize();
85 | foreach ($collection as $entity) {
86 | $this->uiManager->delete((int)$entity->getId());
87 | }
88 | $this->messageManager->addSuccessMessage(
89 | $this->uiConfig->getMassDeleteSuccessMessage($collectionSize)
90 | );
91 | } catch (LocalizedException $e) {
92 | $this->messageManager->addErrorMessage($e->getMessage());
93 | } catch (\Exception $e) {
94 | $this->messageManager->addErrorMessage($this->uiConfig->getMassDeleteErrorMessage());
95 | }
96 | /** @var \Magento\Framework\Controller\Result\Redirect $redirectResult */
97 | $redirectResult = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
98 | $redirectResult->setPath('*/*/index');
99 | return $redirectResult;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/NewAction.php:
--------------------------------------------------------------------------------
1 | resultFactory->create(ResultFactory::TYPE_FORWARD);
37 | $forward->forward('edit');
38 | return $forward;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/Upload.php:
--------------------------------------------------------------------------------
1 | uploader = $uploader;
47 | parent::__construct($context);
48 | }
49 |
50 | /**
51 | * @return \Magento\Framework\Controller\ResultInterface
52 | */
53 | public function execute()
54 | {
55 | try {
56 | $result = $this->uploader->saveFileToTmpDir($this->getFieldName());
57 | } catch (\Exception $e) {
58 | $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()];
59 | }
60 | $response = $this->resultFactory->create(ResultFactory::TYPE_JSON);
61 | $response->setData($result);
62 | return $response;
63 | }
64 |
65 | /**
66 | * @return string
67 | */
68 | protected function getFieldName()
69 | {
70 | return $this->getRequest()->getParam('field');
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2020 Marius Strajeru
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/Model/FileChecker.php:
--------------------------------------------------------------------------------
1 | file = $file;
40 | }
41 |
42 | /**
43 | * @param $destinationFile
44 | * @param int $sparseLevel
45 | * @return string
46 | */
47 | public function getNewFileName($destinationFile, $sparseLevel = 2)
48 | {
49 | $fileInfo = $this->file->getPathInfo($destinationFile);
50 | if ($this->file->fileExists($destinationFile)) {
51 | $index = 1;
52 | $baseName = $fileInfo['filename'] . '.' . $fileInfo['extension'];
53 | while ($this->file->fileExists($fileInfo['dirname'] . '/' . $baseName)) {
54 | $baseName = $fileInfo['filename'] . '_' . $index . '.' . $fileInfo['extension'];
55 | $index++;
56 | }
57 | return $baseName;
58 | } else {
59 | $prefix = $sparseLevel > 0 ? '/' : '';
60 | $fileName = $fileInfo['filename'];
61 | for ($i = 0; $i < $sparseLevel; $i++) {
62 | $prefix .= ($fileName[$i] ?? '_') . '/';
63 | }
64 | return $prefix . $fileInfo['basename'];
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Model/ResourceModel/AbstractModel.php:
--------------------------------------------------------------------------------
1 | entityManager = $entityManager;
61 | $this->metadataPool = $metadataPool;
62 | $this->interfaceClass = $interfaceClass;
63 | parent::__construct($context, $connectionName);
64 | }
65 |
66 | /**
67 | * @param AbstractModel $object
68 | * @return $this
69 | * @throws \Exception
70 | */
71 | public function save(\Magento\Framework\Model\AbstractModel $object)
72 | {
73 | $this->entityManager->save($object);
74 | return $this;
75 | }
76 |
77 | /**
78 | * @param \Magento\Framework\Model\AbstractModel $object
79 | * @return $this|AbstractDb
80 | * @throws \Exception
81 | */
82 | public function delete(\Magento\Framework\Model\AbstractModel $object)
83 | {
84 | $this->entityManager->delete($object);
85 | return $this;
86 | }
87 |
88 | /**
89 | * @return false|\Magento\Framework\DB\Adapter\AdapterInterface
90 | * @throws \Exception
91 | */
92 | public function getConnection()
93 | {
94 | return $this->metadataPool->getMetadata($this->interfaceClass)->getEntityConnection();
95 | }
96 |
97 | /**
98 | * @param \Magento\Framework\Model\AbstractModel $object
99 | * @param mixed $value
100 | * @param null $field
101 | * @return $this|AbstractDb
102 | * @throws LocalizedException
103 | */
104 | public function load(\Magento\Framework\Model\AbstractModel $object, $value, $field = null)
105 | {
106 | $this->entityManager->load($object, $value);
107 | return $this;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/Model/ResourceModel/Relation/Store/ReadHandler.php:
--------------------------------------------------------------------------------
1 | resource = $resource;
46 | $this->storeIdField = $storeIdField;
47 | }
48 |
49 | /**
50 | * @param object $entity
51 | * @param array $arguments
52 | * @return bool|object
53 | * @throws \Magento\Framework\Exception\LocalizedException
54 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
55 | */
56 | public function execute($entity, $arguments = [])
57 | {
58 | if ($entity->getId()) {
59 | $stores = $this->resource->lookupStoreIds((int)$entity->getId());
60 | $entity->setData($this->storeIdField, $stores);
61 | }
62 | return $entity;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Model/ResourceModel/Relation/Store/SaveHandler.php:
--------------------------------------------------------------------------------
1 | metadataPool = $metadataPool;
68 | $this->resource = $resource;
69 | $this->entityType = $entityType;
70 | $this->storeTable = $storeTable;
71 | $this->storeIdField = $storeIdField;
72 | }
73 |
74 | /**
75 | * @param object $entity
76 | * @param array $arguments
77 | * @return object
78 | * @throws \Exception
79 | */
80 | public function execute($entity, $arguments = [])
81 | {
82 | $entityMetadata = $this->metadataPool->getMetadata($this->entityType);
83 | $linkField = $entityMetadata->getLinkField();
84 |
85 | $connection = $this->resource->getConnection();
86 |
87 | $oldStores = $this->resource->lookupStoreIds((int)$entity->getId());
88 | $newStores = (array)$entity->getData($this->storeIdField);
89 |
90 | $table = $connection->getTableName($this->storeTable);
91 |
92 | $delete = array_diff($oldStores, $newStores);
93 | if ($delete) {
94 | $where = [
95 | $linkField . ' = ?' => (int)$entity->getData($linkField),
96 | $this->storeIdField . ' IN (?)' => $delete,
97 | ];
98 | $connection->delete($table, $where);
99 | }
100 |
101 | $insert = array_diff($newStores, $oldStores);
102 | if ($insert) {
103 | $data = [];
104 | foreach ($insert as $storeId) {
105 | $data[] = [
106 | $linkField => (int)$entity->getData($linkField),
107 | $this->storeIdField => (int)$storeId,
108 | ];
109 | }
110 | $connection->insertMultiple($table, $data);
111 | }
112 | return $entity;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/Model/ResourceModel/Store.php:
--------------------------------------------------------------------------------
1 | getColumnValues($linkField);
43 | if (count($linkedIds)) {
44 | $connection = $collection->getConnection();
45 | $select = $connection->select()->from(['entity_store' => $collection->getTable($tableName)])
46 | ->where('entity_store.' . $linkField . ' IN (?)', $linkedIds);
47 | $result = $connection->fetchAll($select);
48 | if ($result) {
49 | $storesData = [];
50 | foreach ($result as $storeData) {
51 | $storesData[$storeData[$linkField]][] = $storeData[$storeIdField];
52 | }
53 | foreach ($collection as $item) {
54 | $linkedId = $item->getData($linkField);
55 | if (!isset($storesData[$linkedId])) {
56 | continue;
57 | }
58 | $item->setData($storeIdField, $storesData[$linkedId]);
59 | }
60 | }
61 | }
62 | }
63 |
64 | /**
65 | * @param AbstractCollection $collection
66 | * @param $store
67 | * @param string $storeField
68 | * @param bool $withAdmin
69 | */
70 | public function addStoreFilter(AbstractCollection $collection, $store, $storeField = 'store_id', $withAdmin = true)
71 | {
72 | if (!is_array($store)) {
73 | $store = [$store];
74 | }
75 | if ($withAdmin) {
76 | $store[] = \Magento\Store\Model\Store::DEFAULT_STORE_ID;
77 | }
78 | $collection->addFilter($storeField, ['in' => $store], 'public');
79 | }
80 |
81 | /**
82 | * @param AbstractCollection $collection
83 | * @param string $tableName
84 | * @param string $linkField
85 | */
86 | public function joinStoreRelationTable(AbstractCollection $collection, string $tableName, string $linkField)
87 | {
88 | $collection->getSelect()->join(
89 | ['store_table' => $collection->getTable($tableName)],
90 | 'main_table.' . $linkField . ' = store_table.' . $linkField,
91 | []
92 | )->group(
93 | 'main_table.' . $linkField
94 | );
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Umc_Crud Magento 2 module
2 |
3 | ## Purpose:
4 |
5 | This module is intended for Magento 2 developers, in order to reduce the boilerplate code when creating a CRUD extension.
6 |
7 | ## Compatibility
8 | - 2.4.6
9 | - 2.4.5
10 | - 2.4.4
11 | - 2.4.3
12 | - 2.4.2
13 | - 2.4.1
14 | - 2.4.0
15 | - 2.3.5
16 | - 2.3.4
17 | - probably works for versions before 2.3.4, but it's not guaranteed.
18 | ## What it does:
19 | It provides a set of classes and interfaces that can be configured, composed or prerenced in order to avoid writing the same code over and over again.
20 |
21 | Example:
22 |
23 | (Almost) every `Save` controller for an entity does the following:
24 |
25 | - retrieves the data from POST.
26 | - may or may not transform the data received via POST
27 | - creates a new entity or retrieves a requested entity from the database
28 | - assigns the data to the entity from the point above
29 | - persists the entity
30 | - redirects "somewhere" with a success or an error message.
31 |
32 | And the only variables here are
33 | - the entity being added / modified
34 | - the way the data is processed before attaching it to the entity
35 |
36 | This module provides a general admin `Save` controller that has as dependencies a set of other classes / interfaces that have only one of the responsibilities above
37 | - an Entity manager responsible for retrieving the data from db or instantiating a new entity
38 | - a data processor (interface) that processes the data
39 | - an entity config class will contain the details about the entity being processed.
40 | - side objects: a data persistor (which is basically the session) to save the data submitted in case there is an error and you need to redirect back to the form with the previously submitted data prefilled.
41 |
42 | All of these can be configured via `di.xml` for each entity you want to manage.
43 |
44 | This module also adds a few more code generators (similar to the core ones for factory or proxy for example) that will autogenerate repository classes and a few others.
45 |
46 | # Target audience
47 |
48 | - this module is intended for experienced Magento 2 developers that are tired of writing the same thing over and over again.
49 | - this is not intended for junior developers.
50 | - In order to use this you have to have good knowledge of ...
51 | - how a magento CRUD module works
52 | - what is a virtual type
53 | - how DI works in Magento
54 |
55 | ## Advantages in using this module
56 | - less code to write, which means less code to test and less code that can malfunction
57 | - your copy/paste analyzer will stop complaining you have classes that look the same.
58 | - decrease development time. (hopefully)
59 | - you will have a standard way of writing all your CRUD modules (no matter if it's good or bad, at least it is consistent)
60 | - This covers most of the cases you will encounter in your development process. If one of your cases is not covered by this module you can choose not to extend or compose the classes in this module and use your own.
61 |
62 |
63 | ## Disadvantages of using this module
64 | - there is a lot more configuration you need to write (YEAH....xmls).
65 | - makes debugging a little harder.
66 | - adds a new abstractization layer ... or 7. Just kidding. it's 1.
67 | - you add a new dependency to your project and all your CRUD module will depend on this module.
68 |
69 | # Installation:
70 | - Via composer (recommended)
71 | - `composer require "umc/module-crud=*"`
72 | - Manual install
73 | - download a copy from `https://github.com/UltimateModuleCreator/umc-crud` and all the files in `app/code/Umc/Crud`.
74 |
75 | After installation
76 | - run `php bin/magento setup:upgrade [--keep-generated]`
77 | - check if this file exists `app/etc/crud/di.xml`. If it does not exist, run the command `bin/magento umc:crud:deploy`. If you get an error you can copy it from `vendor/umc/module-crud/etc/crud/di.xml` to `app/etc/crud/di.xml`.
78 |
79 | # Documentation
80 |
81 | For more details about how this extension should work, visit https://github.com/UltimateModuleCreator/umc-crud/wiki
82 |
83 | # Donations
84 |
85 | If you really like this and get the hang of it and it saves you a lot of development time, consider a small (or large - I won't mind) donation via PayPal
86 |
--------------------------------------------------------------------------------
/Source/Catalog/ProductAttribute.php:
--------------------------------------------------------------------------------
1 | attributeRepository = $attributeRepository;
67 | $this->searchCriteriaBuilder = $searchCriteriaBuilder;
68 | $this->sortOrderBuilder = $sortOrderBuilder;
69 | $this->filters = $filters;
70 | }
71 |
72 | /**
73 | * @return array
74 | */
75 | public function toOptionArray()
76 | {
77 | if ($this->options === null) {
78 | $this->sortOrderBuilder->setAscendingDirection();
79 | $this->sortOrderBuilder->setField(ProductAttributeInterface::FRONTEND_LABEL);
80 | $sortOrder = $this->sortOrderBuilder->create();
81 | $this->searchCriteriaBuilder->addSortOrder($sortOrder);
82 | $this->searchCriteriaBuilder->addFilter(ProductAttributeInterface::FRONTEND_LABEL, '', 'neq');
83 | foreach ($this->getValidFilters() as $filter) {
84 | $this->searchCriteriaBuilder->addFilter(
85 | $filter['key'],
86 | $filter['value'],
87 | $filter['condition'] ?? 'eq'
88 | );
89 | }
90 | $this->options = array_map(
91 | function (ProductAttributeInterface $attribute) {
92 | return [
93 | 'label' => $attribute->getDefaultFrontendLabel(),
94 | 'value' => $attribute->getAttributeCode()
95 | ];
96 | },
97 | $this->attributeRepository->getList($this->searchCriteriaBuilder->create())->getItems()
98 | );
99 | }
100 | return $this->options;
101 | }
102 |
103 | /**
104 | * @return array
105 | */
106 | private function getValidFilters()
107 | {
108 | return array_filter(
109 | $this->filters,
110 | function ($filter) {
111 | return array_key_exists('key', $filter) && array_key_exists('value', $filter);
112 | }
113 | );
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/Source/Catalog/ProductAttributeOptions.php:
--------------------------------------------------------------------------------
1 | attributeRepository = $attributeRepository;
52 | $this->attributeCode = $attributeCode;
53 | }
54 |
55 | /**
56 | * @return array
57 | */
58 | public function toOptionArray()
59 | {
60 | if ($this->options === null) {
61 | try {
62 | $attribute = $this->attributeRepository->get($this->attributeCode);
63 | $this->options = array_map(
64 | function (AttributeOptionInterface $option) {
65 | return [
66 | 'value' => $option->getValue(),
67 | 'label' => $option->getLabel()
68 | ];
69 | },
70 | $attribute->getOptions()
71 | );
72 | } catch (NoSuchEntityException $e) {
73 | $this->options = [];
74 | }
75 | }
76 | return $this->options;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Source/Catalog/ProductAttributeSet.php:
--------------------------------------------------------------------------------
1 | attributeSetRepository = $attributeSetRepository;
60 | $this->searchCriteriaBuilder = $searchCriteriaBuilder;
61 | $this->sortOrderBuilder = $sortOrderBuilder;
62 | }
63 |
64 | /**
65 | * to option array
66 | *
67 | * @return array
68 | */
69 | public function toOptionArray()
70 | {
71 | if ($this->options === null) {
72 | $this->options = [];
73 | $this->sortOrderBuilder->setField('attribute_set_name');
74 | $this->sortOrderBuilder->setAscendingDirection();
75 | $this->searchCriteriaBuilder->addSortOrder(
76 | $this->sortOrderBuilder->create()
77 | );
78 | $attributeSets = $this->attributeSetRepository->getList(
79 | $this->searchCriteriaBuilder->create()
80 | )->getItems();
81 | foreach ($attributeSets as $set) {
82 | $this->options[] = [
83 | 'label' => $set->getAttributeSetName(),
84 | 'value' => $set->getAttributeSetId()
85 | ];
86 | }
87 | }
88 | return $this->options;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Source/Options.php:
--------------------------------------------------------------------------------
1 | options = $options;
44 | }
45 |
46 | /**
47 | * @return array
48 | */
49 | public function toOptionArray()
50 | {
51 | if ($this->processed === null) {
52 | $filteredOptions = array_filter(
53 | $this->options,
54 | function ($option) {
55 | if (!is_array($option)) {
56 | return false;
57 | }
58 | return array_key_exists('label', $option)
59 | && array_key_exists('value', $option)
60 | && (!array_key_exists('disabled', $option) || !$option['disabled']);
61 | }
62 | );
63 | $this->processed = array_values(
64 | array_map(
65 | function ($option) {
66 | return [
67 | 'label' => $option['label'],
68 | 'value' => $option['value']
69 | ];
70 | },
71 | $filteredOptions
72 | )
73 | );
74 | }
75 | return $this->processed;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Source/StoreView.php:
--------------------------------------------------------------------------------
1 | options !== null) {
42 | return $this->options;
43 | }
44 |
45 | $this->currentOptions['All Store Views']['label'] = __('All Store Views');
46 | $this->currentOptions['All Store Views']['value'] = self::ALL_STORE_VIEWS;
47 |
48 | $this->generateCurrentOptions();
49 |
50 | $this->options = array_values($this->currentOptions);
51 |
52 | return $this->options;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Test/Unit/Block/Adminhtml/Button/BackTest.php:
--------------------------------------------------------------------------------
1 | url = $this->createMock(UrlInterface::class);
47 | $this->uiConfig = $this->createMock(EntityUiConfig::class);
48 | }
49 |
50 | /**
51 | * @covers \Umc\Crud\Block\Adminhtml\Button\Back::getButtonData
52 | * @covers \Umc\Crud\Block\Adminhtml\Button\Back::getLabel
53 | * @covers \Umc\Crud\Block\Adminhtml\Button\Back::__construct
54 | */
55 | public function testGetButtonData()
56 | {
57 | $back = new Back($this->url, $this->uiConfig);
58 | $this->uiConfig->method('getBackLabel')->willReturn('Back to list');
59 | $this->url->method('getUrl')->willReturn('url');
60 | $result = $back->getButtonData();
61 | $this->assertEquals('Back to list', $result['label']);
62 | $this->assertEquals("location.href = 'url';", $result['on_click']);
63 | }
64 |
65 | /**
66 | * @covers \Umc\Crud\Block\Adminhtml\Button\Back::getButtonData
67 | * @covers \Umc\Crud\Block\Adminhtml\Button\Back::getLabel
68 | * @covers \Umc\Crud\Block\Adminhtml\Button\Back::__construct
69 | */
70 | public function testGetButtonDataNoUiConfig()
71 | {
72 | $back = new Back($this->url);
73 | $this->url->method('getUrl')->willReturn('url');
74 | $result = $back->getButtonData();
75 | $this->assertEquals('Back', $result['label']);
76 | $this->assertEquals("location.href = 'url';", $result['on_click']);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Test/Unit/Block/Adminhtml/Button/DeleteTest.php:
--------------------------------------------------------------------------------
1 | request = $this->createMock(RequestInterface::class);
63 | $this->entityUiManager = $this->createMock(EntityUiManagerInterface::class);
64 | $this->uiConfig = $this->createMock(EntityUiConfig::class);
65 | $this->url = $this->createMock(UrlInterface::class);
66 | $this->delete = new Delete(
67 | $this->request,
68 | $this->entityUiManager,
69 | $this->uiConfig,
70 | $this->url
71 | );
72 | }
73 |
74 | /**
75 | * @covers \Umc\Crud\Block\Adminhtml\Button\Delete::getButtonData
76 | * @covers \Umc\Crud\Block\Adminhtml\Button\Delete::getDeleteUrl
77 | * @covers \Umc\Crud\Block\Adminhtml\Button\Delete::getEntityId
78 | * @covers \Umc\Crud\Block\Adminhtml\Button\Delete::__construct
79 | */
80 | public function testGetButtonData()
81 | {
82 | $entity = $this->createMock(AbstractModel::class);
83 | $entity->method('getId')->willReturn(1);
84 | $this->entityUiManager->method('get')->willReturn($entity);
85 | $this->url->expects($this->once())->method('getUrl')->willReturn('url');
86 | $result = $this->delete->getButtonData();
87 | $this->assertArrayHasKey('label', $result);
88 | $this->assertArrayHasKey('on_click', $result);
89 | }
90 |
91 | /**
92 | * @covers \Umc\Crud\Block\Adminhtml\Button\Delete::getButtonData
93 | * @covers \Umc\Crud\Block\Adminhtml\Button\Delete::getEntityId
94 | * @covers \Umc\Crud\Block\Adminhtml\Button\Delete::__construct
95 | */
96 | public function testGetButtonNoEntity()
97 | {
98 | $this->entityUiManager->method('get')->willThrowException(
99 | $this->createMock(NoSuchEntityException::class)
100 | );
101 | $this->url->expects($this->never())->method('getUrl');
102 | $this->assertEquals([], $this->delete->getButtonData());
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Test/Unit/Block/Adminhtml/Button/ResetTest.php:
--------------------------------------------------------------------------------
1 | getButtonData();
36 | $this->assertEquals(__('Reset'), $result['label']);
37 | $this->assertEquals("location.reload();", $result['on_click']);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Test/Unit/Controller/Heartbeat/IndexTest.php:
--------------------------------------------------------------------------------
1 | createMock(Context::class);
38 | $resultFactory = $this->createMock(ResultFactory::class);
39 | $jsonResult = $this->createMock(Json::class);
40 | $context->expects($this->once())->method('getResultFactory')->willReturn($resultFactory);
41 | $resultFactory->expects($this->once())->method('create')->willReturn($jsonResult);
42 | $jsonResult->expects($this->once())->method('setData')->with([]);
43 | $index = new Index($context);
44 | $this->assertEquals($jsonResult, $index->execute());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Test/Unit/Controller/IndexTest.php:
--------------------------------------------------------------------------------
1 | context = $this->createMock(Context::class);
71 | $this->uiConfig = $this->createMock(EntityUiConfig::class);
72 | $this->resultPage = $this->createMock(Page::class);
73 | $this->pageConfig = $this->createMock(Config::class);
74 | $this->pageTitle = $this->createMock(Title::class);
75 | $this->resultFactory = $this->createMock(ResultFactory::class);
76 | $this->context->method('getResultFactory')->willReturn($this->resultFactory);
77 | $this->pageConfig->method('getTitle')->willReturn($this->pageTitle);
78 | $this->resultFactory->method('create')->willReturn($this->resultPage);
79 | $this->resultPage->method('getConfig')->willReturn($this->pageConfig);
80 | $this->index = new Index(
81 | $this->context,
82 | $this->uiConfig
83 | );
84 | }
85 |
86 | /**
87 | * @covers \Umc\Crud\Controller\Adminhtml\Index::execute
88 | * @covers \Umc\Crud\Controller\Adminhtml\Index::_isAllowed
89 | * @covers \Umc\Crud\Controller\Adminhtml\Index::__construct
90 | */
91 | public function testExecute()
92 | {
93 | $this->uiConfig->expects($this->once())->method('getMenuItem')->willReturn('SelectedMenu');
94 | $this->uiConfig->expects($this->once())->method('getListPageTitle')->willReturn('PageTitle');
95 | $this->resultPage->expects($this->once())->method('setActiveMenu')->with('SelectedMenu');
96 | $this->pageTitle->expects($this->once())->method('prepend')->with('PageTitle');
97 | $this->assertEquals($this->resultPage, $this->index->execute());
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Test/Unit/Controller/NewActionTest.php:
--------------------------------------------------------------------------------
1 | createMock(Context::class);
38 | $resultFactory = $this->createMock(ResultFactory::class);
39 | $forward = $this->createMock(Forward::class);
40 | $context->expects($this->once())->method('getResultFactory')->willReturn($resultFactory);
41 | $resultFactory->expects($this->once())->method('create')->willReturn($forward);
42 | $newAction = new NewAction($context);
43 | $forward->expects($this->once())->method('forward')->with('edit');
44 | $this->assertEquals($forward, $newAction->execute());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Test/Unit/Controller/UploadTest.php:
--------------------------------------------------------------------------------
1 | context = $this->createMock(Context::class);
66 | $this->uploader = $this->createMock(Uploader::class);
67 | $this->request = $this->createMock(RequestInterface::class);
68 | $this->resultFactory = $this->createMock(ResultFactory::class);
69 | $this->resultJson = $this->createMock(Json::class);
70 | $this->resultFactory->method('create')->willReturn($this->resultJson);
71 | $this->context->method('getRequest')->willReturn($this->request);
72 | $this->context->method('getResultFactory')->willReturn($this->resultFactory);
73 | $this->upload = new Upload(
74 | $this->context,
75 | $this->uploader
76 | );
77 | }
78 |
79 | /**
80 | * @covers \Umc\Crud\Controller\Adminhtml\Upload::execute
81 | * @covers \Umc\Crud\Controller\Adminhtml\Upload::getFieldName
82 | * @covers \Umc\Crud\Controller\Adminhtml\Upload::__construct
83 | */
84 | public function testExecute()
85 | {
86 | $this->uploader->method('saveFileToTmpDir')->willReturn(['result']);
87 | $this->resultJson->expects($this->once())->method('setData')->with(['result']);
88 | $this->assertEquals($this->resultJson, $this->upload->execute());
89 | }
90 |
91 | /**
92 | * @covers \Umc\Crud\Controller\Adminhtml\Upload::execute
93 | * @covers \Umc\Crud\Controller\Adminhtml\Upload::getFieldName
94 | * @covers \Umc\Crud\Controller\Adminhtml\Upload::__construct
95 | */
96 | public function testExecuteWithException()
97 | {
98 | $this->uploader->method('saveFileToTmpDir')->willThrowException(new \Exception());
99 | $this->assertEquals($this->resultJson, $this->upload->execute());
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/Test/Unit/Generator/RepoTest.php:
--------------------------------------------------------------------------------
1 | nameMatcher = $this->createMock(NameMatcher::class);
67 | $this->ioObject = $this->createMock(Io::class);
68 | $this->classGenerator = $this->createMock(CodeGeneratorInterface::class);
69 | $this->definedClasses = $this->createMock(DefinedClasses::class);
70 | $this->parameter = $this->createMock(ReflectionParameter::class);
71 | $this->repo = new Repo(
72 | $this->nameMatcher,
73 | DataObject::class,
74 | '\Result\Class',
75 | $this->ioObject,
76 | $this->classGenerator,
77 | $this->definedClasses
78 | );
79 | }
80 |
81 | /**
82 | * @covers \Umc\Crud\Generator\Repo
83 | */
84 | public function testGenerate()
85 | {
86 | $this->ioObject->expects($this->once())->method('generateResultFileName')->willReturn('filename.php');
87 | $this->nameMatcher->expects($this->any())->method('getRepositoryInterfaceName');
88 | $this->nameMatcher->expects($this->any())->method('getInterfaceName');
89 | $this->nameMatcher->expects($this->any())->method('getInterfaceFactoryClass');
90 | $this->nameMatcher->expects($this->any())->method('getResourceClassName');
91 | $this->definedClasses->method('isClassLoadable')->willReturn(true);
92 | $this->ioObject->method('makeResultFileDirectory')->willReturn(true);
93 | $this->ioObject->method('fileExists')->willReturn(true);
94 | $this->classGenerator->expects($this->once())->method('setName')->willReturnSelf();
95 | $this->classGenerator->expects($this->once())->method('addProperties')->willReturnSelf();
96 | $this->classGenerator->expects($this->once())->method('addMethods')->willReturnSelf();
97 | $this->classGenerator->expects($this->once())->method('setClassDocBlock')->willReturnSelf();
98 | $this->classGenerator->expects($this->once())->method('generate')->willReturn('generated code');
99 | $this->assertEquals('filename.php', $this->repo->generate());
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/Test/Unit/Generator/UiCollectionProviderTest.php:
--------------------------------------------------------------------------------
1 | nameMatcher = $this->createMock(NameMatcher::class);
67 | $this->ioObject = $this->createMock(Io::class);
68 | $this->classGenerator = $this->createMock(CodeGeneratorInterface::class);
69 | $this->definedClasses = $this->createMock(DefinedClasses::class);
70 | $this->parameter = $this->createMock(ReflectionParameter::class);
71 | $this->uiCollectionProvider = new UiCollectionProvider(
72 | $this->nameMatcher,
73 | DataObject::class,
74 | '\Result\Class',
75 | $this->ioObject,
76 | $this->classGenerator,
77 | $this->definedClasses
78 | );
79 | }
80 |
81 | /**
82 | * @covers \Umc\Crud\Generator\UiCollectionProvider
83 | */
84 | public function testGenerate()
85 | {
86 | $this->ioObject->expects($this->once())->method('generateResultFileName')->willReturn('filename.php');
87 | $this->nameMatcher->expects($this->any())->method('getCollectionFactoryClass');
88 | $this->definedClasses->method('isClassLoadable')->willReturn(true);
89 | $this->ioObject->method('makeResultFileDirectory')->willReturn(true);
90 | $this->ioObject->method('fileExists')->willReturn(true);
91 | $this->classGenerator->expects($this->once())->method('setName')->willReturnSelf();
92 | $this->classGenerator->expects($this->once())->method('addProperties')->willReturnSelf();
93 | $this->classGenerator->expects($this->once())->method('addMethods')->willReturnSelf();
94 | $this->classGenerator->expects($this->once())->method('setClassDocBlock')->willReturnSelf();
95 | $this->classGenerator->expects($this->once())->method('generate')->willReturn('generated code');
96 | $this->assertEquals('filename.php', $this->uiCollectionProvider->generate());
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Test/Unit/Generator/UiManagerTest.php:
--------------------------------------------------------------------------------
1 | nameMatcher = $this->createMock(NameMatcher::class);
67 | $this->ioObject = $this->createMock(Io::class);
68 | $this->classGenerator = $this->createMock(CodeGeneratorInterface::class);
69 | $this->definedClasses = $this->createMock(DefinedClasses::class);
70 | $this->parameter = $this->createMock(ReflectionParameter::class);
71 | $this->uiManager = new UiManager(
72 | $this->nameMatcher,
73 | DataObject::class,
74 | '\Result\Class',
75 | $this->ioObject,
76 | $this->classGenerator,
77 | $this->definedClasses
78 | );
79 | }
80 |
81 | /**
82 | * @covers \Umc\Crud\Generator\UiManager
83 | */
84 | public function testGenerate()
85 | {
86 | $this->ioObject->expects($this->once())->method('generateResultFileName')->willReturn('filename.php');
87 | $this->nameMatcher->expects($this->any())->method('getInterfaceName');
88 | $this->definedClasses->method('isClassLoadable')->willReturn(true);
89 | $this->ioObject->method('makeResultFileDirectory')->willReturn(true);
90 | $this->ioObject->method('fileExists')->willReturn(true);
91 | $this->classGenerator->expects($this->once())->method('setName')->willReturnSelf();
92 | $this->classGenerator->expects($this->once())->method('addProperties')->willReturnSelf();
93 | $this->classGenerator->expects($this->once())->method('addMethods')->willReturnSelf();
94 | $this->classGenerator->expects($this->once())->method('setClassDocBlock')->willReturnSelf();
95 | $this->classGenerator->expects($this->once())->method('generate')->willReturn('generated code');
96 | $this->assertEquals('filename.php', $this->uiManager->generate());
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Test/Unit/Model/FileCheckerTest.php:
--------------------------------------------------------------------------------
1 | file = $this->createMock(File::class);
46 | $this->fileChecker = new FileChecker(
47 | $this->file
48 | );
49 | }
50 |
51 | /**
52 | * @covers \Umc\Crud\Model\FileChecker::getNewFileName
53 | * @covers \Umc\Crud\Model\FileChecker::__construct
54 | */
55 | public function testGetNewFileName()
56 | {
57 | $this->file->method('getPathInfo')->willReturn([
58 | 'filename' => 'file',
59 | 'extension' => 'ext',
60 | 'basename' => 'file.ext',
61 | 'dirname' => 'dir'
62 | ]);
63 | $this->file->method('fileExists')->willReturn(false);
64 | $this->assertEquals('file.ext', $this->fileChecker->getNewFileName('file', 0));
65 | $this->assertEquals('/f/i/file.ext', $this->fileChecker->getNewFileName('file'));
66 | $this->assertEquals('/f/i/l/e/_/_/file.ext', $this->fileChecker->getNewFileName('file', 6));
67 | }
68 |
69 | /**
70 | * @covers \Umc\Crud\Model\FileChecker::getNewFileName
71 | * @covers \Umc\Crud\Model\FileChecker::__construct
72 | */
73 | public function testGetNewFileNameFileExists()
74 | {
75 | $this->file->method('getPathInfo')->willReturn([
76 | 'filename' => 'file',
77 | 'extension' => 'ext',
78 | 'basename' => 'file.ext',
79 | 'dirname' => 'dir'
80 | ]);
81 | $this->file->method('fileExists')->willReturnOnConsecutiveCalls(true, true, false);
82 | $this->assertEquals('file_1.ext', $this->fileChecker->getNewFileName('file', 0));
83 | }
84 |
85 | /**
86 | * @covers \Umc\Crud\Model\FileChecker::getNewFileName
87 | * @covers \Umc\Crud\Model\FileChecker::__construct
88 | */
89 | public function testGetNewFileNameFileExistsThreeLevels()
90 | {
91 | $this->file->method('getPathInfo')->willReturn([
92 | 'filename' => 'file',
93 | 'extension' => 'ext',
94 | 'basename' => 'file.ext',
95 | 'dirname' => 'dir'
96 | ]);
97 | $this->file->method('fileExists')->willReturnOnConsecutiveCalls(true, true, true, true, false);
98 | $this->assertEquals('file_3.ext', $this->fileChecker->getNewFileName('file', 0));
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Test/Unit/Model/ResourceModel/Relation/Store/ReadHandlerTest.php:
--------------------------------------------------------------------------------
1 | resource = $this->createMock(StoreAwareAbstractModel::class);
47 | $this->readHandler = new ReadHandler($this->resource);
48 | }
49 |
50 | /**
51 | * @covers \Umc\Crud\Model\ResourceModel\Relation\Store\ReadHandler::execute
52 | * @covers \Umc\Crud\Model\ResourceModel\Relation\Store\ReadHandler::__construct
53 | */
54 | public function testExecute()
55 | {
56 | $entity = $this->createMock(AbstractModel::class);
57 | $entity->method('getId')->willReturn(1);
58 | $this->resource->expects($this->once())->method('lookupStoreIds')->willReturn([1, 3]);
59 | $entity->expects($this->once())->method('setData')->with('store_id', [1, 3]);
60 | $this->assertEquals($entity, $this->readHandler->execute($entity));
61 | }
62 |
63 | /**
64 | * @covers \Umc\Crud\Model\ResourceModel\Relation\Store\ReadHandler::execute
65 | * @covers \Umc\Crud\Model\ResourceModel\Relation\Store\ReadHandler::__construct
66 | */
67 | public function testExecuteNoId()
68 | {
69 | $entity = $this->createMock(AbstractModel::class);
70 | $entity->method('getId')->willReturn(null);
71 | $this->resource->expects($this->never())->method('lookupStoreIds');
72 | $entity->expects($this->never())->method('setData');
73 | $this->assertEquals($entity, $this->readHandler->execute($entity));
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Test/Unit/Model/ResourceModel/Relation/Store/SaveHandlerTest.php:
--------------------------------------------------------------------------------
1 | metadataPool = $this->createMock(MetadataPool::class);
66 | $this->resource = $this->createMock(StoreAwareAbstractModel::class);
67 | $this->metadata = $this->createMock(EntityMetadataInterface::class);
68 | $this->metadataPool->method('getMetadata')->willReturn($this->metadata);
69 | $this->connection = $this->createMock(AdapterInterface::class);
70 | $this->resource->method('getConnection')->willReturn($this->connection);
71 | $this->entity = $this->createMock(AbstractModel::class);
72 | $this->saveHandler = new SaveHandler(
73 | $this->metadataPool,
74 | $this->resource,
75 | 'entityType',
76 | 'store_table',
77 | 'store_id'
78 | );
79 | }
80 |
81 | /**
82 | * @covers \Umc\Crud\Model\ResourceModel\Relation\Store\SaveHandler::execute
83 | * @covers \Umc\Crud\Model\ResourceModel\Relation\Store\SaveHandler::__construct
84 | */
85 | public function testExecute()
86 | {
87 | $this->metadata->method('getLinkField')->willReturn('entity_id');
88 | $this->resource->method('lookupStoreIds')->willReturn([1, 2, 3]);
89 | $this->entity->method('getData')->willReturnMap([
90 | ['store_id', null, [1, 2, 4, 5]],
91 | ['entity_id', null, 1]
92 | ]);
93 | $this->connection->expects($this->once())->method('delete');
94 | $this->connection->expects($this->once())->method('insertMultiple');
95 | $this->saveHandler->execute($this->entity);
96 | }
97 |
98 | /**
99 | * @covers \Umc\Crud\Model\ResourceModel\Relation\Store\SaveHandler::execute
100 | * @covers \Umc\Crud\Model\ResourceModel\Relation\Store\SaveHandler::__construct
101 | */
102 | public function testExecuteNoInsert()
103 | {
104 | $this->resource->method('lookupStoreIds')->willReturn([1, 2, 3]);
105 | $this->entity->method('getData')->willReturnMap([
106 | ['store_id', null, [1, 2]],
107 | ['entity_id', null, 1]
108 | ]);
109 | $this->connection->expects($this->once())->method('delete');
110 | $this->connection->expects($this->never())->method('insertMultiple');
111 | $this->saveHandler->execute($this->entity);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Test/Unit/Model/ResourceModel/StoreTest.php:
--------------------------------------------------------------------------------
1 | collection = $om->getCollectionMock(
59 | AbstractCollection::class,
60 | [
61 | $this->getItemMock('1'),
62 | $this->getItemMock('2'),
63 | $this->getItemMock('3')
64 | ]
65 | );
66 | $this->connection = $this->createMock(AdapterInterface::class);
67 | $this->collection->method('getConnection')->willReturn($this->connection);
68 | $this->select = $this->createMock(Select::class);
69 | $this->store = new Store();
70 | }
71 |
72 | /**
73 | * @covers \Umc\Crud\Model\ResourceModel\Store::addStoresToCollection
74 | */
75 | public function testAddStoresToCollection()
76 | {
77 | $this->collection->method('getColumnValues')->willReturn([1, 2, 3]);
78 | $this->connection->method('select')->willReturn($this->select);
79 | $this->select->expects($this->once())->method('from')->willReturnSelf();
80 | $this->select->expects($this->once())->method('where')->willReturnSelf();
81 | $this->connection->method('fetchAll')->willReturn([
82 | [
83 | 'store_id' => 1,
84 | 'linked_id' => 1
85 | ],
86 | [
87 | 'store_id' => 2,
88 | 'linked_id' => 1
89 | ],
90 | [
91 | 'store_id' => 2,
92 | 'linked_id' => 2
93 | ],
94 | ]);
95 | $this->store->addStoresToCollection($this->collection, 'table', 'linked_id');
96 | }
97 |
98 | /**
99 | * @covers \Umc\Crud\Model\ResourceModel\Store::addStoreFilter
100 | */
101 | public function testAddStoreFilter()
102 | {
103 | $this->collection->expects($this->once())->method('addFilter')->with('store_id', ['in' => [1, 0]], 'public');
104 | $this->store->addStoreFilter($this->collection, 1);
105 | }
106 |
107 | /**
108 | * @covers \Umc\Crud\Model\ResourceModel\Store::joinStoreRelationTable
109 | */
110 | public function testJoinStoreRelationTable()
111 | {
112 | $this->collection->method('getSelect')->willReturn($this->select);
113 | $this->select->expects($this->once())->method('join')->willReturnSelf();
114 | $this->select->expects($this->once())->method('group');
115 | $this->store->joinStoreRelationTable($this->collection, 'table', 'link_id');
116 | }
117 |
118 | /**
119 | * @param $linkedField
120 | * @return MockObject
121 | */
122 | private function getItemMock($linkedField)
123 | {
124 | $mock = $this->createMock(AbstractModel::class);
125 | $mock->method('getData')->willReturn($linkedField);
126 | return $mock;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/Test/Unit/Source/Catalog/ProductAttributeOptionsTest.php:
--------------------------------------------------------------------------------
1 | attributeRepository = $this->createMock(ProductAttributeRepositoryInterface::class);
53 | $this->attribute = $this->createMock(ProductAttributeInterface::class);
54 | $this->productAttributeOptions = new ProductAttributeOptions(
55 | $this->attributeRepository,
56 | 'attributeCode'
57 | );
58 | }
59 |
60 | /**
61 | * @covers \Umc\Crud\Source\Catalog\ProductAttributeOptions::toOptionArray
62 | * @covers \Umc\Crud\Source\Catalog\ProductAttributeOptions::__construct
63 | */
64 | public function testToOptionArray()
65 | {
66 | $this->attributeRepository->expects($this->once())->method('get')->willReturn($this->attribute);
67 | $this->attribute->expects($this->once())->method('getOptions')->willReturn([
68 | $this->getOptionMock(1, 'label1'),
69 | $this->getOptionMock(2, 'label2'),
70 | ]);
71 | $expected = [
72 | [
73 | 'label' => 'label1',
74 | 'value' => 1
75 | ],
76 | [
77 | 'label' => 'label2',
78 | 'value' => 2
79 | ]
80 | ];
81 | $this->assertEquals($expected, $this->productAttributeOptions->toOptionArray());
82 | //call twice to test memoizing
83 | $this->assertEquals($expected, $this->productAttributeOptions->toOptionArray());
84 | }
85 |
86 | /**
87 | * @covers \Umc\Crud\Source\Catalog\ProductAttributeOptions::toOptionArray
88 | * @covers \Umc\Crud\Source\Catalog\ProductAttributeOptions::__construct
89 | */
90 | public function testToOptionArrayNoAttribute()
91 | {
92 | $this->attributeRepository->expects($this->once())->method('get')->willThrowException(
93 | $this->createMock(NoSuchEntityException::class)
94 | );
95 | $this->attribute->expects($this->never())->method('getOptions');
96 | $this->assertEquals([], $this->productAttributeOptions->toOptionArray());
97 | //call twice to test memoizing
98 | $this->assertEquals([], $this->productAttributeOptions->toOptionArray());
99 | }
100 |
101 | /**
102 | * @param $value
103 | * @param $label
104 | * @return MockObject
105 | */
106 | private function getOptionMock($value, $label)
107 | {
108 | $mock = $this->createMock(AttributeOptionInterface::class);
109 | $mock->method('getValue')->willReturn($value);
110 | $mock->method('getLabel')->willReturn($label);
111 | return $mock;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Test/Unit/Source/OptionsTest.php:
--------------------------------------------------------------------------------
1 | 'label1',
38 | 'value' => 'value1'
39 | ],
40 | [
41 | 'label' => 'label2',
42 | 'value' => 'value2',
43 | 'disabled' => true
44 | ],
45 | [
46 | 'value' => 'value3',
47 | 'disabled' => false
48 | ],
49 | [
50 | 'label' => 'label4',
51 | ],
52 | [
53 | 'label' => 'label5',
54 | 'value' => 'value5',
55 | 'disabled' => false
56 | ],
57 | 'dummy'
58 | ];
59 | $expected = [
60 | [
61 | 'label' => 'label1',
62 | 'value' => 'value1'
63 | ],
64 | [
65 | 'label' => 'label5',
66 | 'value' => 'value5'
67 | ],
68 | ];
69 | $options = new Options($input);
70 | $this->assertEquals($expected, $options->toOptionArray());
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Test/Unit/Source/StoreViewTest.php:
--------------------------------------------------------------------------------
1 | systemStore = $this->createMock(Store::class);
49 | $escaper = $this->createMock(Escaper::class);
50 | $this->storeView = new StoreView(
51 | $this->systemStore,
52 | $escaper
53 | );
54 | $escaper->method('escapeJs')->willReturnArgument(0);
55 | $escaper->method('escapeHtml')->willReturnArgument(0);
56 | }
57 |
58 | /**
59 | * @covers \Umc\Crud\Source\StoreView
60 | */
61 | public function testToOptionArray()
62 | {
63 | $this->systemStore->expects($this->once())->method('getWebsiteCollection')->willReturn([
64 | $this->getWebsiteMock(1, 'website1'),
65 | $this->getWebsiteMock(2, 'website2')
66 | ]);
67 | $this->systemStore->expects($this->once())->method('getGroupCollection')->willReturn([
68 | $this->getGroupMock(11, 1, 'group1'),
69 | $this->getGroupMock(12, 1, 'group2'),
70 | ]);
71 | $this->systemStore->expects($this->once())->method('getStoreCollection')->willReturn([
72 | $this->getStoreViewMock(1, 11, 'storeview1'),
73 | $this->getStoreViewMock(2, 12, 'storeview2'),
74 | ]);
75 | $this->storeView->toOptionArray();
76 | //call twice to test memoizing
77 | $result = $this->storeView->toOptionArray();
78 | $this->assertEquals(2, count($result));
79 | $this->assertEquals(0, $result[0]['value']);
80 | $this->assertEquals(2, count($result[1]['value']));
81 | }
82 |
83 | /**
84 | * @param $id
85 | * @param $name
86 | * @return MockObject
87 | */
88 | private function getWebsiteMock($id, $name)
89 | {
90 | $mock = $this->createMock(Website::class);
91 | $mock->method('getId')->willReturn($id);
92 | $mock->method('getName')->willReturn($name);
93 | return $mock;
94 | }
95 |
96 | /**
97 | * @param $id
98 | * @param $websiteId
99 | * @param $name
100 | * @return MockObject
101 | */
102 | private function getGroupMock($id, $websiteId, $name)
103 | {
104 | $mock = $this->createMock(Group::class);
105 | $mock->method('getId')->willReturn($id);
106 | $mock->method('getWebsiteId')->willReturn($websiteId);
107 | $mock->method('getName')->willReturn($name);
108 | return $mock;
109 | }
110 |
111 | /**
112 | * @param $id
113 | * @param $groupId
114 | * @param $name
115 | * @return MockObject
116 | */
117 | private function getStoreViewMock($id, $groupId, $name)
118 | {
119 | $mock = $this->createMock(\Magento\Store\Model\Store::class);
120 | $mock->method('getId')->willReturn($id);
121 | $mock->method('getGroupId')->willReturn($groupId);
122 | $mock->method('getName')->willReturn($name);
123 | return $mock;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Test/Unit/Ui/Form/DataModifier/CompositeDataModifierTest.php:
--------------------------------------------------------------------------------
1 | createMock(AbstractModel::class);
38 | $processor1 = $this->createMock(DataModifierInterface::class);
39 | $processor1->method('modifyData')->willReturnCallback(
40 | function (AbstractModel $model, array $data) {
41 | $data['element1'] = ($data['element1'] ?? '') . '_processed1';
42 | return $data;
43 | }
44 | );
45 | $processor2 = $this->createMock(DataModifierInterface::class);
46 | $processor2->method('modifyData')->willReturnCallback(
47 | function (AbstractModel $model, array $data) {
48 | $data['element1'] = ($data['element1'] ?? '') . '_processed2';
49 | $data['element2'] = ($data['element2'] ?? '') . '_processed2';
50 | return $data;
51 | }
52 | );
53 | $compositeProcessor = new CompositeDataModifier([$processor1, $processor2]);
54 | $data = [
55 | 'element1' => 'value1',
56 | 'element2' => 'value2',
57 | 'element3' => 'value3'
58 | ];
59 | $expected = [
60 | 'element1' => 'value1_processed1_processed2',
61 | 'element2' => 'value2_processed2',
62 | 'element3' => 'value3'
63 | ];
64 | $this->assertEquals($expected, $compositeProcessor->modifyData($model, $data));
65 | }
66 |
67 | /**
68 | * @covers \Umc\Crud\Ui\Form\DataModifier\CompositeDataModifier::__construct
69 | */
70 | public function testGetConstructor()
71 | {
72 | $this->expectException(\InvalidArgumentException::class);
73 | new CompositeDataModifier(['string value']);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Test/Unit/Ui/Form/DataModifier/DataModifierTest.php:
--------------------------------------------------------------------------------
1 | serializer = $this->createMock(Json::class);
51 | $this->model = $this->createMock(AbstractModel::class);
52 | $this->dynamicRows = new DynamicRows(
53 | $this->serializer,
54 | ['field1', 'field2', 'field3']
55 | );
56 | }
57 |
58 | /**
59 | * @covers \Umc\Crud\Ui\Form\DataModifier\DynamicRows::modifyData
60 | * @covers \Umc\Crud\Ui\Form\DataModifier\DynamicRows::__construct
61 | */
62 | public function testModifyData()
63 | {
64 | $data = [
65 | 'field1' => 'value1',
66 | 'field2' => ['value2'],
67 | 'dummy' => 'dummy'
68 | ];
69 | $this->serializer->expects($this->once())->method('unserialize')->willReturnCallback(function ($item) {
70 | return [$item];
71 | });
72 | $expected = [
73 | 'field1' => ['value1'],
74 | 'field2' => ['value2'],
75 | 'dummy' => 'dummy'
76 | ];
77 | $this->assertEquals($expected, $this->dynamicRows->modifyData($this->model, $data));
78 | }
79 |
80 | /**
81 | * @covers \Umc\Crud\Ui\Form\DataModifier\DynamicRows::modifyData
82 | * @covers \Umc\Crud\Ui\Form\DataModifier\DynamicRows::__construct
83 | */
84 | public function testModifyDataWithUnserializeError()
85 | {
86 | $data = [
87 | 'field1' => 'value1',
88 | 'field2' => ['value2'],
89 | 'dummy' => 'dummy'
90 | ];
91 | $this->serializer->expects($this->once())->method('unserialize')->willThrowException(new \Exception());
92 | $expected = [
93 | 'field1' => [],
94 | 'field2' => ['value2'],
95 | 'dummy' => 'dummy'
96 | ];
97 | $this->assertEquals($expected, $this->dynamicRows->modifyData($this->model, $data));
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Test/Unit/Ui/Form/DataModifier/MultiselectTest.php:
--------------------------------------------------------------------------------
1 | createMock(AbstractModel::class);
36 | $modifier = new Multiselect(['field1', 'field2', 'field3', 'field4']);
37 | $data = [
38 | 'field1' => '1,2,3',
39 | 'field2' => [3, 4, 5],
40 | 'field3' => null,
41 | 'dummy' => 'dummy'
42 | ];
43 | $expected = [
44 | 'field1' => [1, 2, 3],
45 | 'field2' => [3, 4, 5],
46 | 'field3' => [],
47 | 'dummy' => 'dummy'
48 | ];
49 | $this->assertEquals($expected, $modifier->modifyData($model, $data));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Test/Unit/Ui/Form/DataModifier/NullModifierTest.php:
--------------------------------------------------------------------------------
1 | createMock(AbstractModel::class);
36 | $data = ['dummy'];
37 | $this->assertEquals($data, (new NullModifier())->modifyData($model, $data));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Test/Unit/Ui/SaveDataProcessor/CompositeProcessorTest.php:
--------------------------------------------------------------------------------
1 | createMock(SaveDataProcessorInterface::class);
37 | $processor1->method('modifyData')->willReturnCallback(function (array $data) {
38 | $data['element1'] = ($data['element1'] ?? '') . '_processed1';
39 | return $data;
40 | });
41 | $processor2 = $this->createMock(SaveDataProcessorInterface::class);
42 | $processor2->method('modifyData')->willReturnCallback(function (array $data) {
43 | $data['element1'] = ($data['element1'] ?? '') . '_processed2';
44 | $data['element2'] = ($data['element2'] ?? '') . '_processed2';
45 | return $data;
46 | });
47 | $compositeProcessor = new CompositeProcessor([$processor1, $processor2]);
48 | $data = [
49 | 'element1' => 'value1',
50 | 'element2' => 'value2',
51 | 'element3' => 'value3'
52 | ];
53 | $expected = [
54 | 'element1' => 'value1_processed1_processed2',
55 | 'element2' => 'value2_processed2',
56 | 'element3' => 'value3'
57 | ];
58 | $this->assertEquals($expected, $compositeProcessor->modifyData($data));
59 | }
60 |
61 | /**
62 | * @covers \Umc\Crud\Ui\SaveDataProcessor\CompositeProcessor::__construct
63 | */
64 | public function testGetConstructor()
65 | {
66 | $this->expectException(\InvalidArgumentException::class);
67 | new CompositeProcessor(['string value']);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Test/Unit/Ui/SaveDataProcessor/DateTest.php:
--------------------------------------------------------------------------------
1 | filterFactory = $this->createMock(\Magento\Framework\Filter\FilterInputFactory::class);
54 | $this->dateFilter = $this->createMock(DateFilter::class);
55 | $this->filter = $this->createMock(\Magento\Framework\Filter\FilterInput::class);
56 | $this->date = new Date(
57 | ['field1', 'field2', 'field3'],
58 | $this->filterFactory,
59 | $this->dateFilter
60 | );
61 | }
62 |
63 | /**
64 | * @covers \Umc\Crud\Ui\SaveDataProcessor\Date
65 | */
66 | public function testModifyData()
67 | {
68 | $data = [
69 | 'field1' => '2020-01-01',
70 | 'field2' => '2021-01-01',
71 | ];
72 | $expectedFactoryData = [
73 | 'filterRules' => [
74 | 'field1' => $this->dateFilter,
75 | 'field2' => $this->dateFilter
76 | ],
77 | 'validatorRules' => [],
78 | 'data' => $data
79 | ];
80 | $this->filterFactory->expects($this->once())->method('create')
81 | ->with($expectedFactoryData)
82 | ->willReturn($this->filter);
83 | $this->filter->expects($this->once())->method('getUnescaped')->willReturn(['result']);
84 | $this->assertEquals(['result'], $this->date->modifyData($data));
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Test/Unit/Ui/SaveDataProcessor/DynamicRowsTest.php:
--------------------------------------------------------------------------------
1 | serializer = $this->createMock(Json::class);
41 | }
42 |
43 | /**
44 | * @covers \Umc\Crud\Ui\SaveDataProcessor\DynamicRows::modifyData
45 | * @covers \Umc\Crud\Ui\SaveDataProcessor\DynamicRows::__construct
46 | */
47 | public function testModifyData()
48 | {
49 | $dynamicRows = new DynamicRows(
50 | $this->serializer,
51 | ['field1', 'field2', 'field3'],
52 | false
53 | );
54 | $data = [
55 | 'field1' => [1, 2, 3],
56 | 'field2' => 'string',
57 | 'field4' => ['not_processed']
58 | ];
59 | $this->serializer->expects($this->once())->method('serialize')->willReturnCallback(
60 | function (array $item) {
61 | $item['serialized'] = 1;
62 | return $item;
63 | }
64 | );
65 | $expected = [
66 | 'field1' => [
67 | 0 => 1,
68 | 1 => 2,
69 | 2 => 3,
70 | 'serialized' => 1
71 | ],
72 | 'field2' => 'string',
73 | 'field4' => ['not_processed']
74 | ];
75 | $this->assertEquals($expected, $dynamicRows->modifyData($data));
76 | }
77 |
78 | /**
79 | * @covers \Umc\Crud\Ui\SaveDataProcessor\DynamicRows::modifyData
80 | * @covers \Umc\Crud\Ui\SaveDataProcessor\DynamicRows::__construct
81 | */
82 | public function testModifyDataStrictMode()
83 | {
84 | $dynamicRows = new DynamicRows(
85 | $this->serializer,
86 | ['field1', 'field2', 'field3'],
87 | true
88 | );
89 | $data = [
90 | 'field1' => [1, 2, 3],
91 | 'field2' => 'string',
92 | 'field4' => ['not_processed']
93 | ];
94 | $this->serializer->expects($this->exactly(2))->method('serialize')->willReturnCallback(
95 | function (array $item) {
96 | $item['serialized'] = 1;
97 | return $item;
98 | }
99 | );
100 | $expected = [
101 | 'field1' => [
102 | 0 => 1,
103 | 1 => 2,
104 | 2 => 3,
105 | 'serialized' => 1
106 | ],
107 | 'field2' => 'string',
108 | 'field3' => ['serialized' => 1],
109 | 'field4' => ['not_processed']
110 | ];
111 | $this->assertEquals($expected, $dynamicRows->modifyData($data));
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Test/Unit/Ui/SaveDataProcessor/MultiselectTest.php:
--------------------------------------------------------------------------------
1 | [1, 2, 3],
37 | 'field2' => '4,5,6',
38 | 'dummy' => 'dummy'
39 | ];
40 | $expected = [
41 | 'field1' => '1,2,3',
42 | 'field2' => '4,5,6',
43 | 'dummy' => 'dummy'
44 | ];
45 | $this->assertEquals($expected, $modifier->modifyData($data));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Test/Unit/Ui/SaveDataProcessor/NullProcessorTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($data, (new NullProcessor())->modifyData($data));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Test/Unit/ViewModel/Formatter/DateTest.php:
--------------------------------------------------------------------------------
1 | localeDate = $this->createMock(TimezoneInterface::class);
46 | $this->date = new Date($this->localeDate);
47 | }
48 |
49 | /**
50 | * @covers \Umc\Crud\ViewModel\Formatter\Date::formatHtml
51 | * @covers \Umc\Crud\ViewModel\Formatter\Date::__construct
52 | */
53 | public function testFormatHtml()
54 | {
55 | $this->localeDate->expects($this->once())->method('formatDateTime')
56 | ->with(new \DateTime('1984-04-04'), \IntlDateFormatter::LONG, \IntlDateFormatter::NONE, null, null)
57 | ->willReturn('formatted');
58 | $this->assertEquals('formatted', $this->date->formatHtml('1984-04-04'));
59 | }
60 |
61 | /**
62 | * @covers \Umc\Crud\ViewModel\Formatter\Date::formatHtml
63 | * @covers \Umc\Crud\ViewModel\Formatter\Date::__construct
64 | */
65 | public function testFormatHtmlWithParams()
66 | {
67 | $this->localeDate->expects($this->once())->method('formatDateTime')
68 | ->with(new \DateTime('1984-04-04'), \IntlDateFormatter::SHORT, \IntlDateFormatter::SHORT, null, null)
69 | ->willReturn('formatted');
70 | $this->assertEquals(
71 | 'formatted',
72 | $this->date->formatHtml(
73 | '1984-04-04',
74 | [
75 | 'format' => \IntlDateFormatter::SHORT,
76 | 'show_time' => true
77 | ]
78 | )
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Test/Unit/ViewModel/Formatter/FileTest.php:
--------------------------------------------------------------------------------
1 | fileInfoFactory = $this->createMock(FileInfoFactory::class);
66 | $this->filesystem = $this->createMock(Filesystem::class);
67 | $this->storeManager = $this->createMock(StoreManagerInterface::class);
68 | $this->fileInfo = $this->createMock(FileInfo::class);
69 | $this->store = $this->createMock(Store::class);
70 | $this->file = new File(
71 | $this->fileInfoFactory,
72 | $this->filesystem,
73 | $this->storeManager
74 | );
75 | }
76 |
77 | /**
78 | * @covers \Umc\Crud\ViewModel\Formatter\File::formatHtml
79 | * @covers \Umc\Crud\ViewModel\Formatter\File::getFileInfo
80 | * @covers \Umc\Crud\ViewModel\Formatter\File::__construct
81 | */
82 | public function testFormatHtmlWithWrongPath()
83 | {
84 | $this->fileInfoFactory->expects($this->once())->method('create')->willReturn($this->fileInfo);
85 | $this->fileInfo->method('getFilePath')->willReturn('');
86 | $this->storeManager->expects($this->never())->method('getStore');
87 | $this->assertEquals('', $this->file->formatHtml('value'));
88 | }
89 |
90 | /**
91 | * @covers \Umc\Crud\ViewModel\Formatter\File::formatHtml
92 | * @covers \Umc\Crud\ViewModel\Formatter\File::getFileInfo
93 | * @covers \Umc\Crud\ViewModel\Formatter\File::__construct
94 | */
95 | public function testFormatHtml()
96 | {
97 | $this->fileInfoFactory->expects($this->once())->method('create')->willReturn($this->fileInfo);
98 | $this->fileInfo->method('getFilePath')->willReturn('/path');
99 | $this->storeManager->expects($this->once())->method('getStore')->willReturn($this->store);
100 | $this->store->method('getBaseUrl')->willReturn('base/');
101 | $this->assertEquals('base/path', $this->file->formatHtml('value'));
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/Test/Unit/ViewModel/Formatter/TextTest.php:
--------------------------------------------------------------------------------
1 | escaper = $this->createMock(Escaper::class);
46 | $this->text = new Text($this->escaper);
47 | }
48 |
49 | /**
50 | * @covers \Umc\Crud\ViewModel\Formatter\Text::formatHtml
51 | * @covers \Umc\Crud\ViewModel\Formatter\Text::__construct
52 | */
53 | public function testFormatHtml()
54 | {
55 | $this->escaper->expects($this->once())->method('escapeHtml')->willReturn('escaped');
56 | $this->assertEquals('escaped', $this->text->formatHtml('value'));
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Test/Unit/ViewModel/Formatter/WysiwygTest.php:
--------------------------------------------------------------------------------
1 | filter = $this->createMock(\Laminas\Filter\FilterInterface::class);
45 | $this->wysiwyg = new Wysiwyg($this->filter);
46 | }
47 |
48 | /**
49 | * @covers \Umc\Crud\ViewModel\Formatter\Wysiwyg::formatHtml
50 | * @covers \Umc\Crud\ViewModel\Formatter\Wysiwyg::__construct
51 | */
52 | public function testFormatHtml()
53 | {
54 | $this->filter->expects($this->once())->method('filter')->willReturn('filtered');
55 | $this->assertEquals('filtered', $this->wysiwyg->formatHtml('value'));
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Test/Unit/ViewModel/FormatterTest.php:
--------------------------------------------------------------------------------
1 | escaper = $this->createMock(Escaper::class);
42 | }
43 |
44 | /**
45 | * @covers \Umc\Crud\ViewModel\Formatter::formatHtml
46 | * @covers \Umc\Crud\ViewModel\Formatter::getFormatter
47 | * @covers \Umc\Crud\ViewModel\Formatter::__construct
48 | */
49 | public function testFormatHtml()
50 | {
51 | $formatter1 = $this->getFormatterMock();
52 | $formatter2 = $this->getFormatterMock();
53 | $formatter = new Formatter(
54 | [
55 | 'type1' => $formatter1,
56 | 'type2' => $formatter2,
57 | ],
58 | $this->escaper
59 | );
60 | $formatter1->expects($this->once())->method('formatHtml')->willReturn('formatted');
61 | $this->assertEquals('formatted', $formatter->formatHtml('value', ['type' => 'type1']));
62 | }
63 |
64 | /**
65 | * @covers \Umc\Crud\ViewModel\Formatter::formatHtml
66 | * @covers \Umc\Crud\ViewModel\Formatter::getFormatter
67 | * @covers \Umc\Crud\ViewModel\Formatter::__construct
68 | */
69 | public function testFormatHtmlNoArgument()
70 | {
71 | $formatter1 = $this->getFormatterMock();
72 | $formatter2 = $this->getFormatterMock();
73 | $formatter = new Formatter(
74 | [
75 | 'type1' => $formatter1,
76 | 'type2' => $formatter2,
77 | ],
78 | $this->escaper
79 | );
80 | $formatter1->expects($this->never())->method('formatHtml');
81 | $this->escaper->expects($this->once())->method('escapeHtml')->willReturn('formatted');
82 | $this->assertEquals('formatted', $formatter->formatHtml('value', []));
83 | }
84 |
85 | /**
86 | * @covers \Umc\Crud\ViewModel\Formatter::formatHtml
87 | * @covers \Umc\Crud\ViewModel\Formatter::getFormatter
88 | * @covers \Umc\Crud\ViewModel\Formatter::__construct
89 | */
90 | public function testFormatHtmlNoTypeConfigured()
91 | {
92 | $formatter1 = $this->getFormatterMock();
93 | $formatter2 = $this->getFormatterMock();
94 | $formatter = new Formatter(
95 | [
96 | 'type1' => $formatter1,
97 | 'type2' => $formatter2,
98 | ],
99 | $this->escaper
100 | );
101 | $this->expectException(\InvalidArgumentException::class);
102 | $formatter->formatHtml('value', ['type' => 'type3']);
103 | }
104 |
105 | /**
106 | * @covers \Umc\Crud\ViewModel\Formatter::__construct
107 | */
108 | public function testConstruct()
109 | {
110 | $this->expectException(\InvalidArgumentException::class);
111 | new Formatter(
112 | [
113 | 'type1' => $this->getFormatterMock(),
114 | 'type2' => 'string',
115 | ],
116 | $this->escaper
117 | );
118 | }
119 |
120 | /**
121 | * @return Formatter'FormatterInterface | MockObject
122 | * @throws \ReflectionException
123 | */
124 | private function getFormatterMock()
125 | {
126 | $formatter = $this->createMock(Formatter\FormatterInterface::class);
127 | return $formatter;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/Test/Unit/ViewModel/HeartbeatTest.php:
--------------------------------------------------------------------------------
1 | url = $this->createMock(UrlInterface::class);
46 | $this->heartbeat = new Heartbeat(
47 | $this->url
48 | );
49 | }
50 |
51 | /**
52 | * @covers \Umc\Crud\ViewModel\Heartbeat
53 | */
54 | public function testGetUrl()
55 | {
56 | $this->url->expects($this->once())->method('getUrl')->with('crud/heartbeat/index', null)->willReturn('url');
57 | $this->assertEquals('url', $this->heartbeat->getUrl());
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Ui/CollectionProviderInterface.php:
--------------------------------------------------------------------------------
1 | urlBuilder = $urlBuilder;
59 | $this->uiConfig = $uiConfig;
60 | parent::__construct($context, $uiComponentFactory, $components, $data);
61 | }
62 |
63 | /**
64 | * Prepare Data Source
65 | *
66 | * @param array $dataSource
67 | * @return array
68 | */
69 | public function prepareDataSource(array $dataSource)
70 | {
71 | $param = $this->uiConfig->getRequestParamName();
72 | $nameAttribute = $this->uiConfig->getNameAttribute();
73 | foreach ($dataSource['data']['items'] as & $item) {
74 | $params = [$param => $item[$param] ?? null];
75 | $item[$this->getData('name')] = [
76 | 'edit' => [
77 | 'href' => $this->urlBuilder->getUrl($this->uiConfig->getEditUrlPath(), $params),
78 | 'label' => __('Edit')->render()
79 | ],
80 | 'delete' => [
81 | 'href' => $this->urlBuilder->getUrl($this->uiConfig->getDeleteUrlPath(), $params),
82 | 'label' => __('Delete'),
83 | 'confirm' => [
84 | 'title' => __('Delete %1', $item[$nameAttribute] ?? '')->render(),
85 | 'message' => $this->uiConfig->getDeleteMessage()
86 | ],
87 | 'post' => true
88 | ]
89 | ];
90 | }
91 | return $dataSource;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Ui/Component/Listing/Image.php:
--------------------------------------------------------------------------------
1 | storeManager = $storeManager;
63 | $this->uiConfig = $uiConfig;
64 | $this->fileInfo = $fileInfo;
65 | parent::__construct($context, $uiComponentFactory, $components, $data);
66 | }
67 |
68 | /**
69 | * @param array $dataSource
70 | * @return array
71 | * @throws \Magento\Framework\Exception\NoSuchEntityException
72 | */
73 | public function prepareDataSource(array $dataSource)
74 | {
75 | if (isset($dataSource['data']['items'])) {
76 | $fieldName = $this->getData('name');
77 | foreach ($dataSource['data']['items'] as & $item) {
78 | $url = $this->getUrl($item[$fieldName] ?? '');
79 | $item[$fieldName . '_src'] = $url;
80 | $item[$fieldName . '_alt'] = $this->getAlt($item) ?: '';
81 | $item[$fieldName . '_orig_src'] = $url;
82 | $item[$fieldName . '_link'] = $this->getEditUrl($item);
83 | }
84 | }
85 | return $dataSource;
86 | }
87 |
88 | /**
89 | * @param $value
90 | * @return string
91 | * @throws \Magento\Framework\Exception\FileSystemException
92 | * @throws \Magento\Framework\Exception\NoSuchEntityException
93 | */
94 | private function getUrl($value)
95 | {
96 | if (!$value) {
97 | return '';
98 | }
99 | if ($this->fileInfo->isBeginsWithMediaDirectoryPath($value)) {
100 | return $value;
101 | }
102 | $store = $this->storeManager->getStore();
103 | $mediaBaseUrl = $store->getBaseUrl(
104 | \Magento\Framework\UrlInterface::URL_TYPE_MEDIA
105 | );
106 | return $mediaBaseUrl . ltrim($this->fileInfo->getBaseFilePath(), '/') . '/' . ltrim($value, '/');
107 | }
108 |
109 | /**
110 | * @param $item
111 | * @return string
112 | */
113 | private function getEditUrl($item)
114 | {
115 | $base = $this->uiConfig->getEditUrlPath();
116 | $idParam = $this->uiConfig->getRequestParamName();
117 | $params = [$idParam => $item[$idParam] ?? null];
118 | return $this->context->getUrl($base, $params);
119 | }
120 |
121 | /**
122 | * @param array $row
123 | *
124 | * @return null|string
125 | */
126 | private function getAlt($row)
127 | {
128 | $altField = $this->uiConfig->getNameAttribute();
129 | return $altField && isset($row[$altField]) ? $row[$altField] : null;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/Ui/EntityUiManagerInterface.php:
--------------------------------------------------------------------------------
1 | modifiers = $modifiers;
48 | }
49 |
50 | /**
51 | * @param AbstractModel $model
52 | * @param array $data
53 | * @return array
54 | */
55 | public function modifyData(AbstractModel $model, array $data): array
56 | {
57 | foreach ($this->modifiers as $modifier) {
58 | $data = $modifier->modifyData($model, $data);
59 | }
60 | return $data;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Ui/Form/DataModifier/DynamicRows.php:
--------------------------------------------------------------------------------
1 | serializer = $serializer;
47 | $this->fields = $fields;
48 | }
49 |
50 | /**
51 | * @param AbstractModel $model
52 | * @param array $data
53 | * @return array
54 | */
55 | public function modifyData(AbstractModel $model, array $data): array
56 | {
57 | foreach ($this->fields as $field) {
58 | if (array_key_exists($field, $data) && !is_array($data[$field])) {
59 | try {
60 | $data[$field] = $this->serializer->unserialize($data[$field]);
61 | } catch (\Exception $e) {
62 | $data[$field] = [];
63 | }
64 | }
65 | }
66 | return $data;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Ui/Form/DataModifier/Multiselect.php:
--------------------------------------------------------------------------------
1 | fields = $fields;
41 | }
42 |
43 | /**
44 | * @param AbstractModel $model
45 | * @param array $data
46 | * @return array
47 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
48 | */
49 | public function modifyData(AbstractModel $model, array $data): array
50 | {
51 | foreach ($this->fields as $field) {
52 | if (!array_key_exists($field, $data)) {
53 | continue;
54 | }
55 | if ($data[$field] === null) {
56 | $data[$field] = [];
57 | }
58 | if (is_string($data[$field])) {
59 | $data[$field] = explode(',', $data[$field]);
60 | }
61 | }
62 | return $data;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Ui/Form/DataModifier/NullModifier.php:
--------------------------------------------------------------------------------
1 | fields = $fields;
70 | $this->uploader = $uploader;
71 | $this->logger = $logger;
72 | $this->fileInfo = $fileInfo;
73 | $this->storeManager = $storeManager;
74 | }
75 |
76 | /**
77 | * @param AbstractModel $model
78 | * @param array $data
79 | * @return array
80 | * @throws \Magento\Framework\Exception\FileSystemException
81 | * @throws \Magento\Framework\Exception\LocalizedException
82 | */
83 | public function modifyData(AbstractModel $model, array $data): array
84 | {
85 | foreach ($this->fields as $field) {
86 | $value = $data[$field] ?? '';
87 | if ($value) {
88 | if ($this->fileInfo->isExist($value)) {
89 | $stat = $this->fileInfo->getStat($value);
90 | $mime = $this->fileInfo->getMimeType($value);
91 | $beginsWithMediaDirectory = $this->fileInfo->isBeginsWithMediaDirectoryPath($value);
92 | $url = ($beginsWithMediaDirectory) ? $value : $this->getUrl($value);
93 | $data[$field] = [
94 | 0 => [
95 | 'name' => $value,
96 | 'url' => $url,
97 | 'size' => isset($stat) ? $stat['size'] : 0,
98 | 'type' => $mime
99 | ]
100 | ];
101 | }
102 | }
103 | }
104 | return $data;
105 | }
106 |
107 | /**
108 | * @param $file
109 | * @return bool|string
110 | * @throws \Magento\Framework\Exception\LocalizedException
111 | * @throws \Magento\Framework\Exception\NoSuchEntityException
112 | */
113 | public function getUrl($file)
114 | {
115 | if (is_string($file)) {
116 | $store = $this->storeManager->getStore();
117 | $mediaBaseUrl = $store->getBaseUrl(
118 | \Magento\Framework\UrlInterface::URL_TYPE_MEDIA
119 | );
120 | return $mediaBaseUrl . ltrim($this->fileInfo->getBaseFilePath(), '/') . '/' . ltrim($file, '/');
121 | } else {
122 | throw new \Magento\Framework\Exception\LocalizedException(
123 | __('Something went wrong while getting the file url.')
124 | );
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/Ui/Form/DataModifierInterface.php:
--------------------------------------------------------------------------------
1 | dataPersistor = $dataPersistor;
71 | $this->uiConfig = $uiConfig;
72 | $this->dataModifier = $dataModifier;
73 | parent::__construct($name, $uiConfig->getRequestParamName(), $uiConfig->getRequestParamName(), $meta, $data);
74 | $this->collection = $collectionProvider->getCollection();
75 | }
76 |
77 | /**
78 | * @return array
79 | */
80 | public function getData()
81 | {
82 | if (isset($this->loadedData)) {
83 | return $this->loadedData;
84 | }
85 | /** @var AbstractModel $entity */
86 | foreach ($this->collection as $entity) {
87 | $this->loadedData[$entity->getId()] = $this->dataModifier->modifyData($entity, $entity->getData());
88 | }
89 | $persistorKey = $this->uiConfig->getPersistoryKey();
90 | $data = $this->dataPersistor->get($persistorKey);
91 | if (!empty($data)) {
92 | $entity = $this->collection->getNewEmptyItem();
93 | $entity->setData($data);
94 | $this->loadedData[$entity->getId()] = $entity->getData();
95 | $this->dataPersistor->clear($persistorKey);
96 | }
97 | return $this->loadedData;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Ui/SaveDataProcessor/CompositeProcessor.php:
--------------------------------------------------------------------------------
1 | modifiers = $modifiers;
47 | }
48 |
49 | public function modifyData(array $data): array
50 | {
51 | foreach ($this->modifiers as $modifier) {
52 | $data = $modifier->modifyData($data);
53 | }
54 | return $data;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Ui/SaveDataProcessor/Date.php:
--------------------------------------------------------------------------------
1 | fields = $fields;
54 | $this->filterFactory = $filterFactory;
55 | $this->dateFilter = $dateFilter;
56 | }
57 |
58 | /**
59 | * @param array $data
60 | * @return array
61 | */
62 | public function modifyData(array $data): array
63 | {
64 | $filterRules = [];
65 | foreach ($this->fields as $dateField) {
66 | if (!array_key_exists($dateField, $data)) {
67 | continue;
68 | }
69 | if (!empty($data[$dateField])) {
70 | $filterRules[$dateField] = $this->dateFilter;
71 | }
72 | }
73 | /** @var \Magento\Framework\Filter\FilterInput $filter */
74 | $filter = $this->filterFactory->create([
75 | 'filterRules' => $filterRules,
76 | 'validatorRules' => [],
77 | 'data' => $data
78 | ]);
79 | return $filter->getUnescaped();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Ui/SaveDataProcessor/DynamicRows.php:
--------------------------------------------------------------------------------
1 | serializer = $serializer;
51 | $this->fields = $fields;
52 | $this->strict = $strict;
53 | }
54 |
55 | /**
56 | * @param array $data
57 | * @return array
58 | */
59 | public function modifyData(array $data): array
60 | {
61 | foreach ($this->fields as $field) {
62 | if (!array_key_exists($field, $data) && $this->strict) {
63 | $data[$field] = [];
64 | }
65 | if (array_key_exists($field, $data) && is_array($data[$field])) {
66 | $data[$field] = $this->serializer->serialize($data[$field]);
67 | }
68 | }
69 | return $data;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Ui/SaveDataProcessor/Multiselect.php:
--------------------------------------------------------------------------------
1 | fields = $fields;
40 | }
41 |
42 | /**
43 | * @param array $data
44 | * @return array
45 | */
46 | public function modifyData(array $data): array
47 | {
48 | foreach ($this->fields as $field) {
49 | if (!array_key_exists($field, $data)) {
50 | continue;
51 | }
52 | $value = $data[$field] ?? [];
53 | if (is_array($value)) {
54 | $data[$field] = implode(',', $value);
55 | }
56 | }
57 | return $data;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Ui/SaveDataProcessor/NullProcessor.php:
--------------------------------------------------------------------------------
1 | fields = $fields;
76 | $this->uploader = $uploader;
77 | $this->fileInfo = $fileInfo;
78 | $this->filesystem = $filesystem;
79 | $this->logger = $logger;
80 | $this->strict = $strict;
81 | }
82 |
83 | /**
84 | * @param $value
85 | * @return bool
86 | */
87 | private function isTmpFileAvailable($value)
88 | {
89 | return is_array($value) && isset($value[0]['tmp_name']);
90 | }
91 |
92 | /**
93 | * @param $value
94 | * @return string
95 | */
96 | private function getUploadedImageName($value)
97 | {
98 | return (is_array($value) && isset($value[0]['file'])) ? $value[0]['file'] : '';
99 | }
100 |
101 | /**
102 | * @param array $data
103 | * @return array
104 | */
105 | public function modifyData(array $data): array
106 | {
107 | foreach ($this->fields as $field) {
108 | if (!array_key_exists($field, $data)) {
109 | if ($this->strict) {
110 | $data[$field] = '';
111 | }
112 | continue;
113 | }
114 | $value = $data[$field] ?? '';
115 | if ($this->isTmpFileAvailable($value) && $imageName = $this->getUploadedImageName($value)) {
116 | try {
117 | $data[$field] = $this->uploader->moveFileFromTmp($imageName);
118 | } catch (\Exception $e) {
119 | $this->logger->critical($e);
120 | }
121 | } else {
122 | if ($this->fileResidesOutsideUploadDir($value)) {
123 | // phpcs:ignore Magento2.Functions.DiscouragedFunction
124 | $value[0]['name'] = parse_url($value[0]['url'], PHP_URL_PATH);
125 | }
126 | $data[$field] = $value[0]['name'] ?? '';
127 | }
128 | }
129 | return $data;
130 | }
131 |
132 | /**
133 | * @param $value
134 | * @return bool
135 | */
136 | private function fileResidesOutsideUploadDir($value)
137 | {
138 | if (!is_array($value) || !isset($value[0]['url'])) {
139 | return false;
140 | }
141 | $fileUrl = ltrim($value[0]['url'], '/');
142 | $filePath = $this->fileInfo->getFilePath($fileUrl);
143 | $baseMediaDir = $this->filesystem->getUri(DirectoryList::MEDIA);
144 | return $baseMediaDir && strpos($filePath, $baseMediaDir) !== false;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/Ui/SaveDataProcessorInterface.php:
--------------------------------------------------------------------------------
1 | formatterMap = $formatterMap;
51 | $this->escaper = $escaper;
52 | }
53 |
54 | /**
55 | * @param $value
56 | * @param array $arguments
57 | * @return string
58 | */
59 | public function formatHtml($value, $arguments = []): string
60 | {
61 | $type = $arguments['type'] ?? null;
62 | return $type === null
63 | ? $this->escaper->escapeHtml($value)
64 | : $this->getFormatter($type)->formatHtml($value, $arguments);
65 | }
66 |
67 | /**
68 | * @param $type
69 | * @return FormatterInterface|null
70 | */
71 | private function getFormatter($type)
72 | {
73 | $formatter = $this->formatterMap[$type] ?? null;
74 | if ($formatter === null) {
75 | throw new \InvalidArgumentException("Missing formatter for type {$type}");
76 | }
77 | return $formatter;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/ViewModel/Formatter/Date.php:
--------------------------------------------------------------------------------
1 | localeDate = $localeDate;
52 | }
53 |
54 | /**
55 | * @param $value
56 | * @param array $arguments
57 | * @return string
58 | */
59 | public function formatHtml($value, $arguments = []): string
60 | {
61 | $format = $arguments[self::FORMAT] ?? self::DEFAULT_FORMAT;
62 | $showTime = $arguments[self::SHOW_TIME] ?? false;
63 | $timezone = $arguments[self::TIMEZONE] ?? null;
64 | $value = $value instanceof \DateTimeInterface ? $value : new \DateTime($value);
65 | return $this->localeDate->formatDateTime(
66 | $value,
67 | $format,
68 | $showTime ? $format : \IntlDateFormatter::NONE,
69 | null,
70 | $timezone
71 | );
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/ViewModel/Formatter/File.php:
--------------------------------------------------------------------------------
1 | fileInfoFactory = $fileInfoFactory;
60 | $this->filesystem = $filesystem;
61 | $this->storeManager = $storeManager;
62 | }
63 |
64 | /**
65 | * @param $path
66 | * @return FileInfo
67 | */
68 | private function getFileInfo($path)
69 | {
70 | if (!array_key_exists($path, $this->fileInfoCache)) {
71 | $this->fileInfoCache[$path] = $this->fileInfoFactory->create(['filePath' => $path]);
72 | }
73 | return $this->fileInfoCache[$path];
74 | }
75 |
76 | /**
77 | * @param $value
78 | * @param array $arguments
79 | * @return string
80 | * @throws \Exception
81 | */
82 | public function formatHtml($value, $arguments = []): string
83 | {
84 | $path = $arguments['path'] ?? '';
85 | $fileInfo = $this->getFileInfo($path);
86 | $filePath = $fileInfo->getFilePath((string)$value);
87 | if (!$filePath) {
88 | return '';
89 | }
90 | $store = $this->storeManager->getStore();
91 | $mediaBaseUrl = $store->getBaseUrl(
92 | \Magento\Framework\UrlInterface::URL_TYPE_MEDIA
93 | );
94 | return $mediaBaseUrl . trim($filePath, '/');
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/ViewModel/Formatter/FormatterInterface.php:
--------------------------------------------------------------------------------
1 | objectManager = $objectManager;
51 | $this->escaper = $escaper;
52 | }
53 |
54 | /**
55 | * @param $value
56 | * @param array $arguments
57 | * @return string
58 | */
59 | public function formatHtml($value, $arguments = []): string
60 | {
61 | $source = $this->getSource($arguments);
62 | $options = $source ? $source->toOptionArray() : $this->getOptions($arguments);
63 | $value = is_array($value) ? $value : [$value];
64 | $texts = array_map(
65 | function ($item) {
66 | return $this->escaper->escapeHtml($item['label'] ?? '');
67 | },
68 | array_filter(
69 | $options,
70 | function ($item) use ($value) {
71 | return isset($item['value']) && in_array($item['value'], $value);
72 | }
73 | )
74 | );
75 | return count($texts) > 0
76 | ? implode(', ', $texts)
77 | : (isset($arguments['default']) ? (string)$arguments['default'] : '');
78 | }
79 |
80 | /**
81 | * @param array $arguments
82 | * @return OptionSourceInterface|null
83 | */
84 | private function getSource(array $arguments): ?OptionSourceInterface
85 | {
86 | $sourceClass = $arguments['source'] ?? null;
87 | if (!$sourceClass) {
88 | return null;
89 | }
90 | if (!array_key_exists($sourceClass, $this->sources)) {
91 | $instance = $this->objectManager->get($sourceClass);
92 | if (!($instance instanceof OptionSourceInterface)) {
93 | throw new \InvalidArgumentException(
94 | "Source model for options formatter should implement " . OptionSourceInterface::class
95 | );
96 | }
97 | $this->sources[$sourceClass] = $instance;
98 | }
99 | return $this->sources[$sourceClass];
100 | }
101 |
102 | /**
103 | * @param array $arguments
104 | * @return array|mixed
105 | */
106 | private function getOptions(array $arguments): array
107 | {
108 | return $arguments['options'] ?? [];
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/ViewModel/Formatter/Text.php:
--------------------------------------------------------------------------------
1 | escaper = $escaper;
40 | }
41 |
42 | /**
43 | * @param $value
44 | * @param array $arguments
45 | * @return string
46 | */
47 | public function formatHtml($value, $arguments = []): string
48 | {
49 | return $this->escaper->escapeHtml($value);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ViewModel/Formatter/Wysiwyg.php:
--------------------------------------------------------------------------------
1 | filter = $filter;
40 | }
41 |
42 | /**
43 | * @param $value
44 | * @param array $arguments
45 | * @return string
46 | * @throws \Laminas\Filter\Exception\ExceptionInterface
47 | */
48 | public function formatHtml($value, $arguments = []): string
49 | {
50 | return $this->filter->filter($value);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/ViewModel/Heartbeat.php:
--------------------------------------------------------------------------------
1 | url = $url;
41 | }
42 |
43 | /**
44 | * @return string
45 | */
46 | public function getUrl()
47 | {
48 | return $this->url->getUrl('crud/heartbeat/index');
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "umc/module-crud",
3 | "description": "Magento 2 module for reducing the CRUD boilerplate",
4 | "require": {
5 | "php": "~7.1.3||~7.2.0||~7.3.0||~7.4.0||~8.0.0||~8.1.0||~8.2.0||~8.3.0||~8.4.0",
6 | "magento/module-backend": "*",
7 | "magento/module-config": "*",
8 | "magento/module-ui": "*",
9 | "magento/framework": "*"
10 | },
11 | "type": "magento2-module",
12 | "license": [
13 | "MIT"
14 | ],
15 | "autoload": {
16 | "files": [
17 | "registration.php"
18 | ],
19 | "psr-4": {
20 | "Umc\\Crud\\": ""
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/etc/adminhtml/routes.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |