diff --git a/app/code/Magento/Catalog/Model/CustomOptions/CustomOptionProcessor.php b/app/code/Magento/Catalog/Model/CustomOptions/CustomOptionProcessor.php index f252f282b7e56..c21390e7e8e47 100644 --- a/app/code/Magento/Catalog/Model/CustomOptions/CustomOptionProcessor.php +++ b/app/code/Magento/Catalog/Model/CustomOptions/CustomOptionProcessor.php @@ -25,6 +25,9 @@ class CustomOptionProcessor implements CartItemProcessorInterface /** @var CustomOptionFactory */ protected $customOptionFactory; + /** @var \Magento\Catalog\Model\Product\Option\UrlBuilder */ + private $urlBuilder; + /** * @param DataObject\Factory $objectFactory * @param ProductOptionFactory $productOptionFactory @@ -63,7 +66,6 @@ public function convertToBuyRequest(CartItemInterface $cartItem) return null; } - /** * @inheritDoc */ @@ -117,10 +119,45 @@ protected function updateOptionsValues(array &$options) $option = $this->customOptionFactory->create(); $option->setOptionId($optionId); if (is_array($optionValue)) { + $optionValue = $this->processFileOptionValue($optionValue); $optionValue = implode(',', $optionValue); } $option->setOptionValue($optionValue); $optionValue = $option; } } + + /** + * Returns option value with file built URL + * + * @param array $optionValue + * @return array + */ + private function processFileOptionValue(array $optionValue) + { + if (array_key_exists('url', $optionValue) && + array_key_exists('route', $optionValue['url']) && + array_key_exists('params', $optionValue['url']) + ) { + $optionValue['url'] = $this->getUrlBuilder()->getUrl( + $optionValue['url']['route'], + $optionValue['url']['params'] + ); + } + return $optionValue; + } + + /** + * @return \Magento\Catalog\Model\Product\Option\UrlBuilder + * + * @deprecated + */ + private function getUrlBuilder() + { + if ($this->urlBuilder === null) { + $this->urlBuilder = \Magento\Framework\App\ObjectManager::getInstance() + ->get('\Magento\Catalog\Model\Product\Option\UrlBuilder'); + } + return $this->urlBuilder; + } } diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 0c9532aad668d..23691ede5a3c4 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -302,11 +302,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements */ protected $_productIdCached; - /** - * @var \Magento\Framework\App\State - */ - private $appState; - /** * List of attributes in ProductInterface * @var array @@ -931,8 +926,10 @@ public function afterSave() // Resize images for catalog product and save results to image cache /** @var Product\Image\Cache $imageCache */ - $imageCache = $this->imageCacheFactory->create(); - $imageCache->generate($this); + if (!$this->_appState->isAreaCodeEmulated()) { + $imageCache = $this->imageCacheFactory->create(); + $imageCache->generate($this); + } return $result; } @@ -2278,7 +2275,7 @@ public function getIdentities() $identities[] = self::CACHE_PRODUCT_CATEGORY_TAG . '_' . $categoryId; } } - if ($this->getAppState()->getAreaCode() == \Magento\Framework\App\Area::AREA_FRONTEND) { + if ($this->_appState->getAreaCode() == \Magento\Framework\App\Area::AREA_FRONTEND) { $identities[] = self::CACHE_TAG; } @@ -2589,22 +2586,6 @@ public function setId($value) return $this->setData('entity_id', $value); } - /** - * Get application state - * - * @deprecated - * @return \Magento\Framework\App\State - */ - private function getAppState() - { - if (!$this->appState instanceof \Magento\Framework\App\State) { - $this->appState = \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\Framework\App\State::class - ); - } - return $this->appState; - } - /** * @return ProductLinkRepositoryInterface */ diff --git a/app/code/Magento/Catalog/Model/ProductOptionProcessor.php b/app/code/Magento/Catalog/Model/ProductOptionProcessor.php index 9f02188008593..a22286c47701b 100644 --- a/app/code/Magento/Catalog/Model/ProductOptionProcessor.php +++ b/app/code/Magento/Catalog/Model/ProductOptionProcessor.php @@ -24,6 +24,11 @@ class ProductOptionProcessor implements ProductOptionProcessorInterface */ protected $customOptionFactory; + /** + * @var \Magento\Catalog\Model\Product\Option\UrlBuilder + */ + private $urlBuilder; + /** * @param DataObjectFactory $objectFactory * @param CustomOptionFactory $customOptionFactory @@ -84,6 +89,7 @@ public function convertToProductOption(DataObject $request) $data = []; foreach ($options as $optionId => $optionValue) { if (is_array($optionValue)) { + $optionValue = $this->processFileOptionValue($optionValue); $optionValue = implode(',', $optionValue); } @@ -98,4 +104,38 @@ public function convertToProductOption(DataObject $request) return []; } + + /** + * Returns option value with file built URL + * + * @param array $optionValue + * @return array + */ + private function processFileOptionValue(array $optionValue) + { + if (array_key_exists('url', $optionValue) && + array_key_exists('route', $optionValue['url']) && + array_key_exists('params', $optionValue['url']) + ) { + $optionValue['url'] = $this->getUrlBuilder()->getUrl( + $optionValue['url']['route'], + $optionValue['url']['params'] + ); + } + return $optionValue; + } + + /** + * @return \Magento\Catalog\Model\Product\Option\UrlBuilder + * + * @deprecated + */ + private function getUrlBuilder() + { + if ($this->urlBuilder === null) { + $this->urlBuilder = \Magento\Framework\App\ObjectManager::getInstance() + ->get('\Magento\Catalog\Model\Product\Option\UrlBuilder'); + } + return $this->urlBuilder; + } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php index c6dca1fd3ccfe..c4eda1c987192 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php @@ -270,6 +270,8 @@ protected function _prepareMultiselectIndex($entityIds = null, $attributeId = nu )->where( 'pvd.attribute_id IN(?)', $attrIds + )->where( + 'cpe.entity_id IS NOT NULL' ); $statusCond = $connection->quoteInto('=?', ProductStatus::STATUS_ENABLED); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductOptionProcessorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductOptionProcessorTest.php index 64f5de19e4cc8..eed65fa175177 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductOptionProcessorTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductOptionProcessorTest.php @@ -78,6 +78,17 @@ protected function setUp() $this->dataObjectFactory, $this->customOptionFactory ); + + $urlBuilder = $this->getMockBuilder('\Magento\Catalog\Model\Product\Option\UrlBuilder') + ->disableOriginalConstructor() + ->setMethods(['getUrl']) + ->getMock(); + $urlBuilder->expects($this->any())->method('getUrl')->willReturn('http://built.url/string/'); + + $reflection = new \ReflectionClass(get_class($this->processor)); + $reflectionProperty = $reflection->getProperty('urlBuilder'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->processor, $urlBuilder); } /** @@ -186,7 +197,14 @@ public function dataProviderConvertToProductOption() [ 'options' => [ 1 => 'value', - 2 => [1, 2], + 2 => [ + 1, + 2, + 'url' => [ + 'route' => 'route', + 'params' => ['id' => 20, 'key' => '8175c7c36ef69432347e'] + ] + ], ], 'expected' => 'custom_options', ], diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php index 8879b8fde3c66..ad637d347fc40 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php @@ -214,8 +214,14 @@ protected function setUp() false ); - $stateMock = $this->getMock('Magento\FrameworkApp\State', ['getAreaCode'], [], '', false); - $stateMock->expects($this->any()) + $this->appStateMock = $this->getMock( + 'Magento\Framework\App\State', + ['getAreaCode', 'isAreaCodeEmulated'], + [], + '', + false + ); + $this->appStateMock->expects($this->any()) ->method('getAreaCode') ->will($this->returnValue(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE)); @@ -234,7 +240,7 @@ protected function setUp() '\Magento\Framework\Model\Context', ['getEventDispatcher', 'getCacheManager', 'getAppState', 'getActionValidator'], [], '', false ); - $contextMock->expects($this->any())->method('getAppState')->will($this->returnValue($stateMock)); + $contextMock->expects($this->any())->method('getAppState')->will($this->returnValue($this->appStateMock)); $contextMock->expects($this->any()) ->method('getEventDispatcher') ->will($this->returnValue($this->eventManagerMock)); @@ -371,15 +377,6 @@ protected function setUp() 'data' => ['id' => 1] ] ); - - $this->appStateMock = $this->getMockBuilder(\Magento\Framework\App\State::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $modelReflection = new \ReflectionClass(get_class($this->model)); - $appStateReflection = $modelReflection->getProperty('appState'); - $appStateReflection->setAccessible(true); - $appStateReflection->setValue($this->model, $this->appStateMock); } public function testGetAttributes() @@ -861,6 +858,20 @@ public function testSave() $this->model->afterSave(); } + /** + * Image cache generation would not be performed if area was emulated + */ + public function testSaveIfAreaEmulated() + { + $this->appStateMock->expects($this->any())->method('isAreaCodeEmulated')->willReturn(true); + $this->imageCache->expects($this->never()) + ->method('generate') + ->with($this->model); + $this->configureSaveTest(); + $this->model->beforeSave(); + $this->model->afterSave(); + } + /** * Test for `save` method for duplicated product */ diff --git a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php index 1f85ea746253a..3e5486507cbe4 100644 --- a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php +++ b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php @@ -18,6 +18,7 @@ class ColumnFactory protected $jsComponentMap = [ 'text' => 'Magento_Ui/js/grid/columns/column', 'select' => 'Magento_Ui/js/grid/columns/select', + 'multiselect' => 'Magento_Ui/js/grid/columns/select', 'date' => 'Magento_Ui/js/grid/columns/date', ]; @@ -29,7 +30,7 @@ class ColumnFactory 'text' => 'text', 'boolean' => 'select', 'select' => 'select', - 'multiselect' => 'select', + 'multiselect' => 'multiselect', 'date' => 'date', ]; diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php index 27f64633932f0..2ebd34a073007 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php @@ -14,6 +14,11 @@ class Validate extends ImportResultController { + /** + * @var Import + */ + private $import; + /** * Validate uploaded files action * @@ -34,52 +39,19 @@ public function execute() ); /** @var $import \Magento\ImportExport\Model\Import */ - $import = $this->_objectManager->create('Magento\ImportExport\Model\Import')->setData($data); - $source = ImportAdapter::findAdapterFor( - $import->uploadSource(), - $this->_objectManager->create('Magento\Framework\Filesystem') - ->getDirectoryWrite(DirectoryList::ROOT), - $data[$import::FIELD_FIELD_SEPARATOR] - ); - $validationResult = $import->validateSource($source); - - if (!$import->getProcessedRowsCount()) { - if (!$import->getErrorAggregator()->getErrorsCount()) { - $resultBlock->addError(__('This file is empty. Please try another one.')); - } else { - foreach ($import->getErrorAggregator()->getAllErrors() as $error) { - $resultBlock->addError($error->getErrorMessage(), false); - } - } - } else { - $errorAggregator = $import->getErrorAggregator(); - if (!$validationResult) { - $resultBlock->addError( - __('Data validation failed. Please fix the following errors and upload the file again.') - ); - $this->addErrorMessages($resultBlock, $errorAggregator); - } else { - if ($import->isImportAllowed()) { - $resultBlock->addSuccess( - __('File is valid! To start import process press "Import" button'), - true - ); - } else { - $resultBlock->addError( - __('The file is valid, but we can\'t import it for some reason.'), - false - ); - } - } - $resultBlock->addNotice( - __( - 'Checked rows: %1, checked entities: %2, invalid rows: %3, total errors: %4', - $import->getProcessedRowsCount(), - $import->getProcessedEntitiesCount(), - $errorAggregator->getInvalidRowsCount(), - $errorAggregator->getErrorsCount() - ) + $import = $this->getImport()->setData($data); + try { + $source = ImportAdapter::findAdapterFor( + $import->uploadSource(), + $this->_objectManager->create('Magento\Framework\Filesystem') + ->getDirectoryWrite(DirectoryList::ROOT), + $data[$import::FIELD_FIELD_SEPARATOR] ); + $this->processValidationResult($import->validateSource($source), $resultBlock); + } catch (\Magento\Framework\Exception\LocalizedException $e) { + $resultBlock->addError($e->getMessage()); + } catch (\Exception $e) { + $resultBlock->addError(__('Sorry, but the data is invalid or the file is not uploaded.')); } return $resultLayout; } elseif ($this->getRequest()->isPost() && empty($_FILES)) { @@ -92,4 +64,61 @@ public function execute() $resultRedirect->setPath('adminhtml/*/index'); return $resultRedirect; } + + /** + * @param bool $validationResult + * @param ImportResultBlock $resultBlock + * @return void + */ + private function processValidationResult($validationResult, $resultBlock) + { + $import = $this->getImport(); + if (!$import->getProcessedRowsCount()) { + if (!$import->getErrorAggregator()->getErrorsCount()) { + $resultBlock->addError(__('This file is empty. Please try another one.')); + } else { + foreach ($import->getErrorAggregator()->getAllErrors() as $error) { + $resultBlock->addError($error->getErrorMessage()); + } + } + } else { + $errorAggregator = $import->getErrorAggregator(); + if (!$validationResult) { + $resultBlock->addError( + __('Data validation failed. Please fix the following errors and upload the file again.') + ); + $this->addErrorMessages($resultBlock, $errorAggregator); + } else { + if ($import->isImportAllowed()) { + $resultBlock->addSuccess( + __('File is valid! To start import process press "Import" button'), + true + ); + } else { + $resultBlock->addError(__('The file is valid, but we can\'t import it for some reason.')); + } + } + $resultBlock->addNotice( + __( + 'Checked rows: %1, checked entities: %2, invalid rows: %3, total errors: %4', + $import->getProcessedRowsCount(), + $import->getProcessedEntitiesCount(), + $errorAggregator->getInvalidRowsCount(), + $errorAggregator->getErrorsCount() + ) + ); + } + } + + /** + * @return Import + * @deprecated + */ + private function getImport() + { + if (!$this->import) { + $this->import = $this->_objectManager->get(Import::class); + } + return $this->import; + } } diff --git a/app/code/Magento/ImportExport/Model/Import/AbstractSource.php b/app/code/Magento/ImportExport/Model/Import/AbstractSource.php index 8f1adbbfc99a7..96d599a004110 100644 --- a/app/code/Magento/ImportExport/Model/Import/AbstractSource.php +++ b/app/code/Magento/ImportExport/Model/Import/AbstractSource.php @@ -102,7 +102,7 @@ public function next() { $this->_key++; $row = $this->_getNextRow(); - if (false === $row) { + if (false === $row || [] === $row) { $this->_row = []; $this->_key = -1; } else { diff --git a/app/code/Magento/ImportExport/Model/Import/Source/Csv.php b/app/code/Magento/ImportExport/Model/Import/Source/Csv.php index 575e67834aa9e..153feec64d3b1 100644 --- a/app/code/Magento/ImportExport/Model/Import/Source/Csv.php +++ b/app/code/Magento/ImportExport/Model/Import/Source/Csv.php @@ -87,7 +87,7 @@ protected function _getNextRow() } else { $this->_foundWrongQuoteFlag = false; } - return $parsed; + return is_array($parsed) ? $parsed : []; } /** diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index ef43df4104b93..cefab24640808 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -442,6 +442,7 @@ public function subscribe($email) $this->setStatusChanged(true); try { + $this->save(); if ($isConfirmNeed === true && $isOwnSubscribes === false ) { @@ -449,7 +450,6 @@ public function subscribe($email) } else { $this->sendConfirmationSuccessEmail(); } - $this->save(); return $this->getStatus(); } catch (\Exception $e) { throw new \Exception($e->getMessage()); diff --git a/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js b/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js index 80f3c7f81bf6e..f19a2640c1401 100644 --- a/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js +++ b/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js @@ -330,9 +330,6 @@ define([ description: data.description } }); - } - - if (playerData.oldVideoId !== playerData.newVideoId) { this._loadRemotePreview(data.thumbnail); } self._onlyVideoPlayer = true; diff --git a/app/code/Magento/Sales/Controller/Download/DownloadCustomOption.php b/app/code/Magento/Sales/Controller/Download/DownloadCustomOption.php index 77ec234e00dd9..f79528d5988b3 100644 --- a/app/code/Magento/Sales/Controller/Download/DownloadCustomOption.php +++ b/app/code/Magento/Sales/Controller/Download/DownloadCustomOption.php @@ -78,11 +78,8 @@ public function execute() /** @var $productOption \Magento\Catalog\Model\Product\Option */ $productOption = $this->_objectManager->create('Magento\Catalog\Model\Product\Option')->load($optionId); } - if (!$productOption || - !$productOption->getId() || - $productOption->getProductId() != $option->getProductId() || - $productOption->getType() != 'file' - ) { + + if (!$productOption || !$productOption->getId() || $productOption->getType() != 'file') { return $resultForward->forward('noroute'); } diff --git a/app/code/Magento/Sales/Model/Download.php b/app/code/Magento/Sales/Model/Download.php index afe7d5ea6b83f..58c626947eb09 100644 --- a/app/code/Magento/Sales/Model/Download.php +++ b/app/code/Magento/Sales/Model/Download.php @@ -89,7 +89,8 @@ public function downloadFile($info) protected function _isCanProcessed($relativePath) { $filePath = $this->_rootDir->getAbsolutePath($relativePath); - return (strpos($this->_rootDir->getDriver()->getRealPath($filePath), $relativePath) !== false + $pathWithFixedSeparator = str_replace('\\', '/', $this->_rootDir->getDriver()->getRealPath($filePath)); + return (strpos($pathWithFixedSeparator, $relativePath) !== false && $this->_rootDir->isFile($relativePath) && $this->_rootDir->isReadable($relativePath)) || $this->_processDatabaseFile($filePath, $relativePath); } diff --git a/app/code/Magento/Swatches/Block/Adminhtml/Attribute/Edit/Options/AbstractSwatch.php b/app/code/Magento/Swatches/Block/Adminhtml/Attribute/Edit/Options/AbstractSwatch.php index 0f58189903962..63d55a9c033fa 100644 --- a/app/code/Magento/Swatches/Block/Adminhtml/Attribute/Edit/Options/AbstractSwatch.php +++ b/app/code/Magento/Swatches/Block/Adminhtml/Attribute/Edit/Options/AbstractSwatch.php @@ -110,10 +110,8 @@ public function getStoreOptionValues($storeId) $valuesCollection = $this->_attrOptionCollectionFactory->create(); $valuesCollection->setAttributeFilter( $this->getAttributeObject()->getId() - )->setStoreFilter( - $storeId, - false ); + $this->addCollectionStoreFilter($valuesCollection, $storeId); $valuesCollection->getSelect()->joinLeft( ['swatch_table' => $valuesCollection->getTable('eav_attribute_option_swatch')], 'swatch_table.option_id = main_table.option_id AND swatch_table.store_id = '.$storeId, @@ -128,4 +126,31 @@ public function getStoreOptionValues($storeId) } return $values; } + + /** + * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection $valuesCollection + * @param int $storeId + * @return void + */ + private function addCollectionStoreFilter($valuesCollection, $storeId) + { + $joinCondition = $valuesCollection->getConnection()->quoteInto( + 'tsv.option_id = main_table.option_id AND tsv.store_id = ?', + $storeId + ); + + $select = $valuesCollection->getSelect(); + $select->joinLeft( + ['tsv' => $valuesCollection->getTable('eav_attribute_option_value')], + $joinCondition, + 'value' + ); + if (\Magento\Store\Model\Store::DEFAULT_STORE_ID == $storeId) { + $select->where( + 'tsv.store_id = ?', + $storeId + ); + } + $valuesCollection->setOrder('value', \Magento\Framework\Data\Collection::SORT_ORDER_ASC); + } } diff --git a/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php b/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php index 4546a01529d33..efb0a3ff9bab6 100644 --- a/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php +++ b/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php @@ -365,18 +365,29 @@ protected function getConfigurableOptionsIds(array $attributeData) } /** - * Return HTML code + * Produce and return block's html output * * @codeCoverageIgnore * @return string */ - protected function _toHtml() + public function toHtml() { $this->initIsProductHasSwatchAttribute(); $this->setTemplate( $this->getRendererTemplate() ); + return parent::toHtml(); + } + + /** + * Return HTML code + * + * @codeCoverageIgnore + * @return string + */ + protected function _toHtml() + { return $this->getHtmlOutput(); } diff --git a/app/code/Magento/Swatches/Test/Unit/Block/Adminhtml/Attribute/Edit/Options/AbstractSwatchTest.php b/app/code/Magento/Swatches/Test/Unit/Block/Adminhtml/Attribute/Edit/Options/AbstractSwatchTest.php index 547efa15ec64d..9816f15e5f430 100644 --- a/app/code/Magento/Swatches/Test/Unit/Block/Adminhtml/Attribute/Edit/Options/AbstractSwatchTest.php +++ b/app/code/Magento/Swatches/Test/Unit/Block/Adminhtml/Attribute/Edit/Options/AbstractSwatchTest.php @@ -45,6 +45,11 @@ class AbstractSwatchTest extends \PHPUnit_Framework_TestCase */ protected $block; + /** + * @var \Magento\Framework\DB\Adapter\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $connectionMock; + protected function setUp() { $this->contextMock = $this->getMock('\Magento\Backend\Block\Template\Context', [], [], '', false); @@ -81,6 +86,11 @@ protected function setUp() '', true ); + + $this->connectionMock = $this->getMockBuilder('\Magento\Framework\DB\Adapter\AdapterInterface') + ->disableOriginalConstructor() + ->setMethods(['quoteInto']) + ->getMockForAbstractClass(); } /** @@ -125,15 +135,19 @@ public function testGetStoreOptionValues($values) ->with(23) ->will($this->returnSelf()); + $this->connectionMock + ->expects($this->any()) + ->method('quoteInto') + ->willReturn('quoted_string_with_value'); + $attrOptionCollectionMock - ->expects($this->once()) - ->method('setStoreFilter') - ->with(1, false) - ->will($this->returnSelf()); + ->expects($this->any()) + ->method('getConnection') + ->willReturn($this->connectionMock); $zendDbSelectMock = $this->getMock('Magento\Framework\DB\Select', [], [], '', false); - $attrOptionCollectionMock->expects($this->once())->method('getSelect')->willReturn($zendDbSelectMock); - $zendDbSelectMock->expects($this->once())->method('joinLeft')->will($this->returnSelf()); + $attrOptionCollectionMock->expects($this->any())->method('getSelect')->willReturn($zendDbSelectMock); + $zendDbSelectMock->expects($this->any())->method('joinLeft')->willReturnSelf(); $option->expects($this->at(0))->method('getId')->willReturn(14); $option->expects($this->at(1))->method('getValue')->willReturn('Blue'); diff --git a/app/code/Magento/Ui/Component/Filters/Type/Select.php b/app/code/Magento/Ui/Component/Filters/Type/Select.php index 77803d69ebf2e..5f0097ae7f364 100644 --- a/app/code/Magento/Ui/Component/Filters/Type/Select.php +++ b/app/code/Magento/Ui/Component/Filters/Type/Select.php @@ -96,9 +96,14 @@ protected function applyFilter() { if (isset($this->filterData[$this->getName()])) { $value = $this->filterData[$this->getName()]; - $conditionType = is_array($value) ? 'in' : 'eq'; if (!empty($value) || is_numeric($value)) { + if (is_array($value)) { + $conditionType = 'in'; + } else { + $dataType = $this->getData('config/dataType'); + $conditionType = $dataType == 'multiselect' ? 'finset' : 'eq'; + } $filter = $this->filterBuilder->setConditionType($conditionType) ->setField($this->getName()) ->setValue($value) diff --git a/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/SelectTest.php b/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/SelectTest.php index 84f95dfc03027..e1697a6ace2e8 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/SelectTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/SelectTest.php @@ -97,14 +97,15 @@ public function testGetComponentName() /** * Run test prepare method * - * @param string $name + * @param array $data * @param array $filterData * @param array|null $expectedCondition * @dataProvider getPrepareDataProvider * @return void */ - public function testPrepare($name, $filterData, $expectedCondition) + public function testPrepare($data, $filterData, $expectedCondition) { + $name = $data['name']; /** @var UiComponentInterface $uiComponent */ $uiComponent = $this->getMockForAbstractClass( 'Magento\Framework\View\Element\UiComponentInterface', @@ -124,13 +125,12 @@ public function testPrepare($name, $filterData, $expectedCondition) ->method('addComponentDefinition') ->with(Select::NAME, ['extends' => Select::NAME]); $this->contextMock->expects($this->any()) - ->method('getRequestParam') - ->with(AbstractFilter::FILTER_VAR) + ->method('getFiltersParams') ->willReturn($filterData); /** @var DataProviderInterface $dataProvider */ $dataProvider = $this->getMockForAbstractClass( 'Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface', - [], + ['addFilter'], '', false ); @@ -139,9 +139,24 @@ public function testPrepare($name, $filterData, $expectedCondition) ->willReturn($dataProvider); if ($expectedCondition !== null) { + $filterMock = $this->getMock('Magento\Framework\Api\Filter'); + $this->filterBuilderMock->expects($this->any()) + ->method('setConditionType') + ->with($expectedCondition) + ->willReturnSelf(); + $this->filterBuilderMock->expects($this->any()) + ->method('setField') + ->with($name) + ->willReturnSelf(); + $this->filterBuilderMock->expects($this->any()) + ->method('setValue') + ->willReturnSelf(); + $this->filterBuilderMock->expects($this->any()) + ->method('create') + ->willReturn($filterMock); $dataProvider->expects($this->any()) ->method('addFilter') - ->with($expectedCondition, $name); + ->with($filterMock); } /** @var \Magento\Framework\Data\OptionSourceInterface $selectOptions */ @@ -164,7 +179,7 @@ public function testPrepare($name, $filterData, $expectedCondition) $this->filterModifierMock, $selectOptions, [], - ['name' => $name] + $data ); $date->prepare(); @@ -177,14 +192,29 @@ public function getPrepareDataProvider() { return [ [ - 'test_date', + ['name' => 'test_date', 'config' => []], + [], + null + ], + [ + ['name' => 'test_date', 'config' => []], ['test_date' => ''], - null, + 'eq' + ], + [ + ['name' => 'test_date', 'config' => ['dataType' => 'text']], + ['test_date' => 'some_value'], + 'eq' + ], + [ + ['name' => 'test_date', 'config' => ['dataType' => 'select']], + ['test_date' => ['some_value1', 'some_value2']], + 'in' ], [ - 'test_date', + ['name' => 'test_date', 'config' => ['dataType' => 'multiselect']], ['test_date' => 'some_value'], - ['eq' => 'some_value'], + 'finset' ], ]; } diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/multiselect.js b/app/code/Magento/Ui/view/base/web/js/form/element/multiselect.js index 0626750d2ba7c..0a2d08b29f154 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/multiselect.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/multiselect.js @@ -16,28 +16,6 @@ define([ elementTmpl: 'ui/form/element/multiselect' }, - /** - * @inheritdoc - */ - initConfig: function () { - this._super(); - - this.value = this.normalizeData(this.value); - - return this; - }, - - /** - * @inheritdoc - */ - initLinks: function () { - var scope = this.source.get(this.dataScope); - - this.multipleScopeValue = _.isArray(scope) ? utils.copy(scope) : undefined; - - return this._super(); - }, - /** * @inheritdoc */ @@ -64,11 +42,14 @@ define([ * @inheritdoc */ getInitialValue: function () { - var values = [this.multipleScopeValue, this.default, this.value.peek(), []], + var values = [ + this.normalizeData(this.source.get(this.dataScope)), + this.normalizeData(this.default) + ], value; values.some(function (v) { - return _.isArray(v) && (value = utils.copy(v)); + return _.isArray(v) && (value = utils.copy(v)) && !_.isEmpty(v); }); return value; diff --git a/app/code/Magento/Ui/view/base/web/js/grid/columns/select.js b/app/code/Magento/Ui/view/base/web/js/grid/columns/select.js index d7cd43b639c71..c710ef606dcd0 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/columns/select.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/columns/select.js @@ -21,6 +21,10 @@ define([ values = this._super(), label = []; + if (_.isString(values)) { + values = values.split(','); + } + if (!Array.isArray(values)) { values = [values]; } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php index 91c9e21906485..93cf5d643a510 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php @@ -173,7 +173,7 @@ class View extends AbstractConfigureBlock * * @var string */ - protected $fullImage = '[data-gallery-role="gallery"] img.fotorama__img.fotorama__img--full'; + protected $fullImage = '[data-gallery-role="gallery"] img.fotorama__img--full'; /** * Full image close selector @@ -560,7 +560,12 @@ public function clickBaseImage() */ public function closeFullImage() { - $this->browser->find($this->fullImageClose, Locator::SELECTOR_CSS)->click(); + $element = $this->browser->find($this->fullImageClose, Locator::SELECTOR_CSS); + if (!$element->isVisible()) { + $element->hover(); + $this->waitForElementVisible($this->fullImageClose); + } + $element->click(); } /** diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php index 8905814f2cc5c..d74541b803418 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php @@ -95,4 +95,50 @@ public function testReindexEntitiesForConfigurableProduct() $result = $connection->fetchAll($select); $this->assertCount(0, $result); } + + /** + * @magentoDataFixture Magento/Catalog/_files/products_with_multiselect_attribute.php + */ + public function testReindexMultiselectAttribute() + { + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = Bootstrap::getObjectManager() + ->create(ProductRepositoryInterface::class); + + /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attr **/ + $attr = Bootstrap::getObjectManager()->get('Magento\Eav\Model\Config') + ->getAttribute('catalog_product', 'multiselect_attribute'); + $attr->setIsFilterable(1)->save(); + + /** @var $options \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection */ + $options = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + 'Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection' + ); + $options->setAttributeFilter($attr->getId()); + $optionIds = $options->getAllIds(); + $product1Id = $optionIds[0] * 10; + $product2Id = $optionIds[1] * 10; + + /** @var \Magento\Catalog\Model\Product $product1 **/ + $product1 = $productRepository->getById($product1Id); + $product1->setSpecialFromDate(date('Y-m-d H:i:s')); + $product1->setNewsFromDate(date('Y-m-d H:i:s')); + $productRepository->save($product1); + + /** @var \Magento\Catalog\Model\Product $product2 **/ + $product2 = $productRepository->getById($product2Id); + $product1->setSpecialFromDate(date('Y-m-d H:i:s')); + $product1->setNewsFromDate(date('Y-m-d H:i:s')); + $productRepository->save($product2); + + $this->_eavIndexerProcessor->reindexAll(); + + $connection = $this->productResource->getConnection(); + $select = $connection->select()->from($this->productResource->getTable('catalog_product_index_eav')) + ->where('entity_id in (?)', [$product1Id, $product2Id]) + ->where('attribute_id = ?', $attr->getId()); + + $result = $connection->fetchAll($select); + $this->assertCount(3, $result); + } } diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/ValidateTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/ValidateTest.php index f1c2e61714ed0..f8669bfa229a4 100644 --- a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/ValidateTest.php +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/ValidateTest.php @@ -13,10 +13,13 @@ class ValidateTest extends \Magento\TestFramework\TestCase\AbstractBackendController { /** + * @dataProvider validationDataProvider + * @param string $fileName + * @param string $message * @backupGlobals enabled * @magentoDbIsolation enabled */ - public function testFieldStateAfterValidation() + public function testValidationReturn($fileName, $message) { $this->getRequest()->setParam('isAjax', true); $this->getRequest()->setMethod('POST'); @@ -29,20 +32,17 @@ public function testFieldStateAfterValidation() $this->getRequest()->setPostValue('behavior', 'append'); $this->getRequest()->setPostValue('_import_field_separator', ','); - - $name = 'catalog_product.csv'; - /** @var \Magento\TestFramework\App\Filesystem $filesystem */ $filesystem = $this->_objectManager->get('Magento\Framework\Filesystem'); $tmpDir = $filesystem->getDirectoryWrite(DirectoryList::SYS_TMP); $subDir = str_replace('\\', '_', __CLASS__); $tmpDir->create($subDir); - $target = $tmpDir->getAbsolutePath("{$subDir}/{$name}"); - copy(__DIR__ . "/_files/{$name}", $target); + $target = $tmpDir->getAbsolutePath("{$subDir}/{$fileName}"); + copy(__DIR__ . "/_files/{$fileName}", $target); $_FILES = [ 'import_file' => [ - 'name' => $name, + 'name' => $fileName, 'type' => 'text/csv', 'tmp_name' => $target, 'error' => 0, @@ -55,17 +55,34 @@ public function testFieldStateAfterValidation() 'preferences' => [ 'Magento\Framework\HTTP\Adapter\FileTransferFactory' => 'Magento\ImportExport\Controller\Adminhtml\Import\HttpFactoryMock' - ] + ] ] ); $this->dispatch('backend/admin/import/validate'); - $this->assertContains('File is valid', $this->getResponse()->getBody()); + $this->assertContains($message, $this->getResponse()->getBody()); $this->assertNotContains('The file was not uploaded.', $this->getResponse()->getBody()); $this->assertNotRegExp( '/clear[^\[]*\[[^\]]*(import_file|import_image_archive)[^\]]*\]/m', $this->getResponse()->getBody() ); } + + /** + * @return array + */ + public function validationDataProvider() + { + return [ + [ + 'file_name' => 'catalog_product.csv', + 'message' => 'File is valid' + ], + [ + 'file_name' => 'test.txt', + 'message' => '\'txt\' file extension is not supported' + ] + ]; + } } diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/_files/test.txt b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/_files/test.txt new file mode 100644 index 0000000000000..800154b85270a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/_files/test.txt @@ -0,0 +1 @@ +Import test file diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Model/ResourceModel/Subscriber/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Model/ResourceModel/Subscriber/CollectionTest.php index 4bb8d17cf3294..ac81e1920fa36 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Model/ResourceModel/Subscriber/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Model/ResourceModel/Subscriber/CollectionTest.php @@ -28,12 +28,17 @@ public function testShowCustomerInfo() /** @var \Magento\Newsletter\Model\Subscriber[] $subscribers */ $subscribers = $this->_collectionModel->getItems(); - $this->assertCount(2, $subscribers); - $subscriber = array_shift($subscribers); - $this->assertEquals('John', $subscriber->getFirstname(), $subscriber->getSubscriberEmail()); - $this->assertEquals('Smith', $subscriber->getLastname(), $subscriber->getSubscriberEmail()); - $subscriber = array_shift($subscribers); - $this->assertNull($subscriber->getFirstname(), $subscriber->getSubscriberEmail()); - $this->assertNull($subscriber->getLastname(), $subscriber->getSubscriberEmail()); + $this->assertCount(3, $subscribers); + + while ($subscribers) { + $subscriber = array_shift($subscribers); + if ($subscriber->getCustomerId()) { + $this->assertEquals('John', $subscriber->getFirstname(), $subscriber->getSubscriberEmail()); + $this->assertEquals('Smith', $subscriber->getLastname(), $subscriber->getSubscriberEmail()); + } else { + $this->assertNull($subscriber->getFirstname(), $subscriber->getSubscriberEmail()); + $this->assertNull($subscriber->getLastname(), $subscriber->getSubscriberEmail()); + } + } } } diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php index 9c94d84b9f167..1d61fd45f8708 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php @@ -20,6 +20,24 @@ protected function setUp() ); } + /** + * @magentoDataFixture Magento/Newsletter/_files/subscribers.php + * @magentoConfigFixture current_store newsletter/subscription/confirm 1 + */ + public function testEmailConfirmation() + { + $this->_model->subscribe('customer_confirm@example.com'); + $transportBuilder = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get('Magento\TestFramework\Mail\Template\TransportBuilderMock'); + // confirmationCode 'ysayquyajua23iq29gxwu2eax2qb6gvy' is taken from fixture + $this->assertContains( + '/newsletter/subscriber/confirm/id/' . $this->_model->getSubscriberId() + . '/code/ysayquyajua23iq29gxwu2eax2qb6gvy', + $transportBuilder->getSentMessage()->getBodyHtml()->getRawContent() + ); + $this->assertEquals(Subscriber::STATUS_NOT_ACTIVE, $this->_model->getSubscriberStatus()); + } + /** * @magentoDataFixture Magento/Newsletter/_files/subscribers.php */ diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/_files/subscribers.php b/dev/tests/integration/testsuite/Magento/Newsletter/_files/subscribers.php index cdc67fc7a0dbd..47795630aef2b 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/_files/subscribers.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/_files/subscribers.php @@ -34,3 +34,13 @@ ->setSubscriberEmail('customer_two@example.com') ->setSubscriberStatus(\Magento\Newsletter\Model\Subscriber::STATUS_SUBSCRIBED) ->save(); + +/** @var \Magento\Newsletter\Model\Subscriber $subscriber */ +$subscriber = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create('Magento\Newsletter\Model\Subscriber'); +$subscriber->setStoreId($currentStore) + ->setCustomerId(1) + ->setSubscriberEmail('customer_confirm@example.com') + ->setSubscriberConfirmCode('ysayquyajua23iq29gxwu2eax2qb6gvy') + ->setSubscriberStatus(\Magento\Newsletter\Model\Subscriber::STATUS_UNSUBSCRIBED) + ->save();