From 59dc7ce8c5b85fbfdc8bff9f79ad9a889de3dafb Mon Sep 17 00:00:00 2001 From: Marius Strajeru Date: Fri, 8 May 2020 16:03:45 +0300 Subject: [PATCH] #7 and upload file fixes (#8) --- Generator/ListRepo.php | 4 +- Model/FileChecker.php | 67 ++++++ Model/FileInfo.php | 18 +- .../Collection/AbstractCollection.php | 2 +- .../StoreAwareAbstractCollection.php | 2 +- .../ResourceModel/StoreAwareAbstractModel.php | 22 +- Model/Uploader.php | 56 ++--- README.md | 12 +- Test/Unit/Generator/ListRepoTest.php | 24 +++ Test/Unit/Model/FileCheckerTest.php | 100 +++++++++ Test/Unit/Model/FileInfoTest.php | 20 ++ .../Collection/AbstractCollectionTest.php | 194 ++++++++++++++++++ .../StoreAwareAbstractCollectionTest.php | 193 +++++++++++++++++ .../Relation/Store/SaveHandlerTest.php | 113 ++++++++++ .../StoreAwareAbstractModelTest.php | 176 ++++++++++++++++ Test/Unit/Model/UploaderTest.php | 9 + .../Ui/SaveDataProcessor/DynamicRowsTest.php | 52 ++++- Test/Unit/Ui/SaveDataProcessor/UploadTest.php | 131 ++++++++++-- Test/Unit/ViewModel/Formatter/DateTest.php | 81 ++++++++ Test/Unit/ViewModel/Formatter/FileTest.php | 103 ++++++++++ Test/Unit/ViewModel/Formatter/ImageTest.php | 153 ++++++++++++++ Test/Unit/ViewModel/Formatter/OptionTest.php | 141 +++++++++++++ Test/Unit/ViewModel/Formatter/TextTest.php | 58 ++++++ Test/Unit/ViewModel/Formatter/WysiwygTest.php | 57 +++++ Test/Unit/ViewModel/FormatterTest.php | 129 ++++++++++++ Ui/SaveDataProcessor/DynamicRows.php | 11 +- Ui/SaveDataProcessor/Upload.php | 24 ++- ViewModel/Formatter.php | 79 +++++++ ViewModel/Formatter/Date.php | 73 +++++++ ViewModel/Formatter/File.php | 96 +++++++++ ViewModel/Formatter/FormatterInterface.php | 34 +++ ViewModel/Formatter/Image.php | 190 +++++++++++++++++ ViewModel/Formatter/Options.php | 110 ++++++++++ ViewModel/Formatter/Text.php | 51 +++++ ViewModel/Formatter/Wysiwyg.php | 50 +++++ composer.json | 6 +- etc/di.xml | 17 ++ registration.php | 2 + view/adminhtml/templates/heartbeat.phtml | 2 +- 39 files changed, 2576 insertions(+), 86 deletions(-) create mode 100644 Model/FileChecker.php create mode 100644 Test/Unit/Model/FileCheckerTest.php create mode 100644 Test/Unit/Model/ResourceModel/Collection/AbstractCollectionTest.php create mode 100644 Test/Unit/Model/ResourceModel/Collection/StoreAwareAbstractCollectionTest.php create mode 100644 Test/Unit/Model/ResourceModel/Relation/Store/SaveHandlerTest.php create mode 100644 Test/Unit/Model/ResourceModel/StoreAwareAbstractModelTest.php create mode 100644 Test/Unit/ViewModel/Formatter/DateTest.php create mode 100644 Test/Unit/ViewModel/Formatter/FileTest.php create mode 100644 Test/Unit/ViewModel/Formatter/ImageTest.php create mode 100644 Test/Unit/ViewModel/Formatter/OptionTest.php create mode 100644 Test/Unit/ViewModel/Formatter/TextTest.php create mode 100644 Test/Unit/ViewModel/Formatter/WysiwygTest.php create mode 100644 Test/Unit/ViewModel/FormatterTest.php create mode 100644 ViewModel/Formatter.php create mode 100644 ViewModel/Formatter/Date.php create mode 100644 ViewModel/Formatter/File.php create mode 100644 ViewModel/Formatter/FormatterInterface.php create mode 100644 ViewModel/Formatter/Image.php create mode 100644 ViewModel/Formatter/Options.php create mode 100644 ViewModel/Formatter/Text.php create mode 100644 ViewModel/Formatter/Wysiwyg.php diff --git a/Generator/ListRepo.php b/Generator/ListRepo.php index 001d6e9..77cc92c 100644 --- a/Generator/ListRepo.php +++ b/Generator/ListRepo.php @@ -86,7 +86,8 @@ protected function _getDefaultConstructorDefinition() ], [ 'name' => 'param', - 'description' => '\\' . $this->nameMatcher->getCollectionFactoryClass($this->getSourceClassName()) + 'description' => '\\' + . $this->nameMatcher->getCollectionFactoryClass($this->getSourceClassName()) . ' $collectionFactory', ] ] @@ -269,5 +270,4 @@ private function getFilterConditions($indent) $text .= $padd . '$conditions[] = [$condition => $filter->getValue()];' . "\n"; return $text; } - } diff --git a/Model/FileChecker.php b/Model/FileChecker.php new file mode 100644 index 0000000..d50cd04 --- /dev/null +++ b/Model/FileChecker.php @@ -0,0 +1,67 @@ +file = $file; + } + + /** + * @param $destinationFile + * @param int $sparseLevel + * @return string + */ + public function getNewFileName($destinationFile, $sparseLevel = 2) + { + $fileInfo = $this->file->getPathInfo($destinationFile); + if ($this->file->fileExists($destinationFile)) { + $index = 1; + $baseName = $fileInfo['filename'] . '.' . $fileInfo['extension']; + while ($this->file->fileExists($fileInfo['dirname'] . '/' . $baseName)) { + $baseName = $fileInfo['filename'] . '_' . $index . '.' . $fileInfo['extension']; + $index++; + } + return $baseName; + } else { + $prefix = $sparseLevel > 0 ? '/' : ''; + $fileName = $fileInfo['filename']; + for ($i = 0; $i < $sparseLevel; $i++) { + $prefix .= ($fileName[$i] ?? '_') . '/'; + } + return $prefix . $fileInfo['basename']; + } + } +} diff --git a/Model/FileInfo.php b/Model/FileInfo.php index 558e325..a0fda44 100644 --- a/Model/FileInfo.php +++ b/Model/FileInfo.php @@ -139,6 +139,18 @@ public function getMimeType($fileName) return $result; } + /** + * @param $fileName + * @return null|string + * @throws \Magento\Framework\Exception\FileSystemException + */ + public function getAbsoluteFilePath($fileName) + { + return $this->isExist($fileName) + ? $this->getMediaDirectory()->getAbsolutePath($this->getFilePath($fileName)) + : null; + } + /** * @param $fileName * @return array @@ -168,7 +180,7 @@ public function isExist($fileName) * @return bool|string * @throws \Magento\Framework\Exception\FileSystemException */ - private function getFilePath($fileName) + public function getFilePath($fileName) { $filePath = $this->removeStorePath($fileName); $filePath = ltrim($filePath, '/'); @@ -194,9 +206,7 @@ public function isBeginsWithMediaDirectoryPath($fileName) $filePath = ltrim($filePath, '/'); $mediaDirectoryRelativeSubpath = $this->getMediaDirectoryPathRelativeToBaseDirectoryPath($filePath); - $isFileNameBeginsWithMediaDirectoryPath = strpos($filePath, (string) $mediaDirectoryRelativeSubpath) === 0; - - return $isFileNameBeginsWithMediaDirectoryPath; + return strpos($filePath, (string) $mediaDirectoryRelativeSubpath) === 0; } /** diff --git a/Model/ResourceModel/Collection/AbstractCollection.php b/Model/ResourceModel/Collection/AbstractCollection.php index 2476670..4b049f3 100644 --- a/Model/ResourceModel/Collection/AbstractCollection.php +++ b/Model/ResourceModel/Collection/AbstractCollection.php @@ -25,7 +25,7 @@ use Magento\Framework\Api\Search\SearchResultInterface; use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection as DbCollection; -abstract class AbstractCollection extends DbCollection implements SearchResultInterface +class AbstractCollection extends DbCollection implements SearchResultInterface { /** * @var AggregationInterface diff --git a/Model/ResourceModel/Collection/StoreAwareAbstractCollection.php b/Model/ResourceModel/Collection/StoreAwareAbstractCollection.php index 314721a..47c1ff8 100644 --- a/Model/ResourceModel/Collection/StoreAwareAbstractCollection.php +++ b/Model/ResourceModel/Collection/StoreAwareAbstractCollection.php @@ -107,7 +107,7 @@ protected function _afterLoad() /** * @throws \Exception */ - public function _renderFiltersBefore() + protected function _renderFiltersBefore() { $entityMetadata = $this->metadataPool->getMetadata($this->interfaceClass); $this->storeResource->joinStoreRelationTable($this, $this->storeTable, $entityMetadata->getLinkField()); diff --git a/Model/ResourceModel/StoreAwareAbstractModel.php b/Model/ResourceModel/StoreAwareAbstractModel.php index 0152be5..102a40f 100644 --- a/Model/ResourceModel/StoreAwareAbstractModel.php +++ b/Model/ResourceModel/StoreAwareAbstractModel.php @@ -121,9 +121,9 @@ private function getEntityId(\Magento\Framework\Model\AbstractModel $object, $va $entityId = $value; if ($field != $entityMetadata->getIdentifierField() || $object->getData($this->storeIdField)) { $select = $this->_getLoadSelect($field, $value, $object); - $select->reset(Select::COLUMNS) - ->columns($this->getMainTable() . '.' . $entityMetadata->getIdentifierField()) - ->limit(1); + $select->reset(Select::COLUMNS); + $select->columns($this->getMainTable() . '.' . $entityMetadata->getIdentifierField()); + $select->limit(1); $result = $this->getConnection()->fetchCol($select); $entityId = count($result) ? $result[0] : false; } @@ -159,14 +159,14 @@ public function lookupStoreIds($id): array $idField = $entityMetadata->getIdentifierField(); $linkField = $entityMetadata->getLinkField(); - $select = $connection->select() - ->from(['entity_store_table' => $this->getTable($this->storeTable)], $this->storeIdField) - ->join( - ['entity_table' => $this->getMainTable()], - 'entity_store_table.' . $linkField . ' = entity_table.' . $linkField, - [] - ) - ->where('entity_table.' . $entityMetadata->getIdentifierField() . ' = :' . $idField); + $select = $connection->select(); + $select->from(['entity_store_table' => $this->getTable($this->storeTable)], $this->storeIdField); + $select->join( + ['entity_table' => $this->getMainTable()], + 'entity_store_table.' . $linkField . ' = entity_table.' . $linkField, + [] + ); + $select->where('entity_table.' . $entityMetadata->getIdentifierField() . ' = :' . $idField); return $connection->fetchCol($select, [$idField => (int)$id]); } diff --git a/Model/Uploader.php b/Model/Uploader.php index d363e8f..687d7c9 100644 --- a/Model/Uploader.php +++ b/Model/Uploader.php @@ -22,7 +22,9 @@ namespace Umc\Crud\Model; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\WriteInterface; use Magento\Framework\UrlInterface; @@ -61,7 +63,10 @@ class Uploader * @var string */ private $basePath; - + /** + * @var FileChecker + */ + private $fileChecker; /** * @var array */ @@ -74,10 +79,11 @@ class Uploader * @param UploaderFactory $uploaderFactory * @param StoreManagerInterface $storeManager * @param LoggerInterface $logger - * @param $baseTmpPath - * @param $basePath + * @param FileChecker $fileChecker + * @param string $baseTmpPath + * @param string $basePath * @param array $allowedExtensions - * @throws \Magento\Framework\Exception\FileSystemException + * @throws FileSystemException */ public function __construct( Database $coreFileStorageDatabase, @@ -85,17 +91,19 @@ public function __construct( UploaderFactory $uploaderFactory, StoreManagerInterface $storeManager, LoggerInterface $logger, + FileChecker $fileChecker, string $baseTmpPath, string $basePath, array $allowedExtensions = [] ) { $this->coreFileStorageDatabase = $coreFileStorageDatabase; - $this->uploaderFactory = $uploaderFactory; - $this->storeManager = $storeManager; - $this->logger = $logger; - $this->baseTmpPath = $baseTmpPath; - $this->basePath = $basePath; - $this->allowedExtensions = $allowedExtensions; + $this->uploaderFactory = $uploaderFactory; + $this->storeManager = $storeManager; + $this->logger = $logger; + $this->fileChecker = $fileChecker; + $this->baseTmpPath = $baseTmpPath; + $this->basePath = $basePath; + $this->allowedExtensions = $allowedExtensions; $this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); } @@ -146,15 +154,22 @@ public function getFilePath($path, $name) * * @param string $name * @return string - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function moveFileFromTmp($name) { $baseTmpPath = $this->getBaseTmpPath(); $basePath = $this->getBasePath(); - $baseFilePath = $this->getFilePath($basePath, $name); $baseTmpFilePath = $this->getFilePath($baseTmpPath, $name); + $baseFilePath = $this->getFilePath( + $basePath, + $this->fileChecker->getNewFileName( + $this->mediaDirectory->getAbsolutePath( + $this->getFilePath($basePath, $name) + ) + ) + ); try { $this->coreFileStorageDatabase->copyFile( @@ -175,9 +190,8 @@ public function moveFileFromTmp($name) } /** - * get base url - * - * @return string + * @return mixed + * @throws NoSuchEntityException */ public function getBaseUrl() { @@ -189,13 +203,10 @@ public function getBaseUrl() } /** - * Checking file for save and save it to tmp dir - * - * @param string $fileId - * - * @return string[] - * - * @throws \Magento\Framework\Exception\LocalizedException + * @param $fileId + * @return array|bool + * @throws LocalizedException + * @throws NoSuchEntityException */ public function saveFileToTmpDir($fileId) { @@ -231,7 +242,6 @@ public function saveFileToTmpDir($fileId) ); } } - return $result; } } diff --git a/README.md b/README.md index 29df524..1c9305b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This module is intended for Magento 2 developers, in order to reduce the boilerp ## Compatibility -This module was developed an tested on Magento Open Source 2.3.4. I plan to support this for later versions also. +This module was developed and tested on Magento Open Source 2.3.4 and 2.3.5. I plan to support this for later versions also. It may work for versions before 2.3.4, but I didn't test. ## What it does: @@ -30,7 +30,7 @@ And the only variables here are 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 - an Entity manager responsible for retrieving the data from db or instantiating a new entity - a data processor (interface) that processes the data - - an entity config class that will contain the details about the entity being processed. + - an entity config class will contain the details about the entity being processed. - 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. All of these can be configured via `di.xml` for each entity you want to manage. @@ -48,10 +48,10 @@ This module also adds a few more code generators (similar to the core ones for f ## Advantages in using this module - less code to write, which means less code to test and less code that can malfunction - - your copy/paste analyzer will stop compalining that you have classes that look the same. + - your copy/paste analyzer will stop complaining you have classes that look the same. - decrease development time. (hopefully) - 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) - - 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 chose not to extend or compose the classes in this module and use your own. + - 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. ## Disadvantages of using this module @@ -67,8 +67,8 @@ This module also adds a few more code generators (similar to the core ones for f - download a copy from `https://github.com/UltimateModuleCreator/umc-crud` and all the files in `app/code/Umc/Crud`. After installation - - check if this file exists `app/etc/crud/di.xml`. If it does not exist, run the commmand `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` - - run `bin/magento setup:upgrade` + - run `php bin/magento setup:upgrade [--keep-generated]` + - 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`. # Documentation diff --git a/Test/Unit/Generator/ListRepoTest.php b/Test/Unit/Generator/ListRepoTest.php index 66f1017..d707d4d 100644 --- a/Test/Unit/Generator/ListRepoTest.php +++ b/Test/Unit/Generator/ListRepoTest.php @@ -30,6 +30,7 @@ use ReflectionParameter; use Umc\Crud\Generator\ListRepo; use Umc\Crud\Generator\NameMatcher; +use Umc\Crud\Model\ResourceModel\StoreAwareAbstractModel; class ListRepoTest extends TestCase { @@ -98,4 +99,27 @@ public function testGenerate() $this->classGenerator->expects($this->once())->method('generate')->willReturn('generated code'); $this->assertEquals('filename.php', $this->listRepo->generate()); } + + /** + * @covers \Umc\Crud\Generator\ListRepo + */ + public function testGenerateStoreAwareModel() + { + $this->ioObject->expects($this->once())->method('generateResultFileName')->willReturn('filename.php'); + $this->nameMatcher->expects($this->once())->method('getListRepoInterface'); + $this->nameMatcher->expects($this->any())->method('getSearchResultsClass'); + $this->nameMatcher->expects($this->any())->method('getSearchResultFactory')->willReturn('searchResults'); + $this->nameMatcher->expects($this->any())->method('getCollectionFactoryClass'); + $class = $this->createMock(StoreAwareAbstractModel::class); + $this->nameMatcher->method('getResourceClassName')->willReturn(get_class($class)); + $this->definedClasses->method('isClassLoadable')->willReturn(true); + $this->ioObject->method('makeResultFileDirectory')->willReturn(true); + $this->ioObject->method('fileExists')->willReturn(true); + $this->classGenerator->expects($this->once())->method('setName')->willReturnSelf(); + $this->classGenerator->expects($this->once())->method('addProperties')->willReturnSelf(); + $this->classGenerator->expects($this->once())->method('addMethods')->willReturnSelf(); + $this->classGenerator->expects($this->once())->method('setClassDocBlock')->willReturnSelf(); + $this->classGenerator->expects($this->once())->method('generate')->willReturn('generated code'); + $this->assertEquals('filename.php', $this->listRepo->generate()); + } } diff --git a/Test/Unit/Model/FileCheckerTest.php b/Test/Unit/Model/FileCheckerTest.php new file mode 100644 index 0000000..848629b --- /dev/null +++ b/Test/Unit/Model/FileCheckerTest.php @@ -0,0 +1,100 @@ +file = $this->createMock(File::class); + $this->fileChecker = new FileChecker( + $this->file + ); + } + + /** + * @covers \Umc\Crud\Model\FileChecker::getNewFileName + * @covers \Umc\Crud\Model\FileChecker::__construct + */ + public function testGetNewFileName() + { + $this->file->method('getPathInfo')->willReturn([ + 'filename' => 'file', + 'extension' => 'ext', + 'basename' => 'file.ext', + 'dirname' => 'dir' + ]); + $this->file->method('fileExists')->willReturn(false); + $this->assertEquals('file.ext', $this->fileChecker->getNewFileName('file', 0)); + $this->assertEquals('/f/i/file.ext', $this->fileChecker->getNewFileName('file')); + $this->assertEquals('/f/i/l/e/_/_/file.ext', $this->fileChecker->getNewFileName('file', 6)); + } + + /** + * @covers \Umc\Crud\Model\FileChecker::getNewFileName + * @covers \Umc\Crud\Model\FileChecker::__construct + */ + public function testGetNewFileNameFileExists() + { + $this->file->method('getPathInfo')->willReturn([ + 'filename' => 'file', + 'extension' => 'ext', + 'basename' => 'file.ext', + 'dirname' => 'dir' + ]); + $this->file->method('fileExists')->willReturnOnConsecutiveCalls(true, true, false); + $this->assertEquals('file_1.ext', $this->fileChecker->getNewFileName('file', 0)); + } + + /** + * @covers \Umc\Crud\Model\FileChecker::getNewFileName + * @covers \Umc\Crud\Model\FileChecker::__construct + */ + public function testGetNewFileNameFileExistsThreeLevels() + { + $this->file->method('getPathInfo')->willReturn([ + 'filename' => 'file', + 'extension' => 'ext', + 'basename' => 'file.ext', + 'dirname' => 'dir' + ]); + $this->file->method('fileExists')->willReturnOnConsecutiveCalls(true, true, true, true, false); + $this->assertEquals('file_3.ext', $this->fileChecker->getNewFileName('file', 0)); + } +} diff --git a/Test/Unit/Model/FileInfoTest.php b/Test/Unit/Model/FileInfoTest.php index df2d08d..40aeed3 100644 --- a/Test/Unit/Model/FileInfoTest.php +++ b/Test/Unit/Model/FileInfoTest.php @@ -151,4 +151,24 @@ public function testIsExist() $this->mediaDirectory->method('isExist')->willReturn(true); $this->assertTrue($this->fileInfo->isExist('some/file.png')); } + + /** + * @covers \Umc\Crud\Model\FileInfo::getAbsoluteFilePath + * @covers \Umc\Crud\Model\FileInfo::getMediaDirectory + * @covers \Umc\Crud\Model\FileInfo::getFilePath + * @covers \Umc\Crud\Model\FileInfo::getPubDirectory + * @covers \Umc\Crud\Model\FileInfo::getBaseDirectory + * @covers \Umc\Crud\Model\FileInfo::getMediaDirectoryPathRelativeToBaseDirectoryPath + * @covers \Umc\Crud\Model\FileInfo::removeStorePath + * @covers \Umc\Crud\Model\FileInfo::isBeginsWithMediaDirectoryPath + * @covers \Umc\Crud\Model\FileInfo::__construct + */ + public function testGetAbsoluteFilePath() + { + $this->readDirectory->method('getAbsolutePath')->willReturn('absolute/path'); + $this->readDirectory->method('getRelativePath')->willReturn('relative'); + $this->mediaDirectory->method('getAbsolutePath')->willReturn('media/absolute/path'); + $this->mediaDirectory->method('isExist')->willReturn(true); + $this->assertEquals('media/absolute/path', $this->fileInfo->getAbsoluteFilePath('some/file.png')); + } } diff --git a/Test/Unit/Model/ResourceModel/Collection/AbstractCollectionTest.php b/Test/Unit/Model/ResourceModel/Collection/AbstractCollectionTest.php new file mode 100644 index 0000000..5fe24e1 --- /dev/null +++ b/Test/Unit/Model/ResourceModel/Collection/AbstractCollectionTest.php @@ -0,0 +1,194 @@ +entityFactory = $this->createMock(EntityFactoryInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->fetchStrategy = $this->createMock(FetchStrategyInterface::class); + $this->eventManager = $this->createMock(ManagerInterface::class); + $this->connection = $this->createMock(AdapterInterface::class); + $this->resource = $this->createMock(AbstractDb::class); + $this->searchCriteria = $this->createMock(SearchCriteriaInterface::class); + $this->item = $this->createMock(DataObject::class); + $this->select = $this->createMock(Select::class); + $this->join = $this->createMock(JoinDataInterface::class); + $this->extensionAttributesJoinProcessor = $this->createMock(JoinProcessorInterface::class); + $this->resource->method('getConnection')->willReturn($this->connection); + $this->connection->method('select')->willReturn($this->select); + $this->abstractCollection = new AbstractCollection( + $this->entityFactory, + $this->logger, + $this->fetchStrategy, + $this->eventManager, + 'main_table', + 'event_prefix', + 'object', + 'resourceModel', + DataObject::class, + $this->connection, + $this->resource + ); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::getSelectCountSql + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::__construct + */ + public function testGetSelectCountSql() + { + $this->select->expects($this->at(6))->method('reset')->with('group'); + $this->abstractCollection->getSelectCountSql(); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::setItems + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::__construct + */ + public function testSetItems() + { + $this->assertEquals($this->abstractCollection, $this->abstractCollection->setItems()); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::setAggregations + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::getAggregations + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::__construct + */ + public function testGetAggregations() + { + $aggregations = $this->createMock(AggregationInterface::class); + $this->abstractCollection->setAggregations($aggregations); + $this->assertEquals($aggregations, $this->abstractCollection->getAggregations()); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::getSearchCriteria + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::__construct + */ + public function testGetSearchCriteria() + { + $this->assertNull($this->abstractCollection->getSearchCriteria()); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::setSearchCriteria + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::__construct + */ + public function testSetSearchCriteria() + { + $this->assertEquals( + $this->abstractCollection, + $this->abstractCollection->setSearchCriteria($this->searchCriteria) + ); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::getTotalCount + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::__construct + */ + public function testGetTotalCount() + { + $this->connection->method('fetchOne')->willReturn(9); + $this->assertEquals(9, $this->abstractCollection->getTotalCount()); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::setTotalCount + * @covers \Umc\Crud\Model\ResourceModel\Collection\AbstractCollection::__construct + */ + public function testSetTotalCount() + { + $this->assertEquals($this->abstractCollection, $this->abstractCollection->setTotalCount(9)); + } +} diff --git a/Test/Unit/Model/ResourceModel/Collection/StoreAwareAbstractCollectionTest.php b/Test/Unit/Model/ResourceModel/Collection/StoreAwareAbstractCollectionTest.php new file mode 100644 index 0000000..0ac33cc --- /dev/null +++ b/Test/Unit/Model/ResourceModel/Collection/StoreAwareAbstractCollectionTest.php @@ -0,0 +1,193 @@ +entityFactory = $this->createMock(EntityFactoryInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->fetchStrategy = $this->createMock(FetchStrategyInterface::class); + $this->eventManager = $this->createMock(ManagerInterface::class); + $this->storeResource = $this->createMock(Store::class); + $this->metadataPool = $this->createMock(MetadataPool::class); + $this->connection = $this->createMock(AdapterInterface::class); + $this->resource = $this->createMock(AbstractDb::class); + $this->searchCriteria = $this->createMock(SearchCriteriaInterface::class); + $this->item = $this->createMock(DataObject::class); + $this->conn = $this->createMock(AdapterInterface::class); + $this->select = $this->createMock(Select::class); + $this->join = $this->createMock(JoinDataInterface::class); + $this->extensionAttributesJoinProcessor = $this->createMock(JoinProcessorInterface::class); + $this->resource->method('getConnection')->willReturn($this->connection); + $this->connection->method('select')->willReturn($this->select); + $this->storeAwareAbstractCollection = new StoreAwareAbstractCollection( + $this->entityFactory, + $this->logger, + $this->fetchStrategy, + $this->eventManager, + 'main_table', + 'event_prefix', + 'object', + 'resourceModel', + DataObject::class, + $this->storeResource, + $this->metadataPool, + 'InterfaceName', + 'store_table', + $this->connection, + $this->resource + ); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\Collection\StoreAwareAbstractCollection::addStoreFilter + * @covers \Umc\Crud\Model\ResourceModel\Collection\StoreAwareAbstractCollection::__construct + */ + public function testAddStoreFilter() + { + $this->storeResource->expects($this->once())->method('addStoreFilter'); + $this->assertEquals( + $this->storeAwareAbstractCollection, + $this->storeAwareAbstractCollection->addStoreFilter(3) + ); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\Collection\StoreAwareAbstractCollection::addFieldToFilter + * @covers \Umc\Crud\Model\ResourceModel\Collection\StoreAwareAbstractCollection::addStoreFilter + * @covers \Umc\Crud\Model\ResourceModel\Collection\StoreAwareAbstractCollection::__construct + */ + public function testAddFieldToFilter() + { + $this->storeResource->expects($this->once())->method('addStoreFilter'); + $this->storeAwareAbstractCollection->addFieldToFilter('store_id', 'condition'); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\Collection\StoreAwareAbstractCollection::addFieldToFilter + * @covers \Umc\Crud\Model\ResourceModel\Collection\StoreAwareAbstractCollection::__construct + */ + public function testAddFieldToFilterNotStore() + { + $this->storeResource->expects($this->never())->method('addStoreFilter'); + $this->storeAwareAbstractCollection->addFieldToFilter('dummy', 'condition'); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\Collection\StoreAwareAbstractCollection::_renderFiltersBefore + * @covers \Umc\Crud\Model\ResourceModel\Collection\StoreAwareAbstractCollection::_afterLoad + * @covers \Umc\Crud\Model\ResourceModel\Collection\StoreAwareAbstractCollection::__construct + */ + public function testLoad() + { + $metadata = $this->createMock(EntityMetadataInterface::class); + $metadata->method('getLinkField')->willReturn('field'); + $this->metadataPool->method('getMetadata')->with('InterfaceName')->willReturn($metadata); + $this->storeResource->expects($this->once())->method('addStoresToCollection'); + $this->storeResource->expects($this->once())->method('joinStoreRelationTable'); + $this->storeAwareAbstractCollection->load(); + } +} diff --git a/Test/Unit/Model/ResourceModel/Relation/Store/SaveHandlerTest.php b/Test/Unit/Model/ResourceModel/Relation/Store/SaveHandlerTest.php new file mode 100644 index 0000000..dc4118e --- /dev/null +++ b/Test/Unit/Model/ResourceModel/Relation/Store/SaveHandlerTest.php @@ -0,0 +1,113 @@ +metadataPool = $this->createMock(MetadataPool::class); + $this->resource = $this->createMock(StoreAwareAbstractModel::class); + $this->metadata = $this->createMock(EntityMetadataInterface::class); + $this->metadataPool->method('getMetadata')->willReturn($this->metadata); + $this->connection = $this->createMock(AdapterInterface::class); + $this->resource->method('getConnection')->willReturn($this->connection); + $this->entity = $this->createMock(AbstractModel::class); + $this->saveHandler = new SaveHandler( + $this->metadataPool, + $this->resource, + 'entityType', + 'store_table', + 'store_id' + ); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\Relation\Store\SaveHandler::execute + * @covers \Umc\Crud\Model\ResourceModel\Relation\Store\SaveHandler::__construct + */ + public function testExecute() + { + $this->metadata->method('getLinkField')->willReturn('entity_id'); + $this->resource->method('lookupStoreIds')->willReturn([1, 2, 3]); + $this->entity->method('getData')->willReturnMap([ + ['store_id', null, [1, 2, 4, 5]], + ['entity_id', null, 1] + ]); + $this->connection->expects($this->once())->method('delete'); + $this->connection->expects($this->once())->method('insertMultiple'); + $this->saveHandler->execute($this->entity); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\Relation\Store\SaveHandler::execute + * @covers \Umc\Crud\Model\ResourceModel\Relation\Store\SaveHandler::__construct + */ + public function testExecuteNoInsert() + { + $this->resource->method('lookupStoreIds')->willReturn([1, 2, 3]); + $this->entity->method('getData')->willReturnMap([ + ['store_id', null, [1, 2]], + ['entity_id', null, 1] + ]); + $this->connection->expects($this->once())->method('delete'); + $this->connection->expects($this->never())->method('insertMultiple'); + $this->saveHandler->execute($this->entity); + } +} diff --git a/Test/Unit/Model/ResourceModel/StoreAwareAbstractModelTest.php b/Test/Unit/Model/ResourceModel/StoreAwareAbstractModelTest.php new file mode 100644 index 0000000..6a8b3a2 --- /dev/null +++ b/Test/Unit/Model/ResourceModel/StoreAwareAbstractModelTest.php @@ -0,0 +1,176 @@ +context = $this->createMock(Context::class); + $this->entityManager = $this->createMock(EntityManager::class); + $this->metadataPool = $this->createMock(MetadataPool::class); + $this->storeManager = $this->createMock(StoreManagerInterface::class); + $this->object = $this->createMock(AbstractModel::class); + $this->connection = $this->createMock(AdapterInterface::class); + $this->select = $this->createMock(Select::class); + $this->metadata = $this->createMock(EntityMetadataInterface::class); + $this->metadata->method('getEntityConnection')->willReturn($this->connection); + $this->metadataPool->method('getMetadata')->willReturn($this->metadata); + $this->resource = $this->createMock(ResourceConnection::class); + $this->context->method('getResources')->willReturn($this->resource); + $modelClass = new class ( + $this->context, + $this->entityManager, + $this->metadataPool, + 'interfaceName', + $this->storeManager, + 'store_table' + ) extends StoreAwareAbstractModel + { + //phpcs:disable PSR2.Methods.MethodDeclaration.Underscore,PSR12.Methods.MethodDeclaration.Underscore + protected function _construct() + { + $this->_setMainTable('main_table'); + } + //phpcs:enable + }; + $this->storeAwareAbstractModel = new $modelClass( + $this->context, + $this->entityManager, + $this->metadataPool, + 'interfaceName', + $this->storeManager, + 'store_table' + ); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\StoreAwareAbstractModel::load + * @covers \Umc\Crud\Model\ResourceModel\StoreAwareAbstractModel::getEntityId + * @covers \Umc\Crud\Model\ResourceModel\StoreAwareAbstractModel::_getLoadSelect + * @covers \Umc\Crud\Model\ResourceModel\StoreAwareAbstractModel::__construct + */ + public function testLoad() + { + $this->connection->method('select')->willReturn($this->select); + $this->select->method('from')->willReturnSelf(); + $this->select->method('join')->willReturnSelf(); + $this->select->method('where')->willReturnSelf(); + $this->select->method('columns')->willReturnSelf(); + $this->object->method('getData')->with('store_id')->willReturn(1); + $this->connection->method('fetchCol')->willReturn([1, 2]); + $this->metadata->method('getLinkField')->willReturn('link_field'); + $this->entityManager->expects($this->once())->method('load')->with($this->object, 1); + $this->storeAwareAbstractModel->load($this->object, 1); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\StoreAwareAbstractModel::load + * @covers \Umc\Crud\Model\ResourceModel\StoreAwareAbstractModel::getEntityId + * @covers \Umc\Crud\Model\ResourceModel\StoreAwareAbstractModel::_getLoadSelect + * @covers \Umc\Crud\Model\ResourceModel\StoreAwareAbstractModel::__construct + */ + public function testLoadNoResult() + { + $this->connection->method('select')->willReturn($this->select); + $this->select->method('from')->willReturnSelf(); + $this->select->method('join')->willReturnSelf(); + $this->select->method('where')->willReturnSelf(); + $this->select->method('columns')->willReturnSelf(); + $this->object->method('getData')->with('store_id')->willReturn(1); + $this->connection->method('fetchCol')->willReturn([]); + $this->metadata->method('getLinkField')->willReturn('link_field'); + $this->entityManager->expects($this->never())->method('load'); + $this->storeAwareAbstractModel->load($this->object, 1, 'field'); + } + + /** + * @covers \Umc\Crud\Model\ResourceModel\StoreAwareAbstractModel::lookupStoreIds + * @covers \Umc\Crud\Model\ResourceModel\StoreAwareAbstractModel::__construct + */ + public function testLookupStoreIds() + { + $this->resource->method('getTableName')->willReturn('main_table'); + $this->connection->expects($this->once())->method('select')->willReturn($this->select); + $this->select->expects($this->once())->method('from'); + $this->select->expects($this->once())->method('join'); + $this->connection->expects($this->once())->method('fetchCol')->willReturn([1, 2, 3]); + $this->assertEquals([1, 2, 3], $this->storeAwareAbstractModel->lookupStoreIds(1)); + } +} diff --git a/Test/Unit/Model/UploaderTest.php b/Test/Unit/Model/UploaderTest.php index db227da..e4d2974 100644 --- a/Test/Unit/Model/UploaderTest.php +++ b/Test/Unit/Model/UploaderTest.php @@ -30,6 +30,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use Umc\Crud\Model\FileChecker; use Umc\Crud\Model\Uploader; class UploaderTest extends TestCase @@ -66,6 +67,10 @@ class UploaderTest extends TestCase * @var Filesystem\Directory\WriteInterface | MockObject */ private $mediaDirectory; + /** + * @var FileChecker | MockObject + */ + private $fileChecker; /** * setup tests @@ -81,12 +86,14 @@ protected function setUp() $this->storeManager->method('getStore')->willReturn($this->store); $this->mediaDirectory = $this->createMock(Filesystem\Directory\WriteInterface::class); $this->filesystem->method('getDirectoryWrite')->willReturn($this->mediaDirectory); + $this->fileChecker = $this->createMock(FileChecker::class); $this->uploader = new Uploader( $this->coreFileStorageDatabase, $this->filesystem, $this->uploaderFactory, $this->storeManager, $this->logger, + $this->fileChecker, 'base_tmp_path', 'base_path', ['ext1', 'ext2'] @@ -137,6 +144,7 @@ public function testMoveFileFromTmp() { $this->coreFileStorageDatabase->expects($this->once())->method('copyFile'); $this->mediaDirectory->expects($this->once())->method('renameFile'); + $this->fileChecker->expects($this->once())->method('getNewFilename')->willReturn('name'); $this->assertEquals('name', $this->uploader->moveFileFromTmp('name')); } @@ -148,6 +156,7 @@ public function testMoveFileFromTmpWithException() { $this->coreFileStorageDatabase->expects($this->once())->method('copyFile'); $this->mediaDirectory->expects($this->once())->method('renameFile')->willThrowException(new \Exception()); + $this->fileChecker->expects($this->once())->method('getNewFilename')->willReturn('name'); $this->expectException(LocalizedException::class); $this->assertEquals('name', $this->uploader->moveFileFromTmp('name')); } diff --git a/Test/Unit/Ui/SaveDataProcessor/DynamicRowsTest.php b/Test/Unit/Ui/SaveDataProcessor/DynamicRowsTest.php index 0a75a91..d11bbbf 100644 --- a/Test/Unit/Ui/SaveDataProcessor/DynamicRowsTest.php +++ b/Test/Unit/Ui/SaveDataProcessor/DynamicRowsTest.php @@ -32,21 +32,12 @@ class DynamicRowsTest extends TestCase * @var Json | MockObject */ private $serializer; - /** - * @var DynamicRows - */ - private $dynamicRows; - /** * setup tests */ protected function setUp() { $this->serializer = $this->createMock(Json::class); - $this->dynamicRows = new DynamicRows( - $this->serializer, - ['field1', 'field2', 'field3'] - ); } /** @@ -55,6 +46,11 @@ protected function setUp() */ public function testModifyData() { + $dynamicRows = new DynamicRows( + $this->serializer, + ['field1', 'field2', 'field3'], + false + ); $data = [ 'field1' => [1, 2, 3], 'field2' => 'string', @@ -76,6 +72,42 @@ function (array $item) { 'field2' => 'string', 'field4' => ['not_processed'] ]; - $this->assertEquals($expected, $this->dynamicRows->modifyData($data)); + $this->assertEquals($expected, $dynamicRows->modifyData($data)); + } + + /** + * @covers \Umc\Crud\Ui\SaveDataProcessor\DynamicRows::modifyData + * @covers \Umc\Crud\Ui\SaveDataProcessor\DynamicRows::__construct + */ + public function testModifyDataStrictMode() + { + $dynamicRows = new DynamicRows( + $this->serializer, + ['field1', 'field2', 'field3'], + true + ); + $data = [ + 'field1' => [1, 2, 3], + 'field2' => 'string', + 'field4' => ['not_processed'] + ]; + $this->serializer->expects($this->exactly(2))->method('serialize')->willReturnCallback( + function (array $item) { + $item['serialized'] = 1; + return $item; + } + ); + $expected = [ + 'field1' => [ + 0 => 1, + 1 => 2, + 2 => 3, + 'serialized' => 1 + ], + 'field2' => 'string', + 'field3' => ['serialized' => 1], + 'field4' => ['not_processed'] + ]; + $this->assertEquals($expected, $dynamicRows->modifyData($data)); } } diff --git a/Test/Unit/Ui/SaveDataProcessor/UploadTest.php b/Test/Unit/Ui/SaveDataProcessor/UploadTest.php index 2085a06..450e5c0 100644 --- a/Test/Unit/Ui/SaveDataProcessor/UploadTest.php +++ b/Test/Unit/Ui/SaveDataProcessor/UploadTest.php @@ -47,10 +47,6 @@ class UploadTest extends TestCase * @var LoggerInterface | MockObject */ private $logger; - /** - * @var Upload - */ - private $upload; /** * setup tests @@ -61,13 +57,70 @@ protected function setUp() $this->fileInfo = $this->createMock(FileInfo::class); $this->filesystem = $this->createMock(Filesystem::class); $this->logger = $this->createMock(LoggerInterface::class); - $this->upload = new Upload( + } + + /** + * @covers \Umc\Crud\Ui\SaveDataProcessor\Upload::modifyData + * @covers \Umc\Crud\Ui\SaveDataProcessor\Upload::fileResidesOutsideUploadDir + * @covers \Umc\Crud\Ui\SaveDataProcessor\Upload::isTmpFileAvailable + * @covers \Umc\Crud\Ui\SaveDataProcessor\Upload::getUploadedImageName + * @covers \Umc\Crud\Ui\SaveDataProcessor\Upload::__construct + */ + public function testModifyData() + { + $this->fileInfo->method('getFilePath')->willReturnArgument(0); + $upload = new Upload( + ['field1', 'field2', 'field3', 'field4'], + $this->uploader, + $this->fileInfo, + $this->filesystem, + $this->logger, + false + ); + $uploadStrict = new Upload( ['field1', 'field2', 'field3', 'field4'], $this->uploader, $this->fileInfo, $this->filesystem, - $this->logger + $this->logger, + true ); + $data = [ + 'field1' => [ + [ + 'tmp_name' => 'tmp_name', + 'file' => 'file1', + 'url' => 'path/url', + 'name' => 'path/url' + ] + ], + 'field2' => [ + [ + 'url' => 'value2', + 'name' => 'value2' + ] + ], + 'field3' => [], + 'dummy' => 'dummy' + ]; + $this->filesystem->method('getUri')->willReturn('path'); + $this->uploader->method('moveFileFromTmp')->willReturn('tmp_moved'); + + $expected = [ + 'field1' => 'tmp_moved', + 'field2' => 'value2', + 'field3' => '', + 'dummy' => 'dummy' + ]; + $expectedStrict = [ + 'field1' => 'tmp_moved', + 'field2' => 'value2', + 'field3' => '', + 'dummy' => 'dummy', + 'field4' => '' + ]; + $this->assertEquals($expected, $upload->modifyData($data)); + $this->assertEquals($expectedStrict, $uploadStrict->modifyData($data)); } /** @@ -77,33 +130,85 @@ protected function setUp() * @covers \Umc\Crud\Ui\SaveDataProcessor\Upload::getUploadedImageName * @covers \Umc\Crud\Ui\SaveDataProcessor\Upload::__construct */ - public function testModifyData() + public function testModifyDataWithException() { + $this->fileInfo->method('getFilePath')->willReturnArgument(0); + $upload = new Upload( + ['field1', 'field2', 'field3', 'field4'], + $this->uploader, + $this->fileInfo, + $this->filesystem, + $this->logger, + false + ); $data = [ 'field1' => [ [ 'tmp_name' => 'tmp_name', 'file' => 'file1', - 'url' => 'path/url' + 'url' => 'path/url', + 'name' => 'path/url', ] ], 'field2' => [ [ - 'url' => 'value2' + 'url' => 'value2', + 'name' => 'value2' ] ], 'field3' => [], 'dummy' => 'dummy' ]; $this->filesystem->method('getUri')->willReturn('path'); - $this->uploader->expects($this->once())->method('moveFileFromTmp')->willReturn('tmp_moved'); - + $this->uploader->expects($this->once())->method('moveFileFromTmp')->willThrowException(new \Exception()); + $this->logger->expects($this->once())->method('critical'); $expected = [ - 'field1' => 'tmp_moved', + 'field1' => [ + 0 => [ + 'tmp_name' => 'tmp_name', + 'file' => 'file1', + 'url' => 'path/url', + 'name' => 'path/url', + ], + ], 'field2' => 'value2', 'field3' => '', 'dummy' => 'dummy' ]; - $this->assertEquals($expected, $this->upload->modifyData($data)); + $this->assertEquals($expected, $upload->modifyData($data)); + } + + /** + * @covers \Umc\Crud\Ui\SaveDataProcessor\Upload::modifyData + * @covers \Umc\Crud\Ui\SaveDataProcessor\Upload::fileResidesOutsideUploadDir + * @covers \Umc\Crud\Ui\SaveDataProcessor\Upload::isTmpFileAvailable + * @covers \Umc\Crud\Ui\SaveDataProcessor\Upload::getUploadedImageName + * @covers \Umc\Crud\Ui\SaveDataProcessor\Upload::__construct + */ + public function testModifyDataFileOutside() + { + $upload = new Upload( + ['field1', 'field2', 'field3', 'field4'], + $this->uploader, + $this->fileInfo, + $this->filesystem, + $this->logger, + false + ); + $this->fileInfo->method('getFilePath')->willReturn('media/path'); + $this->filesystem->method('getUri')->willReturn('media'); + $data = [ + 'field1' => [ + [ + 'file' => 'file1', + 'name' => 'media/path/url', + 'url' => 'media/path/url' + ] + ], + ]; + $expected = [ + 'field1' => 'media/path/url' + ]; + $this->assertEquals($expected, $upload->modifyData($data)); } } diff --git a/Test/Unit/ViewModel/Formatter/DateTest.php b/Test/Unit/ViewModel/Formatter/DateTest.php new file mode 100644 index 0000000..0287d69 --- /dev/null +++ b/Test/Unit/ViewModel/Formatter/DateTest.php @@ -0,0 +1,81 @@ +localeDate = $this->createMock(TimezoneInterface::class); + $this->date = new Date($this->localeDate); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter\Date::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter\Date::__construct + */ + public function testFormatHtml() + { + $this->localeDate->expects($this->once())->method('formatDateTime') + ->with(new \DateTime('1984-04-04'), \IntlDateFormatter::LONG, \IntlDateFormatter::NONE, null, null) + ->willReturn('formatted'); + $this->assertEquals('formatted', $this->date->formatHtml('1984-04-04')); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter\Date::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter\Date::__construct + */ + public function testFormatHtmlWithParams() + { + $this->localeDate->expects($this->once())->method('formatDateTime') + ->with(new \DateTime('1984-04-04'), \IntlDateFormatter::SHORT, \IntlDateFormatter::SHORT, null, null) + ->willReturn('formatted'); + $this->assertEquals( + 'formatted', + $this->date->formatHtml( + '1984-04-04', + [ + 'format' => \IntlDateFormatter::SHORT, + 'show_time' => true + ] + ) + ); + } +} diff --git a/Test/Unit/ViewModel/Formatter/FileTest.php b/Test/Unit/ViewModel/Formatter/FileTest.php new file mode 100644 index 0000000..89c0826 --- /dev/null +++ b/Test/Unit/ViewModel/Formatter/FileTest.php @@ -0,0 +1,103 @@ +fileInfoFactory = $this->createMock(FileInfoFactory::class); + $this->filesystem = $this->createMock(Filesystem::class); + $this->storeManager = $this->createMock(StoreManagerInterface::class); + $this->fileInfo = $this->createMock(FileInfo::class); + $this->store = $this->createMock(Store::class); + $this->file = new File( + $this->fileInfoFactory, + $this->filesystem, + $this->storeManager + ); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter\File::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter\File::getFileInfo + * @covers \Umc\Crud\ViewModel\Formatter\File::__construct + */ + public function testFormatHtmlWithWrongPath() + { + $this->fileInfoFactory->expects($this->once())->method('create')->willReturn($this->fileInfo); + $this->fileInfo->method('getFilePath')->willReturn(''); + $this->storeManager->expects($this->never())->method('getStore'); + $this->assertEquals('', $this->file->formatHtml('value')); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter\File::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter\File::getFileInfo + * @covers \Umc\Crud\ViewModel\Formatter\File::__construct + */ + public function testFormatHtml() + { + $this->fileInfoFactory->expects($this->once())->method('create')->willReturn($this->fileInfo); + $this->fileInfo->method('getFilePath')->willReturn('/path'); + $this->storeManager->expects($this->once())->method('getStore')->willReturn($this->store); + $this->store->method('getBaseUrl')->willReturn('base/'); + $this->assertEquals('base/path', $this->file->formatHtml('value')); + } +} diff --git a/Test/Unit/ViewModel/Formatter/ImageTest.php b/Test/Unit/ViewModel/Formatter/ImageTest.php new file mode 100644 index 0000000..e67bcd9 --- /dev/null +++ b/Test/Unit/ViewModel/Formatter/ImageTest.php @@ -0,0 +1,153 @@ +adapterFactory = $this->createMock(AdapterFactory::class); + $this->fileInfoFactory = $this->createMock(FileInfoFactory::class); + $this->filesystem = $this->createMock(Filesystem::class); + $this->storeManager = $this->createMock(StoreManagerInterface::class); + $this->mediaDir = $this->createMock(Filesystem\Directory\WriteInterface::class); + $this->fileInfo = $this->createMock(FileInfo::class); + $this->store = $this->createMock(Store::class); + $this->adapter = $this->createMock(AbstractAdapter::class); + $this->image = new Image( + $this->adapterFactory, + $this->fileInfoFactory, + $this->filesystem, + $this->storeManager + ); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter\Image::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter\Image::getFileInfo + * @covers \Umc\Crud\ViewModel\Formatter\Image::__construct + */ + public function testFormatHtmlNoPath() + { + $this->fileInfoFactory->method('create')->willReturn($this->fileInfo); + $this->fileInfo->method('getAbsoluteFilePath')->willReturn(''); + $this->filesystem->expects($this->never())->method('getDirectoryWrite'); + $this->assertEquals('', $this->image->formatHtml('value')); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter\Image::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter\Image::getFileInfo + * @covers \Umc\Crud\ViewModel\Formatter\Image::getDestinationRelativePath + * @covers \Umc\Crud\ViewModel\Formatter\Image::getImageSettings + * @covers \Umc\Crud\ViewModel\Formatter\Image::__construct + */ + public function testFormatHtml() + { + $this->fileInfoFactory->method('create')->willReturn($this->fileInfo); + $this->fileInfo->method('getFilePath')->willReturn('absolute/path/to/image/path/goes/here.jpg'); + $this->fileInfo->method('getAbsoluteFilePath')->willReturn('absolute/path'); + $this->filesystem->method('getDirectoryWrite')->willReturn($this->mediaDir); + $this->mediaDir->method('isFile')->willReturn(false); + $this->adapterFactory->method('create')->willReturn($this->adapter); + $this->storeManager->method('getStore')->willReturn($this->store); + $this->store->method('getBaseUrl')->willReturn('base/'); + $this->assertEquals( + 'base/absolute/path/to/image/cache/2e5cc4e4036c930cd893e7434a9fc500/path/goes/here.jpg', + $this->image->formatHtml('image/path/goes/here.jpg', ['resize' => [100, 100]]) + ); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter\Image::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter\Image::getFileInfo + * @covers \Umc\Crud\ViewModel\Formatter\Image::getDestinationRelativePath + * @covers \Umc\Crud\ViewModel\Formatter\Image::getImageSettings + * @covers \Umc\Crud\ViewModel\Formatter\Image::__construct + */ + public function testFormatHtmlWithStringResize() + { + $this->fileInfoFactory->method('create')->willReturn($this->fileInfo); + $this->fileInfo->method('getFilePath')->willReturn('absolute/path/to/image/path/goes/here.jpg'); + $this->fileInfo->method('getAbsoluteFilePath')->willReturn('absolute/path'); + $this->filesystem->method('getDirectoryWrite')->willReturn($this->mediaDir); + $this->mediaDir->method('isFile')->willReturn(false); + $this->adapterFactory->method('create')->willReturn($this->adapter); + $this->storeManager->method('getStore')->willReturn($this->store); + $this->store->method('getBaseUrl')->willReturn('base/'); + $this->assertEquals( + 'base/absolute/path/to/image/path/cache/a593982ff801608e4aaebdd647b05a73/goes/here.jpg', + $this->image->formatHtml('image/path/goes/here.jpg', ['resize' => 100, 'image_name_parts' => 2]) + ); + } +} diff --git a/Test/Unit/ViewModel/Formatter/OptionTest.php b/Test/Unit/ViewModel/Formatter/OptionTest.php new file mode 100644 index 0000000..03f7d62 --- /dev/null +++ b/Test/Unit/ViewModel/Formatter/OptionTest.php @@ -0,0 +1,141 @@ +objectManager = $this->createMock(ObjectManagerInterface::class); + $this->escaper = $this->createMock(Escaper::class); + $this->options = new Options( + $this->objectManager, + $this->escaper + ); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter\Options::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter\Options::getSource + * @covers \Umc\Crud\ViewModel\Formatter\Options::__construct + */ + public function testFormatHtmlNoOptions() + { + $this->assertEquals('', $this->options->formatHtml('1')); + $this->assertEquals('N/A', $this->options->formatHtml('1', ['default' => 'N/A'])); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter\Options::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter\Options::getSource + * @covers \Umc\Crud\ViewModel\Formatter\Options::__construct + */ + public function testFormatHtmlWithSource() + { + $this->objectManager->expects($this->once())->method('get')->with('SourceModel') + ->willReturn( + $this->getSourceMock([ + ['label' => 'label 1', 'value' => 1], + ['label' => 'label 2', 'value' => 2], + ['label' => 'label 3', 'value' => 3], + ['label' => 'label 4', 'value' => 4], + ]) + ); + $this->escaper->method('escapeHtml')->willReturnArgument(0); + $this->assertEquals('label 1, label 2', $this->options->formatHtml([1, 2], ['source' => 'SourceModel'])); + $this->assertEquals('label 1, label 4', $this->options->formatHtml([4, 1], ['source' => 'SourceModel'])); + $this->assertEquals('label 3', $this->options->formatHtml(3, ['source' => 'SourceModel'])); + $this->assertEquals('label 4', $this->options->formatHtml([4, 8, 9], ['source' => 'SourceModel'])); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter\Options::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter\Options::getSource + * @covers \Umc\Crud\ViewModel\Formatter\Options::getOptions + * @covers \Umc\Crud\ViewModel\Formatter\Options::__construct + */ + public function testFormatHtmlWithOptions() + { + $this->objectManager->expects($this->never())->method('get'); + $arguments = [ + 'options' => [ + ['label' => 'label 1', 'value' => 1], + ['label' => 'label 2', 'value' => 2], + ['label' => 'label 3', 'value' => 3], + ['label' => 'label 4', 'value' => 4] + ] + ]; + $this->escaper->method('escapeHtml')->willReturnArgument(0); + $this->assertEquals('label 1, label 2', $this->options->formatHtml([1, 2], $arguments)); + $this->assertEquals('label 1, label 4', $this->options->formatHtml([4, 1], $arguments)); + $this->assertEquals('label 3', $this->options->formatHtml(3, $arguments)); + $this->assertEquals('label 4', $this->options->formatHtml([4, 8, 9], $arguments)); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter\Options::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter\Options::getSource + * @covers \Umc\Crud\ViewModel\Formatter\Options::__construct + */ + public function testFormatHtmlWithSourceError() + { + $this->objectManager->expects($this->once())->method('get')->with('SourceModel') + ->willReturn(new \stdClass()); + $this->expectException(\InvalidArgumentException::class); + $this->options->formatHtml(1, ['source' => 'SourceModel']); + } + + /** + * @param $values + * @return MockObject + * @throws \ReflectionException + */ + private function getSourceMock($values) + { + $source = $this->createMock(ArrayInterface::class); + $source->method('toOptionArray')->willReturn($values); + return $source; + } +} diff --git a/Test/Unit/ViewModel/Formatter/TextTest.php b/Test/Unit/ViewModel/Formatter/TextTest.php new file mode 100644 index 0000000..5cd61f5 --- /dev/null +++ b/Test/Unit/ViewModel/Formatter/TextTest.php @@ -0,0 +1,58 @@ +escaper = $this->createMock(Escaper::class); + $this->text = new Text($this->escaper); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter\Text::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter\Text::__construct + */ + public function testFormatHtml() + { + $this->escaper->expects($this->once())->method('escapeHtml')->willReturn('escaped'); + $this->assertEquals('escaped', $this->text->formatHtml('value')); + } +} diff --git a/Test/Unit/ViewModel/Formatter/WysiwygTest.php b/Test/Unit/ViewModel/Formatter/WysiwygTest.php new file mode 100644 index 0000000..bd1214c --- /dev/null +++ b/Test/Unit/ViewModel/Formatter/WysiwygTest.php @@ -0,0 +1,57 @@ +filter = $this->createMock(\Zend_Filter_Interface::class); + $this->wysiwyg = new Wysiwyg($this->filter); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter\Wysiwyg::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter\Wysiwyg::__construct + */ + public function testFormatHtml() + { + $this->filter->expects($this->once())->method('filter')->willReturn('filtered'); + $this->assertEquals('filtered', $this->wysiwyg->formatHtml('value')); + } +} diff --git a/Test/Unit/ViewModel/FormatterTest.php b/Test/Unit/ViewModel/FormatterTest.php new file mode 100644 index 0000000..2a46628 --- /dev/null +++ b/Test/Unit/ViewModel/FormatterTest.php @@ -0,0 +1,129 @@ +escaper = $this->createMock(Escaper::class); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter::getFormatter + * @covers \Umc\Crud\ViewModel\Formatter::__construct + */ + public function testFormatHtml() + { + $formatter1 = $this->getFormatterMock(); + $formatter2 = $this->getFormatterMock(); + $formatter = new Formatter( + [ + 'type1' => $formatter1, + 'type2' => $formatter2, + ], + $this->escaper + ); + $formatter1->expects($this->once())->method('formatHtml')->willReturn('formatted'); + $this->assertEquals('formatted', $formatter->formatHtml('value', ['type' => 'type1'])); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter::getFormatter + * @covers \Umc\Crud\ViewModel\Formatter::__construct + */ + public function testFormatHtmlNoArgument() + { + $formatter1 = $this->getFormatterMock(); + $formatter2 = $this->getFormatterMock(); + $formatter = new Formatter( + [ + 'type1' => $formatter1, + 'type2' => $formatter2, + ], + $this->escaper + ); + $formatter1->expects($this->never())->method('formatHtml'); + $this->escaper->expects($this->once())->method('escapeHtml')->willReturn('formatted'); + $this->assertEquals('formatted', $formatter->formatHtml('value', [])); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter::formatHtml + * @covers \Umc\Crud\ViewModel\Formatter::getFormatter + * @covers \Umc\Crud\ViewModel\Formatter::__construct + */ + public function testFormatHtmlNoTypeConfigured() + { + $formatter1 = $this->getFormatterMock(); + $formatter2 = $this->getFormatterMock(); + $formatter = new Formatter( + [ + 'type1' => $formatter1, + 'type2' => $formatter2, + ], + $this->escaper + ); + $this->expectException(\InvalidArgumentException::class); + $formatter->formatHtml('value', ['type' => 'type3']); + } + + /** + * @covers \Umc\Crud\ViewModel\Formatter::__construct + */ + public function testConstruct() + { + $this->expectException(\InvalidArgumentException::class); + new Formatter( + [ + 'type1' => $this->getFormatterMock(), + 'type2' => 'string', + ], + $this->escaper + ); + } + + /** + * @return Formatter'FormatterInterface | MockObject + * @throws \ReflectionException + */ + private function getFormatterMock() + { + $formatter = $this->createMock(Formatter\FormatterInterface::class); + return $formatter; + } +} diff --git a/Ui/SaveDataProcessor/DynamicRows.php b/Ui/SaveDataProcessor/DynamicRows.php index 9745eda..c4490d1 100644 --- a/Ui/SaveDataProcessor/DynamicRows.php +++ b/Ui/SaveDataProcessor/DynamicRows.php @@ -34,16 +34,22 @@ class DynamicRows implements SaveDataProcessorInterface * @var array */ private $fields = []; + /** + * @var bool + */ + private $strict; /** * DynamicRows constructor. * @param Json $serializer * @param array $fields + * @param bool $strict */ - public function __construct(Json $serializer, array $fields) + public function __construct(Json $serializer, array $fields, bool $strict) { $this->serializer = $serializer; $this->fields = $fields; + $this->strict = $strict; } /** @@ -53,6 +59,9 @@ public function __construct(Json $serializer, array $fields) public function modifyData(array $data): array { foreach ($this->fields as $field) { + if (!array_key_exists($field, $data) && $this->strict) { + $data[$field] = []; + } if (array_key_exists($field, $data) && is_array($data[$field])) { $data[$field] = $this->serializer->serialize($data[$field]); } diff --git a/Ui/SaveDataProcessor/Upload.php b/Ui/SaveDataProcessor/Upload.php index 12c3b7e..0cbce7b 100644 --- a/Ui/SaveDataProcessor/Upload.php +++ b/Ui/SaveDataProcessor/Upload.php @@ -50,27 +50,34 @@ class Upload implements SaveDataProcessorInterface * @var Filesystem */ private $filesystem; + /** + * @var bool + */ + private $strict; /** - * Image constructor. + * Upload constructor. * @param array $fields * @param Uploader $uploader * @param FileInfo $fileInfo * @param Filesystem $filesystem * @param LoggerInterface $logger + * @param bool $strict */ public function __construct( array $fields, Uploader $uploader, FileInfo $fileInfo, Filesystem $filesystem, - LoggerInterface $logger + LoggerInterface $logger, + bool $strict ) { $this->fields = $fields; $this->uploader = $uploader; $this->fileInfo = $fileInfo; $this->filesystem = $filesystem; $this->logger = $logger; + $this->strict = $strict; } /** @@ -99,6 +106,9 @@ public function modifyData(array $data): array { foreach ($this->fields as $field) { if (!array_key_exists($field, $data)) { + if ($this->strict) { + $data[$field] = ''; + } continue; } $value = $data[$field] ?? ''; @@ -111,10 +121,9 @@ public function modifyData(array $data): array } else { if ($this->fileResidesOutsideUploadDir($value)) { // phpcs:ignore Magento2.Functions.DiscouragedFunction - $value[0]['url'] = parse_url($value[0]['url'], PHP_URL_PATH); - $value[0]['name'] = $value[0]['url']; + $value[0]['name'] = parse_url($value[0]['url'], PHP_URL_PATH); } - $data[$field] = $value[0]['url'] ?? ''; + $data[$field] = $value[0]['name'] ?? ''; } } return $data; @@ -129,10 +138,9 @@ private function fileResidesOutsideUploadDir($value) if (!is_array($value) || !isset($value[0]['url'])) { return false; } - $fileUrl = ltrim($value[0]['url'], '/'); + $filePath = $this->fileInfo->getFilePath($fileUrl); $baseMediaDir = $this->filesystem->getUri(DirectoryList::MEDIA); - - return $baseMediaDir && strpos($fileUrl, $baseMediaDir) !== false; + return $baseMediaDir && strpos($filePath, $baseMediaDir) !== false; } } diff --git a/ViewModel/Formatter.php b/ViewModel/Formatter.php new file mode 100644 index 0000000..1e35cf2 --- /dev/null +++ b/ViewModel/Formatter.php @@ -0,0 +1,79 @@ +formatterMap = $formatterMap; + $this->escaper = $escaper; + } + + /** + * @param $value + * @param array $arguments + * @return string + */ + public function formatHtml($value, $arguments = []): string + { + $type = $arguments['type'] ?? null; + return $type === null + ? $this->escaper->escapeHtml($value) + : $this->getFormatter($type)->formatHtml($value, $arguments); + } + + /** + * @param $type + * @return FormatterInterface|null + */ + private function getFormatter($type) + { + $formatter = $this->formatterMap[$type] ?? null; + if ($formatter === null) { + throw new \InvalidArgumentException("Missing formatter for type {$type}"); + } + return $formatter; + } +} diff --git a/ViewModel/Formatter/Date.php b/ViewModel/Formatter/Date.php new file mode 100644 index 0000000..d78cb2d --- /dev/null +++ b/ViewModel/Formatter/Date.php @@ -0,0 +1,73 @@ +localeDate = $localeDate; + } + + /** + * @param $value + * @param array $arguments + * @return string + */ + public function formatHtml($value, $arguments = []): string + { + $format = $arguments[self::FORMAT] ?? self::DEFAULT_FORMAT; + $showTime = $arguments[self::SHOW_TIME] ?? false; + $timezone = $arguments[self::TIMEZONE] ?? null; + $value = $value instanceof \DateTimeInterface ? $value : new \DateTime($value); + return $this->localeDate->formatDateTime( + $value, + $format, + $showTime ? $format : \IntlDateFormatter::NONE, + null, + $timezone + ); + } +} diff --git a/ViewModel/Formatter/File.php b/ViewModel/Formatter/File.php new file mode 100644 index 0000000..c5391d1 --- /dev/null +++ b/ViewModel/Formatter/File.php @@ -0,0 +1,96 @@ +fileInfoFactory = $fileInfoFactory; + $this->filesystem = $filesystem; + $this->storeManager = $storeManager; + } + + /** + * @param $path + * @return FileInfo + */ + private function getFileInfo($path) + { + if (!array_key_exists($path, $this->fileInfoCache)) { + $this->fileInfoCache[$path] = $this->fileInfoFactory->create(['filePath' => $path]); + } + return $this->fileInfoCache[$path]; + } + + /** + * @param $value + * @param array $arguments + * @return string + * @throws \Exception + */ + public function formatHtml($value, $arguments = []): string + { + $path = $arguments['path'] ?? ''; + $fileInfo = $this->getFileInfo($path); + $filePath = $fileInfo->getFilePath((string)$value); + if (!$filePath) { + return ''; + } + $store = $this->storeManager->getStore(); + $mediaBaseUrl = $store->getBaseUrl( + \Magento\Framework\UrlInterface::URL_TYPE_MEDIA + ); + return $mediaBaseUrl . trim($filePath, '/'); + } +} diff --git a/ViewModel/Formatter/FormatterInterface.php b/ViewModel/Formatter/FormatterInterface.php new file mode 100644 index 0000000..1298d68 --- /dev/null +++ b/ViewModel/Formatter/FormatterInterface.php @@ -0,0 +1,34 @@ +adapterFactory = $adapterFactory; + $this->fileInfoFactory = $fileInfoFactory; + $this->filesystem = $filesystem; + $this->storeManager = $storeManager; + } + + /** + * @param $path + * @return FileInfo + */ + private function getFileInfo($path) + { + if (!array_key_exists($path, $this->fileInfoCache)) { + $this->fileInfoCache[$path] = $this->fileInfoFactory->create(['filePath' => $path]); + } + return $this->fileInfoCache[$path]; + } + + /** + * @param $value + * @param array $arguments + * @return string + * @throws \Exception + */ + public function formatHtml($value, $arguments = []): string + { + $path = $arguments['path'] ?? ''; + $fileInfo = $this->getFileInfo($path); + $filePath = $fileInfo->getFilePath((string)$value); + $absolutePath = $fileInfo->getAbsoluteFilePath($value); + if (!$absolutePath) { + return ''; + } + $destinationPath = $this->getDestinationRelativePath($filePath, $arguments); + $mediaDir = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); + $absoluteDestinationPath = $mediaDir->getAbsolutePath($destinationPath); + + $store = $this->storeManager->getStore(); + $mediaBaseUrl = $store->getBaseUrl( + \Magento\Framework\UrlInterface::URL_TYPE_MEDIA + ); + if (!$mediaDir->isFile($absoluteDestinationPath)) { + /** @var AbstractAdapter $adapter */ + $adapter = $this->adapterFactory->create(); + $adapter->open($absolutePath); + $imageParams = $this->getImageSettings($arguments); + $adapter->keepAspectRatio($imageParams[self::KEEP_ASPECT_RATIO]); + $adapter->keepFrame($imageParams[self::KEEP_FRAME]); + $adapter->keepTransparency($imageParams[self::KEEP_TRANSPARENCY]); + $adapter->constrainOnly($imageParams[self::CONSTRAIN_ONLY]); + $adapter->backgroundColor($imageParams[self::BACKGROUND_COLOR]); + if (isset($imageParams['resize_width'])) { + $adapter->resize($imageParams['resize_width'], $imageParams['resize_height']); + } + $adapter->save($absoluteDestinationPath); + } + return $mediaBaseUrl . trim($destinationPath, '/'); + } + + /** + * @param $value + * @param array $params + * @return null|string + */ + private function getDestinationRelativePath($value, array $params = []) + { + $value = (string)$value; + $imageNameParts = []; + $parts = explode('/', $value); + $totalParts = count($parts); + $imageParams = $this->getImageSettings($params); + $mainImageNameParts = isset($params[self::IMAGE_NAME_PARTS]) + ? (int)$params[self::IMAGE_NAME_PARTS] + : self::DEFAULT_IMAGE_NAME_PARTS; + for ($i = $totalParts - 1; $i > $totalParts - (1 + $mainImageNameParts); $i--) { + $imageNameParts[] = $parts[$i]; + unset($parts[$i]); + } + $imageName = implode('/', array_reverse($imageNameParts)); + $basePath = implode('/', $parts); + return $basePath . '/cache/' . hash('md5', json_encode($imageParams)) . '/' . $imageName; + } + + /** + * @param array $params + * @return array + */ + private function getImageSettings(array $params) + { + $result = [ + self::KEEP_ASPECT_RATIO => $params[self::KEEP_ASPECT_RATIO] ?? true, + self::KEEP_FRAME => $params[self::KEEP_FRAME] ?? false, + self::KEEP_TRANSPARENCY => $params[self::KEEP_TRANSPARENCY] ?? false, + self::CONSTRAIN_ONLY => $params[self::CONSTRAIN_ONLY] ?? true, + self::BACKGROUND_COLOR => $params[self::BACKGROUND_COLOR] ?? null + ]; + $resize = $params[self::RESIZE] ?? null; + if ($resize !== null) { + $width = null; + $height = null; + if (is_array($resize)) { + $width = $resize[0] ?? null; + $height = $resize[1] ?? null; + } elseif (is_integer($resize)) { + $width = $resize; + } + $result['resize_width'] = $width; + $result['resize_height'] = $height; + } + return $result; + } +} diff --git a/ViewModel/Formatter/Options.php b/ViewModel/Formatter/Options.php new file mode 100644 index 0000000..fb713de --- /dev/null +++ b/ViewModel/Formatter/Options.php @@ -0,0 +1,110 @@ +objectManager = $objectManager; + $this->escaper = $escaper; + } + + /** + * @param $value + * @param array $arguments + * @return string + */ + public function formatHtml($value, $arguments = []): string + { + $source = $this->getSource($arguments); + $options = $source ? $source->toOptionArray() : $this->getOptions($arguments); + $value = is_array($value) ? $value : [$value]; + $texts = array_map( + function ($item) { + return $this->escaper->escapeHtml($item['label'] ?? ''); + }, + array_filter( + $options, + function ($item) use ($value) { + return isset($item['value']) && in_array($item['value'], $value); + } + ) + ); + return count($texts) > 0 + ? implode(', ', $texts) + : (isset($arguments['default']) ? (string)$arguments['default'] : ''); + } + + /** + * @param array $arguments + * @return ArrayInterface|null + */ + private function getSource(array $arguments): ?ArrayInterface + { + $sourceClass = $arguments['source'] ?? null; + if (!$sourceClass) { + return null; + } + if (!array_key_exists($sourceClass, $this->sources)) { + $instance = $this->objectManager->get($sourceClass); + if (!($instance instanceof ArrayInterface)) { + throw new \InvalidArgumentException( + "Source model for options formatter should implement " . ArrayInterface::class + ); + } + $this->sources[$sourceClass] = $instance; + } + return $this->sources[$sourceClass]; + } + + /** + * @param array $arguments + * @return array|mixed + */ + private function getOptions(array $arguments): array + { + return $arguments['options'] ?? []; + } +} diff --git a/ViewModel/Formatter/Text.php b/ViewModel/Formatter/Text.php new file mode 100644 index 0000000..3d1361c --- /dev/null +++ b/ViewModel/Formatter/Text.php @@ -0,0 +1,51 @@ +escaper = $escaper; + } + + /** + * @param $value + * @param array $arguments + * @return string + */ + public function formatHtml($value, $arguments = []): string + { + return $this->escaper->escapeHtml($value); + } +} diff --git a/ViewModel/Formatter/Wysiwyg.php b/ViewModel/Formatter/Wysiwyg.php new file mode 100644 index 0000000..c15df0c --- /dev/null +++ b/ViewModel/Formatter/Wysiwyg.php @@ -0,0 +1,50 @@ +filter = $filter; + } + + /** + * @param $value + * @param array $arguments + * @return string + * @throws \Zend_Filter_Exception + */ + public function formatHtml($value, $arguments = []): string + { + return $this->filter->filter($value); + } +} diff --git a/composer.json b/composer.json index b677240..8426da6 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "umc/module-crud", - "description": "Magento 2 module for reducing the CRUD boierplate", + "description": "Magento 2 module for reducing the CRUD boilerplate", "require": { "php": "~7.1.3||~7.2.0||~7.3.0", "magento/module-backend": "*", @@ -12,10 +12,6 @@ "license": [ "MIT" ], - "scripts": { - "post-install-cmd": "bin/magento umc:crud:deploy", - "post-update-cmd": "bin/magento umc:crud:deploy -f" - }, "autoload": { "files": [ "registration.php" diff --git a/etc/di.xml b/etc/di.xml index faae57b..28d7d25 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -32,4 +32,21 @@ Magento\Framework\Filesystem\Io\File\Proxy + + + Magento\Widget\Model\Template\Filter + + + + + + Umc\Crud\ViewModel\Formatter\Date + Umc\Crud\ViewModel\Formatter\Text + Umc\Crud\ViewModel\Formatter\Wysiwyg + Umc\Crud\ViewModel\Formatter\Options + Umc\Crud\ViewModel\Formatter\Image + Umc\Crud\ViewModel\Formatter\File + + + diff --git a/registration.php b/registration.php index c0fdfe4..74019e5 100644 --- a/registration.php +++ b/registration.php @@ -21,4 +21,6 @@ use Magento\Framework\Component\ComponentRegistrar; +// @codeCoverageIgnoreStart ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Umc_Crud', __DIR__); +// @codeCoverageIgnoreEnd diff --git a/view/adminhtml/templates/heartbeat.phtml b/view/adminhtml/templates/heartbeat.phtml index e83a803..ee2a8e6 100644 --- a/view/adminhtml/templates/heartbeat.phtml +++ b/view/adminhtml/templates/heartbeat.phtml @@ -19,7 +19,7 @@ getData('heartbeat'); ?> - +