diff --git a/COPYING.txt b/COPYING.txt index d2cbcd01539dd..040bdd5f3ce72 100644 --- a/COPYING.txt +++ b/COPYING.txt @@ -1,4 +1,4 @@ -Copyright © 2013-2017 Magento, Inc. +Copyright © 2013-present Magento, Inc. Each Magento source file included in this distribution is licensed under OSL 3.0 or the Magento Enterprise Edition (MEE) license diff --git a/README.md b/README.md index 1dd81a7eed272..c72357db26d16 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ Welcome to Magento 2 installation! We're glad you chose to install Magento 2, a cutting edge, feature-rich eCommerce solution that gets results. ## Magento system requirements -[Magento system requirements](http://devdocs.magento.com/magento-system-requirements.html) +[Magento system requirements](http://devdocs.magento.com/guides/v2.2/install-gde/system-requirements2.html) ## Install Magento To install Magento, see either: * [Magento DevBox](https://magento.com/tech-resources/download), the easiest way to get started with Magento. -* [Installation guide](http://devdocs.magento.com/guides/v2.0/install-gde/bk-install-guide.html) +* [Installation guide](http://devdocs.magento.com/guides/v2.2/install-gde/bk-install-guide.html)

Contributing to the Magento 2 code base

Contributions can take the form of new components or features, changes to existing features, tests, documentation (such as developer guides, user guides, examples, or specifications), bug fixes, optimizations, or just good suggestions. @@ -22,8 +22,8 @@ To learn about issues, click [here][2]. To open an issue, click [here][3]. To suggest documentation improvements, click [here][4]. -[1]: -[2]: +[1]: +[2]: [3]: [4]: diff --git a/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php index c4118792255cd..34f2b7d53d9be 100644 --- a/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php +++ b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php @@ -5,13 +5,35 @@ */ namespace Magento\Analytics\Block\Adminhtml\System\Config; +use Magento\Framework\App\ObjectManager; + /** * Provides label with default Time Zone */ class CollectionTimeLabel extends \Magento\Config\Block\System\Config\Form\Field { /** - * Add default time zone to comment + * @var \Magento\Framework\Locale\ResolverInterface + */ + private $localeResolver; + + /** + * @param \Magento\Backend\Block\Template\Context $context + * @param array $data + * @param \Magento\Framework\Locale\ResolverInterface|null $localeResolver + */ + public function __construct( + \Magento\Backend\Block\Template\Context $context, + array $data = [], + \Magento\Framework\Locale\ResolverInterface $localeResolver = null + ) { + $this->localeResolver = $localeResolver ?: + ObjectManager::getInstance()->get(\Magento\Framework\Locale\ResolverInterface::class); + parent::__construct($context, $data); + } + + /** + * Add current time zone to comment, properly translated according to locale * * @param \Magento\Framework\Data\Form\Element\AbstractElement $element * @return string @@ -19,7 +41,9 @@ class CollectionTimeLabel extends \Magento\Config\Block\System\Config\Form\Field public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element) { $timeZoneCode = $this->_localeDate->getConfigTimezone(); - $getLongTimeZoneName = \IntlTimeZone::createTimeZone($timeZoneCode)->getDisplayName(); + $locale = $this->localeResolver->getLocale(); + $getLongTimeZoneName = \IntlTimeZone::createTimeZone($timeZoneCode) + ->getDisplayName(false, \IntlTimeZone::DISPLAY_LONG, $locale); $element->setData( 'comment', sprintf("%s (%s)", $getLongTimeZoneName, $timeZoneCode) diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 27fd16cc920dc..b6324416d26d5 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -215,7 +215,7 @@ Magento\Directory\Model\Config\Source\Country - + Magento\Directory\Model\Config\Source\Country diff --git a/app/code/Magento/Backup/Model/Db.php b/app/code/Magento/Backup/Model/Db.php index 776141249f92b..8fbd5da1c9842 100644 --- a/app/code/Magento/Backup/Model/Db.php +++ b/app/code/Magento/Backup/Model/Db.php @@ -154,7 +154,7 @@ public function createBackup(\Magento\Framework\Backup\Db\BackupInterface $backu if ($tableStatus->getDataLength() > self::BUFFER_LENGTH) { if ($tableStatus->getAvgRowLength() < self::BUFFER_LENGTH) { - $limit = floor(self::BUFFER_LENGTH / $tableStatus->getAvgRowLength()); + $limit = floor(self::BUFFER_LENGTH / max($tableStatus->getAvgRowLength(), 1)); $multiRowsLength = ceil($tableStatus->getRows() / $limit); } else { $limit = 1; @@ -173,6 +173,7 @@ public function createBackup(\Magento\Framework\Backup\Db\BackupInterface $backu } } $backup->write($this->getResource()->getTableForeignKeysSql()); + $backup->write($this->getResource()->getTableTriggersSql()); $backup->write($this->getResource()->getFooter()); $this->getResource()->commitTransaction(); diff --git a/app/code/Magento/Backup/Model/ResourceModel/Db.php b/app/code/Magento/Backup/Model/ResourceModel/Db.php index 3fbaf44ebb063..f50a3c5b091ad 100644 --- a/app/code/Magento/Backup/Model/ResourceModel/Db.php +++ b/app/code/Magento/Backup/Model/ResourceModel/Db.php @@ -114,6 +114,30 @@ public function getTableForeignKeysSql($tableName = null) return $fkScript; } + /** + * Return triggers fro table(s) + * + * @param string|null $tableName + * @param bool $addDropIfExists + * @return string + */ + public function getTableTriggersSql($tableName = null, $addDropIfExists = true) + { + $triggerScript = ''; + if (!$tableName) { + $tables = $this->getTables(); + foreach ($tables as $table) { + $tableTriggerScript = $this->_resourceHelper->getTableTriggersSql($table, $addDropIfExists); + if (!empty($tableTriggerScript)) { + $triggerScript .= "\n" . $tableTriggerScript; + } + } + } else { + $triggerScript = $this->getTableTriggersSql($tableName, $addDropIfExists); + } + return $triggerScript; + } + /** * Retrieve table status * diff --git a/app/code/Magento/Backup/Model/ResourceModel/Helper.php b/app/code/Magento/Backup/Model/ResourceModel/Helper.php index b5418865339c3..6d7084a87546c 100644 --- a/app/code/Magento/Backup/Model/ResourceModel/Helper.php +++ b/app/code/Magento/Backup/Model/ResourceModel/Helper.php @@ -337,4 +337,40 @@ public function restoreTransactionIsolationLevel() { $this->getConnection()->query('SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ'); } + + /** + * Get create script for triggers + * + * @param string $tableName + * @param boolean $addDropIfExists + * @param boolean $stripDefiner + * @return string + */ + public function getTableTriggersSql($tableName, $addDropIfExists = false, $stripDefiner = true) + { + $script = "--\n-- Triggers structure for table `{$tableName}`\n--\n"; + $triggers = $this->getConnection()->query('SHOW TRIGGERS LIKE \''. $tableName . '\'')->fetchAll(); + + if (!$triggers) { + return ''; + } + foreach ($triggers as $trigger) { + if ($addDropIfExists) { + $script .= 'DROP TRIGGER IF EXISTS ' . $trigger['Trigger'] . ";\n"; + } + $script .= "delimiter ;;\n"; + + $triggerData = $this->getConnection()->query('SHOW CREATE TRIGGER '. $trigger['Trigger'])->fetch(); + if ($stripDefiner) { + $cleanedScript = preg_replace('/DEFINER=[^\s]*/', '', $triggerData['SQL Original Statement']); + $script .= $cleanedScript . "\n"; + } else { + $script .= $triggerData['SQL Original Statement'] . "\n"; + } + $script .= ";;\n"; + $script .= "delimiter ;\n"; + } + + return $script; + } } diff --git a/app/code/Magento/Bundle/Model/Product/CopyConstructor/Bundle.php b/app/code/Magento/Bundle/Model/Product/CopyConstructor/Bundle.php index 61559df4d2cf6..20e4828835d06 100644 --- a/app/code/Magento/Bundle/Model/Product/CopyConstructor/Bundle.php +++ b/app/code/Magento/Bundle/Model/Product/CopyConstructor/Bundle.php @@ -27,7 +27,17 @@ public function build(Product $product, Product $duplicate) $bundleOptions = $product->getExtensionAttributes()->getBundleProductOptions() ?: []; $duplicatedBundleOptions = []; foreach ($bundleOptions as $key => $bundleOption) { - $duplicatedBundleOptions[$key] = clone $bundleOption; + $duplicatedBundleOption = clone $bundleOption; + /** + * Set option and selection ids to 'null' in order to create new option(selection) for duplicated product, + * but not modifying existing one, which led to lost of option(selection) in original product. + */ + $productLinks = $duplicatedBundleOption->getProductLinks() ?: []; + foreach ($productLinks as $productLink) { + $productLink->setSelectionId(null); + } + $duplicatedBundleOption->setOptionId(null); + $duplicatedBundleOptions[$key] = $duplicatedBundleOption; } $duplicate->getExtensionAttributes()->setBundleProductOptions($duplicatedBundleOptions); } diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Option.php b/app/code/Magento/Bundle/Model/ResourceModel/Option.php index 2ad7e57f522d6..46fd8b910f6f1 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Option.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Option.php @@ -81,31 +81,39 @@ protected function _afterSave(\Magento\Framework\Model\AbstractModel $object) { parent::_afterSave($object); - $condition = [ + $conditions = [ 'option_id = ?' => $object->getId(), 'store_id = ? OR store_id = 0' => $object->getStoreId(), 'parent_product_id = ?' => $object->getParentId() ]; $connection = $this->getConnection(); - $connection->delete($this->getTable('catalog_product_bundle_option_value'), $condition); - $data = new \Magento\Framework\DataObject(); - $data->setOptionId($object->getId()) - ->setStoreId($object->getStoreId()) - ->setParentProductId($object->getParentId()) - ->setTitle($object->getTitle()); - - $connection->insert($this->getTable('catalog_product_bundle_option_value'), $data->getData()); - - /** - * also saving default value if this store view scope - */ + if ($this->isOptionPresent($conditions)) { + $connection->update( + $this->getTable('catalog_product_bundle_option_value'), + [ + 'title' => $object->getTitle() + ], + $conditions + ); + } else { + $data = new \Magento\Framework\DataObject(); + $data->setOptionId($object->getId()) + ->setStoreId($object->getStoreId()) + ->setParentProductId($object->getParentId()) + ->setTitle($object->getTitle()); - if ($object->getStoreId()) { - $data->setStoreId(0); - $data->setTitle($object->getDefaultTitle()); $connection->insert($this->getTable('catalog_product_bundle_option_value'), $data->getData()); + + /** + * also saving default value if this store view scope + */ + if ($object->getStoreId()) { + $data->setStoreId(0); + $data->setTitle($object->getDefaultTitle()); + $connection->insert($this->getTable('catalog_product_bundle_option_value'), $data->getData()); + } } return $this; @@ -210,4 +218,26 @@ public function save(\Magento\Framework\Model\AbstractModel $object) return $this; } + + /** + * Is Bundle option present in the database + * + * @param array $conditions + * + * @return bool + */ + private function isOptionPresent($conditions) + { + $connection = $this->getConnection(); + + $select = $connection->select()->from($this->getTable('catalog_product_bundle_option_value')); + foreach ($conditions as $condition => $conditionValue) { + $select->where($condition, $conditionValue); + } + $select->limit(1); + + $rowSelect = $connection->fetchRow($select); + + return (is_array($rowSelect) && !empty($rowSelect)); + } } diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Product/CopyConstructor/BundleTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Product/CopyConstructor/BundleTest.php index 831098cc44c38..4df60d07d98ef 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/Product/CopyConstructor/BundleTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/Product/CopyConstructor/BundleTest.php @@ -6,6 +6,7 @@ namespace Magento\Bundle\Test\Unit\Model\Product\CopyConstructor; use Magento\Bundle\Api\Data\BundleOptionInterface; +use Magento\Bundle\Model\Link; use Magento\Bundle\Model\Product\CopyConstructor\Bundle; use Magento\Catalog\Api\Data\ProductExtensionInterface; use Magento\Catalog\Model\Product; @@ -45,6 +46,7 @@ public function testBuildNegative() */ public function testBuildPositive() { + /** @var Product|\PHPUnit_Framework_MockObject_MockObject $product */ $product = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() ->getMock(); @@ -60,18 +62,42 @@ public function testBuildPositive() ->method('getExtensionAttributes') ->willReturn($extensionAttributesProduct); + $productLink = $this->getMockBuilder(Link::class) + ->setMethods(['setSelectionId']) + ->disableOriginalConstructor() + ->getMock(); + $productLink->expects($this->exactly(2)) + ->method('setSelectionId') + ->with($this->identicalTo(null)); + $firstOption = $this->getMockBuilder(BundleOptionInterface::class) + ->setMethods(['getProductLinks']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $firstOption->expects($this->once()) + ->method('getProductLinks') + ->willReturn([$productLink]); + $firstOption->expects($this->once()) + ->method('setOptionId') + ->with($this->identicalTo(null)); + $secondOption = $this->getMockBuilder(BundleOptionInterface::class) + ->setMethods(['getProductLinks']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $secondOption->expects($this->once()) + ->method('getProductLinks') + ->willReturn([$productLink]); + $secondOption->expects($this->once()) + ->method('setOptionId') + ->with($this->identicalTo(null)); $bundleOptions = [ - $this->getMockBuilder(BundleOptionInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(), - $this->getMockBuilder(BundleOptionInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass() + $firstOption, + $secondOption ]; $extensionAttributesProduct->expects($this->once()) ->method('getBundleProductOptions') ->willReturn($bundleOptions); + /** @var Product|\PHPUnit_Framework_MockObject_MockObject $duplicate */ $duplicate = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() ->getMock(); diff --git a/app/code/Magento/Bundle/view/frontend/templates/sales/order/creditmemo/items/renderer.phtml b/app/code/Magento/Bundle/view/frontend/templates/sales/order/creditmemo/items/renderer.phtml index b4c2c00e5ac43..b9d075966c5d1 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/sales/order/creditmemo/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/sales/order/creditmemo/items/renderer.phtml @@ -12,7 +12,6 @@ getChildren($parentItem) ?> getItem()->getOrderItem()->getOrder() ?> - diff --git a/app/code/Magento/Bundle/view/frontend/templates/sales/order/invoice/items/renderer.phtml b/app/code/Magento/Bundle/view/frontend/templates/sales/order/invoice/items/renderer.phtml index f3866d7b1b682..e18d75ce77b9c 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/sales/order/invoice/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/sales/order/invoice/items/renderer.phtml @@ -12,7 +12,6 @@ getItem()->getOrderItem()->getOrder() ?> getChildren($parentItem) ?> - diff --git a/app/code/Magento/Bundle/view/frontend/templates/sales/order/items/renderer.phtml b/app/code/Magento/Bundle/view/frontend/templates/sales/order/items/renderer.phtml index 84cbd54d3bdcc..063d66edb9e70 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/sales/order/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/sales/order/items/renderer.phtml @@ -10,7 +10,6 @@ ?> getItem() ?> getChildrenItems()); ?> - diff --git a/app/code/Magento/Bundle/view/frontend/templates/sales/order/shipment/items/renderer.phtml b/app/code/Magento/Bundle/view/frontend/templates/sales/order/shipment/items/renderer.phtml index bd99afc59a8c0..0cd39156b2513 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/sales/order/shipment/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/sales/order/shipment/items/renderer.phtml @@ -12,7 +12,6 @@ getItem() ?> getOrderItem()], $parentItem->getOrderItem()->getChildrenItems()) ?> getChildren($parentItem) ?> - diff --git a/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php b/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php index 9b8518a41ffff..32efb9a04191d 100644 --- a/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php +++ b/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php @@ -9,9 +9,10 @@ use Magento\CatalogImportExport\Model\Export\RowCustomizerInterface; use Magento\CatalogImportExport\Model\Import\Product as ImportProductModel; use Magento\Bundle\Model\ResourceModel\Selection\Collection as SelectionCollection; -use Magento\ImportExport\Controller\Adminhtml\Import; use Magento\ImportExport\Model\Import as ImportModel; use \Magento\Catalog\Model\Product\Type\AbstractType; +use \Magento\Framework\App\ObjectManager; +use \Magento\Store\Model\StoreManagerInterface; /** * Class RowCustomizer @@ -105,6 +106,35 @@ class RowCustomizer implements RowCustomizerInterface AbstractType::SHIPMENT_SEPARATELY => 'separately', ]; + /** + * @var \Magento\Bundle\Model\ResourceModel\Option\Collection[] + */ + private $optionCollections = []; + + /** + * @var array + */ + private $storeIdToCode = []; + + /** + * @var string + */ + private $optionCollectionCacheKey = '_cache_instance_options_collection'; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param StoreManagerInterface $storeManager + * @throws \RuntimeException + */ + public function __construct(StoreManagerInterface $storeManager) + { + $this->storeManager = $storeManager; + } + /** * Retrieve list of bundle specific columns * @return array @@ -207,15 +237,13 @@ protected function populateBundleData($collection) */ protected function getFormattedBundleOptionValues($product) { - /** @var \Magento\Bundle\Model\ResourceModel\Option\Collection $optionsCollection */ - $optionsCollection = $product->getTypeInstance() - ->getOptionsCollection($product) - ->setOrder('position', Collection::SORT_ORDER_ASC); - + $optionCollections = $this->getProductOptionCollections($product); $bundleData = ''; - foreach ($optionsCollection as $option) { + $optionTitles = $this->getBundleOptionTitles($product); + foreach ($optionCollections->getItems() as $option) { + $optionValues = $this->getFormattedOptionValues($option, $optionTitles); $bundleData .= $this->getFormattedBundleSelections( - $this->getFormattedOptionValues($option), + $optionValues, $product->getTypeInstance() ->getSelectionsCollection([$option->getId()], $product) ->setOrder('position', Collection::SORT_ORDER_ASC) @@ -266,16 +294,23 @@ function ($value, $key) { * Retrieve option value of bundle product * * @param \Magento\Bundle\Model\Option $option + * @param string[] $optionTitles * @return string */ - protected function getFormattedOptionValues($option) + protected function getFormattedOptionValues($option, $optionTitles = []) { - return 'name' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR - . $option->getTitle() . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR - . 'type' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR - . $option->getType() . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR - . 'required' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR - . $option->getRequired(); + $names = implode(ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, array_map( + function ($title, $storeName) { + return $storeName . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR . $title; + }, + $optionTitles[$option->getOptionId()], + array_keys($optionTitles[$option->getOptionId()]) + )); + return $names . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR + . 'type' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR + . $option->getType() . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR + . 'required' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR + . $option->getRequired(); } /** @@ -380,4 +415,82 @@ private function parseAdditionalAttributes($additionalAttributes) } return $preparedAttributes; } + + /** + * Get product options titles. + * + * Values for all store views (default) should be specified with 'name' key. + * If user want to specify value or change existing for non default store views it should be specified with + * 'name_' prefix and needed store view suffix. + * + * For example: + * - 'name=All store views name' for all store views + * - 'name_specific_store=Specific store name' for store view with 'specific_store' store code + * + * @param \Magento\Catalog\Model\Product $product + * @return array + */ + private function getBundleOptionTitles(\Magento\Catalog\Model\Product $product): array + { + $optionCollections = $this->getProductOptionCollections($product); + $optionsTitles = []; + /** @var \Magento\Bundle\Model\Option $option */ + foreach ($optionCollections->getItems() as $option) { + $optionsTitles[$option->getId()]['name'] = $option->getTitle(); + } + $storeIds = $product->getStoreIds(); + if (count($storeIds) > 1) { + foreach ($storeIds as $storeId) { + $optionCollections = $this->getProductOptionCollections($product, $storeId); + /** @var \Magento\Bundle\Model\Option $option */ + foreach ($optionCollections->getItems() as $option) { + $optionTitle = $option->getTitle(); + if ($optionsTitles[$option->getId()]['name'] != $optionTitle) { + $optionsTitles[$option->getId()]['name_' . $this->getStoreCodeById($storeId)] = $optionTitle; + } + } + } + } + return $optionsTitles; + } + + /** + * Get product options collection by provided product model. + * + * Set given store id to the product if it was defined (default store id will be set if was not). + * + * @param \Magento\Catalog\Model\Product $product $product + * @param int $storeId + * @return \Magento\Bundle\Model\ResourceModel\Option\Collection + */ + private function getProductOptionCollections( + \Magento\Catalog\Model\Product $product, + $storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID + ): \Magento\Bundle\Model\ResourceModel\Option\Collection { + $productSku = $product->getSku(); + if (!isset($this->optionCollections[$productSku][$storeId])) { + $product->unsetData($this->optionCollectionCacheKey); + $product->setStoreId($storeId); + $this->optionCollections[$productSku][$storeId] = $product->getTypeInstance() + ->getOptionsCollection($product) + ->setOrder('position', Collection::SORT_ORDER_ASC); + } + return $this->optionCollections[$productSku][$storeId]; + } + + /** + * Retrieve store code by it's ID. + * + * Collect store id in $storeIdToCode[] private variable if it was not initialized earlier. + * + * @param int $storeId + * @return string + */ + private function getStoreCodeById($storeId): string + { + if (!isset($this->storeIdToCode[$storeId])) { + $this->storeIdToCode[$storeId] = $this->storeManager->getStore($storeId)->getCode(); + } + return $this->storeIdToCode[$storeId]; + } } diff --git a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php index 96b7c7b1430b0..6d427a17d694c 100644 --- a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php +++ b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php @@ -11,12 +11,14 @@ use \Magento\Framework\App\ObjectManager; use \Magento\Bundle\Model\Product\Price as BundlePrice; use \Magento\Catalog\Model\Product\Type\AbstractType; -use Magento\CatalogImportExport\Model\Import\Product; +use \Magento\CatalogImportExport\Model\Import\Product; +use \Magento\Store\Model\StoreManagerInterface; /** * Class Bundle * @package Magento\BundleImportExport\Model\Import\Product\Type * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Bundle extends \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType { @@ -136,6 +138,16 @@ class Bundle extends \Magento\CatalogImportExport\Model\Import\Product\Type\Abst */ private $relationsDataSaver; + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var array + */ + private $storeCodeToId = []; + /** * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFac * @param \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $prodAttrColFac @@ -143,6 +155,9 @@ class Bundle extends \Magento\CatalogImportExport\Model\Import\Product\Type\Abst * @param array $params * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool * @param Bundle\RelationsDataSaver|null $relationsDataSaver + * @param StoreManagerInterface $storeManager + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \RuntimeException */ public function __construct( \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFac, @@ -150,12 +165,14 @@ public function __construct( \Magento\Framework\App\ResourceConnection $resource, array $params, \Magento\Framework\EntityManager\MetadataPool $metadataPool = null, - Bundle\RelationsDataSaver $relationsDataSaver = null + Bundle\RelationsDataSaver $relationsDataSaver = null, + StoreManagerInterface $storeManager = null ) { parent::__construct($attrSetColFac, $prodAttrColFac, $resource, $params, $metadataPool); - $this->relationsDataSaver = $relationsDataSaver ?: ObjectManager::getInstance()->get(Bundle\RelationsDataSaver::class); + $this->storeManager = $storeManager + ?: ObjectManager::getInstance()->get(StoreManagerInterface::class); } /** @@ -261,20 +278,28 @@ protected function populateOptionTemplate($option, $entityId, $index = null) * @param array $option * @param int $optionId * @param int $storeId - * - * @return array|bool + * @return array */ protected function populateOptionValueTemplate($option, $optionId, $storeId = 0) { - if (!isset($option['name']) || !isset($option['parent_id']) || !$optionId) { - return false; + $optionValues = []; + if (isset($option['name']) && isset($option['parent_id']) && $optionId) { + $pattern = '/^name[_]?(.*)/'; + $keys = array_keys($option); + $optionNames = preg_grep($pattern, $keys); + foreach ($optionNames as $optionName) { + preg_match($pattern, $optionName, $storeCodes); + $storeCode = array_pop($storeCodes); + $storeId = $storeCode ? $this->getStoreIdByCode($storeCode) : $storeId; + $optionValues[] = [ + 'option_id' => $optionId, + 'parent_product_id' => $option['parent_id'], + 'store_id' => $storeId, + 'title' => $option[$optionName], + ]; + } } - return [ - 'option_id' => $optionId, - 'parent_product_id' => $option['parent_id'], - 'store_id' => $storeId, - 'title' => $option['name'], - ]; + return $optionValues; } /** @@ -284,7 +309,7 @@ protected function populateOptionValueTemplate($option, $optionId, $storeId = 0) * @param int $optionId * @param int $parentId * @param int $index - * @return array + * @return array|bool * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ @@ -564,21 +589,24 @@ protected function insertOptions() */ protected function populateInsertOptionValues($optionIds) { - $insertValues = []; + $optionValues = []; foreach ($this->_cachedOptions as $entityId => $options) { foreach ($options as $key => $option) { foreach ($optionIds as $optionId => $assoc) { if ($assoc['position'] == $this->_cachedOptions[$entityId][$key]['index'] && $assoc['parent_id'] == $entityId) { $option['parent_id'] = $entityId; - $insertValues[] = $this->populateOptionValueTemplate($option, $optionId); + $optionValues = array_merge( + $optionValues, + $this->populateOptionValueTemplate($option, $optionId) + ); $this->_cachedOptions[$entityId][$key]['option_id'] = $optionId; break; } } } } - return $insertValues; + return $optionValues; } /** @@ -695,4 +723,21 @@ protected function clear() $this->_cachedSkuToProducts = []; return $this; } + + /** + * Get store id by store code. + * + * @param string $storeCode + * @return int + */ + private function getStoreIdByCode(string $storeCode): int + { + if (!isset($this->storeIdToCode[$storeCode])) { + /** @var $store \Magento\Store\Model\Store */ + foreach ($this->storeManager->getStores() as $store) { + $this->storeCodeToId[$store->getCode()] = $store->getId(); + } + } + return $this->storeCodeToId[$storeCode]; + } } diff --git a/app/code/Magento/BundleImportExport/Test/Unit/Model/Export/Product/RowCustomizerTest.php b/app/code/Magento/BundleImportExport/Test/Unit/Model/Export/Product/RowCustomizerTest.php index e76e9e1ba565f..027d30e0da39d 100644 --- a/app/code/Magento/BundleImportExport/Test/Unit/Model/Export/Product/RowCustomizerTest.php +++ b/app/code/Magento/BundleImportExport/Test/Unit/Model/Export/Product/RowCustomizerTest.php @@ -52,14 +52,24 @@ class RowCustomizerTest extends \PHPUnit\Framework\TestCase */ protected $selection; + /** @var \Magento\Framework\App\ScopeResolverInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $scopeResolver; + /** * Set up */ protected function setUp() { $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->scopeResolver = $this->getMockBuilder(\Magento\Framework\App\ScopeResolverInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getScope']) + ->getMockForAbstractClass(); $this->rowCustomizerMock = $this->objectManagerHelper->getObject( - \Magento\BundleImportExport\Model\Export\RowCustomizer::class + \Magento\BundleImportExport\Model\Export\RowCustomizer::class, + [ + 'scopeResolver' => $this->scopeResolver + ] ); $this->productResourceCollection = $this->createPartialMock( \Magento\Catalog\Model\ResourceModel\Product\Collection::class, @@ -72,6 +82,8 @@ protected function setUp() 'getPriceType', 'getShipmentType', 'getSkuType', + 'getSku', + 'getStoreIds', 'getPriceView', 'getWeightType', 'getTypeInstance', @@ -79,6 +91,7 @@ protected function setUp() 'getSelectionsCollection' ] ); + $this->product->expects($this->any())->method('getStoreIds')->willReturn([1]); $this->product->expects($this->any())->method('getEntityId')->willReturn(1); $this->product->expects($this->any())->method('getPriceType')->willReturn(1); $this->product->expects($this->any())->method('getShipmentType')->willReturn(1); @@ -88,19 +101,20 @@ protected function setUp() $this->product->expects($this->any())->method('getTypeInstance')->willReturnSelf(); $this->optionsCollection = $this->createPartialMock( \Magento\Bundle\Model\ResourceModel\Option\Collection::class, - ['setOrder', 'getIterator'] + ['setOrder', 'getItems'] ); $this->product->expects($this->any())->method('getOptionsCollection')->willReturn($this->optionsCollection); $this->optionsCollection->expects($this->any())->method('setOrder')->willReturnSelf(); $this->option = $this->createPartialMock( \Magento\Bundle\Model\Option::class, - ['getId', 'getTitle', 'getType', 'getRequired'] + ['getId', 'getOptionId', 'getTitle', 'getType', 'getRequired'] ); $this->option->expects($this->any())->method('getId')->willReturn(1); + $this->option->expects($this->any())->method('getOptionId')->willReturn(1); $this->option->expects($this->any())->method('getTitle')->willReturn('title'); $this->option->expects($this->any())->method('getType')->willReturn(1); $this->option->expects($this->any())->method('getRequired')->willReturn(1); - $this->optionsCollection->expects($this->any())->method('getIterator')->will( + $this->optionsCollection->expects($this->any())->method('getItems')->will( $this->returnValue(new \ArrayIterator([$this->option])) ); $this->selection = $this->createPartialMock( @@ -122,6 +136,7 @@ protected function setUp() $this->product->expects($this->any())->method('getSelectionsCollection')->willReturn( $this->selectionsCollection ); + $this->product->expects($this->any())->method('getSku')->willReturn(1); $this->productResourceCollection->expects($this->any())->method('addAttributeToFilter')->willReturnSelf(); $this->productResourceCollection->expects($this->any())->method('getIterator')->will( $this->returnValue(new \ArrayIterator([$this->product])) @@ -133,6 +148,9 @@ protected function setUp() */ public function testPrepareData() { + $scope = $this->getMockBuilder(\Magento\Framework\App\ScopeInterface::class)->getMockForAbstractClass(); + $this->scopeResolver->expects($this->any())->method('getScope') + ->willReturn($scope); $result = $this->rowCustomizerMock->prepareData($this->productResourceCollection, [1]); $this->assertNotNull($result); } @@ -160,6 +178,9 @@ public function testAddHeaderColumns() */ public function testAddData() { + $scope = $this->getMockBuilder(\Magento\Framework\App\ScopeInterface::class)->getMockForAbstractClass(); + $this->scopeResolver->expects($this->any())->method('getScope') + ->willReturn($scope); $preparedData = $this->rowCustomizerMock->prepareData($this->productResourceCollection, [1]); $attributes = 'attribute=1,sku_type=1,attribute2="Text",price_type=1,price_view=1,weight_type=1,' . 'values=values,shipment_type=1,attribute3=One,Two,Three'; diff --git a/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php b/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php index 8e1243b5eb3af..773fa6a5349a5 100644 --- a/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php +++ b/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php @@ -58,6 +58,9 @@ class BundleTest extends \Magento\ImportExport\Test\Unit\Model\Import\AbstractIm */ protected $setCollection; + /** @var \Magento\Framework\App\ScopeResolverInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $scopeResolver; + /** * * @return void @@ -170,14 +173,18 @@ protected function setUp() 0 => $this->entityModel, 1 => 'bundle' ]; - + $this->scopeResolver = $this->getMockBuilder(\Magento\Framework\App\ScopeResolverInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getScope']) + ->getMockForAbstractClass(); $this->bundle = $this->objectManagerHelper->getObject( \Magento\BundleImportExport\Model\Import\Product\Type\Bundle::class, [ 'attrSetColFac' => $this->attrSetColFac, 'prodAttrColFac' => $this->prodAttrColFac, 'resource' => $this->resource, - 'params' => $this->params + 'params' => $this->params, + 'scopeResolver' => $this->scopeResolver ] ); @@ -214,7 +221,8 @@ public function testSaveData($skus, $bunch, $allowImport) $this->entityModel->expects($this->any())->method('isRowAllowedToImport')->will($this->returnValue( $allowImport )); - + $scope = $this->getMockBuilder(\Magento\Framework\App\ScopeInterface::class)->getMockForAbstractClass(); + $this->scopeResolver->expects($this->any())->method('getScope')->willReturn($scope); $this->connection->expects($this->any())->method('fetchAssoc')->with($this->select)->will($this->returnValue([ '1' => [ 'option_id' => '1', diff --git a/app/code/Magento/BundleImportExport/composer.json b/app/code/Magento/BundleImportExport/composer.json index f5ad6143dc501..407665b0e942e 100644 --- a/app/code/Magento/BundleImportExport/composer.json +++ b/app/code/Magento/BundleImportExport/composer.json @@ -7,6 +7,7 @@ "magento/module-import-export": "100.2.*", "magento/module-catalog-import-export": "100.2.*", "magento/module-bundle": "100.2.*", + "magento/module-store": "100.2.*", "magento/module-eav": "101.0.*", "magento/framework": "101.0.*" }, diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php b/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php index 6f8a45c6ac7ed..a5b6b34d324f7 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php @@ -228,7 +228,7 @@ public function getStoreSwitcherHtml() public function getLoadTreeUrl($expanded = null) { $params = ['_current' => true, 'id' => null, 'store' => null]; - if (is_null($expanded) && $this->_backendSession->getIsTreeWasExpanded() || $expanded == true) { + if ($expanded === null && $this->_backendSession->getIsTreeWasExpanded() || $expanded == true) { $params['expand_all'] = true; } return $this->getUrl('*/*/categoriesJson', $params); diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Option.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Option.php index 64856a5c69dc7..339239ea491e0 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Option.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Option.php @@ -313,9 +313,9 @@ public function getOptionValues() $value['checkboxScopeTitle'] = $this->getCheckboxScopeHtml( $option->getOptionId(), 'title', - is_null($option->getStoreTitle()) + $option->getStoreTitle() === null ); - $value['scopeTitleDisabled'] = is_null($option->getStoreTitle()) ? 'disabled' : null; + $value['scopeTitleDisabled'] = $option->getStoreTitle() === null ? 'disabled' : null; } if ($option->getGroupByType() == ProductCustomOptionInterface::OPTION_GROUP_SELECT) { @@ -341,22 +341,22 @@ public function getOptionValues() $value['optionValues'][$i]['checkboxScopeTitle'] = $this->getCheckboxScopeHtml( $_value->getOptionId(), 'title', - is_null($_value->getStoreTitle()), + $_value->getStoreTitle() === null, $_value->getOptionTypeId() ); - $value['optionValues'][$i]['scopeTitleDisabled'] = is_null( - $_value->getStoreTitle() + $value['optionValues'][$i]['scopeTitleDisabled'] = ( + $_value->getStoreTitle() === null ) ? 'disabled' : null; if ($scope == \Magento\Store\Model\Store::PRICE_SCOPE_WEBSITE) { $value['optionValues'][$i]['checkboxScopePrice'] = $this->getCheckboxScopeHtml( $_value->getOptionId(), 'price', - is_null($_value->getstorePrice()), + $_value->getstorePrice() === null, $_value->getOptionTypeId(), ['$(this).up(1).previous()'] ); - $value['optionValues'][$i]['scopePriceDisabled'] = is_null( - $_value->getStorePrice() + $value['optionValues'][$i]['scopePriceDisabled'] = ( + $_value->getStorePrice() === null ) ? 'disabled' : null; } } @@ -379,9 +379,9 @@ public function getOptionValues() $value['checkboxScopePrice'] = $this->getCheckboxScopeHtml( $option->getOptionId(), 'price', - is_null($option->getStorePrice()) + $option->getStorePrice() === null ); - $value['scopePriceDisabled'] = is_null($option->getStorePrice()) ? 'disabled' : null; + $value['scopePriceDisabled'] = $option->getStorePrice() === null ? 'disabled' : null; } } $values[] = new \Magento\Framework\DataObject($value); diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php index 95d9b1ae61208..3f9dac98033aa 100644 --- a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php +++ b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php @@ -121,7 +121,7 @@ public function getItems() * getIdentities() depends on _itemCollection populated, but it can be empty if the block is hidden * @see https://github.com/magento/magento2/issues/5897 */ - if (is_null($this->_itemCollection)) { + if ($this->_itemCollection === null) { $this->_prepareData(); } return $this->_itemCollection; diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php index f97d1a788dafb..40afd44305262 100644 --- a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php +++ b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php @@ -140,7 +140,7 @@ public function getItemCollection() * getIdentities() depends on _itemCollection populated, but it can be empty if the block is hidden * @see https://github.com/magento/magento2/issues/5897 */ - if (is_null($this->_itemCollection)) { + if ($this->_itemCollection === null) { $this->_prepareData(); } return $this->_itemCollection; @@ -151,7 +151,7 @@ public function getItemCollection() */ public function getItems() { - if (is_null($this->_items)) { + if ($this->_items === null) { $this->_items = $this->getItemCollection()->getItems(); } return $this->_items; diff --git a/app/code/Magento/Catalog/Helper/Product/View.php b/app/code/Magento/Catalog/Helper/Product/View.php index 46ac05168715b..ae948a362ab5a 100644 --- a/app/code/Magento/Catalog/Helper/Product/View.php +++ b/app/code/Magento/Catalog/Helper/Product/View.php @@ -122,18 +122,18 @@ public function initProductLayout(ResultPage $resultPage, $product, $params = nu // Load default page handles and page configurations if ($params && $params->getBeforeHandles()) { foreach ($params->getBeforeHandles() as $handle) { - $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku], $handle); $resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], $handle, false); + $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku], $handle); } } - - $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku]); + $resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], null, false); + $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku]); if ($params && $params->getAfterHandles()) { foreach ($params->getAfterHandles() as $handle) { - $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku], $handle); $resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], $handle, false); + $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku], $handle); } } diff --git a/app/code/Magento/Catalog/Model/Config.php b/app/code/Magento/Catalog/Model/Config.php index 227821463b7f0..b3b5204887ea1 100644 --- a/app/code/Magento/Catalog/Model/Config.php +++ b/app/code/Magento/Catalog/Model/Config.php @@ -407,7 +407,7 @@ public function getSourceOptionId($source, $value) */ public function getProductAttributes() { - if (is_null($this->_productAttributes)) { + if ($this->_productAttributes === null) { $this->_productAttributes = array_keys($this->getAttributesUsedInProductListing()); } return $this->_productAttributes; @@ -430,7 +430,7 @@ protected function _getResource() */ public function getAttributesUsedInProductListing() { - if (is_null($this->_usedInProductListing)) { + if ($this->_usedInProductListing === null) { $this->_usedInProductListing = []; $entityType = \Magento\Catalog\Model\Product::ENTITY; $attributesData = $this->_getResource()->setStoreId($this->getStoreId())->getAttributesUsedInListing(); @@ -453,7 +453,7 @@ public function getAttributesUsedInProductListing() */ public function getAttributesUsedForSortBy() { - if (is_null($this->_usedForSortBy)) { + if ($this->_usedForSortBy === null) { $this->_usedForSortBy = []; $entityType = \Magento\Catalog\Model\Product::ENTITY; $attributesData = $this->_getResource()->getAttributesUsedForSortBy(); diff --git a/app/code/Magento/Catalog/Model/Layer/Filter/Price.php b/app/code/Magento/Catalog/Model/Layer/Filter/Price.php index ec5e2bff81ab3..68ef96c0f36a1 100644 --- a/app/code/Magento/Catalog/Model/Layer/Filter/Price.php +++ b/app/code/Magento/Catalog/Model/Layer/Filter/Price.php @@ -150,7 +150,7 @@ public function apply(\Magento\Framework\App\RequestInterface $request) public function getCustomerGroupId() { $customerGroupId = $this->_getData('customer_group_id'); - if (is_null($customerGroupId)) { + if ($customerGroupId === null) { $customerGroupId = $this->_customerSession->getCustomerGroupId(); } @@ -176,7 +176,7 @@ public function setCustomerGroupId($customerGroupId) public function getCurrencyRate() { $rate = $this->_getData('currency_rate'); - if (is_null($rate)) { + if ($rate === null) { $rate = $this->_storeManager->getStore($this->getStoreId()) ->getCurrentCurrencyRate(); } diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php index cd686c05908ce..84770a4a93ed4 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php @@ -91,7 +91,7 @@ public function __construct( */ protected function _getWebsiteCurrencyRates() { - if (is_null($this->_rates)) { + if ($this->_rates === null) { $this->_rates = []; $baseCurrency = $this->_config->getValue( \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE, diff --git a/app/code/Magento/Catalog/Model/Product/Image.php b/app/code/Magento/Catalog/Model/Product/Image.php index 91f589f9b5bd7..971f34e02f9e5 100644 --- a/app/code/Magento/Catalog/Model/Product/Image.php +++ b/app/code/Magento/Catalog/Model/Product/Image.php @@ -9,6 +9,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; use Magento\Framework\Image as MagentoImage; +use Magento\Framework\Serialize\SerializerInterface; /** * @method string getFile() @@ -172,6 +173,16 @@ class Image extends \Magento\Framework\Model\AbstractModel */ private $imageAsset; + /** + * @var string + */ + private $cachePrefix = 'IMG_INFO'; + + /** + * @var SerializerInterface + */ + private $serializer; + /** * Constructor * @@ -190,6 +201,7 @@ class Image extends \Magento\Framework\Model\AbstractModel * @param array $data * @param \Magento\Catalog\Model\View\Asset\ImageFactory|null $viewAssetImageFactory * @param \Magento\Catalog\Model\View\Asset\PlaceholderFactory|null $viewAssetPlaceholderFactory + * @param SerializerInterface|null $serializer * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ @@ -208,7 +220,8 @@ public function __construct( \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], \Magento\Catalog\Model\View\Asset\ImageFactory $viewAssetImageFactory = null, - \Magento\Catalog\Model\View\Asset\PlaceholderFactory $viewAssetPlaceholderFactory = null + \Magento\Catalog\Model\View\Asset\PlaceholderFactory $viewAssetPlaceholderFactory = null, + SerializerInterface $serializer = null ) { $this->_storeManager = $storeManager; $this->_catalogProductMediaConfig = $catalogProductMediaConfig; @@ -223,6 +236,7 @@ public function __construct( ->get(\Magento\Catalog\Model\View\Asset\ImageFactory::class); $this->viewAssetPlaceholderFactory = $viewAssetPlaceholderFactory ?: ObjectManager::getInstance() ->get(\Magento\Catalog\Model\View\Asset\PlaceholderFactory::class); + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); } /** @@ -356,86 +370,6 @@ public function setSize($size) return $this; } - /** - * @param string|null $file - * @return bool - */ - protected function _checkMemory($file = null) - { - return $this->_getMemoryLimit() > $this->_getMemoryUsage() + $this->_getNeedMemoryForFile( - $file - ) - || $this->_getMemoryLimit() == -1; - } - - /** - * @return string - */ - protected function _getMemoryLimit() - { - $memoryLimit = trim(strtoupper(ini_get('memory_limit'))); - - if (!isset($memoryLimit[0])) { - $memoryLimit = "128M"; - } - - if (substr($memoryLimit, -1) == 'K') { - return substr($memoryLimit, 0, -1) * 1024; - } - if (substr($memoryLimit, -1) == 'M') { - return substr($memoryLimit, 0, -1) * 1024 * 1024; - } - if (substr($memoryLimit, -1) == 'G') { - return substr($memoryLimit, 0, -1) * 1024 * 1024 * 1024; - } - return $memoryLimit; - } - - /** - * @return int - */ - protected function _getMemoryUsage() - { - if (function_exists('memory_get_usage')) { - return memory_get_usage(); - } - return 0; - } - - /** - * @param string|null $file - * @return float|int - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - protected function _getNeedMemoryForFile($file = null) - { - $file = $file === null ? $this->getBaseFile() : $file; - if (!$file) { - return 0; - } - - if (!$this->_mediaDirectory->isExist($file)) { - return 0; - } - - $imageInfo = getimagesize($this->_mediaDirectory->getAbsolutePath($file)); - - if (!isset($imageInfo[0]) || !isset($imageInfo[1])) { - return 0; - } - if (!isset($imageInfo['channels'])) { - // if there is no info about this parameter lets set it for maximum - $imageInfo['channels'] = 4; - } - if (!isset($imageInfo['bits'])) { - // if there is no info about this parameter lets set it for maximum - $imageInfo['bits'] = 8; - } - return round( - ($imageInfo[0] * $imageInfo[1] * $imageInfo['bits'] * $imageInfo['channels'] / 8 + Pow(2, 16)) * 1.65 - ); - } - /** * Convert array of 3 items (decimal r, g, b) to string of their hex values * @@ -472,9 +406,7 @@ public function setBaseFile($file) 'filePath' => $file, ] ); - if ($file == 'no_selection' || !$this->_fileExists($this->imageAsset->getSourceFile()) - || !$this->_checkMemory($this->imageAsset->getSourceFile()) - ) { + if ($file == 'no_selection' || !$this->_fileExists($this->imageAsset->getSourceFile())) { $this->_isBaseFilePlaceholder = true; $this->imageAsset = $this->viewAssetPlaceholderFactory->create( [ @@ -682,11 +614,14 @@ public function getDestinationSubdir() } /** - * @return bool|void + * @return bool */ public function isCached() { - return file_exists($this->imageAsset->getPath()); + return ( + is_array($this->loadImageInfoFromCache($this->imageAsset->getPath())) || + file_exists($this->imageAsset->getPath()) + ); } /** @@ -856,6 +791,7 @@ public function clearCache() $this->_mediaDirectory->delete($directory); $this->_coreFileStorageDatabase->deleteFolder($this->_mediaDirectory->getAbsolutePath($directory)); + $this->clearImageInfoFromCache(); } /** @@ -890,7 +826,7 @@ public function getResizedImageInfo() $image = $this->imageAsset->getPath(); } - $imageProperties = getimagesize($image); + $imageProperties = $this->getimagesize($image); return $imageProperties; } finally { @@ -932,4 +868,66 @@ private function getMiscParams() return $miscParams; } + + /** + * Get image size + * + * @param string $imagePath + * @return array + */ + private function getImageSize($imagePath) + { + $imageInfo = $this->loadImageInfoFromCache($imagePath); + if (!isset($imageInfo['size'])) { + $imageSize = getimagesize($imagePath); + $this->saveImageInfoToCache(['size' => $imageSize], $imagePath); + return $imageSize; + } else { + return $imageInfo['size']; + } + } + + /** + * Save image data to cache + * + * @param array $imageInfo + * @param string $imagePath + * @return void + */ + private function saveImageInfoToCache(array $imageInfo, string $imagePath) + { + $imagePath = $this->cachePrefix . $imagePath; + $this->_cacheManager->save( + $this->serializer->serialize($imageInfo), + $imagePath, + [$this->cachePrefix] + ); + } + + /** + * Load image data from cache + * + * @param string $imagePath + * @return array|false + */ + private function loadImageInfoFromCache(string $imagePath) + { + $imagePath = $this->cachePrefix . $imagePath; + $cacheData = $this->_cacheManager->load($imagePath); + if (!$cacheData) { + return false; + } else { + return $this->serializer->unserialize($cacheData); + } + } + + /** + * Clear image data from cache + * + * @return void + */ + private function clearImageInfoFromCache() + { + $this->_cacheManager->clean([$this->cachePrefix]); + } } diff --git a/app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php index 13c6a13a50407..6d053537a659e 100644 --- a/app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php @@ -31,18 +31,27 @@ class SaveHandler private $linkResource; /** + * @var linkTypeProvider + */ + private $linkTypeProvider; + + /** + * SaveHandler constructor. * @param MetadataPool $metadataPool * @param Link $linkResource * @param ProductLinkRepositoryInterface $productLinkRepository + * @param \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider */ public function __construct( MetadataPool $metadataPool, Link $linkResource, - ProductLinkRepositoryInterface $productLinkRepository + ProductLinkRepositoryInterface $productLinkRepository, + \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider ) { $this->metadataPool = $metadataPool; $this->linkResource = $linkResource; $this->productLinkRepository = $productLinkRepository; + $this->linkTypeProvider = $linkTypeProvider; } /** @@ -55,12 +64,30 @@ public function execute($entityType, $entity) { $link = $entity->getData($this->metadataPool->getMetadata($entityType)->getLinkField()); if ($this->linkResource->hasProductLinks($link)) { - /** @var \Magento\Catalog\Api\Data\ProductInterface $entity*/ + /** @var \Magento\Catalog\Api\Data\ProductInterface $entity */ foreach ($this->productLinkRepository->getList($entity) as $link) { $this->productLinkRepository->delete($link); } } - $productLinks = $entity->getProductLinks(); + + // Build links per type + $linksByType = []; + foreach ($entity->getProductLinks() as $link) { + $linksByType[$link->getLinkType()][] = $link; + } + + // Set array position as a fallback position if necessary + foreach ($linksByType as $linkType => $links) { + if (!$this->hasPosition($links)) { + array_walk($linksByType[$linkType], function ($productLink, $position) { + $productLink->setPosition(++$position); + }); + } + } + + // Flatten multi-dimensional linksByType in ProductLinks + $productLinks = array_reduce($linksByType, 'array_merge', []); + if (count($productLinks) > 0) { foreach ($entity->getProductLinks() as $link) { $this->productLinkRepository->save($link); @@ -68,4 +95,19 @@ public function execute($entityType, $entity) } return $entity; } + + /** + * Check if at least one link without position + * @param array $links + * @return bool + */ + private function hasPosition(array $links) + { + foreach ($links as $link) { + if (!array_key_exists('position', $link->getData())) { + return false; + } + } + return true; + } } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Value.php b/app/code/Magento/Catalog/Model/Product/Option/Value.php index d92646769b13b..10aae63ed349c 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Value.php @@ -179,7 +179,7 @@ public function setProduct($product) */ public function getProduct() { - if (is_null($this->_product)) { + if ($this->_product === null) { $this->_product = $this->getOption()->getProduct(); } return $this->_product; diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index 83feea903f993..f260b01c02ef4 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -6,10 +6,8 @@ */ namespace Magento\Catalog\Model; -use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product\Gallery\MimeTypeExtensionMap; use Magento\Catalog\Model\ResourceModel\Product\Collection; -use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\Data\ImageContentInterfaceFactory; use Magento\Framework\Api\ImageContentValidatorInterface; use Magento\Framework\Api\ImageProcessorInterface; @@ -18,10 +16,8 @@ use Magento\Framework\DB\Adapter\DeadlockException; use Magento\Framework\DB\Adapter\LockWaitException; use Magento\Framework\Exception\CouldNotSaveException; -use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\Exception\StateException; use Magento\Framework\Exception\ValidatorException; /** @@ -116,11 +112,15 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa protected $fileSystem; /** + * @deprecated + * @see \Magento\Catalog\Model\MediaGalleryProcessor * @var ImageContentInterfaceFactory */ protected $contentFactory; /** + * @deprecated + * @see \Magento\Catalog\Model\MediaGalleryProcessor * @var ImageProcessorInterface */ protected $imageProcessor; @@ -131,7 +131,7 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa protected $extensionAttributesJoinProcessor; /** - * @var \Magento\Catalog\Model\Product\Gallery\Processor + * @var ProductRepository\MediaGalleryProcessor */ protected $mediaGalleryProcessor; @@ -329,6 +329,9 @@ protected function initializeProductData(array $productData, $createNew) unset($productData['media_gallery']); if ($createNew) { $product = $this->productFactory->create(); + if (isset($productData['price']) && !isset($productData['product_type'])) { + $product->setTypeId(Product\Type::TYPE_SIMPLE); + } if ($this->storeManager->hasSingleStore()) { $product->setWebsiteIds([$this->storeManager->getStore(true)->getWebsiteId()]); } @@ -375,53 +378,6 @@ private function assignProductToWebsites(\Magento\Catalog\Model\Product $product $product->setWebsiteIds($websiteIds); } - /** - * @param ProductInterface $product - * @param array $newEntry - * @return $this - * @throws InputException - * @throws StateException - * @throws \Magento\Framework\Exception\LocalizedException - */ - protected function processNewMediaGalleryEntry( - ProductInterface $product, - array $newEntry - ) { - /** @var ImageContentInterface $contentDataObject */ - $contentDataObject = $newEntry['content']; - - /** @var \Magento\Catalog\Model\Product\Media\Config $mediaConfig */ - $mediaConfig = $product->getMediaConfig(); - $mediaTmpPath = $mediaConfig->getBaseTmpMediaPath(); - - $relativeFilePath = $this->imageProcessor->processImageContent($mediaTmpPath, $contentDataObject); - $tmpFilePath = $mediaConfig->getTmpMediaShortUrl($relativeFilePath); - - if (!$product->hasGalleryAttribute()) { - throw new StateException(__('Requested product does not support images.')); - } - - $imageFileUri = $this->getMediaGalleryProcessor()->addImage( - $product, - $tmpFilePath, - isset($newEntry['types']) ? $newEntry['types'] : [], - true, - isset($newEntry['disabled']) ? $newEntry['disabled'] : true - ); - // Update additional fields that are still empty after addImage call - $this->getMediaGalleryProcessor()->updateImage( - $product, - $imageFileUri, - [ - 'label' => $newEntry['label'], - 'position' => $newEntry['position'], - 'disabled' => $newEntry['disabled'], - 'media_type' => $newEntry['media_type'], - ] - ); - return $this; - } - /** * Process product links, creating new links, updating and deleting existing links * @@ -480,67 +436,6 @@ private function processLinks(\Magento\Catalog\Api\Data\ProductInterface $produc return $this; } - /** - * Process Media gallery data before save product. - * - * Compare Media Gallery Entries Data with existing Media Gallery - * * If Media entry has not value_id set it as new - * * If Existing entry 'value_id' absent in Media Gallery set 'removed' flag - * * Merge Existing and new media gallery - * - * @param ProductInterface $product contains only existing media gallery items - * @param array $mediaGalleryEntries array which contains all media gallery items - * @return $this - * @throws InputException - * @throws StateException - * @throws LocalizedException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - protected function processMediaGallery(ProductInterface $product, $mediaGalleryEntries) - { - $existingMediaGallery = $product->getMediaGallery('images'); - $newEntries = []; - $entriesById = []; - if (!empty($existingMediaGallery)) { - foreach ($mediaGalleryEntries as $entry) { - if (isset($entry['id'])) { - $entriesById[$entry['id']] = $entry; - } else { - $newEntries[] = $entry; - } - } - foreach ($existingMediaGallery as $key => &$existingEntry) { - if (isset($entriesById[$existingEntry['value_id']])) { - $updatedEntry = $entriesById[$existingEntry['value_id']]; - if (array_key_exists('file', $updatedEntry) && $updatedEntry['file'] === null) { - unset($updatedEntry['file']); - } - $existingMediaGallery[$key] = array_merge($existingEntry, $updatedEntry); - } else { - //set the removed flag - $existingEntry['removed'] = true; - } - } - unset($existingEntry); - $product->setData('media_gallery', ["images" => $existingMediaGallery]); - } else { - $newEntries = $mediaGalleryEntries; - } - - $this->getMediaGalleryProcessor()->clearMediaAttribute($product, array_keys($product->getMediaAttributes())); - $images = $product->getMediaGallery('images'); - if ($images) { - foreach ($images as $image) { - if (!isset($image['removed']) && !empty($image['types'])) { - $this->getMediaGalleryProcessor()->setMediaAttribute($product, $image['types'], $image['file']); - } - } - } - $this->processEntries($product, $newEntries, $entriesById); - - return $this; - } - /** * {@inheritdoc} * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -577,7 +472,10 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO $this->processLinks($product, $productLinks); if (isset($productDataArray['media_gallery_entries'])) { - $this->processMediaGallery($product, $productDataArray['media_gallery_entries']); + $this->getMediaGalleryProcessor()->processMediaGallery( + $product, + $productDataArray['media_gallery_entries'] + ); } if (!$product->getOptionsReadonly()) { @@ -749,13 +647,13 @@ public function cleanCache() } /** - * @return Product\Gallery\Processor + * @return ProductRepository\MediaGalleryProcessor */ private function getMediaGalleryProcessor() { if (null === $this->mediaGalleryProcessor) { $this->mediaGalleryProcessor = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Catalog\Model\Product\Gallery\Processor::class); + ->get(ProductRepository\MediaGalleryProcessor::class); } return $this->mediaGalleryProcessor; } @@ -775,60 +673,4 @@ private function getCollectionProcessor() } return $this->collectionProcessor; } - - /** - * Convert extension attribute for product media gallery. - * - * @param array $newEntry - * @param array $extensionAttributes - * @return void - */ - private function processExtensionAttributes(array &$newEntry, array $extensionAttributes) - { - foreach ($extensionAttributes as $code => $value) { - if (is_array($value)) { - $this->processExtensionAttributes($newEntry, $value); - } else { - $newEntry[$code] = $value; - } - } - unset($newEntry['extension_attributes']); - } - - /** - * Convert entries into product media gallery data and set to product. - * - * @param ProductInterface $product - * @param array $newEntries - * @param array $entriesById - * @throws InputException - * @throws LocalizedException - * @throws StateException - * @return void - */ - private function processEntries(ProductInterface $product, array $newEntries, array $entriesById) - { - foreach ($newEntries as $newEntry) { - if (!isset($newEntry['content'])) { - throw new InputException(__('The image content is not valid.')); - } - /** @var ImageContentInterface $contentDataObject */ - $contentDataObject = $this->contentFactory->create() - ->setName($newEntry['content'][ImageContentInterface::NAME]) - ->setBase64EncodedData($newEntry['content'][ImageContentInterface::BASE64_ENCODED_DATA]) - ->setType($newEntry['content'][ImageContentInterface::TYPE]); - $newEntry['content'] = $contentDataObject; - $this->processNewMediaGalleryEntry($product, $newEntry); - - $finalGallery = $product->getData('media_gallery'); - $newEntryId = key(array_diff_key($product->getData('media_gallery')['images'], $entriesById)); - if (isset($newEntry['extension_attributes'])) { - $this->processExtensionAttributes($newEntry, $newEntry['extension_attributes']); - } - $newEntry = array_replace_recursive($newEntry, $finalGallery['images'][$newEntryId]); - $entriesById[$newEntryId] = $newEntry; - $finalGallery['images'][$newEntryId] = $newEntry; - $product->setData('media_gallery', $finalGallery); - } - } } diff --git a/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php new file mode 100644 index 0000000000000..4cc31d98fdfc2 --- /dev/null +++ b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php @@ -0,0 +1,218 @@ +processor = $processor; + $this->contentFactory = $contentFactory; + $this->imageProcessor = $imageProcessor; + } + + /** + * Process Media gallery data before save product. + * + * Compare Media Gallery Entries Data with existing Media Gallery + * * If Media entry has not value_id set it as new + * * If Existing entry 'value_id' absent in Media Gallery set 'removed' flag + * * Merge Existing and new media gallery + * + * @param ProductInterface $product contains only existing media gallery items. + * @param array $mediaGalleryEntries array which contains all media gallery items. + * @return void + * @throws InputException + * @throws StateException + * @throws LocalizedException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function processMediaGallery(ProductInterface $product, array $mediaGalleryEntries) + { + $existingMediaGallery = $product->getMediaGallery('images'); + $newEntries = []; + $entriesById = []; + if (!empty($existingMediaGallery)) { + foreach ($mediaGalleryEntries as $entry) { + if (isset($entry['id'])) { + $entriesById[$entry['id']] = $entry; + } else { + $newEntries[] = $entry; + } + } + foreach ($existingMediaGallery as $key => &$existingEntry) { + if (isset($entriesById[$existingEntry['value_id']])) { + $updatedEntry = $entriesById[$existingEntry['value_id']]; + if (array_key_exists('file', $updatedEntry) && $updatedEntry['file'] === null) { + unset($updatedEntry['file']); + } + $existingMediaGallery[$key] = array_merge($existingEntry, $updatedEntry); + } else { + //set the removed flag. + $existingEntry['removed'] = true; + } + } + unset($existingEntry); + $product->setData('media_gallery', ["images" => $existingMediaGallery]); + } else { + $newEntries = $mediaGalleryEntries; + } + + $this->processor->clearMediaAttribute($product, array_keys($product->getMediaAttributes())); + $images = $product->getMediaGallery('images'); + if ($images) { + foreach ($images as $image) { + if (!isset($image['removed']) && !empty($image['types'])) { + $this->processor->setMediaAttribute($product, $image['types'], $image['file']); + } + } + } + $this->processEntries($product, $newEntries, $entriesById); + } + + /** + * Convert entries into product media gallery data and set to product. + * + * @param ProductInterface $product + * @param array $newEntries + * @param array $entriesById + * @throws InputException + * @throws LocalizedException + * @throws StateException + * @return void + */ + private function processEntries(ProductInterface $product, array $newEntries, array $entriesById) + { + foreach ($newEntries as $newEntry) { + if (!isset($newEntry['content'])) { + throw new InputException(__('The image content is not valid.')); + } + /** @var ImageContentInterface $contentDataObject */ + $contentDataObject = $this->contentFactory->create() + ->setName($newEntry['content'][ImageContentInterface::NAME]) + ->setBase64EncodedData($newEntry['content'][ImageContentInterface::BASE64_ENCODED_DATA]) + ->setType($newEntry['content'][ImageContentInterface::TYPE]); + $newEntry['content'] = $contentDataObject; + $this->processNewMediaGalleryEntry($product, $newEntry); + + $finalGallery = $product->getData('media_gallery'); + $newEntryId = key(array_diff_key($product->getData('media_gallery')['images'], $entriesById)); + if (isset($newEntry['extension_attributes'])) { + $this->processExtensionAttributes($newEntry, $newEntry['extension_attributes']); + } + $newEntry = array_replace_recursive($newEntry, $finalGallery['images'][$newEntryId]); + $entriesById[$newEntryId] = $newEntry; + $finalGallery['images'][$newEntryId] = $newEntry; + $product->setData('media_gallery', $finalGallery); + } + } + + /** + * Save gallery entry as image. + * + * @param ProductInterface $product + * @param array $newEntry + * @return void + * @throws InputException + * @throws StateException + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function processNewMediaGalleryEntry( + ProductInterface $product, + array $newEntry + ) { + /** @var ImageContentInterface $contentDataObject */ + $contentDataObject = $newEntry['content']; + + /** @var \Magento\Catalog\Model\Product\Media\Config $mediaConfig */ + $mediaConfig = $product->getMediaConfig(); + $mediaTmpPath = $mediaConfig->getBaseTmpMediaPath(); + + $relativeFilePath = $this->imageProcessor->processImageContent($mediaTmpPath, $contentDataObject); + $tmpFilePath = $mediaConfig->getTmpMediaShortUrl($relativeFilePath); + + if (!$product->hasGalleryAttribute()) { + throw new StateException(__('Requested product does not support images.')); + } + + $imageFileUri = $this->processor->addImage( + $product, + $tmpFilePath, + isset($newEntry['types']) ? $newEntry['types'] : [], + true, + isset($newEntry['disabled']) ? $newEntry['disabled'] : true + ); + // Update additional fields that are still empty after addImage call. + $this->processor->updateImage( + $product, + $imageFileUri, + [ + 'label' => $newEntry['label'], + 'position' => $newEntry['position'], + 'disabled' => $newEntry['disabled'], + 'media_type' => $newEntry['media_type'], + ] + ); + } + + /** + * Convert extension attribute for product media gallery. + * + * @param array $newEntry + * @param array $extensionAttributes + * @return void + */ + private function processExtensionAttributes(array &$newEntry, array $extensionAttributes) + { + foreach ($extensionAttributes as $code => $value) { + if (is_array($value)) { + $this->processExtensionAttributes($newEntry, $value); + } else { + $newEntry[$code] = $value; + } + } + unset($newEntry['extension_attributes']); + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 58e8424663c83..cb39273d3553e 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -1092,7 +1092,7 @@ public function getSelectCountSql() protected function _getSelectCountSql($select = null, $resetLeftJoins = true) { $this->_renderFilters(); - $countSelect = is_null($select) ? $this->_getClearSelect() : $this->_buildClearSelect($select); + $countSelect = $select === null ? $this->_getClearSelect() : $this->_buildClearSelect($select); $countSelect->columns('COUNT(DISTINCT e.entity_id)'); if ($resetLeftJoins) { $countSelect->resetJoinLeft(); @@ -1435,7 +1435,7 @@ public function getAllIdsCache($resetCache = false) $ids = $this->_allIdsCache; } - if (is_null($ids)) { + if ($ids === null) { $ids = $this->getAllIds(); $this->setAllIdsCache($ids); } @@ -1466,17 +1466,17 @@ public function addPriceData($customerGroupId = null, $websiteId = null) { $this->_productLimitationFilters->setUsePriceIndex(true); - if (!isset($this->_productLimitationFilters['customer_group_id']) && is_null($customerGroupId)) { + if (!isset($this->_productLimitationFilters['customer_group_id']) && $customerGroupId === null) { $customerGroupId = $this->_customerSession->getCustomerGroupId(); } - if (!isset($this->_productLimitationFilters['website_id']) && is_null($websiteId)) { + if (!isset($this->_productLimitationFilters['website_id']) && $websiteId === null) { $websiteId = $this->_storeManager->getStore($this->getStoreId())->getWebsiteId(); } - if (!is_null($customerGroupId)) { + if ($customerGroupId !== null) { $this->_productLimitationFilters['customer_group_id'] = $customerGroupId; } - if (!is_null($websiteId)) { + if ($websiteId !== null) { $this->_productLimitationFilters['website_id'] = $websiteId; } @@ -1670,7 +1670,7 @@ public function addAttributeToSort($attribute, $dir = self::SORT_ORDER_ASC) return $this; } elseif ($attribute == 'is_saleable') { - $this->getSelect()->order("is_saleable " . $dir); + $this->getSelect()->order("is_salable " . $dir); return $this; } @@ -2347,7 +2347,7 @@ public function setOrder($attribute, $dir = Select::SQL_DESC) */ public function getMaxPrice() { - if (is_null($this->_maxPrice)) { + if ($this->_maxPrice === null) { $this->_prepareStatisticsData(); } @@ -2361,7 +2361,7 @@ public function getMaxPrice() */ public function getMinPrice() { - if (is_null($this->_minPrice)) { + if ($this->_minPrice === null) { $this->_prepareStatisticsData(); } @@ -2375,7 +2375,7 @@ public function getMinPrice() */ public function getPriceStandardDeviation() { - if (is_null($this->_priceStandardDeviation)) { + if ($this->_priceStandardDeviation === null) { $this->_prepareStatisticsData(); } @@ -2389,7 +2389,7 @@ public function getPriceStandardDeviation() */ public function getPricesCount() { - if (is_null($this->_pricesCount)) { + if ($this->_pricesCount === null) { $this->_prepareStatisticsData(); } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php index b4f7e43387d0e..ee1df8f23424d 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php @@ -87,6 +87,7 @@ public function build($productId) ->where('t.website_id = ?', $this->storeManager->getStore()->getWebsiteId()) ->where('t.customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->order('t.min_price ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $priceSelect = $this->baseSelectProcessor->process($priceSelect); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php index f018e2b148f15..8841b6059c46f 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php @@ -95,6 +95,7 @@ public function build($productId) ->where('t.attribute_id = ?', $priceAttribute->getAttributeId()) ->where('t.value IS NOT NULL') ->order('t.value ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $priceSelect = $this->baseSelectProcessor->process($priceSelect); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php index b4459cd1eea07..5c47185a85bf4 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php @@ -139,6 +139,7 @@ public function build($productId) 'special_to.value IS NULL OR ' . $connection->getDatePartSql('special_to.value') .' >= ?', $currentDate )->order('t.value ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $specialPrice = $this->baseSelectProcessor->process($specialPrice); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php index 79323e57b033e..37281193d6a1b 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php @@ -97,6 +97,7 @@ public function build($productId) ->where('t.all_groups = 1 OR customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->where('t.qty = ?', 1) ->order('t.value ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $priceSelect = $this->baseSelectProcessor->process($priceSelect); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php index f918692cb2753..627aa1848506e 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php @@ -5,12 +5,10 @@ */ namespace Magento\Catalog\Test\Unit\Model\Product; -use Magento\Catalog\Model\View\Asset\Image\ContextFactory; use Magento\Catalog\Model\View\Asset\ImageFactory; use Magento\Catalog\Model\View\Asset\PlaceholderFactory; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\View\Asset\ContextInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -73,10 +71,24 @@ class ImageTest extends \PHPUnit\Framework\TestCase */ private $viewAssetPlaceholderFactory; + /** + * @var \Magento\Framework\Serialize\SerializerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $serializer; + + /** + * @var \Magento\Framework\App\CacheInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $cacheManager; + protected function setUp() { $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->context = $this->createMock(\Magento\Framework\Model\Context::class); + $this->cacheManager = $this->getMockBuilder(\Magento\Framework\App\CacheInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->context->expects($this->any())->method('getCacheManager')->will($this->returnValue($this->cacheManager)); $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManager::class) ->disableOriginalConstructor() @@ -112,17 +124,36 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); + $this->serializer = $this->getMockBuilder( + \Magento\Framework\Serialize\SerializerInterface::class + )->getMockForAbstractClass(); + $this->serializer->expects($this->any()) + ->method('serialize') + ->willReturnCallback( + function ($value) { + return json_encode($value); + } + ); + $this->serializer->expects($this->any()) + ->method('unserialize') + ->willReturnCallback( + function ($value) { + return json_decode($value, true); + } + ); $this->image = $objectManager->getObject( \Magento\Catalog\Model\Product\Image::class, [ + 'context' => $this->context, 'storeManager' => $this->storeManager, 'catalogProductMediaConfig' => $this->config, 'coreFileStorageDatabase' => $this->coreFileHelper, 'filesystem' => $this->filesystem, 'imageFactory' => $this->factory, 'viewAssetImageFactory' => $this->viewAssetImageFactory, - 'viewAssetPlaceholderFactory' => $this->viewAssetPlaceholderFactory + 'viewAssetPlaceholderFactory' => $this->viewAssetPlaceholderFactory, + 'serializer' => $this->serializer ] ); @@ -354,12 +385,16 @@ public function testIsCached() $this->testSetGetBaseFile(); $absolutePath = dirname(dirname(__DIR__)) . '/_files/catalog/product/watermark/somefile.png'; $this->imageAsset->expects($this->any())->method('getPath')->willReturn($absolutePath); + $this->cacheManager->expects($this->once())->method('load')->willReturn( + json_encode(['size' => ['image data']]) + ); $this->assertTrue($this->image->isCached()); } public function testClearCache() { $this->coreFileHelper->expects($this->once())->method('deleteFolder')->will($this->returnValue(true)); + $this->cacheManager->expects($this->once())->method('clean'); $this->image->clearCache(); } @@ -383,4 +418,24 @@ public function testIsBaseFilePlaceholder() { $this->assertFalse($this->image->isBaseFilePlaceholder()); } + + public function testGetResizedImageInfoWithCache() + { + $absolutePath = dirname(dirname(__DIR__)) . '/_files/catalog/product/watermark/somefile.png'; + $this->imageAsset->expects($this->any())->method('getPath')->willReturn($absolutePath); + $this->cacheManager->expects($this->once())->method('load')->willReturn( + json_encode(['size' => ['image data']]) + ); + $this->cacheManager->expects($this->never())->method('save'); + $this->assertEquals(['image data'], $this->image->getResizedImageInfo()); + } + + public function testGetResizedImageInfoEmptyCache() + { + $absolutePath = dirname(dirname(__DIR__)) . '/_files/catalog/product/watermark/somefile.png'; + $this->imageAsset->expects($this->any())->method('getPath')->willReturn($absolutePath); + $this->cacheManager->expects($this->once())->method('load')->willReturn(false); + $this->cacheManager->expects($this->once())->method('save'); + $this->assertTrue(is_array($this->image->getResizedImageInfo())); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepository/MediaGalleryProcessorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepository/MediaGalleryProcessorTest.php new file mode 100644 index 0000000000000..02773b2fb3d70 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepository/MediaGalleryProcessorTest.php @@ -0,0 +1,227 @@ +product = $this->createPartialMock( + \Magento\Catalog\Model\Product::class, + [ + 'hasGalleryAttribute', + 'getMediaConfig', + 'getMediaAttributes', + 'getMediaGalleryEntries', + ] + ); + $this->product->expects($this->any()) + ->method('hasGalleryAttribute') + ->willReturn(true); + $this->processor = $this->getMockBuilder(Processor::class) + ->disableOriginalConstructor() + ->getMock(); + $this->contentFactory = $this->getMockBuilder(ImageContentInterfaceFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->imageProcessor = $this->getMockBuilder(ImageProcessorInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $objectManager = new ObjectManager($this); + $this->model = $objectManager->getObject( + MediaGalleryProcessor::class, + [ + 'processor' => $this->processor, + 'contentFactory' => $this->contentFactory, + 'imageProcessor' => $this->imageProcessor, + ] + ); + } + + /** + * Test add image. + * + * @return void + */ + public function testProcessWithNewMediaEntry() + { + $mediaGalleryEntries = [ + [ + 'value_id' => null, + 'label' => 'label_text', + 'position' => 10, + 'disabled' => false, + 'types' => ['image', 'small_image'], + 'content' => [ + ImageContentInterface::NAME => 'filename', + ImageContentInterface::TYPE => 'image/jpeg', + ImageContentInterface::BASE64_ENCODED_DATA => 'encoded_content', + ], + 'media_type' => 'media_type', + ], + ]; + + //setup media attribute backend. + $mediaTmpPath = '/tmp'; + $absolutePath = '/a/b/filename.jpg'; + $mediaConfigMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Media\Config::class) + ->disableOriginalConstructor() + ->getMock(); + $mediaConfigMock->expects($this->once()) + ->method('getTmpMediaShortUrl') + ->with($absolutePath) + ->willReturn($mediaTmpPath . $absolutePath); + $this->product->setData('media_gallery', ['images' => $mediaGalleryEntries]); + $this->product->expects($this->any()) + ->method('getMediaAttributes') + ->willReturn(['image' => 'imageAttribute', 'small_image' => 'small_image_attribute']); + $this->product->expects($this->once()) + ->method('getMediaConfig') + ->willReturn($mediaConfigMock); + $this->processor->expects($this->once())->method('clearMediaAttribute') + ->with($this->product, ['image', 'small_image']); + + //verify new entries. + $contentDataObject = $this->getMockBuilder(\Magento\Framework\Api\ImageContent::class) + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->contentFactory->expects($this->once()) + ->method('create') + ->willReturn($contentDataObject); + + $this->imageProcessor->expects($this->once()) + ->method('processImageContent') + ->willReturn($absolutePath); + + $imageFileUri = 'imageFileUri'; + $this->processor->expects($this->once())->method('addImage') + ->with($this->product, $mediaTmpPath . $absolutePath, ['image', 'small_image'], true, false) + ->willReturn($imageFileUri); + $this->processor->expects($this->once())->method('updateImage') + ->with( + $this->product, + $imageFileUri, + [ + 'label' => 'label_text', + 'position' => 10, + 'disabled' => false, + 'media_type' => 'media_type', + ] + ); + + $this->model->processMediaGallery($this->product, $mediaGalleryEntries); + } + + /** + * Test update(delete) images. + */ + public function testProcessExistingWithMediaGalleryEntries() + { + //update one entry, delete one entry. + $newEntries = [ + [ + 'id' => 5, + 'label' => 'new_label_text', + 'file' => 'filename1', + 'position' => 10, + 'disabled' => false, + 'types' => ['image', 'small_image'], + ], + ]; + + $existingMediaGallery = [ + 'images' => [ + [ + 'value_id' => 5, + 'label' => 'label_text', + 'file' => 'filename1', + 'position' => 10, + 'disabled' => true, + ], + [ + 'value_id' => 6, //will be deleted. + 'file' => 'filename2', + ], + ], + ]; + + $expectedResult = [ + [ + 'value_id' => 5, + 'id' => 5, + 'label' => 'new_label_text', + 'file' => 'filename1', + 'position' => 10, + 'disabled' => false, + 'types' => ['image', 'small_image'], + ], + [ + 'value_id' => 6, //will be deleted. + 'file' => 'filename2', + 'removed' => true, + ], + ]; + + $this->product->setData('media_gallery', $existingMediaGallery); + $this->product->expects($this->any()) + ->method('getMediaAttributes') + ->willReturn(['image' => 'filename1', 'small_image' => 'filename2']); + + $this->processor->expects($this->once())->method('clearMediaAttribute') + ->with($this->product, ['image', 'small_image']); + $this->processor->expects($this->once()) + ->method('setMediaAttribute') + ->with($this->product, ['image', 'small_image'], 'filename1'); + $this->model->processMediaGallery($this->product, $newEntries); + $this->assertEquals($expectedResult, $this->product->getMediaGallery('images')); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php index a220b9a5768fe..14c84f4781a3a 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php @@ -9,6 +9,7 @@ namespace Magento\Catalog\Test\Unit\Model; +use Magento\Catalog\Model\ProductRepository\MediaGalleryProcessor; use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; use Magento\Framework\DB\Adapter\ConnectionException; @@ -139,7 +140,7 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase protected $storeManagerMock; /** - * @var \Magento\Catalog\Model\Product\Gallery\Processor|\PHPUnit_Framework_MockObject_MockObject + * @var MediaGalleryProcessor|\PHPUnit_Framework_MockObject_MockObject */ protected $mediaGalleryProcessor; @@ -234,7 +235,7 @@ protected function setUp() $storeMock->expects($this->any())->method('getCode')->willReturn(\Magento\Store\Model\Store::ADMIN_CODE); $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock); - $this->mediaGalleryProcessor = $this->createMock(\Magento\Catalog\Model\Product\Gallery\Processor::class); + $this->mediaGalleryProcessor = $this->createMock(MediaGalleryProcessor::class); $this->collectionProcessorMock = $this->getMockBuilder(CollectionProcessorInterface::class) ->getMock(); @@ -1174,7 +1175,21 @@ public function testSaveExistingWithNewMediaGalleryEntries() ] ] ]; - + $expectedEntriesData = [ + [ + 'id' => null, + 'label' => "label_text", + 'position' => 10, + 'disabled' => false, + 'types' => ['image', 'small_image'], + 'content' => [ + ImageContentInterface::NAME => 'filename', + ImageContentInterface::TYPE => 'image/jpeg', + ImageContentInterface::BASE64_ENCODED_DATA => 'encoded_content', + ], + 'media_type' => 'media_type', + ], + ]; $this->setupProductMocksForSave(); //media gallery data $this->productData['media_gallery_entries'] = [ @@ -1198,56 +1213,8 @@ public function testSaveExistingWithNewMediaGalleryEntries() ->will($this->returnValue($this->productData)); $this->initializedProductMock->setData('media_gallery', $newEntriesData); - $this->initializedProductMock->expects($this->any()) - ->method('getMediaAttributes') - ->willReturn(["image" => "imageAttribute", "small_image" => "small_image_attribute"]); - - //setup media attribute backend - $mediaTmpPath = '/tmp'; - $absolutePath = '/a/b/filename.jpg'; - - $this->mediaGalleryProcessor->expects($this->once())->method('clearMediaAttribute') - ->with($this->initializedProductMock, ['image', 'small_image']); - - $mediaConfigMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Media\Config::class) - ->disableOriginalConstructor() - ->getMock(); - $mediaConfigMock->expects($this->once()) - ->method('getTmpMediaShortUrl') - ->with($absolutePath) - ->willReturn($mediaTmpPath . $absolutePath); - $this->initializedProductMock->expects($this->once()) - ->method('getMediaConfig') - ->willReturn($mediaConfigMock); - - //verify new entries - $contentDataObject = $this->getMockBuilder(\Magento\Framework\Api\ImageContent::class) - ->disableOriginalConstructor() - ->setMethods(null) - ->getMock(); - $this->contentFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($contentDataObject); - - $this->imageProcessorMock->expects($this->once()) - ->method('processImageContent') - ->willReturn($absolutePath); - - $imageFileUri = "imageFileUri"; - $this->mediaGalleryProcessor->expects($this->once())->method('addImage') - ->with($this->initializedProductMock, $mediaTmpPath . $absolutePath, ['image', 'small_image'], true, false) - ->willReturn($imageFileUri); - $this->mediaGalleryProcessor->expects($this->once())->method('updateImage') - ->with( - $this->initializedProductMock, - $imageFileUri, - [ - 'label' => 'label_text', - 'position' => 10, - 'disabled' => false, - 'media_type' => 'media_type', - ] - ); + $this->mediaGalleryProcessor->expects($this->once())->method('processMediaGallery') + ->with($this->initializedProductMock, $expectedEntriesData); $this->initializedProductMock->expects($this->once())->method('getWebsiteIds')->willReturn([]); $this->initializedProductMock->expects($this->atLeastOnce()) ->method('getSku')->willReturn($this->productData['sku']); @@ -1325,24 +1292,6 @@ public function testSaveExistingWithMediaGalleryEntries() ], ], ]; - - $expectedResult = [ - [ - 'value_id' => 5, - 'id' => 5, - "label" => "new_label_text", - 'file' => 'filename1', - 'position' => 10, - 'disabled' => false, - 'types' => ['image', 'small_image'], - ], - [ - 'value_id' => 6, //will be deleted - 'file' => 'filename2', - 'removed' => true, - ], - ]; - $this->setupProductMocksForSave(); //media gallery data $this->productData['media_gallery_entries'] = $newEntries; @@ -1352,21 +1301,15 @@ public function testSaveExistingWithMediaGalleryEntries() ->will($this->returnValue($this->productData)); $this->initializedProductMock->setData('media_gallery', $existingMediaGallery); - $this->initializedProductMock->expects($this->any()) - ->method('getMediaAttributes') - ->willReturn(["image" => "filename1", "small_image" => "filename2"]); - $this->mediaGalleryProcessor->expects($this->once())->method('clearMediaAttribute') - ->with($this->initializedProductMock, ['image', 'small_image']); $this->mediaGalleryProcessor->expects($this->once()) - ->method('setMediaAttribute') - ->with($this->initializedProductMock, ['image', 'small_image'], 'filename1'); + ->method('processMediaGallery') + ->with($this->initializedProductMock, $newEntries); $this->initializedProductMock->expects($this->once())->method('getWebsiteIds')->willReturn([]); $this->initializedProductMock->expects($this->atLeastOnce()) ->method('getSku')->willReturn($this->productData['sku']); $this->productMock->expects($this->atLeastOnce())->method('getSku')->willReturn($this->productData['sku']); $this->productMock->expects($this->any())->method('getMediaGalleryEntries')->willReturn(null); $this->model->save($this->productMock); - $this->assertEquals($expectedResult, $this->initializedProductMock->getMediaGallery('images')); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php index 6b908d317aa5b..cec862ee9661f 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php @@ -84,7 +84,7 @@ public function testBuild() $select->expects($this->any())->method('from')->willReturnSelf(); $select->expects($this->any())->method('joinInner')->willReturnSelf(); $select->expects($this->any())->method('where')->willReturnSelf(); - $select->expects($this->once())->method('order')->willReturnSelf(); + $select->expects($this->exactly(2))->method('order')->willReturnSelf(); $select->expects($this->once())->method('limit')->willReturnSelf(); $this->resourceMock->expects($this->any())->method('getConnection')->willReturn($connection); $this->metadataPoolMock->expects($this->once())->method('getMetadata')->willReturn($metadata); diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml index fc0967ca60d2d..862375503691c 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml @@ -146,8 +146,8 @@ switch ($type = $block->getType()) { } break; - case 'other': - break; + default: + $exist = null; } ?> diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php index 45530ed6d7bae..6a14eb8b7a817 100644 --- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php @@ -1346,6 +1346,12 @@ protected function optionRowToCellString($option) } /** + * Collect custom options data for products that will be exported. + * + * Option name and type will be collected for all store views, all other data (which can't be changed on store view + * level will be collected for DEFAULT_STORE_ID only. + * Store view specified data will be saved to the additional store view row. + * * @param int[] $productIds * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -1356,13 +1362,12 @@ protected function getCustomOptionsData($productIds) $customOptionsData = []; foreach (array_keys($this->_storeIdToCode) as $storeId) { - if (Store::DEFAULT_STORE_ID != $storeId) { - continue; - } $options = $this->_optionColFactory->create(); /* @var \Magento\Catalog\Model\ResourceModel\Product\Option\Collection $options*/ - $options->addOrder('sort_order'); - $options->reset()->addOrder('sort_order')->addTitleToResult( + $options->reset()->addOrder( + 'sort_order', + \Magento\Catalog\Model\ResourceModel\Product\Option\Collection::SORT_ORDER_ASC + )->addTitleToResult( $storeId )->addPriceToResult( $storeId @@ -1375,34 +1380,36 @@ protected function getCustomOptionsData($productIds) foreach ($options as $option) { $row = []; $productId = $option['product_id']; - $row['name'] = $option['title']; $row['type'] = $option['type']; - $row['required'] = $option['is_require']; - $row['price'] = $option['price']; - $row['price_type'] = ($option['price_type'] == 'percent') ? $option['price_type'] : 'fixed'; - $row['sku'] = $option['sku']; - if ($option['max_characters']) { - $row['max_characters'] = $option['max_characters']; - } - - foreach (['file_extension', 'image_size_x', 'image_size_y'] as $fileOptionKey) { - if (!isset($option[$fileOptionKey])) { - continue; + if (Store::DEFAULT_STORE_ID === $storeId) { + $row['required'] = $option['is_require']; + $row['price'] = $option['price']; + $row['price_type'] = ($option['price_type'] === 'percent') ? 'percent' : 'fixed'; + $row['sku'] = $option['sku']; + if ($option['max_characters']) { + $row['max_characters'] = $option['max_characters']; } - $row[$fileOptionKey] = $option[$fileOptionKey]; - } + foreach (['file_extension', 'image_size_x', 'image_size_y'] as $fileOptionKey) { + if (!isset($option[$fileOptionKey])) { + continue; + } + $row[$fileOptionKey] = $option[$fileOptionKey]; + } + } $values = $option->getValues(); if ($values) { foreach ($values as $value) { - $valuePriceType = ($value['price_type'] == 'percent') ? $value['price_type'] : 'fixed'; $row['option_title'] = $value['title']; - $row['price'] = $value['price']; - $row['price_type'] = $valuePriceType; - $row['sku'] = $value['sku']; + if (Store::DEFAULT_STORE_ID === $storeId) { + $row['option_title'] = $value['title']; + $row['price'] = $value['price']; + $row['price_type'] = ($value['price_type'] === 'percent') ? 'percent' : 'fixed'; + $row['sku'] = $value['sku']; + } $customOptionsData[$productId][$storeId][] = $this->optionRowToCellString($row); } } else { diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php index aa3f46a433a4d..be027405e6d1c 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php @@ -14,6 +14,7 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\ResourceModel\Product\Option\Value\Collection as ProductOptionValueCollection; use Magento\Catalog\Model\ResourceModel\Product\Option\Value\CollectionFactory as ProductOptionValueCollectionFactory; +use Magento\Store\Model\Store; /** * Entity class which provide possibility to import product custom options @@ -23,6 +24,8 @@ * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) * @since 100.0.2 */ class Option extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity @@ -761,7 +764,7 @@ protected function _findExistingOptionId(array $newOptionData, array $newOptionT ksort($newOptionTitles); $existingOptions = $this->_oldCustomOptions[$productId]; foreach ($existingOptions as $optionId => $optionData) { - if ($optionData['type'] == $newOptionData['type'] && $optionData['titles'] == $newOptionTitles) { + if ($optionData['type'] == $newOptionData['type'] && $optionData['titles'][0] == $newOptionTitles[0]) { return $optionId; } } @@ -1124,13 +1127,19 @@ private function processOptionRow($name, $optionRow) { $result = [ self::COLUMN_TYPE => $name ? $optionRow['type'] : '', - self::COLUMN_IS_REQUIRED => $optionRow['required'], - self::COLUMN_ROW_SKU => $optionRow['sku'], - self::COLUMN_PREFIX . 'sku' => $optionRow['sku'], self::COLUMN_ROW_TITLE => '', self::COLUMN_ROW_PRICE => '' ]; - + if (isset($optionRow['_custom_option_store'])) { + $result[self::COLUMN_STORE] = $optionRow['_custom_option_store']; + } + if (isset($optionRow['required'])) { + $result[self::COLUMN_IS_REQUIRED] = $optionRow['required']; + } + if (isset($optionRow['sku'])) { + $result[self::COLUMN_ROW_SKU] = $optionRow['sku']; + $result[self::COLUMN_PREFIX . 'sku'] = $optionRow['sku']; + } if (isset($optionRow['option_title'])) { $result[self::COLUMN_ROW_TITLE] = $optionRow['option_title']; } @@ -1175,7 +1184,8 @@ private function addFileOptions($result, $optionRow) } /** - * Import data rows + * Import data rows. + * Additional store view data (option titles) will be sought in store view specified import file rows * * @return boolean * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -1189,7 +1199,8 @@ protected function _importData() $this->_tables['catalog_product_option_type_value'] ); $prevOptionId = 0; - + $optionId = null; + $valueId = null; while ($bunch = $this->_dataSourceModel->getNextBunch()) { $products = []; $options = []; @@ -1202,11 +1213,14 @@ protected function _importData() $childCount = []; foreach ($bunch as $rowNumber => $rowData) { - + if (isset($optionId, $valueId) && empty($rowData[PRODUCT::COL_STORE_VIEW_CODE])) { + $nextOptionId = $optionId; + $nextValueId = $valueId; + } + $optionId = $nextOptionId; + $valueId = $nextValueId; $multiRowData = $this->_getMultiRowFormat($rowData); - foreach ($multiRowData as $optionData) { - $combinedData = array_merge($rowData, $optionData); if (!$this->isRowAllowedToImport($combinedData, $rowNumber)) { @@ -1218,7 +1232,7 @@ protected function _importData() $optionData = $this->_collectOptionMainData( $combinedData, $prevOptionId, - $nextOptionId, + $optionId, $products, $prices ); @@ -1228,7 +1242,7 @@ protected function _importData() $this->_collectOptionTypeData( $combinedData, $prevOptionId, - $nextValueId, + $valueId, $typeValues, $typePrices, $typeTitles, @@ -1311,15 +1325,12 @@ protected function _collectOptionMainData( $optionData = null; if ($this->_rowIsMain) { - $optionData = $this->_getOptionData($rowData, $this->_rowProductId, $nextOptionId, $this->_rowType); - - if (!$this->_isRowHasSpecificType( - $this->_rowType - ) && ($priceData = $this->_getPriceData( - $rowData, - $nextOptionId, - $this->_rowType - )) + $optionData = empty($rowData[Product::COL_STORE_VIEW_CODE]) + ? $this->_getOptionData($rowData, $this->_rowProductId, $nextOptionId, $this->_rowType) + : ''; + + if (!$this->_isRowHasSpecificType($this->_rowType) + && ($priceData = $this->_getPriceData($rowData, $nextOptionId, $this->_rowType)) ) { $prices[$nextOptionId] = $priceData; } @@ -1347,6 +1358,7 @@ protected function _collectOptionMainData( * @param array &$childCount * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _collectOptionTypeData( array $rowData, @@ -1365,39 +1377,27 @@ protected function _collectOptionTypeData( $typeValues[$prevOptionId][] = $specificTypeData['value']; // ensure default title is set - if (!isset($typeTitles[$nextValueId][\Magento\Store\Model\Store::DEFAULT_STORE_ID])) { - $typeTitles[$nextValueId][\Magento\Store\Model\Store::DEFAULT_STORE_ID] = $specificTypeData['title']; + if (!isset($typeTitles[$nextValueId][Store::DEFAULT_STORE_ID])) { + $typeTitles[$nextValueId][Store::DEFAULT_STORE_ID] = $specificTypeData['title']; } if ($specificTypeData['price']) { if ($this->_isPriceGlobal) { - $typePrices[$nextValueId][\Magento\Store\Model\Store::DEFAULT_STORE_ID] = $specificTypeData['price']; + $typePrices[$nextValueId][Store::DEFAULT_STORE_ID] = $specificTypeData['price']; } else { // ensure default price is set - if (!isset($typePrices[$nextValueId][\Magento\Store\Model\Store::DEFAULT_STORE_ID])) { - $typePrices[$nextValueId][\Magento\Store\Model\Store::DEFAULT_STORE_ID] = $specificTypeData['price']; + if (!isset($typePrices[$nextValueId][Store::DEFAULT_STORE_ID])) { + $typePrices[$nextValueId][Store::DEFAULT_STORE_ID] = $specificTypeData['price']; } $typePrices[$nextValueId][$this->_rowStoreId] = $specificTypeData['price']; } } - $nextValueId++; - if (isset($parentCount[$prevOptionId])) { - $parentCount[$prevOptionId]++; - } else { - $parentCount[$prevOptionId] = 1; - } - } - - if (!isset($childCount[$this->_rowStoreId][$prevOptionId])) { - $childCount[$this->_rowStoreId][$prevOptionId] = 0; } - $parentValueId = $nextValueId - $parentCount[$prevOptionId] + $childCount[$this->_rowStoreId][$prevOptionId]; - $specificTypeData = $this->_getSpecificTypeData($rowData, $parentValueId, false); + $specificTypeData = $this->_getSpecificTypeData($rowData, 0, false); //For others stores if ($specificTypeData) { - $typeTitles[$parentValueId][$this->_rowStoreId] = $specificTypeData['title']; - $childCount[$this->_rowStoreId][$prevOptionId]++; + $typeTitles[$nextValueId++][$this->_rowStoreId] = $specificTypeData['title']; } } } @@ -1412,7 +1412,7 @@ protected function _collectOptionTypeData( */ protected function _collectOptionTitle(array $rowData, $prevOptionId, array &$titles) { - $defaultStoreId = \Magento\Store\Model\Store::DEFAULT_STORE_ID; + $defaultStoreId = Store::DEFAULT_STORE_ID; if (!empty($rowData[self::COLUMN_TITLE])) { if (!isset($titles[$prevOptionId][$defaultStoreId])) { // ensure default title is set @@ -1536,7 +1536,7 @@ protected function _parseRequiredData(array $rowData) } $this->_rowStoreId = $this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; } else { - $this->_rowStoreId = \Magento\Store\Model\Store::DEFAULT_STORE_ID; + $this->_rowStoreId = Store::DEFAULT_STORE_ID; } // Init option type and set param which tell that row is main if (!empty($rowData[self::COLUMN_TYPE])) { @@ -1655,7 +1655,7 @@ protected function _getPriceData(array $rowData, $optionId, $type) ) { $priceData = [ 'option_id' => $optionId, - 'store_id' => \Magento\Store\Model\Store::DEFAULT_STORE_ID, + 'store_id' => Store::DEFAULT_STORE_ID, 'price_type' => 'fixed', ]; diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php index e9f3cd59af0bb..4e04ed059c8e2 100644 --- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php @@ -5,8 +5,8 @@ */ namespace Magento\CatalogInventory\Model\ResourceModel\Stock; -use Magento\CatalogInventory\Model\Stock; use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\CatalogInventory\Model\Stock; use Magento\Framework\App\ObjectManager; /** @@ -46,19 +46,23 @@ class Status extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb * @param \Magento\Store\Model\WebsiteFactory $websiteFactory * @param \Magento\Eav\Model\Config $eavConfig * @param string $connectionName + * @param \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration */ public function __construct( \Magento\Framework\Model\ResourceModel\Db\Context $context, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Store\Model\WebsiteFactory $websiteFactory, \Magento\Eav\Model\Config $eavConfig, - $connectionName = null + $connectionName = null, + $stockConfiguration = null ) { parent::__construct($context, $connectionName); $this->_storeManager = $storeManager; $this->_websiteFactory = $websiteFactory; $this->eavConfig = $eavConfig; + $this->stockConfiguration = $stockConfiguration ?: ObjectManager::getInstance() + ->get(StockConfigurationInterface::class); } /** @@ -204,7 +208,7 @@ public function getProductCollection($lastEntityId = 0, $limit = 1000) */ public function addStockStatusToSelect(\Magento\Framework\DB\Select $select, \Magento\Store\Model\Website $website) { - $websiteId = $this->getStockConfiguration()->getDefaultScopeId(); + $websiteId = $this->getWebsiteId($website->getId()); $select->joinLeft( ['stock_status' => $this->getMainTable()], 'e.entity_id = stock_status.product_id AND stock_status.website_id=' . $websiteId, @@ -221,7 +225,7 @@ public function addStockStatusToSelect(\Magento\Framework\DB\Select $select, \Ma */ public function addStockDataToCollection($collection, $isFilterInStock) { - $websiteId = $this->getStockConfiguration()->getDefaultScopeId(); + $websiteId = $this->getWebsiteId(); $joinCondition = $this->getConnection()->quoteInto( 'e.entity_id = stock_status_index.product_id' . ' AND stock_status_index.website_id = ?', $websiteId @@ -255,7 +259,7 @@ public function addStockDataToCollection($collection, $isFilterInStock) */ public function addIsInStockFilterToCollection($collection) { - $websiteId = $this->getStockConfiguration()->getDefaultScopeId(); + $websiteId = $this->getWebsiteId(); $joinCondition = $this->getConnection()->quoteInto( 'e.entity_id = stock_status_index.product_id' . ' AND stock_status_index.website_id = ?', $websiteId @@ -277,6 +281,19 @@ public function addIsInStockFilterToCollection($collection) return $this; } + /** + * @param \Magento\Store\Model\Website $websiteId + * @return int + */ + private function getWebsiteId($websiteId = null) + { + if (null === $websiteId) { + $websiteId = $this->stockConfiguration->getDefaultScopeId(); + } + + return $websiteId; + } + /** * Retrieve Product(s) status for store * Return array where key is a product_id, value - status @@ -335,18 +352,4 @@ public function getProductStatus($productIds, $storeId = null) } return $statuses; } - - /** - * @return StockConfigurationInterface - * - * @deprecated 100.1.0 - */ - private function getStockConfiguration() - { - if ($this->stockConfiguration === null) { - $this->stockConfiguration = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\CatalogInventory\Api\StockConfigurationInterface::class); - } - return $this->stockConfiguration; - } } diff --git a/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php b/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php index e5154a10f0a19..6a723f5c22c5a 100644 --- a/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php +++ b/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php @@ -145,7 +145,7 @@ public function __construct( /** * @inheritdoc */ - public function save(\Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem) + public function save(StockItemInterface $stockItem) { try { /** @var \Magento\Catalog\Model\Product $product */ @@ -161,10 +161,7 @@ public function save(\Magento\CatalogInventory\Api\Data\StockItemInterface $stoc $typeId = $product->getTypeId() ?: $product->getTypeInstance()->getTypeId(); $isQty = $this->stockConfiguration->isQty($typeId); if ($isQty) { - $isInStock = $this->stockStateProvider->verifyStock($stockItem); - if ($stockItem->getManageStock() && !$isInStock) { - $stockItem->setIsInStock(false)->setStockStatusChangedAutomaticallyFlag(true); - } + $this->changeIsInStockIfNecessary($stockItem); // if qty is below notify qty, update the low stock date to today date otherwise set null $stockItem->setLowStockDate(null); if ($this->stockStateProvider->verifyNotification($stockItem)) { @@ -260,4 +257,28 @@ private function getStockRegistryStorage() } return $this->stockRegistryStorage; } + + /** + * Change is_in_stock value if necessary. + * + * @param StockItemInterface $stockItem + * + * @return void + */ + private function changeIsInStockIfNecessary(StockItemInterface $stockItem) + { + $isInStock = $this->stockStateProvider->verifyStock($stockItem); + if ($stockItem->getManageStock() && !$isInStock) { + $stockItem->setIsInStock(false)->setStockStatusChangedAutomaticallyFlag(true); + } + + if ($stockItem->getManageStock() + && $isInStock + && !$stockItem->getIsInStock() + && $stockItem->getOrigData(\Magento\CatalogInventory\Api\Data\StockItemInterface::QTY) == 0 + && $stockItem->getOrigData(\Magento\CatalogInventory\Api\Data\StockItemInterface::QTY) !== null + ) { + $stockItem->setIsInStock(true)->setStockStatusChangedAutomaticallyFlag(true); + } + } } diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php index 293874bb32b9f..6b1770ff7d403 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php @@ -276,7 +276,7 @@ public function testSave() ->method('verifyStock') ->with($this->stockItemMock) ->willReturn(false); - $this->stockItemMock->expects($this->once())->method('getManageStock')->willReturn(true); + $this->stockItemMock->expects($this->exactly(2))->method('getManageStock')->willReturn(true); $this->stockItemMock->expects($this->once())->method('setIsInStock')->with(false)->willReturnSelf(); $this->stockItemMock->expects($this->once()) ->method('setStockStatusChangedAutomaticallyFlag') diff --git a/app/code/Magento/CatalogInventory/etc/events.xml b/app/code/Magento/CatalogInventory/etc/events.xml index 0a9f3c2d40dca..3197501e9b70b 100644 --- a/app/code/Magento/CatalogInventory/etc/events.xml +++ b/app/code/Magento/CatalogInventory/etc/events.xml @@ -27,9 +27,6 @@ - - - diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php index 00808f38c9132..3f396cacd37da 100644 --- a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php +++ b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php @@ -105,6 +105,7 @@ public function build($productId) ->where('t.customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->where('t.rule_date = ?', $currentDate) ->order('t.rule_price ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $priceSelect = $this->baseSelectProcessor->process($priceSelect); diff --git a/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml b/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml index 201d6ffe4c683..574cbe1107e88 100644 --- a/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml +++ b/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml @@ -35,8 +35,7 @@
    - -
  1. + ' : '
  2. ' ?>
    getImage($_item, $image)->toHtml() ?> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml index b224c96f07e9b..1d67b325e01c5 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml @@ -24,7 +24,7 @@
    - + getCouponCode())): ?> disabled="disabled" />
    diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/payload-extender.js b/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/payload-extender.js index 9a082a056a382..dcd4340774af4 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/payload-extender.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/payload-extender.js @@ -1,5 +1,5 @@ /** - * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ define(function () { diff --git a/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js b/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js index 79050ca087740..dacebd75c3c68 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js @@ -195,6 +195,8 @@ define([ regionInput.hide(); label.attr('for', regionList.attr('id')); } else { + this._removeSelectOptions(regionList); + if (this.options.isRegionRequired) { regionInput.addClass('required-entry').removeAttr('disabled'); requiredLabel.addClass('required'); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/configure/product-customer-data.js b/app/code/Magento/Checkout/view/frontend/web/js/view/configure/product-customer-data.js index a612b5e2dc6b7..d7a81decbadef 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/configure/product-customer-data.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/configure/product-customer-data.js @@ -1,6 +1,7 @@ require([ 'jquery', - 'Magento_Customer/js/customer-data' + 'Magento_Customer/js/customer-data', + 'domReady!' ], function ($, customerData) { 'use strict'; diff --git a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html index f2baf5d50030e..fd994a4e8a955 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html @@ -7,7 +7,7 @@

    -
    +
    ,

    diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html index ec41cae0bdc5e..b66526f660af7 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html @@ -7,7 +7,7 @@
    -
    +
    ,

    diff --git a/app/code/Magento/Cms/Api/GetUtilityPageIdentifiersInterface.php b/app/code/Magento/Cms/Api/GetUtilityPageIdentifiersInterface.php new file mode 100644 index 0000000000000..c6bf4c8404701 --- /dev/null +++ b/app/code/Magento/Cms/Api/GetUtilityPageIdentifiersInterface.php @@ -0,0 +1,20 @@ +_objectManager->get(\Magento\Cms\Helper\Wysiwyg\Images::class); - $storeId = $this->getRequest()->getParam('store'); + $storeId = (int)$this->getRequest()->getParam('store'); $filename = $this->getRequest()->getParam('filename'); $filename = $helper->idDecode($filename); diff --git a/app/code/Magento/Cms/Model/GetUtilityPageIdentifiers.php b/app/code/Magento/Cms/Model/GetUtilityPageIdentifiers.php new file mode 100644 index 0000000000000..09c68ee9cf82d --- /dev/null +++ b/app/code/Magento/Cms/Model/GetUtilityPageIdentifiers.php @@ -0,0 +1,54 @@ +scopeConfig = $scopeConfig; + } + + /** + * Get List Page Identifiers + * @return array + */ + public function execute() + { + $homePageIdentifier = $this->scopeConfig->getValue( + 'web/default/cms_home_page', + ScopeInterface::SCOPE_STORE + ); + $noRouteIdentifier = $this->scopeConfig->getValue( + 'web/default/cms_no_route', + ScopeInterface::SCOPE_STORE + ); + + $noCookieIdentifier = $this->scopeConfig->getValue( + 'web/default/cms_no_cookies', + ScopeInterface::SCOPE_STORE + ); + + return [$homePageIdentifier, $noRouteIdentifier, $noCookieIdentifier]; + } +} diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index 8309e3b5b6150..d262ebca1591c 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -14,6 +14,7 @@ + diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php index fbaa4e60c29cc..47f70897dde06 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php @@ -10,11 +10,11 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\Data\ProductInterfaceFactory; use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\ConfigurableProduct\Model\Product\Type\Collection\SalableProcessor; use Magento\Catalog\Model\Config; +use Magento\Catalog\Model\Product\Gallery\ReadHandler as GalleryReadHandler; +use Magento\ConfigurableProduct\Model\Product\Type\Collection\SalableProcessor; use Magento\Framework\App\ObjectManager; use Magento\Framework\EntityManager\MetadataPool; -use Magento\Catalog\Model\Product\Gallery\ReadHandler as GalleryReadHandler; /** * Configurable product type implementation @@ -267,7 +267,6 @@ public function __construct( $productRepository, $serializer ); - } /** @@ -1277,6 +1276,8 @@ public function getSalableUsedProducts(\Magento\Catalog\Model\Product $product, * Load collection on sub-products for specified configurable product * * Load collection of sub-products, apply result to specified configurable product and store result to cache + * Please note $salableOnly parameter is used for backwards compatibility because of deprecated method + * getSalableUsedProducts * Number of loaded sub-products depends on $salableOnly parameter * $salableOnly = true - result array contains only salable sub-products * $salableOnly = false - result array contains all sub-products @@ -1293,7 +1294,7 @@ private function loadUsedProducts(\Magento\Catalog\Model\Product $product, $cach if (!$product->hasData($dataFieldName)) { $usedProducts = $this->readUsedProductsCacheData($cacheKey); if ($usedProducts === null) { - $collection = $this->getConfiguredUsedProductCollection($product); + $collection = $this->getConfiguredUsedProductCollection($product, false); if ($salableOnly) { $collection = $this->salableProcessor->process($collection); } @@ -1387,13 +1388,18 @@ private function getUsedProductsCacheKey($keyParts) * Retrieve related products collection with additional configuration * * @param \Magento\Catalog\Model\Product $product + * @param bool $skipStockFilter * @return \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection */ - private function getConfiguredUsedProductCollection(\Magento\Catalog\Model\Product $product) - { + private function getConfiguredUsedProductCollection( + \Magento\Catalog\Model\Product $product, + $skipStockFilter = true + ) { $collection = $this->getUsedProductCollection($product); + if ($skipStockFilter) { + $collection->setFlag('has_stock_status_filter', true); + } $collection - ->setFlag('has_stock_status_filter', true) ->addAttributeToSelect($this->getCatalogConfig()->getProductAttributes()) ->addFilterByRequiredOptions() ->setStoreId($product->getStoreId()); diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Attribute/OptionSelectBuilder.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Attribute/OptionSelectBuilder.php index 958d802682d52..5d9eed0a188fc 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Attribute/OptionSelectBuilder.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Attribute/OptionSelectBuilder.php @@ -91,6 +91,12 @@ public function getSelect(AbstractAttribute $superAttribute, int $productId, Sco ] ), [] + )->joinInner( + ['attribute_option' => $this->attributeResource->getTable('eav_attribute_option')], + 'attribute_option.option_id = entity_value.value', + [] + )->order( + 'attribute_option.sort_order ASC' )->where( 'super_attribute.product_id = ?', $productId diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php index ea136dd037baf..70a0c4550bdea 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php @@ -8,20 +8,20 @@ use Magento\Catalog\Api\Data\ProductExtensionInterface; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Config; -use Magento\ConfigurableProduct\Model\Product\Type\Configurable; -use Magento\Framework\EntityManager\EntityMetadata; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Customer\Model\Session; -use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\CollectionFactory; -use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection; use Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface; -use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface; +use Magento\ConfigurableProduct\Model\Product\Type\Collection\SalableProcessor; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; use Magento\ConfigurableProduct\Model\Product\Type\Configurable\AttributeFactory; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\CollectionFactory; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection as ProductCollection; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\ConfigurableFactory; +use Magento\Customer\Model\Session; use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend; use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource; -use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection as ProductCollection; -use Magento\ConfigurableProduct\Model\Product\Type\Collection\SalableProcessor; +use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface; +use Magento\Framework\EntityManager\EntityMetadata; +use Magento\Framework\EntityManager\MetadataPool; /** * @SuppressWarnings(PHPMD.LongVariable) diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Attribute/OptionSelectBuilderTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Attribute/OptionSelectBuilderTest.php index 235c16c9b556c..9802c97372bbb 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Attribute/OptionSelectBuilderTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Attribute/OptionSelectBuilderTest.php @@ -66,7 +66,7 @@ protected function setUp() ->disableOriginalConstructor() ->getMockForAbstractClass(); $this->select = $this->getMockBuilder(Select::class) - ->setMethods(['from', 'joinInner', 'joinLeft', 'where', 'columns']) + ->setMethods(['from', 'joinInner', 'joinLeft', 'where', 'columns', 'order']) ->disableOriginalConstructor() ->getMock(); $this->connectionMock->expects($this->atLeastOnce()) @@ -112,10 +112,28 @@ public function testGetSelect() { $this->select->expects($this->exactly(1))->method('from')->willReturnSelf(); $this->select->expects($this->exactly(1))->method('columns')->willReturnSelf(); - $this->select->expects($this->exactly(5))->method('joinInner')->willReturnSelf(); + $this->select->expects($this->exactly(6))->method('joinInner')->willReturnSelf(); $this->select->expects($this->exactly(3))->method('joinLeft')->willReturnSelf(); + $this->select->expects($this->exactly(1))->method('order')->willReturnSelf(); $this->select->expects($this->exactly(2))->method('where')->willReturnSelf(); + $this->attributeResourceMock->expects($this->exactly(9)) + ->method('getTable') + ->will( + $this->returnValueMap( + [ + ['catalog_product_super_attribute', 'catalog_product_super_attribute value'], + ['catalog_product_entity', 'catalog_product_entity value'], + ['catalog_product_super_link', 'catalog_product_super_link value'], + ['eav_attribute', 'eav_attribute value'], + ['catalog_product_entity', 'catalog_product_entity value'], + ['catalog_product_super_attribute_label', 'catalog_product_super_attribute_label value'], + ['eav_attribute_option', 'eav_attribute_option value'], + ['eav_attribute_option_value', 'eav_attribute_option_value value'] + ] + ) + ); + $this->abstractAttributeMock->expects($this->atLeastOnce()) ->method('getAttributeId') ->willReturn('getAttributeId value'); @@ -138,10 +156,27 @@ public function testGetSelectWithBackendModel() { $this->select->expects($this->exactly(1))->method('from')->willReturnSelf(); $this->select->expects($this->exactly(0))->method('columns')->willReturnSelf(); - $this->select->expects($this->exactly(5))->method('joinInner')->willReturnSelf(); + $this->select->expects($this->exactly(6))->method('joinInner')->willReturnSelf(); $this->select->expects($this->exactly(1))->method('joinLeft')->willReturnSelf(); + $this->select->expects($this->exactly(1))->method('order')->willReturnSelf(); $this->select->expects($this->exactly(2))->method('where')->willReturnSelf(); + $this->attributeResourceMock->expects($this->exactly(7)) + ->method('getTable') + ->will( + $this->returnValueMap( + [ + ['catalog_product_super_attribute', 'catalog_product_super_attribute value'], + ['catalog_product_entity', 'catalog_product_entity value'], + ['catalog_product_super_link', 'catalog_product_super_link value'], + ['eav_attribute', 'eav_attribute value'], + ['catalog_product_entity', 'catalog_product_entity value'], + ['catalog_product_super_attribute_label', 'catalog_product_super_attribute_label value'], + ['eav_attribute_option', 'eav_attribute_option value'] + ] + ) + ); + $this->abstractAttributeMock->expects($this->atLeastOnce()) ->method('getAttributeId') ->willReturn('getAttributeId value'); diff --git a/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php b/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php index dceb5767edae9..42d7d91fb90e8 100644 --- a/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php +++ b/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php @@ -45,7 +45,7 @@ public function __construct( public function isAvailable(Item $item) { $buyRequest = $item->getBuyRequest(); - $superAttribute = $buyRequest->getData()['super_attribute']; + $superAttribute = $buyRequest->getData()['super_attribute'] ?? []; $connection = $this->getConnection(); $select = $connection->select(); $orderItemParentId = $item->getParentItem()->getProductId(); diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php index 44eba83d96d7e..12732f81f78a0 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php @@ -13,6 +13,9 @@ use Magento\Customer\Model\Metadata\Form; use Magento\Framework\Exception\LocalizedException; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class Save extends \Magento\Customer\Controller\Adminhtml\Index { /** @@ -268,6 +271,15 @@ public function execute() $this->_addSessionErrorMessages($messages); $this->_getSession()->setCustomerFormData($originalRequestData); $returnToEdit = true; + } catch (\Magento\Framework\Exception\AbstractAggregateException $exception) { + $errors = $exception->getErrors(); + $messages = []; + foreach ($errors as $error) { + $messages[] = $error->getMessage(); + } + $this->_addSessionErrorMessages($messages); + $this->_getSession()->setCustomerFormData($originalRequestData); + $returnToEdit = true; } catch (LocalizedException $exception) { $this->_addSessionErrorMessages($exception->getMessage()); $this->_getSession()->setCustomerFormData($originalRequestData); diff --git a/app/code/Magento/Customer/Setup/UpgradeData.php b/app/code/Magento/Customer/Setup/UpgradeData.php index b5aba18a92f28..0ad36b1d6d11c 100644 --- a/app/code/Magento/Customer/Setup/UpgradeData.php +++ b/app/code/Magento/Customer/Setup/UpgradeData.php @@ -159,6 +159,10 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $this->upgradeVersionTwoZeroTwelve($customerSetup); } + if (version_compare($context->getVersion(), '2.0.13', '<')) { + $this->upgradeVersionTwoZeroThirteen($customerSetup); + } + $indexer = $this->indexerRegistry->get(Customer::CUSTOMER_GRID_INDEXER_ID); $indexer->reindexAll(); $this->eavConfig->clear(); @@ -663,4 +667,36 @@ private function upgradeCustomerPasswordResetlinkExpirationPeriodConfig($setup) ['path = ?' => \Magento\Customer\Model\Customer::XML_PATH_CUSTOMER_RESET_PASSWORD_LINK_EXPIRATION_PERIOD] ); } + + /** + * @param CustomerSetup $customerSetup + */ + private function upgradeVersionTwoZeroThirteen(CustomerSetup $customerSetup) + { + $entityAttributes = [ + 'customer_address' => [ + 'firstname' => [ + 'input_filter' => 'trim' + ], + 'lastname' => [ + 'input_filter' => 'trim' + ], + 'middlename' => [ + 'input_filter' => 'trim' + ], + ], + 'customer' => [ + 'firstname' => [ + 'input_filter' => 'trim' + ], + 'lastname' => [ + 'input_filter' => 'trim' + ], + 'middlename' => [ + 'input_filter' => 'trim' + ], + ], + ]; + $this->upgradeAttributes($entityAttributes, $customerSetup); + } } diff --git a/app/code/Magento/Customer/etc/module.xml b/app/code/Magento/Customer/etc/module.xml index 3f0d42b12649a..2dfe561d0da8f 100644 --- a/app/code/Magento/Customer/etc/module.xml +++ b/app/code/Magento/Customer/etc/module.xml @@ -6,7 +6,7 @@ */ --> - + diff --git a/app/code/Magento/Customer/etc/webapi_rest/di.xml b/app/code/Magento/Customer/etc/webapi_rest/di.xml index f2457963a5f3d..5f3ca2fdb7453 100644 --- a/app/code/Magento/Customer/etc/webapi_rest/di.xml +++ b/app/code/Magento/Customer/etc/webapi_rest/di.xml @@ -13,7 +13,7 @@ - Magento\Customer\Model\Authorization\CustomerSessionUserContext + Magento\Customer\Model\Authorization\CustomerSessionUserContext\Proxy 20 diff --git a/app/code/Magento/Customer/view/frontend/web/js/invalidation-processor.js b/app/code/Magento/Customer/view/frontend/web/js/invalidation-processor.js index d99574ec3dfbf..a6ae8ff043aa8 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/invalidation-processor.js +++ b/app/code/Magento/Customer/view/frontend/web/js/invalidation-processor.js @@ -1,5 +1,5 @@ /** - * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ define([ diff --git a/app/code/Magento/Customer/view/frontend/web/js/invalidation-rules/website-rule.js b/app/code/Magento/Customer/view/frontend/web/js/invalidation-rules/website-rule.js index eb7f101a6d47e..846edb2c836fa 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/invalidation-rules/website-rule.js +++ b/app/code/Magento/Customer/view/frontend/web/js/invalidation-rules/website-rule.js @@ -1,5 +1,5 @@ /** - * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ define([ diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php index 016dca2fa526c..b135bc39dd560 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php @@ -378,19 +378,11 @@ protected function _prepareDataForUpdate(array $rowData) $this->_newCustomers[$emailInLowercase][$rowData[self::COLUMN_WEBSITE]] = $entityId; } - $entityRow = [ - 'group_id' => empty($rowData['group_id']) ? self::DEFAULT_GROUP_ID : $rowData['group_id'], - 'store_id' => empty($rowData[self::COLUMN_STORE]) ? 0 : $this->_storeCodeToId[$rowData[self::COLUMN_STORE]], - 'created_at' => $createdAt->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT), - 'updated_at' => $now->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT), - 'entity_id' => $entityId, - ]; - // password change/set if (isset($rowData['password']) && strlen($rowData['password'])) { $rowData['password_hash'] = $this->_customerModel->hashPassword($rowData['password']); } - + $entityRow = ['entity_id' => $entityId]; // attribute values foreach (array_intersect_key($rowData, $this->_attributes) as $attributeCode => $value) { $attributeParameters = $this->_attributes[$attributeCode]; @@ -429,12 +421,21 @@ protected function _prepareDataForUpdate(array $rowData) if ($newCustomer) { // create + $entityRow['group_id'] = empty($rowData['group_id']) ? self::DEFAULT_GROUP_ID : $rowData['group_id']; + $entityRow['store_id'] = empty($rowData[self::COLUMN_STORE]) + ? 0 : $this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; + $entityRow['created_at'] = $createdAt->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); + $entityRow['updated_at'] = $now->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); $entityRow['website_id'] = $this->_websiteCodeToId[$rowData[self::COLUMN_WEBSITE]]; $entityRow['email'] = $emailInLowercase; $entityRow['is_active'] = 1; $entitiesToCreate[] = $entityRow; } else { // edit + $entityRow['updated_at'] = $now->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); + if (!empty($rowData[self::COLUMN_STORE])) { + $entityRow['store_id'] = $this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; + } $entitiesToUpdate[] = $entityRow; } diff --git a/app/code/Magento/Deploy/Console/Command/SetModeCommand.php b/app/code/Magento/Deploy/Console/Command/SetModeCommand.php index c4485656fb884..55b4a4f4f9b6a 100644 --- a/app/code/Magento/Deploy/Console/Command/SetModeCommand.php +++ b/app/code/Magento/Deploy/Console/Command/SetModeCommand.php @@ -101,6 +101,9 @@ protected function execute(InputInterface $input, OutputInterface $output) $modeController->enableProductionMode(); } break; + case State::MODE_DEFAULT: + $modeController->enableDefaultMode(); + break; default: throw new LocalizedException(__('Cannot switch into given mode "%1"', $toMode)); } diff --git a/app/code/Magento/Deploy/Model/Mode.php b/app/code/Magento/Deploy/Model/Mode.php index 3810ef0953124..792ee7f1b7968 100644 --- a/app/code/Magento/Deploy/Model/Mode.php +++ b/app/code/Magento/Deploy/Model/Mode.php @@ -177,6 +177,25 @@ public function enableDeveloperMode() $this->setStoreMode(State::MODE_DEVELOPER); } + /** + * Enable Default mode + * + * @return void + */ + public function enableDefaultMode() + { + $this->filesystem->cleanupFilesystem( + [ + DirectoryList::CACHE, + DirectoryList::GENERATED_CODE, + DirectoryList::GENERATED_METADATA, + DirectoryList::TMP_MATERIALIZATION_DIR, + DirectoryList::STATIC_VIEW, + ] + ); + $this->setStoreMode(State::MODE_DEFAULT); + } + /** * Get current mode information * diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/SetModeCommandTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/SetModeCommandTest.php index caa310535f356..a49bae655e87f 100644 --- a/app/code/Magento/Deploy/Test/Unit/Console/Command/SetModeCommandTest.php +++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/SetModeCommandTest.php @@ -67,6 +67,18 @@ public function testSetDeveloperMode() ); } + public function testSetDefaultMode() + { + $this->modeMock->expects($this->once())->method('enableDefaultMode'); + + $tester = new CommandTester($this->command); + $tester->execute(['mode' => 'default']); + $this->assertContains( + "default mode", + $tester->getDisplay() + ); + } + public function testSetProductionSkipCompilation() { $this->modeMock->expects($this->once())->method('enableProductionModeMinimal'); diff --git a/app/code/Magento/Directory/etc/adminhtml/system.xml b/app/code/Magento/Directory/etc/adminhtml/system.xml index 21eccd5bd521e..15a82e006bffe 100644 --- a/app/code/Magento/Directory/etc/adminhtml/system.xml +++ b/app/code/Magento/Directory/etc/adminhtml/system.xml @@ -109,6 +109,7 @@ Magento\Directory\Model\Config\Source\Country + 1 diff --git a/app/code/Magento/Eav/Model/Attribute.php b/app/code/Magento/Eav/Model/Attribute.php index 64504b59fe9c9..e53f3ccc82a75 100644 --- a/app/code/Magento/Eav/Model/Attribute.php +++ b/app/code/Magento/Eav/Model/Attribute.php @@ -62,7 +62,7 @@ public function setWebsite($website) */ public function getWebsite() { - if (is_null($this->_website)) { + if ($this->_website === null) { $this->_website = $this->_storeManager->getWebsite(); } @@ -88,7 +88,7 @@ public function afterSave() public function getUsedInForms() { $forms = $this->getData('used_in_forms'); - if (is_null($forms)) { + if ($forms === null) { $forms = $this->_getResource()->getUsedInForms($this); $this->setData('used_in_forms', $forms); } diff --git a/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php b/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php index ed052cb71e2fa..12023acc3b33b 100644 --- a/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php +++ b/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php @@ -205,7 +205,7 @@ public function setExtractedData(array $data) */ public function getExtractedData($index = null) { - if (!is_null($index)) { + if ($index !== null) { if (isset($this->_extractedData[$index])) { return $this->_extractedData[$index]; } @@ -262,9 +262,9 @@ protected function _getFormFilter() */ protected function _dateFilterFormat($format = null) { - if (is_null($format)) { + if ($format === null) { // get format - if (is_null($this->_dateFilterFormat)) { + if ($this->_dateFilterFormat === null) { $this->_dateFilterFormat = \IntlDateFormatter::SHORT; } return $this->_localeDate->getDateFormat($this->_dateFilterFormat); diff --git a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php index b0d186705026f..feb2c4d7d3f9f 100644 --- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php +++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php @@ -1294,7 +1294,7 @@ protected function _collectSaveData($newObject) $origData = $this->_getOrigObject($newObject)->getOrigData(); } - if (is_null($origData)) { + if ($origData === null) { $origData = []; } diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Backend/Datetime.php b/app/code/Magento/Eav/Model/Entity/Attribute/Backend/Datetime.php index 786060ea7b312..1a5f0a7811de3 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Backend/Datetime.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Backend/Datetime.php @@ -49,7 +49,7 @@ public function beforeSave($object) throw new \Magento\Framework\Exception\LocalizedException(__('Invalid date')); } - if (is_null($value)) { + if ($value === null) { $value = $object->getData($attributeName); } diff --git a/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js b/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js index 324881cdc5028..cd6292b39e989 100644 --- a/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js +++ b/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js @@ -51,10 +51,9 @@ define([ if (config.pageTrackingData.isAnonymizedIpActive) { ga('set', 'anonymizeIp', true); } - ga('send', 'pageview' + config.pageTrackingData.optPageUrl); // Process orders data - if (config.ordersTrackingData) { + if (config.ordersTrackingData.length) { ga('require', 'ec', 'ec.js'); //Set currency code @@ -75,6 +74,9 @@ define([ } ga('send', 'pageview'); + }else{ + // Process Data if not orders + ga('send', 'pageview' + config.pageTrackingData.optPageUrl); } } } diff --git a/app/code/Magento/Indexer/Model/Message/Invalid.php b/app/code/Magento/Indexer/Model/Message/Invalid.php index e13a5709e2362..5a3f879b0ad80 100644 --- a/app/code/Magento/Indexer/Model/Message/Invalid.php +++ b/app/code/Magento/Indexer/Model/Message/Invalid.php @@ -71,7 +71,7 @@ public function getText() return __( 'One or more
    indexers are invalid. Make sure your Magento cron job is running.', $url, - 'http://devdocs.magento.com/guides/v2.0/config-guide/cli/config-cli-subcommands-cron.html#config-cli-cron-bkg' + 'http://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-cron.html#create-or-remove-the-magento-crontab' ); //@codingStandardsIgnoreEnd } diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index b8d7ccf83af7c..8f29798472f19 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -604,14 +604,20 @@ protected function _updateCustomerSubscription($customerId, $subscribe) $this->save(); $sendSubscription = $sendInformationEmail; - if ($sendSubscription === null xor $sendSubscription) { + if ($sendSubscription === null xor $sendSubscription && $this->isStatusChanged()) { try { - if ($isConfirmNeed) { - $this->sendConfirmationRequestEmail(); - } elseif ($this->isStatusChanged() && $status == self::STATUS_UNSUBSCRIBED) { - $this->sendUnsubscriptionEmail(); - } elseif ($this->isStatusChanged() && $status == self::STATUS_SUBSCRIBED) { - $this->sendConfirmationSuccessEmail(); + switch ($status) { + case self::STATUS_UNSUBSCRIBED: + $this->sendUnsubscriptionEmail(); + break; + case self::STATUS_SUBSCRIBED: + $this->sendConfirmationSuccessEmail(); + break; + case self::STATUS_NOT_ACTIVE: + if ($isConfirmNeed) { + $this->sendConfirmationRequestEmail(); + } + break; } } catch (MailException $e) { // If we are not able to send a new account email, this should be ignored diff --git a/app/code/Magento/Newsletter/Setup/UpgradeSchema.php b/app/code/Magento/Newsletter/Setup/UpgradeSchema.php new file mode 100644 index 0000000000000..e7ce898de83a3 --- /dev/null +++ b/app/code/Magento/Newsletter/Setup/UpgradeSchema.php @@ -0,0 +1,36 @@ +startSetup(); + + if (version_compare($context->getVersion(), '2.0.1', '<')) { + $connection = $setup->getConnection(); + + $connection->addIndex( + $setup->getTable('newsletter_subscriber'), + $setup->getIdxName('newsletter_subscriber', ['subscriber_email']), + ['subscriber_email'] + ); + } + + $setup->endSetup(); + } +} diff --git a/app/code/Magento/Newsletter/etc/module.xml b/app/code/Magento/Newsletter/etc/module.xml index f338445225222..5da16a9a3e9ba 100644 --- a/app/code/Magento/Newsletter/etc/module.xml +++ b/app/code/Magento/Newsletter/etc/module.xml @@ -6,7 +6,7 @@ */ --> - + diff --git a/app/code/Magento/Payment/view/adminhtml/templates/info/substitution.phtml b/app/code/Magento/Payment/view/adminhtml/templates/info/substitution.phtml index ad24b113ffdea..582b8ca8a24be 100644 --- a/app/code/Magento/Payment/view/adminhtml/templates/info/substitution.phtml +++ b/app/code/Magento/Payment/view/adminhtml/templates/info/substitution.phtml @@ -10,6 +10,8 @@ */ ?>
    - escapeHtml($block->getMethod()->getTitle());?> + getMethod()->getTitle() + ? $block->escapeHtml($block->getMethod()->getTitle()) + : $block->escapeHtml(__('Payment method')); ?> escapeHtml(__(' is not available. You still can process offline actions.')) ?>
    diff --git a/app/code/Magento/ProductAlert/Controller/Add/TestObserver.php b/app/code/Magento/ProductAlert/Controller/Add/TestObserver.php deleted file mode 100644 index 74f03220e59d3..0000000000000 --- a/app/code/Magento/ProductAlert/Controller/Add/TestObserver.php +++ /dev/null @@ -1,23 +0,0 @@ -_objectManager->get(\Magento\ProductAlert\Model\Observer::class); - $observer->process($object); - } -} diff --git a/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php b/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php index 9fe69b691424d..e18ab8587fc71 100644 --- a/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php +++ b/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php @@ -10,6 +10,7 @@ use Magento\Quote\Api\CartTotalRepositoryInterface; use Magento\Catalog\Helper\Product\ConfigurationPool; use Magento\Framework\Api\DataObjectHelper; +use Magento\Framework\Api\ExtensibleDataInterface; use Magento\Quote\Model\Cart\Totals\ItemConverter; use Magento\Quote\Api\CouponManagementInterface; @@ -94,6 +95,7 @@ public function get($cartId) $addressTotalsData = $quote->getShippingAddress()->getData(); $addressTotals = $quote->getShippingAddress()->getTotals(); } + unset($addressTotalsData[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]); /** @var \Magento\Quote\Api\Data\TotalsInterface $quoteTotals */ $quoteTotals = $this->totalsFactory->create(); diff --git a/app/code/Magento/Quote/Model/Quote/Item.php b/app/code/Magento/Quote/Model/Quote/Item.php index d8177ddfe5236..fe6d712500bcd 100644 --- a/app/code/Magento/Quote/Model/Quote/Item.php +++ b/app/code/Magento/Quote/Model/Quote/Item.php @@ -745,6 +745,9 @@ public function saveItemOptions() unset($this->_options[$index]); unset($this->_optionsByCode[$option->getCode()]); } else { + if (!$option->getItem() || !$option->getItem()->getId()) { + $option->setItem($this); + } $option->save(); } } diff --git a/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php b/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php index 3113721f8a597..38bfcbf1d30ca 100644 --- a/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php +++ b/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php @@ -19,22 +19,32 @@ class ValidationMessage /** * @var \Magento\Framework\Locale\CurrencyInterface + * @deprecated since 101.0.0 */ private $currency; + /** + * @var \Magento\Framework\Pricing\Helper\Data + */ + private $priceHelper; + /** * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\Locale\CurrencyInterface $currency + * @param \Magento\Framework\Pricing\Helper\Data $priceHelper */ public function __construct( \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Framework\Locale\CurrencyInterface $currency + \Magento\Framework\Locale\CurrencyInterface $currency, + \Magento\Framework\Pricing\Helper\Data $priceHelper = null ) { $this->scopeConfig = $scopeConfig; $this->storeManager = $storeManager; $this->currency = $currency; + $this->priceHelper = $priceHelper ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Pricing\Helper\Data::class); } /** @@ -50,13 +60,11 @@ public function getMessage() \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); if (!$message) { - $currencyCode = $this->storeManager->getStore()->getCurrentCurrencyCode(); - $minimumAmount = $this->currency->getCurrency($currencyCode)->toCurrency( - $this->scopeConfig->getValue( - 'sales/minimum_order/amount', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ) - ); + $minimumAmount = $this->priceHelper->currency($this->scopeConfig->getValue( + 'sales/minimum_order/amount', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ), true, false); + $message = __('Minimum order amount is %1', $minimumAmount); } else { //Added in order to address the issue: https://github.com/magento/magento2/issues/8287 diff --git a/app/code/Magento/Quote/Setup/UpgradeSchema.php b/app/code/Magento/Quote/Setup/UpgradeSchema.php index 1bb20a669bdf2..e9221895e18dc 100644 --- a/app/code/Magento/Quote/Setup/UpgradeSchema.php +++ b/app/code/Magento/Quote/Setup/UpgradeSchema.php @@ -5,6 +5,7 @@ */ namespace Magento\Quote\Setup; +use Magento\Framework\DB\Ddl\Table; use Magento\Framework\Setup\UpgradeSchemaInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; @@ -40,7 +41,7 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con 'street', 'street', [ - 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 'type' => Table::TYPE_TEXT, 'length' => 255, 'comment' => 'Street' ] @@ -61,7 +62,7 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con $setup->getTable('quote_address', self::$connectionName), 'shipping_method', [ - 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 'type' => Table::TYPE_TEXT, 'length' => 120 ] ); @@ -72,33 +73,53 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con $setup->getTable('quote_address', self::$connectionName), 'firstname', [ - 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 'type' => Table::TYPE_TEXT, 'length' => 255, ] )->modifyColumn( $setup->getTable('quote_address', self::$connectionName), 'middlename', [ - 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 'type' => Table::TYPE_TEXT, 'length' => 40, ] )->modifyColumn( $setup->getTable('quote_address', self::$connectionName), 'lastname', [ - 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 'type' => Table::TYPE_TEXT, 'length' => 255, ] )->modifyColumn( $setup->getTable('quote', self::$connectionName), 'updated_at', [ - 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, + 'type' => Table::TYPE_TIMESTAMP, 'nullable' => false, - 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE, + 'default' => Table::TIMESTAMP_INIT_UPDATE, ] ); } + if (version_compare($context->getVersion(), '2.0.7', '<')) { + $connection = $setup->getConnection(self::$connectionName); + $connection->modifyColumn( + $setup->getTable('quote_address', self::$connectionName), + 'telephone', + ['type' => Table::TYPE_TEXT, 'length' => 255] + )->modifyColumn( + $setup->getTable('quote_address', self::$connectionName), + 'fax', + ['type' => Table::TYPE_TEXT, 'length' => 255] + )->modifyColumn( + $setup->getTable('quote_address', self::$connectionName), + 'region', + ['type' => Table::TYPE_TEXT, 'length' => 255] + )->modifyColumn( + $setup->getTable('quote_address', self::$connectionName), + 'city', + ['type' => Table::TYPE_TEXT, 'length' => 255] + ); + } $setup->endSetup(); } } diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/Validator/MinimumOrderAmount/ValidationMessageTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/Validator/MinimumOrderAmount/ValidationMessageTest.php index 64204ea1fb93d..272a4e3a4ba49 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/Quote/Validator/MinimumOrderAmount/ValidationMessageTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/Validator/MinimumOrderAmount/ValidationMessageTest.php @@ -26,19 +26,27 @@ class ValidationMessageTest extends \PHPUnit\Framework\TestCase /** * @var \PHPUnit_Framework_MockObject_MockObject + * @deprecated since 101.0.0 */ private $currencyMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $priceHelperMock; + protected function setUp() { $this->scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $this->currencyMock = $this->createMock(\Magento\Framework\Locale\CurrencyInterface::class); + $this->priceHelperMock = $this->createMock(\Magento\Framework\Pricing\Helper\Data::class); $this->model = new \Magento\Quote\Model\Quote\Validator\MinimumOrderAmount\ValidationMessage( $this->scopeConfigMock, $this->storeManagerMock, - $this->currencyMock + $this->currencyMock, + $this->priceHelperMock ); } @@ -46,8 +54,6 @@ public function testGetMessage() { $minimumAmount = 20; $minimumAmountCurrency = '$20'; - $currencyCode = 'currency_code'; - $this->scopeConfigMock->expects($this->at(0)) ->method('getValue') ->with('sales/minimum_order/description', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) @@ -58,27 +64,13 @@ public function testGetMessage() ->with('sales/minimum_order/amount', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) ->willReturn($minimumAmount); - $storeMock = $this->createPartialMock(\Magento\Store\Model\Store::class, ['getCurrentCurrencyCode']); - $storeMock->expects($this->once())->method('getCurrentCurrencyCode')->willReturn($currencyCode); - $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock); + $this->priceHelperMock->expects($this->once()) + ->method('currency') + ->with($minimumAmount, true, false) + ->will($this->returnValue($minimumAmountCurrency)); - $currencyMock = $this->createMock(\Magento\Framework\Currency::class); - $this->currencyMock->expects($this->once()) - ->method('getCurrency') - ->with($currencyCode) - ->willReturn($currencyMock); - - $currencyMock->expects($this->once()) - ->method('toCurrency') - ->with($minimumAmount) - ->willReturn($minimumAmountCurrency); - - $this->assertEquals( - __('Minimum order amount is %1', $minimumAmountCurrency), - $this->model->getMessage() - ); + $this->assertEquals(__('Minimum order amount is %1', $minimumAmountCurrency), $this->model->getMessage()); } - public function testGetConfigMessage() { $configMessage = 'config_message'; diff --git a/app/code/Magento/Quote/etc/module.xml b/app/code/Magento/Quote/etc/module.xml index f682568e63d02..6607dea5809b1 100644 --- a/app/code/Magento/Quote/etc/module.xml +++ b/app/code/Magento/Quote/etc/module.xml @@ -6,6 +6,6 @@ */ --> - + diff --git a/app/code/Magento/Review/Controller/Product/ListAction.php b/app/code/Magento/Review/Controller/Product/ListAction.php index dd8b272867c55..26344d125172a 100644 --- a/app/code/Magento/Review/Controller/Product/ListAction.php +++ b/app/code/Magento/Review/Controller/Product/ListAction.php @@ -26,8 +26,8 @@ protected function getProductPage($product) $resultPage->getConfig()->setPageLayout($product->getPageLayout()); } $urlSafeSku = rawurlencode($product->getSku()); - $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku]); $resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], null, false); + $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku]); $resultPage->addUpdate($product->getCustomLayoutUpdate()); return $resultPage; } diff --git a/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php b/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php index 9a6f1b48620dc..c02bbd64e7ca3 100644 --- a/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php +++ b/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php @@ -137,6 +137,7 @@ public function getDefaultOperatorInputByType() */ $this->_defaultOperatorInputByType['category'] = ['==', '!=', '{}', '!{}', '()', '!()']; $this->_arrayInputTypes[] = 'category'; + $this->_defaultOperatorInputByType['sku'] = ['==', '!=', '{}', '!{}', '()', '!()']; } return $this->_defaultOperatorInputByType; } @@ -382,6 +383,9 @@ public function getInputType() if ($this->getAttributeObject()->getAttributeCode() == 'category_ids') { return 'category'; } + if ($this->getAttributeObject()->getAttributeCode() == 'sku') { + return 'sku'; + } switch ($this->getAttributeObject()->getFrontendInput()) { case 'select': return 'select'; @@ -606,7 +610,12 @@ public function getBindArgumentValue() $this->getValueParsed() )->__toString() ); + } elseif ($this->getAttribute() === 'sku') { + $value = $this->getData('value'); + $value = preg_split('#\s*[,;]\s*#', $value, null, PREG_SPLIT_NO_EMPTY); + $this->setValueParsed($value); } + return parent::getBindArgumentValue(); } @@ -704,7 +713,7 @@ protected function _getAttributeSetId($productId) public function getOperatorForValidate() { $operator = $this->getOperator(); - if ($this->getInputType() == 'category') { + if (in_array($this->getInputType(), ['category', 'sku'])) { if ($operator == '==') { $operator = '{}'; } elseif ($operator == '!=') { diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index 69f4d19e4dd63..22ef39aa30247 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -681,7 +681,7 @@ public function initFromOrderItem(\Magento\Sales\Model\Order\Item $orderItem, $q */ public function getCustomerWishlist($cacheReload = false) { - if (!is_null($this->_wishlist) && !$cacheReload) { + if (($this->_wishlist !== null) && !$cacheReload) { return $this->_wishlist; } @@ -708,16 +708,17 @@ public function getCustomerWishlist($cacheReload = false) */ public function getCustomerCart() { - if (!is_null($this->_cart)) { + if ($this->_cart !== null) { return $this->_cart; } $this->_cart = $this->quoteFactory->create(); $customerId = (int)$this->getSession()->getCustomerId(); + $storeId = (int)$this->getSession()->getStoreId(); if ($customerId) { try { - $this->_cart = $this->quoteRepository->getForCustomer($customerId); + $this->_cart = $this->quoteRepository->getForCustomer($customerId, [$storeId]); } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { $this->_cart->setStore($this->getSession()->getStore()); $customerData = $this->customerRepository->getById($customerId); @@ -736,7 +737,7 @@ public function getCustomerCart() */ public function getCustomerCompareList() { - if (!is_null($this->_compareList)) { + if ($this->_compareList !== null) { return $this->_compareList; } $customerId = (int)$this->getSession()->getCustomerId(); @@ -807,7 +808,7 @@ public function moveQuoteItem($item, $moveTo, $qty) break; case 'cart': $cart = $this->getCustomerCart(); - if ($cart && is_null($item->getOptionByCode('additional_options'))) { + if ($cart && ($item->getOptionByCode('additional_options') === null)) { //options and info buy request $product = $this->_objectManager->create( \Magento\Catalog\Model\Product::class @@ -1727,7 +1728,7 @@ protected function _validateCustomerData(\Magento\Customer\Api\Data\CustomerInte } $data = $form->restoreData($data); foreach ($data as $key => $value) { - if (!is_null($value)) { + if ($value !== null) { unset($data[$key]); } } @@ -1851,12 +1852,12 @@ protected function _prepareCustomerAddress($customer, $quoteCustomerAddress) switch ($addressType) { case \Magento\Quote\Model\Quote\Address::ADDRESS_TYPE_BILLING: - if (is_null($customer->getDefaultBilling())) { + if ($customer->getDefaultBilling() === null) { $customerAddress->setIsDefaultBilling(true); } break; case \Magento\Quote\Model\Quote\Address::ADDRESS_TYPE_SHIPPING: - if (is_null($customer->getDefaultShipping())) { + if ($customer->getDefaultShipping() === null) { $customerAddress->setIsDefaultShipping(true); } break; @@ -1948,7 +1949,7 @@ public function createOrder() protected function _validate() { $customerId = $this->getSession()->getCustomerId(); - if (is_null($customerId)) { + if ($customerId === null) { throw new \Magento\Framework\Exception\LocalizedException(__('Please select a customer')); } diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo.php b/app/code/Magento/Sales/Model/Order/Creditmemo.php index 64b903fe5b5c1..0d0e0d23496b7 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo.php @@ -431,7 +431,7 @@ public function canVoid() /** * If we not retrieve negative answer from payment yet */ - if (is_null($canVoid)) { + if ($canVoid === null) { $canVoid = $this->getOrder()->getPayment()->canVoid(); if ($canVoid === false) { $this->setCanVoidFlag(false); @@ -451,7 +451,7 @@ public function canVoid() */ public static function getStates() { - if (is_null(static::$_states)) { + if (static::$_states === null) { static::$_states = [ self::STATE_OPEN => __('Pending'), self::STATE_REFUNDED => __('Refunded'), @@ -469,11 +469,11 @@ public static function getStates() */ public function getStateName($stateId = null) { - if (is_null($stateId)) { + if ($stateId === null) { $stateId = $this->getState(); } - if (is_null(static::$_states)) { + if (static::$_states === null) { static::getStates(); } if (isset(static::$_states[$stateId])) { diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/CommentRepository.php b/app/code/Magento/Sales/Model/Order/Creditmemo/CommentRepository.php index 14d4ccae22446..a3dde4e5172e7 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo/CommentRepository.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/CommentRepository.php @@ -7,6 +7,7 @@ use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; use Magento\Framework\Api\SearchCriteriaInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\CouldNotDeleteException; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Sales\Api\CreditmemoCommentRepositoryInterface; @@ -14,7 +15,15 @@ use Magento\Sales\Api\Data\CreditmemoCommentInterfaceFactory; use Magento\Sales\Api\Data\CreditmemoCommentSearchResultInterfaceFactory; use Magento\Sales\Model\Spi\CreditmemoCommentResourceInterface; +use Magento\Sales\Model\Order\Email\Sender\CreditmemoCommentSender; +use Magento\Sales\Api\CreditmemoRepositoryInterface; +use Psr\Log\LoggerInterface; +/** + * Class CommentRepository + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class CommentRepository implements CreditmemoCommentRepositoryInterface { /** @@ -37,22 +46,48 @@ class CommentRepository implements CreditmemoCommentRepositoryInterface */ private $collectionProcessor; + /** + * @var CreditmemoCommentSender + */ + private $creditmemoCommentSender; + + /** + * @var CreditmemoRepositoryInterface + */ + private $creditmemoRepository; + + /** + * @var LoggerInterface + */ + private $logger; + /** * @param CreditmemoCommentResourceInterface $commentResource * @param CreditmemoCommentInterfaceFactory $commentFactory * @param CreditmemoCommentSearchResultInterfaceFactory $searchResultFactory * @param CollectionProcessorInterface $collectionProcessor + * @param CreditmemoCommentSender|null $creditmemoCommentSender + * @param CreditmemoRepositoryInterface|null $creditmemoRepository + * @param LoggerInterface|null $logger */ public function __construct( CreditmemoCommentResourceInterface $commentResource, CreditmemoCommentInterfaceFactory $commentFactory, CreditmemoCommentSearchResultInterfaceFactory $searchResultFactory, - CollectionProcessorInterface $collectionProcessor + CollectionProcessorInterface $collectionProcessor, + CreditmemoCommentSender $creditmemoCommentSender = null, + CreditmemoRepositoryInterface $creditmemoRepository = null, + LoggerInterface $logger = null ) { $this->commentResource = $commentResource; $this->commentFactory = $commentFactory; $this->searchResultFactory = $searchResultFactory; $this->collectionProcessor = $collectionProcessor; + $this->creditmemoCommentSender = $creditmemoCommentSender + ?: ObjectManager::getInstance()->get(CreditmemoCommentSender::class); + $this->creditmemoRepository = $creditmemoRepository + ?: ObjectManager::getInstance()->get(CreditmemoRepositoryInterface::class); + $this->logger = $logger ?: ObjectManager::getInstance()->get(LoggerInterface::class); } /** @@ -97,7 +132,14 @@ public function save(CreditmemoCommentInterface $entity) try { $this->commentResource->save($entity); } catch (\Exception $e) { - throw new CouldNotSaveException(__('Could not save the comment.'), $e); + throw new CouldNotSaveException(__('Could not save the creditmemo comment.'), $e); + } + + try { + $creditmemo = $this->creditmemoRepository->get($entity->getParentId()); + $this->creditmemoCommentSender->send($creditmemo, $entity->getIsCustomerNotified(), $entity->getComment()); + } catch (\Exception $exception) { + $this->logger->warning('Something went wrong while sending email.'); } return $entity; } diff --git a/app/code/Magento/Sales/Model/Order/CustomerManagement.php b/app/code/Magento/Sales/Model/Order/CustomerManagement.php index bf54e65d0ce10..466f3ff8adddb 100644 --- a/app/code/Magento/Sales/Model/Order/CustomerManagement.php +++ b/app/code/Magento/Sales/Model/Order/CustomerManagement.php @@ -131,6 +131,7 @@ public function create($orderId) $customer = $this->customerFactory->create(['data' => $customerData]); $account = $this->accountManagement->createAccount($customer); $order->setCustomerId($account->getId()); + $order->setCustomerIsGuest(0); $this->orderRepository->save($order); return $account; diff --git a/app/code/Magento/Sales/Model/Order/Invoice/CommentRepository.php b/app/code/Magento/Sales/Model/Order/Invoice/CommentRepository.php index 858490132e0c7..00b1bb62556f7 100644 --- a/app/code/Magento/Sales/Model/Order/Invoice/CommentRepository.php +++ b/app/code/Magento/Sales/Model/Order/Invoice/CommentRepository.php @@ -7,6 +7,7 @@ use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; use Magento\Framework\Api\SearchCriteriaInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\CouldNotDeleteException; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Sales\Api\Data\InvoiceCommentInterface; @@ -14,7 +15,15 @@ use Magento\Sales\Api\Data\InvoiceCommentSearchResultInterfaceFactory; use Magento\Sales\Api\InvoiceCommentRepositoryInterface; use Magento\Sales\Model\Spi\InvoiceCommentResourceInterface; +use Magento\Sales\Model\Order\Email\Sender\InvoiceCommentSender; +use Magento\Sales\Api\InvoiceRepositoryInterface; +use Psr\Log\LoggerInterface; +/** + * Class CommentRepository + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class CommentRepository implements InvoiceCommentRepositoryInterface { /** @@ -37,22 +46,48 @@ class CommentRepository implements InvoiceCommentRepositoryInterface */ private $collectionProcessor; + /** + * @var InvoiceCommentSender + */ + private $invoiceCommentSender; + + /** + * @var InvoiceRepositoryInterface + */ + private $invoiceRepository; + + /** + * @var LoggerInterface + */ + private $logger; + /** * @param InvoiceCommentResourceInterface $commentResource * @param InvoiceCommentInterfaceFactory $commentFactory * @param InvoiceCommentSearchResultInterfaceFactory $searchResultFactory * @param CollectionProcessorInterface $collectionProcessor + * @param InvoiceCommentSender|null $invoiceCommentSender + * @param InvoiceRepositoryInterface|null $invoiceRepository + * @param LoggerInterface|null $logger */ public function __construct( InvoiceCommentResourceInterface $commentResource, InvoiceCommentInterfaceFactory $commentFactory, InvoiceCommentSearchResultInterfaceFactory $searchResultFactory, - CollectionProcessorInterface $collectionProcessor + CollectionProcessorInterface $collectionProcessor, + InvoiceCommentSender $invoiceCommentSender = null, + InvoiceRepositoryInterface $invoiceRepository = null, + LoggerInterface $logger = null ) { $this->commentResource = $commentResource; $this->commentFactory = $commentFactory; $this->searchResultFactory = $searchResultFactory; $this->collectionProcessor = $collectionProcessor; + $this->invoiceCommentSender = $invoiceCommentSender + ?:ObjectManager::getInstance()->get(InvoiceCommentSender::class); + $this->invoiceRepository = $invoiceRepository + ?:ObjectManager::getInstance()->get(InvoiceRepositoryInterface::class); + $this->logger = $logger ?: ObjectManager::getInstance()->get(LoggerInterface::class); } /** @@ -99,6 +134,13 @@ public function save(InvoiceCommentInterface $entity) } catch (\Exception $e) { throw new CouldNotSaveException(__('Could not save the invoice comment.'), $e); } + + try { + $invoice = $this->invoiceRepository->get($entity->getParentId()); + $this->invoiceCommentSender->send($invoice, $entity->getIsCustomerNotified(), $entity->getComment()); + } catch (\Exception $exception) { + $this->logger->warning('Something went wrong while sending email.'); + } return $entity; } } diff --git a/app/code/Magento/Sales/Model/Order/Payment/Transaction.php b/app/code/Magento/Sales/Model/Order/Payment/Transaction.php index b9b7a142095d9..7540ee1902b57 100644 --- a/app/code/Magento/Sales/Model/Order/Payment/Transaction.php +++ b/app/code/Magento/Sales/Model/Order/Payment/Transaction.php @@ -733,7 +733,7 @@ public function getTransactionTypes() */ public function getOrderWebsiteId() { - if (is_null($this->_orderWebsiteId)) { + if ($this->_orderWebsiteId === null) { $this->_orderWebsiteId = (int)$this->getResource()->getOrderWebsiteId($this->getOrderId()); } return $this->_orderWebsiteId; diff --git a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php index 1a25ff7bfdb80..1b80d08e68cda 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php @@ -804,7 +804,7 @@ protected function _getRenderer($type) throw new \Magento\Framework\Exception\LocalizedException(__('We found an invalid renderer model.')); } - if (is_null($this->_renderers[$type]['renderer'])) { + if ($this->_renderers[$type]['renderer'] === null) { $this->_renderers[$type]['renderer'] = $this->_pdfItemsFactory->get($this->_renderers[$type]['model']); } diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Invoice.php b/app/code/Magento/Sales/Model/Order/Pdf/Invoice.php index f294128a72f9f..ba99ed083e952 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Invoice.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Invoice.php @@ -96,7 +96,7 @@ protected function _drawHeader(\Zend_Pdf_Page $page) $lines[0][] = ['text' => __('Qty'), 'feed' => 435, 'align' => 'right']; - $lines[0][] = ['text' => __('Price'), 'feed' => 360, 'align' => 'right']; + $lines[0][] = ['text' => __('Price'), 'feed' => 375, 'align' => 'right']; $lines[0][] = ['text' => __('Tax'), 'feed' => 495, 'align' => 'right']; diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php index bb6078e425900..7d62e839ad924 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php @@ -81,8 +81,8 @@ public function draw() // draw item Prices $i = 0; $prices = $this->getItemPricesForDisplay(); - $feedPrice = 395; - $feedSubtotal = $feedPrice + 170; + $feedPrice = 375; + $feedSubtotal = $feedPrice + 190; foreach ($prices as $priceData) { if (isset($priceData['label'])) { // draw Price label diff --git a/app/code/Magento/Sales/Model/Order/Shipment/CommentRepository.php b/app/code/Magento/Sales/Model/Order/Shipment/CommentRepository.php index b0d3b03aec4ed..4f6e6ee9c2241 100644 --- a/app/code/Magento/Sales/Model/Order/Shipment/CommentRepository.php +++ b/app/code/Magento/Sales/Model/Order/Shipment/CommentRepository.php @@ -7,6 +7,7 @@ use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; use Magento\Framework\Api\SearchCriteriaInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\CouldNotDeleteException; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Sales\Api\Data\ShipmentCommentInterface; @@ -14,7 +15,15 @@ use Magento\Sales\Api\Data\ShipmentCommentSearchResultInterfaceFactory; use Magento\Sales\Api\ShipmentCommentRepositoryInterface; use Magento\Sales\Model\Spi\ShipmentCommentResourceInterface; +use Magento\Sales\Model\Order\Email\Sender\ShipmentCommentSender; +use Magento\Sales\Api\ShipmentRepositoryInterface; +use Psr\Log\LoggerInterface; +/** + * Class CommentRepository + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class CommentRepository implements ShipmentCommentRepositoryInterface { /** @@ -37,22 +46,48 @@ class CommentRepository implements ShipmentCommentRepositoryInterface */ private $collectionProcessor; + /** + * @var ShipmentCommentSender + */ + private $shipmentCommentSender; + + /** + * @var ShipmentRepositoryInterface + */ + private $shipmentRepository; + + /** + * @var LoggerInterface + */ + private $logger; + /** * @param ShipmentCommentResourceInterface $commentResource * @param ShipmentCommentInterfaceFactory $commentFactory * @param ShipmentCommentSearchResultInterfaceFactory $searchResultFactory * @param CollectionProcessorInterface $collectionProcessor + * @param ShipmentCommentSender|null $shipmentCommentSender + * @param ShipmentRepositoryInterface|null $shipmentRepository + * @param LoggerInterface|null $logger */ public function __construct( ShipmentCommentResourceInterface $commentResource, ShipmentCommentInterfaceFactory $commentFactory, ShipmentCommentSearchResultInterfaceFactory $searchResultFactory, - CollectionProcessorInterface $collectionProcessor + CollectionProcessorInterface $collectionProcessor, + ShipmentCommentSender $shipmentCommentSender = null, + ShipmentRepositoryInterface $shipmentRepository = null, + LoggerInterface $logger = null ) { $this->commentResource = $commentResource; $this->commentFactory = $commentFactory; $this->searchResultFactory = $searchResultFactory; $this->collectionProcessor = $collectionProcessor; + $this->shipmentCommentSender = $shipmentCommentSender + ?: ObjectManager::getInstance()->get(ShipmentCommentSender::class); + $this->shipmentRepository = $shipmentRepository + ?: ObjectManager::getInstance()->get(ShipmentRepositoryInterface::class); + $this->logger = $logger ?: ObjectManager::getInstance()->get(LoggerInterface::class); } /** @@ -99,6 +134,13 @@ public function save(ShipmentCommentInterface $entity) } catch (\Exception $e) { throw new CouldNotSaveException(__('Could not save the shipment comment.'), $e); } + + try { + $shipment = $this->shipmentRepository->get($entity->getParentId()); + $this->shipmentCommentSender->send($shipment, $entity->getIsCustomerNotified(), $entity->getComment()); + } catch (\Exception $exception) { + $this->logger->warning('Something went wrong while sending email.'); + } return $entity; } } diff --git a/app/code/Magento/Sales/Model/ResourceModel/Report/Bestsellers/Collection.php b/app/code/Magento/Sales/Model/ResourceModel/Report/Bestsellers/Collection.php index 7f0aaff02d104..fa4fccb1b17e7 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Report/Bestsellers/Collection.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Report/Bestsellers/Collection.php @@ -255,8 +255,8 @@ protected function _beforeLoad() $selectUnions = []; // apply date boundaries (before calling $this->_applyDateRangeFilter()) - $periodFrom = !is_null($this->_from) ? new \DateTime($this->_from) : null; - $periodTo = !is_null($this->_to) ? new \DateTime($this->_to) : null; + $periodFrom = ($this->_from !== null) ? new \DateTime($this->_from) : null; + $periodTo = ($this->_to !== null) ? new \DateTime($this->_to) : null; if ('year' == $this->_period) { if ($periodFrom) { // not the first day of the year diff --git a/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php b/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php index b284a529d2a15..fc2341b02e94a 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php @@ -26,6 +26,7 @@ use Magento\Quote\Model\Quote\Item\Updater; use Magento\Sales\Model\AdminOrder\Create; use Magento\Sales\Model\AdminOrder\Product; +use Magento\Quote\Model\QuoteFactory; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** @@ -41,6 +42,16 @@ class CreateTest extends \PHPUnit\Framework\TestCase */ private $adminOrderCreate; + /** + * @var \Magento\Quote\Api\CartRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $quoteRepository; + + /** + * @var \Magento\Quote\Model\QuoteFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $quoteFactory; + /** * @var SessionQuote|MockObject */ @@ -78,12 +89,22 @@ class CreateTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->sessionQuote = $this->createMock(SessionQuote::class); $this->formFactory = $this->createPartialMock(FormFactory::class, ['create']); + $this->quoteFactory = $this->createPartialMock(QuoteFactory::class, ['create']); $this->customerFactory = $this->createPartialMock(CustomerInterfaceFactory::class, ['create']); $this->itemUpdater = $this->createMock(Updater::class); + $this->quoteRepository = $this->getMockBuilder(\Magento\Quote\Api\CartRepositoryInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getForCustomer']) + ->getMockForAbstractClass(); + + $this->sessionQuote = $this->getMockBuilder(\Magento\Backend\Model\Session\Quote::class) + ->disableOriginalConstructor() + ->setMethods(['getQuote', 'getStoreId', 'getCustomerId']) + ->getMock(); + $this->customerMapper = $this->getMockBuilder(Mapper::class) ->setMethods(['toFlatArray']) ->disableOriginalConstructor() @@ -105,6 +126,8 @@ protected function setUp() 'quoteItemUpdater' => $this->itemUpdater, 'customerMapper' => $this->customerMapper, 'dataObjectHelper' => $this->dataObjectHelper, + 'quoteRepository' => $this->quoteRepository, + 'quoteFactory' => $this->quoteFactory, ] ); } @@ -266,4 +289,31 @@ public function testApplyCoupon() $object = $this->adminOrderCreate->applyCoupon($couponCode); self::assertEquals($this->adminOrderCreate, $object); } + + public function testGetCustomerCart() + { + $storeId = 2; + $customerId = 2; + $cartResult = [ + 'cart' => true + ]; + + $this->quoteFactory->expects($this->once()) + ->method('create'); + + $this->sessionQuote->expects($this->once()) + ->method('getStoreId') + ->willReturn($storeId); + + $this->sessionQuote->expects($this->once()) + ->method('getCustomerId') + ->willReturn($customerId); + + $this->quoteRepository->expects($this->once()) + ->method('getForCustomer') + ->with($customerId, [$storeId]) + ->willReturn($cartResult); + + $this->assertEquals($cartResult, $this->adminOrderCreate->getCustomerCart()); + } } diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/CommentRepositoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/CommentRepositoryTest.php new file mode 100644 index 0000000000000..115cb4bc7c91c --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/CommentRepositoryTest.php @@ -0,0 +1,187 @@ +commentResource = $this->getMockBuilder(CreditmemoCommentResourceInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->commentFactory = $this->getMockBuilder(CreditmemoCommentInterfaceFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->searchResultFactory = $this->getMockBuilder(CreditmemoCommentSearchResultInterfaceFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->collectionProcessor = $this->getMockBuilder(CollectionProcessorInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoRepositoryMock = $this->getMockBuilder(CreditmemoRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoCommentSender = $this->getMockBuilder(CreditmemoCommentSender::class) + ->disableOriginalConstructor() + ->getMock(); + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock(); + + $this->creditmemoMock = $this->getMockBuilder(Creditmemo::class)->disableOriginalConstructor()->getMock(); + $this->commentMock = $this->getMockBuilder(Comment::class)->disableOriginalConstructor()->getMock(); + + $this->commentRepository = new CommentRepository( + $this->commentResource, + $this->commentFactory, + $this->searchResultFactory, + $this->collectionProcessor, + $this->creditmemoCommentSender, + $this->creditmemoRepositoryMock, + $this->loggerMock + ); + } + + public function testSave() + { + $comment = "Comment text"; + $creditmemoId = 123; + $this->commentResource->expects($this->once()) + ->method('save') + ->with($this->commentMock) + ->willReturnSelf(); + $this->commentMock->expects($this->once()) + ->method('getIsCustomerNotified') + ->willReturn(1); + $this->commentMock->expects($this->once()) + ->method('getParentId') + ->willReturn($creditmemoId); + $this->commentMock->expects($this->once()) + ->method('getComment') + ->willReturn($comment); + + $this->creditmemoRepositoryMock->expects($this->once()) + ->method('get') + ->with($creditmemoId) + ->willReturn($this->creditmemoMock); + $this->creditmemoCommentSender->expects($this->once()) + ->method('send') + ->with($this->creditmemoMock, true, $comment) + ->willReturn(true); + $this->commentRepository->save($this->commentMock); + } + + /** + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + * @expectedExceptionMessage Could not save the creditmemo comment. + */ + public function testSaveWithException() + { + $this->commentResource->expects($this->once()) + ->method('save') + ->with($this->commentMock) + ->willThrowException( + new \Magento\Framework\Exception\CouldNotSaveException(__('Could not save the creditmemo comment.')) + ); + + $this->commentRepository->save($this->commentMock); + } + + public function testSaveSendCatchException() + { + $comment = "Comment text"; + $creditmemoId = 123; + $this->commentResource->expects($this->once()) + ->method('save') + ->with($this->commentMock) + ->willReturnSelf(); + $this->commentMock->expects($this->once()) + ->method('getIsCustomerNotified') + ->willReturn(1); + $this->commentMock->expects($this->once()) + ->method('getParentId') + ->willReturn($creditmemoId); + $this->commentMock->expects($this->once()) + ->method('getComment') + ->willReturn($comment); + + $this->creditmemoRepositoryMock->expects($this->once()) + ->method('get') + ->with($creditmemoId) + ->willReturn($this->creditmemoMock); + $this->creditmemoCommentSender->expects($this->once()) + ->method('send') + ->willThrowException(new \Exception()); + $this->loggerMock->expects($this->once()) + ->method('warning'); + + $this->commentRepository->save($this->commentMock); + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/CommentRepositoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/CommentRepositoryTest.php new file mode 100644 index 0000000000000..4790490c1ecf3 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/CommentRepositoryTest.php @@ -0,0 +1,187 @@ +commentResource = $this->getMockBuilder(InvoiceCommentResourceInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->commentFactory = $this->getMockBuilder(InvoiceCommentInterfaceFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->searchResultFactory = $this->getMockBuilder(InvoiceCommentSearchResultInterfaceFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->collectionProcessor = $this->getMockBuilder(CollectionProcessorInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->invoiceRepositoryMock = $this->getMockBuilder(InvoiceRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->invoiceCommentSender = $this->getMockBuilder(InvoiceCommentSender::class) + ->disableOriginalConstructor() + ->getMock(); + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock(); + + $this->invoiceMock = $this->getMockBuilder(Invoice::class)->disableOriginalConstructor()->getMock(); + $this->commentMock = $this->getMockBuilder(Comment::class)->disableOriginalConstructor()->getMock(); + + $this->commentRepository = new CommentRepository( + $this->commentResource, + $this->commentFactory, + $this->searchResultFactory, + $this->collectionProcessor, + $this->invoiceCommentSender, + $this->invoiceRepositoryMock, + $this->loggerMock + ); + } + + public function testSave() + { + $comment = "Comment text"; + $invoiceId = 123; + $this->commentResource->expects($this->once()) + ->method('save') + ->with($this->commentMock) + ->willReturnSelf(); + $this->commentMock->expects($this->once()) + ->method('getIsCustomerNotified') + ->willReturn(1); + $this->commentMock->expects($this->once()) + ->method('getParentId') + ->willReturn($invoiceId); + $this->commentMock->expects($this->once()) + ->method('getComment') + ->willReturn($comment); + + $this->invoiceRepositoryMock->expects($this->once()) + ->method('get') + ->with($invoiceId) + ->willReturn($this->invoiceMock); + $this->invoiceCommentSender->expects($this->once()) + ->method('send') + ->with($this->invoiceMock, true, $comment) + ->willReturn(true); + $this->commentRepository->save($this->commentMock); + } + + /** + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + * @expectedExceptionMessage Could not save the invoice comment. + */ + public function testSaveWithException() + { + $this->commentResource->expects($this->once()) + ->method('save') + ->with($this->commentMock) + ->willThrowException( + new \Magento\Framework\Exception\CouldNotSaveException(__('Could not save the invoice comment.')) + ); + + $this->commentRepository->save($this->commentMock); + } + + public function testSaveSendCatchException() + { + $comment = "Comment text"; + $creditmemoId = 123; + $this->commentResource->expects($this->once()) + ->method('save') + ->with($this->commentMock) + ->willReturnSelf(); + $this->commentMock->expects($this->once()) + ->method('getIsCustomerNotified') + ->willReturn(1); + $this->commentMock->expects($this->once()) + ->method('getParentId') + ->willReturn($creditmemoId); + $this->commentMock->expects($this->once()) + ->method('getComment') + ->willReturn($comment); + + $this->invoiceRepositoryMock->expects($this->once()) + ->method('get') + ->with($creditmemoId) + ->willReturn($this->invoiceMock); + $this->invoiceCommentSender->expects($this->once()) + ->method('send') + ->willThrowException(new \Exception()); + $this->loggerMock->expects($this->once()) + ->method('warning'); + + $this->commentRepository->save($this->commentMock); + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/CommentRepositoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/CommentRepositoryTest.php new file mode 100644 index 0000000000000..9cab366ef2c33 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/CommentRepositoryTest.php @@ -0,0 +1,186 @@ +commentResource = $this->getMockBuilder(ShipmentCommentResourceInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->commentFactory = $this->getMockBuilder(ShipmentCommentInterfaceFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->searchResultFactory = $this->getMockBuilder(ShipmentCommentSearchResultInterfaceFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->collectionProcessor = $this->getMockBuilder(CollectionProcessorInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->shipmentRepositoryMock = $this->getMockBuilder(ShipmentRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->shipmentCommentSender = $this->getMockBuilder(ShipmentCommentSender::class) + ->disableOriginalConstructor() + ->getMock(); + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock(); + + $this->shipmentMock = $this->getMockBuilder(Shipment::class)->disableOriginalConstructor()->getMock(); + $this->commentMock = $this->getMockBuilder(Comment::class)->disableOriginalConstructor()->getMock(); + + $this->commentRepository = new CommentRepository( + $this->commentResource, + $this->commentFactory, + $this->searchResultFactory, + $this->collectionProcessor, + $this->shipmentCommentSender, + $this->shipmentRepositoryMock, + $this->loggerMock + ); + } + + public function testSave() + { + $comment = "Comment text"; + $shipmentId = 123; + $this->commentResource->expects($this->once()) + ->method('save') + ->with($this->commentMock) + ->willReturnSelf(); + $this->commentMock->expects($this->once()) + ->method('getIsCustomerNotified') + ->willReturn(1); + $this->commentMock->expects($this->once()) + ->method('getParentId') + ->willReturn($shipmentId); + $this->commentMock->expects($this->once()) + ->method('getComment') + ->willReturn($comment); + + $this->shipmentRepositoryMock->expects($this->once()) + ->method('get') + ->with($shipmentId) + ->willReturn($this->shipmentMock); + $this->shipmentCommentSender->expects($this->once()) + ->method('send') + ->with($this->shipmentMock, true, $comment); + $this->assertEquals($this->commentMock, $this->commentRepository->save($this->commentMock)); + } + + /** + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + * @expectedExceptionMessage Could not save the shipment comment. + */ + public function testSaveWithException() + { + $this->commentResource->expects($this->once()) + ->method('save') + ->with($this->commentMock) + ->willThrowException( + new \Magento\Framework\Exception\CouldNotSaveException(__('Could not save the shipment comment.')) + ); + + $this->commentRepository->save($this->commentMock); + } + + public function testSaveSendCatchException() + { + $comment = "Comment text"; + $creditmemoId = 123; + $this->commentResource->expects($this->once()) + ->method('save') + ->with($this->commentMock) + ->willReturnSelf(); + $this->commentMock->expects($this->once()) + ->method('getIsCustomerNotified') + ->willReturn(1); + $this->commentMock->expects($this->once()) + ->method('getParentId') + ->willReturn($creditmemoId); + $this->commentMock->expects($this->once()) + ->method('getComment') + ->willReturn($comment); + + $this->shipmentRepositoryMock->expects($this->once()) + ->method('get') + ->with($creditmemoId) + ->willReturn($this->shipmentMock); + $this->shipmentCommentSender->expects($this->once()) + ->method('send') + ->willThrowException(new \Exception()); + $this->loggerMock->expects($this->once()) + ->method('warning'); + + $this->commentRepository->save($this->commentMock); + } +} diff --git a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_creditmemo_grid.xml b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_creditmemo_grid.xml index ecc2b5beee321..10b7b1c028c66 100644 --- a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_creditmemo_grid.xml +++ b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_creditmemo_grid.xml @@ -35,7 +35,13 @@ - + + + + * + + + diff --git a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_invoice_grid.xml b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_invoice_grid.xml index 3ec450a570b46..ac1233c5e4961 100644 --- a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_invoice_grid.xml +++ b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_invoice_grid.xml @@ -35,7 +35,13 @@ - + + + + * + + + diff --git a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_shipment_grid.xml b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_shipment_grid.xml index 27cef50742163..6db77a79b8c14 100644 --- a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_shipment_grid.xml +++ b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_shipment_grid.xml @@ -35,7 +35,13 @@ - + + + + * + + + diff --git a/app/code/Magento/Sales/view/frontend/templates/email/creditmemo/items.phtml b/app/code/Magento/Sales/view/frontend/templates/email/creditmemo/items.phtml index 297e31d6d2c98..8cef5d57664a9 100644 --- a/app/code/Magento/Sales/view/frontend/templates/email/creditmemo/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/email/creditmemo/items.phtml @@ -25,14 +25,11 @@ getAllItems() as $_item): ?> - getOrderItem()->getParentItem()) { - continue; - } - ?> - - getItemHtml($_item) ?> - + getOrderItem()->getParentItem()) : ?> + + getItemHtml($_item) ?> + + getChildHtml('creditmemo_totals') ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/email/invoice/items.phtml b/app/code/Magento/Sales/view/frontend/templates/email/invoice/items.phtml index 10e07d1365470..4c377dea47da2 100644 --- a/app/code/Magento/Sales/view/frontend/templates/email/invoice/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/email/invoice/items.phtml @@ -25,14 +25,11 @@ getAllItems() as $_item): ?> - getOrderItem()->getParentItem()) { - continue; - } - ?> - - getItemHtml($_item) ?> - + getOrderItem()->getParentItem()) : ?> + + getItemHtml($_item) ?> + + getChildHtml('invoice_totals') ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/email/items.phtml b/app/code/Magento/Sales/view/frontend/templates/email/items.phtml index 358264463d49a..37469582865dc 100644 --- a/app/code/Magento/Sales/view/frontend/templates/email/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/email/items.phtml @@ -25,14 +25,11 @@ - getParentItem()) { - continue; - } - ?> - - getItemHtml($_item) ?> - + getParentItem()) : ?> + + getItemHtml($_item) ?> + + getChildHtml('order_totals') ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/email/shipment/items.phtml b/app/code/Magento/Sales/view/frontend/templates/email/shipment/items.phtml index 2d2b7b2c2b26e..022511ae3cfd0 100644 --- a/app/code/Magento/Sales/view/frontend/templates/email/shipment/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/email/shipment/items.phtml @@ -22,14 +22,11 @@ getAllItems() as $_item): ?> - getOrderItem()->getParentItem()) { - continue; - } - ?> - - getItemHtml($_item) ?> - + getOrderItem()->getParentItem()) : ?> + + getItemHtml($_item) ?> + + diff --git a/app/code/Magento/Sales/view/frontend/templates/order/creditmemo/items.phtml b/app/code/Magento/Sales/view/frontend/templates/order/creditmemo/items.phtml index a90300efaf5c9..dc2cf2433ac8d 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/creditmemo/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/creditmemo/items.phtml @@ -40,14 +40,12 @@ getAllItems(); ?> - - getOrderItem()->getParentItem()) { - continue; -} ?> - - getItemHtml($_item) ?> - + getOrderItem()->getParentItem()): ?> + + getItemHtml($_item) ?> + + getTotalsHtml($_creditmemo) ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/order/invoice/items.phtml b/app/code/Magento/Sales/view/frontend/templates/order/invoice/items.phtml index 980710569fa3b..0f3236ec25bc3 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/invoice/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/invoice/items.phtml @@ -37,14 +37,12 @@ getAllItems(); ?> - - getOrderItem()->getParentItem()) { - continue; -} ?> - - getItemHtml($_item) ?> - + getOrderItem()->getParentItem()) : ?> + + getItemHtml($_item) ?> + + getInvoiceTotalsHtml($_invoice) ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/order/print/creditmemo.phtml b/app/code/Magento/Sales/view/frontend/templates/order/print/creditmemo.phtml index d68b4cdb62fcc..567dfc20f2de2 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/print/creditmemo.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/print/creditmemo.phtml @@ -34,14 +34,12 @@ getAllItems(); ?> - - getOrderItem()->getParentItem()) { - continue; -} ?> - - getItemHtml($_item) ?> - + getOrderItem()->getParentItem()): ?> + + getItemHtml($_item) ?> + + getTotalsHtml($_creditmemo) ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml b/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml index 5aa1cab686e2c..6fe6da9e75209 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml @@ -32,14 +32,12 @@ getItemsCollection(); ?> - count(); ?> - getOrderItem()->getParentItem()) { - continue; -} ?> - - getItemHtml($_item) ?> - + getOrderItem()->getParentItem()): ?> + + getItemHtml($_item) ?> + + getInvoiceTotalsHtml($_invoice) ?> diff --git a/app/code/Magento/SalesRule/view/adminhtml/layout/sales_rule_promo_quote_edit.xml b/app/code/Magento/SalesRule/view/adminhtml/layout/sales_rule_promo_quote_edit.xml index ad5bbbd262da7..3366af650f8e9 100644 --- a/app/code/Magento/SalesRule/view/adminhtml/layout/sales_rule_promo_quote_edit.xml +++ b/app/code/Magento/SalesRule/view/adminhtml/layout/sales_rule_promo_quote_edit.xml @@ -6,6 +6,7 @@ */ --> + diff --git a/app/code/Magento/SalesRule/view/frontend/web/js/view/summary/discount.js b/app/code/Magento/SalesRule/view/frontend/web/js/view/summary/discount.js index 5b04700596272..6df769a90894e 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/js/view/summary/discount.js +++ b/app/code/Magento/SalesRule/view/frontend/web/js/view/summary/discount.js @@ -44,6 +44,25 @@ define([ return this.totals()['coupon_label']; }, + /** + * Get discount title + * + * @returns {null|String} + */ + getTitle: function () { + var discountSegments; + + if (!this.totals()) { + return null; + } + + discountSegments = this.totals()['total_segments'].filter(function (segment) { + return segment.code === 'discount'; + }); + + return discountSegments.length ? discountSegments[0].title : null; + }, + /** * @return {Number} */ diff --git a/app/code/Magento/SalesRule/view/frontend/web/template/cart/totals/discount.html b/app/code/Magento/SalesRule/view/frontend/web/template/cart/totals/discount.html index 4b70b4b110c97..8fbb4a6ce74ae 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/template/cart/totals/discount.html +++ b/app/code/Magento/SalesRule/view/frontend/web/template/cart/totals/discount.html @@ -7,7 +7,7 @@ - + diff --git a/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html b/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html index 7246460382fa7..d622b5ea5762d 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html +++ b/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html @@ -27,7 +27,7 @@ id="discount-code" name="discount_code" data-validate="{'required-entry':true}" - data-bind="value: couponCode, attr:{placeholder: $t('Enter discount code')} " /> + data-bind="value: couponCode, attr:{disabled:isApplied() , placeholder: $t('Enter discount code')} " />
    diff --git a/app/code/Magento/SalesRule/view/frontend/web/template/summary/discount.html b/app/code/Magento/SalesRule/view/frontend/web/template/summary/discount.html index 17a9559fa01f2..017e358c7e419 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/template/summary/discount.html +++ b/app/code/Magento/SalesRule/view/frontend/web/template/summary/discount.html @@ -7,7 +7,7 @@ - + diff --git a/app/code/Magento/Security/Model/AdminSessionsManager.php b/app/code/Magento/Security/Model/AdminSessionsManager.php index 4ebdcc58240a1..af690f1899e7b 100644 --- a/app/code/Magento/Security/Model/AdminSessionsManager.php +++ b/app/code/Magento/Security/Model/AdminSessionsManager.php @@ -66,6 +66,14 @@ class AdminSessionsManager */ private $remoteAddress; + /** + * Max lifetime for session prolong to be valid (sec) + * + * Means that after session was prolonged + * all other prolongs will be ignored within this period + */ + private $maxIntervalBetweenConsecutiveProlongs = 60; + /** * @param ConfigInterface $securityConfig * @param \Magento\Backend\Model\Auth\Session $authSession @@ -124,11 +132,16 @@ public function processLogin() */ public function processProlong() { - $this->getCurrentSession()->setData( - 'updated_at', - $this->authSession->getUpdatedAt() - ); - $this->getCurrentSession()->save(); + if ($this->lastProlongIsOldEnough()) { + $this->getCurrentSession()->setData( + 'updated_at', + date( + \Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT, + $this->authSession->getUpdatedAt() + ) + ); + $this->getCurrentSession()->save(); + } return $this; } @@ -298,4 +311,45 @@ protected function createAdminSessionInfoCollection() { return $this->adminSessionInfoCollectionFactory->create(); } + + /** + * Calculates diff between now and last session updated_at + * and decides whether new prolong must be triggered or not + * + * This is done to limit amount of session prolongs and updates to database + * within some period of time - X + * X - is calculated in getIntervalBetweenConsecutiveProlongs() + * + * @see getIntervalBetweenConsecutiveProlongs() + * @return bool + */ + private function lastProlongIsOldEnough() + { + $lastProlongTimestamp = strtotime($this->getCurrentSession()->getUpdatedAt()); + $nowTimestamp = $this->authSession->getUpdatedAt(); + + $diff = $nowTimestamp - $lastProlongTimestamp; + + return (float) $diff > $this->getIntervalBetweenConsecutiveProlongs(); + } + + /** + * Calculates lifetime for session prolong to be valid + * + * Calculation is based on admin session lifetime + * Calculated result is in seconds and is in the interval + * between 1 (including) and MAX_INTERVAL_BETWEEN_CONSECUTIVE_PROLONGS (including) + * + * @return float + */ + private function getIntervalBetweenConsecutiveProlongs() + { + return (float) max( + 1, + min( + 4 * log((float)$this->securityConfig->getAdminSessionLifetime()), + $this->maxIntervalBetweenConsecutiveProlongs + ) + ); + } } diff --git a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php index d81264f661762..ddfeaa59ac224 100644 --- a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php @@ -99,7 +99,8 @@ public function setUp() 'setIsOtherSessionsTerminated', 'save', 'getUserId', - 'getSessionId' + 'getSessionId', + 'getUpdatedAt' ]); $this->securityConfigMock = $this->getMockBuilder(\Magento\Security\Model\ConfigInterface::class) @@ -216,7 +217,8 @@ public function testProcessLogin() public function testProcessProlong() { $sessionId = 50; - $updatedAt = '2015-12-31 23:59:59'; + $lastUpdatedAt = '2015-12-31 23:59:59'; + $newUpdatedAt = '2016-01-01 00:00:30'; $this->adminSessionInfoFactoryMock->expects($this->any()) ->method('create') @@ -230,13 +232,21 @@ public function testProcessProlong() ->method('load') ->willReturnSelf(); - $this->authSessionMock->expects($this->once()) + $this->currentSessionMock->expects($this->once()) + ->method('getUpdatedAt') + ->willReturn($lastUpdatedAt); + + $this->authSessionMock->expects($this->exactly(2)) ->method('getUpdatedAt') - ->willReturn($updatedAt); + ->willReturn(strtotime($newUpdatedAt)); + + $this->securityConfigMock->expects($this->once()) + ->method('getAdminSessionLifetime') + ->willReturn(100); $this->currentSessionMock->expects($this->once()) ->method('setData') - ->with('updated_at', $updatedAt) + ->with('updated_at', $newUpdatedAt) ->willReturnSelf(); $this->currentSessionMock->expects($this->once()) diff --git a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Email.php b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Email.php index 1f5d6e2cb7a89..f08204fdff0d7 100644 --- a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Email.php +++ b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Email.php @@ -57,7 +57,9 @@ public function execute() $this->_objectManager->create(\Magento\Shipping\Model\ShipmentNotifier::class) ->notify($shipment); $shipment->save(); - $this->messageManager->addSuccess(__('You sent the shipment.')); + $this->messageManager->addSuccess( + __('An email confirming the order is underway has been sent to the customer.') + ); } } catch (\Magento\Framework\Exception\LocalizedException $e) { $this->messageManager->addError($e->getMessage()); diff --git a/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/EmailTest.php b/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/EmailTest.php index 7e4e27efe5ba7..3507b7fe2e274 100644 --- a/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/EmailTest.php +++ b/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/EmailTest.php @@ -207,7 +207,7 @@ public function testEmail() ->will($this->returnValue(true)); $this->messageManager->expects($this->once()) ->method('addSuccess') - ->with('You sent the shipment.'); + ->with('An email confirming the order is underway has been sent to the customer.'); $path = '*/*/view'; $arguments = ['shipment_id' => $shipmentId]; $this->prepareRedirect($path, $arguments, 0); diff --git a/app/code/Magento/Shipping/i18n/en_US.csv b/app/code/Magento/Shipping/i18n/en_US.csv index f777e64ef98c9..0bda9d2a71477 100644 --- a/app/code/Magento/Shipping/i18n/en_US.csv +++ b/app/code/Magento/Shipping/i18n/en_US.csv @@ -28,7 +28,7 @@ Shipments,Shipments "Cannot add tracking number.","Cannot add tracking number." "You created the shipping label.","You created the shipping label." "An error occurred while creating shipping label.","An error occurred while creating shipping label." -"You sent the shipment.","You sent the shipment." +"An email confirming the order is underway has been sent to the customer.","An email confirming the order is underway has been sent to the customer." "Cannot send shipment information.","Cannot send shipment information." "There are no shipping labels related to selected orders.","There are no shipping labels related to selected orders." "New Shipment","New Shipment" diff --git a/app/code/Magento/Shipping/view/frontend/templates/items.phtml b/app/code/Magento/Shipping/view/frontend/templates/items.phtml index 1f4958d1775fb..ebc6163a7bd06 100644 --- a/app/code/Magento/Shipping/view/frontend/templates/items.phtml +++ b/app/code/Magento/Shipping/view/frontend/templates/items.phtml @@ -66,14 +66,12 @@ getAllItems(); ?> - - getOrderItem()->getParentItem()) { - continue; -} ?> - - getItemHtml($_item) ?> - + getOrderItem()->getParentItem()) : ?> + + getItemHtml($_item) ?> + + diff --git a/app/code/Magento/Sitemap/Model/ResourceModel/Cms/Page.php b/app/code/Magento/Sitemap/Model/ResourceModel/Cms/Page.php index d050ea84ecccb..01addd0c19666 100644 --- a/app/code/Magento/Sitemap/Model/ResourceModel/Cms/Page.php +++ b/app/code/Magento/Sitemap/Model/ResourceModel/Cms/Page.php @@ -6,12 +6,15 @@ namespace Magento\Sitemap\Model\ResourceModel\Cms; use Magento\Cms\Api\Data\PageInterface; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Framework\Model\ResourceModel\Db\Context; -use Magento\Framework\Model\AbstractModel; +use Magento\Cms\Api\GetUtilityPageIdentifiersInterface; use Magento\Cms\Model\Page as CmsPage; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\EntityManager; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\Model\AbstractModel; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\Framework\Model\ResourceModel\Db\Context; /** * Sitemap cms page collection model @@ -19,7 +22,7 @@ * @api * @since 100.0.2 */ -class Page extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb +class Page extends AbstractDb { /** * @var MetadataPool @@ -34,19 +37,29 @@ class Page extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb protected $entityManager; /** - * @param Context $context - * @param MetadataPool $metadataPool - * @param EntityManager $entityManager - * @param string $connectionName + * @var GetUtilityPageIdentifiersInterface + * @since 100.2.0 + */ + private $getUtilityPageIdentifiers; + + /** + * @param Context $context + * @param MetadataPool $metadataPool + * @param EntityManager $entityManager + * @param string $connectionName + * @param GetUtilityPageIdentifiersInterface $getUtilityPageIdentifiers */ public function __construct( Context $context, MetadataPool $metadataPool, EntityManager $entityManager, - $connectionName = null + $connectionName = null, + GetUtilityPageIdentifiersInterface $getUtilityPageIdentifiers = null ) { - $this->metadataPool = $metadataPool; - $this->entityManager = $entityManager; + $this->metadataPool = $metadataPool; + $this->entityManager = $entityManager; + $this->getUtilityPageIdentifiers = $getUtilityPageIdentifiers ?: + ObjectManager::getInstance()->get(GetUtilityPageIdentifiersInterface::class); parent::__construct($context, $connectionName); } @@ -90,8 +103,8 @@ public function getCollection($storeId) )->where( 'main_table.is_active = 1' )->where( - 'main_table.identifier != ?', - \Magento\Cms\Model\Page::NOROUTE_PAGE_ID + 'main_table.identifier NOT IN (?)', + $this->getUtilityPageIdentifiers->execute() )->where( 'store_table.store_id IN(?)', [0, $storeId] diff --git a/app/code/Magento/Sitemap/etc/module.xml b/app/code/Magento/Sitemap/etc/module.xml index 0edfcf84f644f..0cfe3d551d162 100644 --- a/app/code/Magento/Sitemap/etc/module.xml +++ b/app/code/Magento/Sitemap/etc/module.xml @@ -9,6 +9,7 @@ + diff --git a/app/code/Magento/Store/Setup/InstallSchema.php b/app/code/Magento/Store/Setup/InstallSchema.php index f6cdfb6fcba71..69b3f86ebdd7b 100644 --- a/app/code/Magento/Store/Setup/InstallSchema.php +++ b/app/code/Magento/Store/Setup/InstallSchema.php @@ -270,7 +270,13 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con */ $connection->insertForce( $installer->getTable('store_group'), - ['group_id' => 0, 'website_id' => 0, 'name' => 'Default', 'root_category_id' => 0, 'default_store_id' => 0] + [ + 'group_id' => 0, + 'website_id' => 0, + 'name' => 'Default', + 'root_category_id' => 0, + 'default_store_id' => 0 + ] ); $connection->insertForce( $installer->getTable('store_group'), diff --git a/app/code/Magento/Swatches/Block/Adminhtml/Attribute/Edit/Options/Visual.php b/app/code/Magento/Swatches/Block/Adminhtml/Attribute/Edit/Options/Visual.php index 306f01af17e71..66ecd06c646e7 100644 --- a/app/code/Magento/Swatches/Block/Adminhtml/Attribute/Edit/Options/Visual.php +++ b/app/code/Magento/Swatches/Block/Adminhtml/Attribute/Edit/Options/Visual.php @@ -84,15 +84,15 @@ public function getJsonConfig() * Parse swatch labels for template * * @codeCoverageIgnore - * @param null $swatchStoreValue - * @return string + * @param null|array $swatchStoreValue + * @return null|array */ protected function reformatSwatchLabels($swatchStoreValue = null) { if ($swatchStoreValue === null) { return; } - $newSwatch = ''; + $newSwatch = []; foreach ($swatchStoreValue as $key => $value) { if ($value[0] == '#') { $newSwatch[$key] = 'background: '.$value; diff --git a/app/code/Magento/Swatches/Model/Plugin/EavAttribute.php b/app/code/Magento/Swatches/Model/Plugin/EavAttribute.php index 599406f455281..d3904f058dc2d 100644 --- a/app/code/Magento/Swatches/Model/Plugin/EavAttribute.php +++ b/app/code/Magento/Swatches/Model/Plugin/EavAttribute.php @@ -432,7 +432,7 @@ protected function validateOptions(Attribute $attribute) $options = $attribute->getData('optiontext'); } if ($options && !$this->isOptionsValid($options, $attribute)) { - throw new InputException(__('Admin is a required field in the each row')); + throw new InputException(__('Admin is a required field in each row')); } return true; } diff --git a/app/code/Magento/Swatches/Test/Unit/Model/Plugin/EavAttributeTest.php b/app/code/Magento/Swatches/Test/Unit/Model/Plugin/EavAttributeTest.php index 258347887ff08..31a45ddb2847c 100644 --- a/app/code/Magento/Swatches/Test/Unit/Model/Plugin/EavAttributeTest.php +++ b/app/code/Magento/Swatches/Test/Unit/Model/Plugin/EavAttributeTest.php @@ -191,7 +191,7 @@ public function testBeforeSaveTextSwatch() /** * @expectedException \Magento\Framework\Exception\InputException - * @expectedExceptionMessage Admin is a required field in the each row + * @expectedExceptionMessage Admin is a required field in each row */ public function testBeforeSaveWithFailedValidation() { diff --git a/app/code/Magento/Swatches/i18n/en_US.csv b/app/code/Magento/Swatches/i18n/en_US.csv index 3ea4977a75348..00874d7611169 100644 --- a/app/code/Magento/Swatches/i18n/en_US.csv +++ b/app/code/Magento/Swatches/i18n/en_US.csv @@ -1,4 +1,4 @@ -"Admin is a required field in the each row","Admin is a required field in the each row" +"Admin is a required field in each row","Admin is a required field in each row" "Update Product Preview Image","Update Product Preview Image" "Filtering by this attribute will update the product image on catalog page","Filtering by this attribute will update the product image on catalog page" "Use Product Image for Swatch if Possible","Use Product Image for Swatch if Possible" diff --git a/app/code/Magento/Tax/Model/Calculation/Rate.php b/app/code/Magento/Tax/Model/Calculation/Rate.php index f639d6680a7fc..9e44cd113ddea 100644 --- a/app/code/Magento/Tax/Model/Calculation/Rate.php +++ b/app/code/Magento/Tax/Model/Calculation/Rate.php @@ -227,7 +227,7 @@ public function afterDelete() */ public function saveTitles($titles = null) { - if (is_null($titles)) { + if ($titles === null) { $titles = $this->getTitle(); } @@ -256,7 +256,7 @@ public function saveTitles($titles = null) */ public function getTitleModel() { - if (is_null($this->_titleModel)) { + if ($this->_titleModel === null) { $this->_titleModel = $this->_titleFactory->create(); } return $this->_titleModel; @@ -270,7 +270,7 @@ public function getTitles() if ($this->getData(self::KEY_TITLES)) { return $this->getData(self::KEY_TITLES); } - if (is_null($this->_titles)) { + if ($this->_titles === null) { $this->_titles = $this->getTitleModel()->getCollection()->loadByRateId($this->getId())->getItems(); } return $this->_titles; diff --git a/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php b/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php index 1fcaf0e413cd5..7752911ceb22f 100644 --- a/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php +++ b/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php @@ -771,7 +771,7 @@ protected function _saveAppliedTaxes( $previouslyAppliedTaxes[$row['id']] = $row; } - if (!is_null($row['percent'])) { + if ($row['percent'] !== null) { $row['percent'] = $row['percent'] ? $row['percent'] : 1; $rate = $rate ? $rate : 1; diff --git a/app/code/Magento/Theme/Block/Html/Topmenu.php b/app/code/Magento/Theme/Block/Html/Topmenu.php index 7747576988077..ed3445e117331 100644 --- a/app/code/Magento/Theme/Block/Html/Topmenu.php +++ b/app/code/Magento/Theme/Block/Html/Topmenu.php @@ -235,7 +235,13 @@ protected function _getHtml( if ($childLevel == 0 && $outermostClass) { $outermostClassCode = ' class="' . $outermostClass . '" '; - $child->setClass($outermostClass); + $currentClass = $child->getClass(); + + if (empty($currentClass)) { + $child->setClass($outermostClass); + } else { + $child->setClass($currentClass . ' ' . $outermostClass); + } } if (count($colBrakes) && $colBrakes[$counter]['colbrake']) { diff --git a/app/code/Magento/Theme/etc/config.xml b/app/code/Magento/Theme/etc/config.xml index 925b906af9523..a6984b449d944 100644 --- a/app/code/Magento/Theme/etc/config.xml +++ b/app/code/Magento/Theme/etc/config.xml @@ -45,7 +45,7 @@ Disallow: /*SID= Default welcome msg!
    - Copyright © 2013-2017 Magento, Inc. All rights reserved. + Copyright © 2013-present Magento, Inc. All rights reserved.
    diff --git a/app/code/Magento/Theme/i18n/en_US.csv b/app/code/Magento/Theme/i18n/en_US.csv index 16cc5596b3a93..b454b7222bd1c 100644 --- a/app/code/Magento/Theme/i18n/en_US.csv +++ b/app/code/Magento/Theme/i18n/en_US.csv @@ -142,7 +142,7 @@ Empty,Empty "1 column","1 column" Configuration,Configuration "Default welcome msg!","Default welcome msg!" -"Copyright © 2013-2017 Magento, Inc. All rights reserved.","Copyright © 2013-2017 Magento, Inc. All rights reserved." +"Copyright © 2013-present Magento, Inc. All rights reserved.","Copyright © 2013-present Magento, Inc. All rights reserved." "Design Config Grid","Design Config Grid" "Rebuild design config grid index","Rebuild design config grid index" "Admin empty","Admin empty" diff --git a/app/code/Magento/Translation/Model/Js/DataProvider.php b/app/code/Magento/Translation/Model/Js/DataProvider.php index 6212647110941..9037574e28a5b 100644 --- a/app/code/Magento/Translation/Model/Js/DataProvider.php +++ b/app/code/Magento/Translation/Model/Js/DataProvider.php @@ -46,7 +46,7 @@ class DataProvider implements DataProviderInterface /** * Basic translate renderer * - * @var \Magento\Framework\Phrase\Renderer\Translate + * @var \Magento\Framework\Phrase\RendererInterface */ protected $translate; @@ -54,7 +54,7 @@ class DataProvider implements DataProviderInterface * @param \Magento\Framework\App\State $appState * @param Config $config * @param \Magento\Framework\Filesystem\File\ReadFactory $fileReadFactory - * @param \Magento\Framework\Phrase\Renderer\Translate $translate + * @param \Magento\Framework\Phrase\RendererInterface $translate * @param \Magento\Framework\Component\ComponentRegistrar $componentRegistrar * @param \Magento\Framework\Component\DirSearch $dirSearch * @param \Magento\Framework\View\Design\Theme\ThemePackageList $themePackageList @@ -64,7 +64,7 @@ public function __construct( \Magento\Framework\App\State $appState, Config $config, \Magento\Framework\Filesystem\File\ReadFactory $fileReadFactory, - \Magento\Framework\Phrase\Renderer\Translate $translate, + \Magento\Framework\Phrase\RendererInterface $translate, \Magento\Framework\Component\ComponentRegistrar $componentRegistrar, \Magento\Framework\Component\DirSearch $dirSearch, \Magento\Framework\View\Design\Theme\ThemePackageList $themePackageList, diff --git a/app/code/Magento/Ui/Component/ExportButton.php b/app/code/Magento/Ui/Component/ExportButton.php index 9d5f125839003..284362a119bad 100644 --- a/app/code/Magento/Ui/Component/ExportButton.php +++ b/app/code/Magento/Ui/Component/ExportButton.php @@ -54,11 +54,13 @@ public function getComponentName() */ public function prepare() { + $context = $this->getContext(); $config = $this->getData('config'); if (isset($config['options'])) { $options = []; foreach ($config['options'] as $option) { - $option['url'] = $this->urlBuilder->getUrl($option['url']); + $additionalParams = $this->getAdditionalParams($config, $context); + $option['url'] = $this->urlBuilder->getUrl($option['url'], $additionalParams); $options[] = $option; } $config['options'] = $options; @@ -66,4 +68,25 @@ public function prepare() } parent::prepare(); } + + /** + * Get export button additional parameters + * + * @param array $config + * @param ContextInterface $context + * @return array + */ + protected function getAdditionalParams($config, $context) + { + $additionalParams = []; + if (isset($config['additionalParams'])) { + foreach ($config['additionalParams'] as $paramName => $paramValue) { + if ('*' == $paramValue) { + $paramValue = $context->getRequestParam($paramName); + } + $additionalParams[$paramName] = $paramValue; + } + } + return $additionalParams; + } } diff --git a/app/code/Magento/Ui/Component/Filters.php b/app/code/Magento/Ui/Component/Filters.php index 3085485521cd3..fe02c23af9c8a 100644 --- a/app/code/Magento/Ui/Component/Filters.php +++ b/app/code/Magento/Ui/Component/Filters.php @@ -82,7 +82,7 @@ public function update(UiComponentInterface $component) return; } - if (isset($this->filterMap[$filterType])) { + if (isset($this->filterMap[$filterType]) && !isset($this->columnFilters[$component->getName()])) { $filterComponent = $this->uiComponentFactory->create( $component->getName(), $this->filterMap[$filterType], diff --git a/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php b/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php index cda3106a14f49..bd1dde10f8eed 100644 --- a/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php +++ b/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php @@ -119,7 +119,7 @@ public function __toString() protected function wrapContent($content) { return ''; } } diff --git a/app/code/Magento/Ui/Test/Unit/Component/FiltersTest.php b/app/code/Magento/Ui/Test/Unit/Component/FiltersTest.php new file mode 100644 index 0000000000000..3935842aa642b --- /dev/null +++ b/app/code/Magento/Ui/Test/Unit/Component/FiltersTest.php @@ -0,0 +1,79 @@ +uiComponentInterface = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponentInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->uiComponentFactory = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponentFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->context = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\ContextInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->filters = $objectManager->getObject( + Filters::class, + [ + 'columnFilters' => ['select' => $this->uiComponentInterface], + 'uiComponentFactory' => $this->uiComponentFactory, + 'context' => $this->context + ] + ); + } + + public function testUpdate() + { + $componentName = 'component_name'; + $componentConfig = [0, 1, 2]; + $columnInterface = $this->getMockBuilder(\Magento\Ui\Component\Listing\Columns\ColumnInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getData', 'getName', 'getConfiguration']) + ->getMockForAbstractClass(); + $columnInterface->expects($this->atLeastOnce())->method('getData')->with('config/filter')->willReturn('text'); + $columnInterface->expects($this->atLeastOnce())->method('getName')->willReturn($componentName); + $columnInterface->expects($this->once())->method('getConfiguration')->willReturn($componentConfig); + $filterComponent = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponentInterface::class) + ->disableOriginalConstructor() + ->setMethods(['setData', 'prepare']) + ->getMockForAbstractClass(); + $filterComponent->expects($this->once())->method('setData')->with('config', $componentConfig) + ->willReturnSelf(); + $filterComponent->expects($this->once())->method('prepare')->willReturnSelf(); + $this->uiComponentFactory->expects($this->once())->method('create') + ->with($componentName, 'filterInput', ['context' => $this->context]) + ->willReturn($filterComponent); + + $this->filters->update($columnInterface); + /** Verify that filter is already set and it wouldn't be set again */ + $this->filters->update($columnInterface); + } +} diff --git a/app/code/Magento/Ui/Test/Unit/TemplateEngine/Xhtml/ResultTest.php b/app/code/Magento/Ui/Test/Unit/TemplateEngine/Xhtml/ResultTest.php new file mode 100644 index 0000000000000..e51062e1cb36b --- /dev/null +++ b/app/code/Magento/Ui/Test/Unit/TemplateEngine/Xhtml/ResultTest.php @@ -0,0 +1,97 @@ +template = $this->createPartialMock(Template::class, ['append']); + $this->compiler = $this->createMock(CompilerInterface::class); + $this->component = $this->createMock(UiComponentInterface::class); + $this->structure = $this->createPartialMock(Structure::class, ['generate']); + $this->logger = $this->createMock(LoggerInterface::class); + + $this->objectManager = new ObjectManager($this); + $this->testModel = $this->objectManager->getObject(Result::class, [ + 'template' => $this->template, + 'compiler' => $this->compiler, + 'component' => $this->component, + 'structure' => $this->structure, + 'logger' => $this->logger, + ]); + } + + /** + * Test Append layout configuration method + */ + public function testAppendLayoutConfiguration() + { + $configWithCdata = 'text before '; + $this->structure->expects($this->once()) + ->method('generate') + ->with($this->component) + ->willReturn([$configWithCdata]); + $this->template->expects($this->once()) + ->method('append') + ->with(''); + + $this->testModel->appendLayoutConfiguration(); + } +} diff --git a/app/code/Magento/Ui/i18n/en_US.csv b/app/code/Magento/Ui/i18n/en_US.csv index 225d83387563b..cff52a3fd6fed 100644 --- a/app/code/Magento/Ui/i18n/en_US.csv +++ b/app/code/Magento/Ui/i18n/en_US.csv @@ -192,7 +192,7 @@ CSV,CSV "Please enter a valid value from list","Please enter a valid value from list" "Please enter valid SKU key.","Please enter valid SKU key." "Please enter a valid number.","Please enter a valid number." -"Admin is a required field in the each row.","Admin is a required field in the each row." +"Admin is a required field in each row.","Admin is a required field in each row." "Please fix this field.","Please fix this field." "Please enter a valid date (ISO).","Please enter a valid date (ISO)." "Please enter only digits.","Please enter only digits." diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/html.js b/app/code/Magento/Ui/view/base/web/js/form/components/html.js index 82e51aff40287..466b6840e9b19 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/html.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/html.js @@ -19,6 +19,7 @@ define([ showSpinner: false, loading: false, visible: true, + error: false, template: 'ui/content/content', additionalClasses: {} }, diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/region.js b/app/code/Magento/Ui/view/base/web/js/form/element/region.js index 1b2ce8889af10..0edb4c1966b54 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/region.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/region.js @@ -35,6 +35,11 @@ define([ return; } option = options[value]; + + if (typeof option === 'undefined') { + return; + } + defaultPostCodeResolver.setUseDefaultPostCode(!option['is_zipcode_optional']); if (this.skipValidation) { diff --git a/app/code/Magento/Ui/view/base/web/templates/content/content.html b/app/code/Magento/Ui/view/base/web/templates/content/content.html index 8cf47120865dc..62e5959a02559 100644 --- a/app/code/Magento/Ui/view/base/web/templates/content/content.html +++ b/app/code/Magento/Ui/view/base/web/templates/content/content.html @@ -17,3 +17,5 @@ + +