diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/StatusBaseSelectProcessor.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/StatusBaseSelectProcessor.php index 656998113fdb9..b4293a3989585 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/StatusBaseSelectProcessor.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/StatusBaseSelectProcessor.php @@ -11,6 +11,8 @@ use Magento\Eav\Model\Config; use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; +use Magento\Store\Api\StoreResolverInterface; +use Magento\Store\Model\Store; /** * Class StatusBaseSelectProcessor @@ -27,16 +29,24 @@ class StatusBaseSelectProcessor implements BaseSelectProcessorInterface */ private $metadataPool; + /** + * @var StoreResolverInterface + */ + private $storeResolver; + /** * @param Config $eavConfig * @param MetadataPool $metadataPool + * @param StoreResolverInterface $storeResolver */ public function __construct( Config $eavConfig, - MetadataPool $metadataPool + MetadataPool $metadataPool, + StoreResolverInterface $storeResolver ) { $this->eavConfig = $eavConfig; $this->metadataPool = $metadataPool; + $this->storeResolver = $storeResolver; } /** @@ -48,13 +58,23 @@ public function process(Select $select) $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); $statusAttribute = $this->eavConfig->getAttribute(Product::ENTITY, ProductInterface::STATUS); - $select->join( + $select->joinLeft( + ['status_global_attr' => $statusAttribute->getBackendTable()], + "status_global_attr.{$linkField} = " . self::PRODUCT_TABLE_ALIAS . ".{$linkField}" + . ' AND status_global_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId() + . ' AND status_global_attr.store_id = ' . Store::DEFAULT_STORE_ID, + [] + ); + + $select->joinLeft( ['status_attr' => $statusAttribute->getBackendTable()], - sprintf('status_attr.%s = %s.%1$s', $linkField, self::PRODUCT_TABLE_ALIAS), + "status_attr.{$linkField} = " . self::PRODUCT_TABLE_ALIAS . ".{$linkField}" + . ' AND status_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId() + . ' AND status_attr.store_id = ' . $this->storeResolver->getCurrentStoreId(), [] - ) - ->where('status_attr.attribute_id = ?', $statusAttribute->getAttributeId()) - ->where('status_attr.value = ?', Status::STATUS_ENABLED); + ); + + $select->where('IFNULL(status_attr.value, status_global_attr.value) = ?', Status::STATUS_ENABLED); return $select; } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php index 0909f754a01c2..1fada997913b6 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php @@ -16,7 +16,12 @@ use Magento\Framework\EntityManager\EntityMetadataInterface; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Store\Api\StoreResolverInterface; +use Magento\Store\Model\Store; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class StatusBaseSelectProcessorTest extends \PHPUnit_Framework_TestCase { /** @@ -29,6 +34,11 @@ class StatusBaseSelectProcessorTest extends \PHPUnit_Framework_TestCase */ private $metadataPool; + /** + * @var StoreResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeResolver; + /** * @var Select|\PHPUnit_Framework_MockObject_MockObject */ @@ -43,11 +53,13 @@ protected function setUp() { $this->eavConfig = $this->getMockBuilder(Config::class)->disableOriginalConstructor()->getMock(); $this->metadataPool = $this->getMockBuilder(MetadataPool::class)->disableOriginalConstructor()->getMock(); + $this->storeResolver = $this->getMockBuilder(StoreResolverInterface::class)->getMock(); $this->select = $this->getMockBuilder(Select::class)->disableOriginalConstructor()->getMock(); $this->statusBaseSelectProcessor = (new ObjectManager($this))->getObject(StatusBaseSelectProcessor::class, [ 'eavConfig' => $this->eavConfig, 'metadataPool' => $this->metadataPool, + 'storeResolver' => $this->storeResolver, ]); } @@ -55,7 +67,8 @@ public function testProcess() { $linkField = 'link_field'; $backendTable = 'backend_table'; - $attributeId = 'attribute_id'; + $attributeId = 2; + $currentStoreId = 1; $metadata = $this->getMock(EntityMetadataInterface::class); $metadata->expects($this->once()) @@ -66,13 +79,14 @@ public function testProcess() ->with(ProductInterface::class) ->willReturn($metadata); + /** @var AttributeInterface|\PHPUnit_Framework_MockObject_MockObject $statusAttribute */ $statusAttribute = $this->getMockBuilder(AttributeInterface::class) ->setMethods(['getBackendTable', 'getAttributeId']) ->getMock(); - $statusAttribute->expects($this->once()) + $statusAttribute->expects($this->atLeastOnce()) ->method('getBackendTable') ->willReturn($backendTable); - $statusAttribute->expects($this->once()) + $statusAttribute->expects($this->atLeastOnce()) ->method('getAttributeId') ->willReturn($attributeId); $this->eavConfig->expects($this->once()) @@ -80,21 +94,34 @@ public function testProcess() ->with(Product::ENTITY, ProductInterface::STATUS) ->willReturn($statusAttribute); - $this->select->expects($this->once()) - ->method('join') + $this->storeResolver->expects($this->once()) + ->method('getCurrentStoreId') + ->willReturn($currentStoreId); + + $this->select->expects($this->at(0)) + ->method('joinLeft') ->with( - ['status_attr' => $backendTable], - sprintf('status_attr.%s = %s.%1$s', $linkField, BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), + ['status_global_attr' => $backendTable], + "status_global_attr.{$linkField} = " + . BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . ".{$linkField}" + . " AND status_global_attr.attribute_id = {$attributeId}" + . ' AND status_global_attr.store_id = ' . Store::DEFAULT_STORE_ID, [] ) ->willReturnSelf(); $this->select->expects($this->at(1)) - ->method('where') - ->with('status_attr.attribute_id = ?', $attributeId) + ->method('joinLeft') + ->with( + ['status_attr' => $backendTable], + "status_attr.{$linkField} = " . BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . ".{$linkField}" + . " AND status_attr.attribute_id = {$attributeId}" + . " AND status_attr.store_id = {$currentStoreId}", + [] + ) ->willReturnSelf(); $this->select->expects($this->at(2)) ->method('where') - ->with('status_attr.value = ?', Status::STATUS_ENABLED) + ->with('IFNULL(status_attr.value, status_global_attr.value) = ?', Status::STATUS_ENABLED) ->willReturnSelf(); $this->assertEquals($this->select, $this->statusBaseSelectProcessor->process($this->select)); diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php index fbb69798ff94f..ed4863342a967 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php @@ -9,9 +9,45 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Store\Api\StoreResolverInterface; +use Magento\Store\Model\Store; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class Configurable extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice { + /** + * @var StoreResolverInterface + */ + private $storeResolver; + + /** + * Class constructor + * + * @param \Magento\Framework\Model\ResourceModel\Db\Context $context + * @param \Magento\Framework\Indexer\Table\StrategyInterface $tableStrategy + * @param \Magento\Eav\Model\Config $eavConfig + * @param \Magento\Framework\Event\ManagerInterface $eventManager + * @param \Magento\Framework\Module\Manager $moduleManager + * @param string $connectionName + * @param StoreResolverInterface $storeResolver + */ + public function __construct( + \Magento\Framework\Model\ResourceModel\Db\Context $context, + \Magento\Framework\Indexer\Table\StrategyInterface $tableStrategy, + \Magento\Eav\Model\Config $eavConfig, + \Magento\Framework\Event\ManagerInterface $eventManager, + \Magento\Framework\Module\Manager $moduleManager, + $connectionName = null, + StoreResolverInterface $storeResolver = null + ) { + parent::__construct($context, $tableStrategy, $eavConfig, $eventManager, $moduleManager, $connectionName); + $this->storeResolver = $storeResolver ?: + \Magento\Framework\App\ObjectManager::getInstance()->get(StoreResolverInterface::class); + } + /** * Reindex temporary (price result data) for all products * @@ -190,16 +226,21 @@ protected function _applyConfigurableOption() [] )->where( 'le.required_options=0' - )->join( - ['product_status' => $this->getTable($statusAttribute->getBackend()->getTable())], - sprintf( - 'le.%1$s = product_status.%1$s AND product_status.attribute_id = %2$s', - $linkField, - $statusAttribute->getAttributeId() - ), + )->joinLeft( + ['status_global_attr' => $statusAttribute->getBackendTable()], + "status_global_attr.{$linkField} = le.{$linkField}" + . ' AND status_global_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId() + . ' AND status_global_attr.store_id = ' . Store::DEFAULT_STORE_ID, + [] + )->joinLeft( + ['status_attr' => $statusAttribute->getBackendTable()], + "status_attr.{$linkField} = le.{$linkField}" + . ' AND status_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId() + . ' AND status_attr.store_id = ' . $this->storeResolver->getCurrentStoreId(), [] )->where( - 'product_status.value=' . ProductStatus::STATUS_ENABLED + 'IFNULL(status_attr.value, status_global_attr.value) = ?', + Status::STATUS_ENABLED )->group( ['e.entity_id', 'i.customer_group_id', 'i.website_id', 'l.product_id'] ); diff --git a/app/code/Magento/Customer/Model/Address/AbstractAddress.php b/app/code/Magento/Customer/Model/Address/AbstractAddress.php index 7b32da94ddedb..786c5f9770a2e 100644 --- a/app/code/Magento/Customer/Model/Address/AbstractAddress.php +++ b/app/code/Magento/Customer/Model/Address/AbstractAddress.php @@ -554,6 +554,8 @@ public function getDataModel($defaultBillingAddressId = null, $defaultShippingAd /** * Validate address attribute values * + * + * * @return bool|array * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -562,23 +564,24 @@ public function validate() { $errors = []; if (!\Zend_Validate::is($this->getFirstname(), 'NotEmpty')) { - $errors[] = __('Please enter the first name.'); + $errors[] = __('%fieldName is a required field.', ['fieldName' => 'firstname']); } if (!\Zend_Validate::is($this->getLastname(), 'NotEmpty')) { - $errors[] = __('Please enter the last name.'); + $errors[] = __('%fieldName is a required field.', ['fieldName' => 'lastname']); } if (!\Zend_Validate::is($this->getStreetLine(1), 'NotEmpty')) { - $errors[] = __('Please enter the street.'); + $errors[] = __('%fieldName is a required field.', ['fieldName' => 'street']); } if (!\Zend_Validate::is($this->getCity(), 'NotEmpty')) { - $errors[] = __('Please enter the city.'); + $errors[] = __('%fieldName is a required field.', ['fieldName' => 'city']); } if (!\Zend_Validate::is($this->getTelephone(), 'NotEmpty')) { - $errors[] = __('Please enter the phone number.'); + $errors[] = __('%fieldName is a required field.', ['fieldName' => 'telephone']); + } $_havingOptionalZip = $this->_directoryData->getCountriesWithOptionalZip(); @@ -590,11 +593,11 @@ public function validate() 'NotEmpty' ) ) { - $errors[] = __('Please enter the zip/postal code.'); + $errors[] = __('%fieldName is a required field.', ['fieldName' => 'postcode']); } if (!\Zend_Validate::is($this->getCountryId(), 'NotEmpty')) { - $errors[] = __('Please enter the country.'); + $errors[] = __('%fieldName is a required field.', ['fieldName' => 'countryId']); } if ($this->getCountryModel()->getRegionCollection()->getSize() && !\Zend_Validate::is( @@ -604,7 +607,7 @@ public function validate() $this->getCountryId() ) ) { - $errors[] = __('Please enter the state/province.'); + $errors[] = __('%fieldName is a required field.', ['fieldName' => 'regionId']); } if (empty($errors) || $this->getShouldIgnoreValidation()) { diff --git a/app/code/Magento/Customer/Model/ResourceModel/AddressRepository.php b/app/code/Magento/Customer/Model/ResourceModel/AddressRepository.php index 67d4ea32369ba..24df57e07e848 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/AddressRepository.php +++ b/app/code/Magento/Customer/Model/ResourceModel/AddressRepository.php @@ -124,8 +124,12 @@ public function save(\Magento\Customer\Api\Data\AddressInterface $address) $addressModel->updateData($address); } - $inputException = $this->_validate($addressModel); - if ($inputException->wasErrorAdded()) { + $errors = $addressModel->validate(); + if ($errors !== true) { + $inputException = new InputException(); + foreach ($errors as $error) { + $inputException->addError($error); + } throw $inputException; } $addressModel->save(); @@ -255,70 +259,6 @@ public function deleteById($addressId) return true; } - /** - * Validate Customer Addresses attribute values. - * - * @param CustomerAddressModel $customerAddressModel the model to validate - * @return InputException - * - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - private function _validate(CustomerAddressModel $customerAddressModel) - { - $exception = new InputException(); - if ($customerAddressModel->getShouldIgnoreValidation()) { - return $exception; - } - - if (!\Zend_Validate::is($customerAddressModel->getFirstname(), 'NotEmpty')) { - $exception->addError(__('%fieldName is a required field.', ['fieldName' => 'firstname'])); - } - - if (!\Zend_Validate::is($customerAddressModel->getLastname(), 'NotEmpty')) { - $exception->addError(__('%fieldName is a required field.', ['fieldName' => 'lastname'])); - } - - if (!\Zend_Validate::is($customerAddressModel->getStreetLine(1), 'NotEmpty')) { - $exception->addError(__('%fieldName is a required field.', ['fieldName' => 'street'])); - } - - if (!\Zend_Validate::is($customerAddressModel->getCity(), 'NotEmpty')) { - $exception->addError(__('%fieldName is a required field.', ['fieldName' => 'city'])); - } - - if (!\Zend_Validate::is($customerAddressModel->getTelephone(), 'NotEmpty')) { - $exception->addError(__('%fieldName is a required field.', ['fieldName' => 'telephone'])); - } - - $havingOptionalZip = $this->directoryData->getCountriesWithOptionalZip(); - if (!in_array($customerAddressModel->getCountryId(), $havingOptionalZip) - && !\Zend_Validate::is($customerAddressModel->getPostcode(), 'NotEmpty') - ) { - $exception->addError(__('%fieldName is a required field.', ['fieldName' => 'postcode'])); - } - - if (!\Zend_Validate::is($customerAddressModel->getCountryId(), 'NotEmpty')) { - $exception->addError(__('%fieldName is a required field.', ['fieldName' => 'countryId'])); - } - - if ($this->directoryData->isRegionRequired($customerAddressModel->getCountryId())) { - $regionCollection = $customerAddressModel->getCountryModel()->getRegionCollection(); - if (!$regionCollection->count() && empty($customerAddressModel->getRegion())) { - $exception->addError(__('%fieldName is a required field.', ['fieldName' => 'region'])); - } elseif ( - $regionCollection->count() - && !in_array( - $customerAddressModel->getRegionId(), - array_column($regionCollection->getData(), 'region_id') - ) - ) { - $exception->addError(__('%fieldName is a required field.', ['fieldName' => 'regionId'])); - } - } - return $exception; - } - /** * Retrieve collection processor * diff --git a/app/code/Magento/Customer/Test/Unit/Model/Address/AbstractAddressTest.php b/app/code/Magento/Customer/Test/Unit/Model/Address/AbstractAddressTest.php index 32112ccdeb1ae..ba833221aba18 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Address/AbstractAddressTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Address/AbstractAddressTest.php @@ -336,31 +336,31 @@ public function validateDataProvider() return [ 'firstname' => [ array_merge(array_diff_key($data, ['firstname' => '']), ['country_id' => $countryId++]), - ['Please enter the first name.'], + ['firstname is a required field.'], ], 'lastname' => [ array_merge(array_diff_key($data, ['lastname' => '']), ['country_id' => $countryId++]), - ['Please enter the last name.'], + ['lastname is a required field.'], ], 'street' => [ array_merge(array_diff_key($data, ['street' => '']), ['country_id' => $countryId++]), - ['Please enter the street.'], + ['street is a required field.'], ], 'city' => [ array_merge(array_diff_key($data, ['city' => '']), ['country_id' => $countryId++]), - ['Please enter the city.'], + ['city is a required field.'], ], 'telephone' => [ array_merge(array_diff_key($data, ['telephone' => '']), ['country_id' => $countryId++]), - ['Please enter the phone number.'], + ['telephone is a required field.'], ], 'postcode' => [ array_merge(array_diff_key($data, ['postcode' => '']), ['country_id' => $countryId++]), - ['Please enter the zip/postal code.'], + ['postcode is a required field.'], ], 'country_id' => [ array_diff_key($data, ['country_id' => '']), - ['Please enter the country.'], + ['countryId is a required field.'], ], 'validated' => [array_merge($data, ['country_id' => $countryId++]), true], ]; diff --git a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/AddressRepositoryTest.php b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/AddressRepositoryTest.php index b72a5f6a417d4..5f6bc315acd16 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/AddressRepositoryTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/AddressRepositoryTest.php @@ -128,6 +128,7 @@ protected function setUp() 'setCustomer', 'getCountryModel', 'getShouldIgnoreValidation', + 'validate', 'save', 'getDataModel', 'getCustomerId', @@ -191,6 +192,9 @@ public function testSave() $this->address->expects($this->once()) ->method('setCustomer') ->with($this->customer); + $this->address->expects($this->once()) + ->method('validate') + ->willReturn(true); $this->address->expects($this->once()) ->method('save'); $this->addressRegistry->expects($this->once()) @@ -208,9 +212,6 @@ public function testSave() $this->address->expects($this->once()) ->method('getDataModel') ->willReturn($customerAddress); - $this->address->expects($this->once()) - ->method('getShouldIgnoreValidation') - ->willReturn(true); $this->repository->save($customerAddress); } @@ -222,6 +223,7 @@ public function testSaveWithException() { $customerId = 34; $addressId = 53; + $errors[] = __('Please enter the state/province.'); $customerAddress = $this->getMockForAbstractClass( \Magento\Customer\Api\Data\AddressInterface::class, [], @@ -245,7 +247,9 @@ public function testSaveWithException() $this->address->expects($this->once()) ->method('updateData') ->with($customerAddress); - $this->prepareMocksForInvalidAddressValidation(); + $this->address->expects($this->once()) + ->method('validate') + ->willReturn($errors); $this->repository->save($customerAddress); } @@ -258,6 +262,7 @@ public function testSaveWithInvalidRegion() { $customerId = 34; $addressId = 53; + $errors[] = __('region is a required field.'); $customerAddress = $this->getMockForAbstractClass( \Magento\Customer\Api\Data\AddressInterface::class, [], @@ -281,60 +286,13 @@ public function testSaveWithInvalidRegion() $this->address->expects($this->once()) ->method('updateData') ->with($customerAddress); - $countryModel = $this->getMock(\Magento\Directory\Model\Country::class, [], [], '', false); - $regionCollection = $this->getMock( - \Magento\Directory\Model\ResourceModel\Region\Collection::class, - [], - [], - '', - false - ); - $this->address->expects($this->once()) - ->method('getShouldIgnoreValidation') - ->willReturn(false); - $this->address->expects($this->atLeastOnce()) - ->method('getCountryId') - ->willReturn(1); - $this->address->expects($this->once()) - ->method('getFirstname') - ->willReturn('firstname'); - $this->address->expects($this->once()) - ->method('getLastname') - ->willReturn('lastname'); - $this->address->expects($this->once()) - ->method('getStreetLine') - ->with(1) - ->willReturn('street line'); - $this->address->expects($this->once()) - ->method('getCity') - ->willReturn('city'); - $this->address->expects($this->once()) - ->method('getTelephone') - ->willReturn('23423423423'); $this->address->expects($this->never()) ->method('getRegionId') ->willReturn(null); - - $this->directoryData->expects($this->once()) - ->method('getCountriesWithOptionalZip') - ->willReturn([1]); - $this->address->expects($this->once()) - ->method('getCountryModel') - ->willReturn($countryModel); - $countryModel->expects($this->once()) - ->method('getRegionCollection') - ->willReturn($regionCollection); - $regionCollection->expects($this->once()) - ->method('count') - ->willReturn(0); - $this->directoryData->expects($this->once()) - ->method('isRegionRequired') - ->with(1) - ->willReturn(true); $this->address->expects($this->once()) - ->method('getRegion') - ->willReturn(''); + ->method('validate') + ->willReturn($errors); $this->repository->save($customerAddress); } @@ -347,6 +305,7 @@ public function testSaveWithInvalidRegionId() { $customerId = 34; $addressId = 53; + $errors[] = __('regionId is a required field.'); $customerAddress = $this->getMockForAbstractClass( \Magento\Customer\Api\Data\AddressInterface::class, [], @@ -370,114 +329,14 @@ public function testSaveWithInvalidRegionId() $this->address->expects($this->once()) ->method('updateData') ->with($customerAddress); - $countryModel = $this->getMock(\Magento\Directory\Model\Country::class, [], [], '', false); - $regionCollection = $this->getMock( - \Magento\Directory\Model\ResourceModel\Region\Collection::class, - [], - [], - '', - false - ); - - $this->address->expects($this->once()) - ->method('getShouldIgnoreValidation') - ->willReturn(false); - $this->address->expects($this->atLeastOnce()) - ->method('getCountryId') - ->willReturn(1); - $this->address->expects($this->once()) - ->method('getFirstname') - ->willReturn('firstname'); - $this->address->expects($this->once()) - ->method('getLastname') - ->willReturn('lastname'); - $this->address->expects($this->once()) - ->method('getStreetLine') - ->with(1) - ->willReturn('street line'); - $this->address->expects($this->once()) - ->method('getCity') - ->willReturn('city'); - $this->address->expects($this->once()) - ->method('getTelephone') - ->willReturn('23423423423'); - $this->address->expects($this->once()) - ->method('getRegionId') - ->willReturn(2); - - $this->directoryData->expects($this->once()) - ->method('getCountriesWithOptionalZip') - ->willReturn([1]); - $this->address->expects($this->once()) - ->method('getCountryModel') - ->willReturn($countryModel); - $countryModel->expects($this->once()) - ->method('getRegionCollection') - ->willReturn($regionCollection); - $regionCollection->expects($this->atLeastOnce()) - ->method('count') - ->willReturn(2); - $regionCollection->expects($this->once()) - ->method('getData') - ->willReturn([5, 6, 7, 8, 9]); - $this->directoryData->expects($this->once()) - ->method('isRegionRequired') - ->with(1) - ->willReturn(true); $this->address->expects($this->never()) ->method('getRegion') ->willReturn(''); - - $this->repository->save($customerAddress); - } - - protected function prepareMocksForInvalidAddressValidation() - { - $countryModel = $this->getMock(\Magento\Directory\Model\Country::class, [], [], '', false); - $regionCollection = $this->getMock( - \Magento\Directory\Model\ResourceModel\Region\Collection::class, - [], - [], - '', - false - ); - - $this->address->expects($this->once()) - ->method('getShouldIgnoreValidation') - ->willReturn(false); - $this->address->expects($this->atLeastOnce()) - ->method('getCountryId'); - $this->address->expects($this->once()) - ->method('getFirstname'); - $this->address->expects($this->once()) - ->method('getLastname'); - $this->address->expects($this->once()) - ->method('getStreetLine') - ->with(1); $this->address->expects($this->once()) - ->method('getCity'); - $this->address->expects($this->once()) - ->method('getTelephone'); - $this->address->expects($this->never()) - ->method('getRegionId') - ->willReturn(null); + ->method('validate') + ->willReturn($errors); - $this->directoryData->expects($this->once()) - ->method('getCountriesWithOptionalZip') - ->willReturn([]); - $this->address->expects($this->once()) - ->method('getCountryModel') - ->willReturn($countryModel); - $countryModel->expects($this->once()) - ->method('getRegionCollection') - ->willReturn($regionCollection); - $regionCollection->expects($this->once()) - ->method('count') - ->willReturn(0); - $this->directoryData->expects($this->once()) - ->method('isRegionRequired') - ->with(null) - ->willReturn(true); + $this->repository->save($customerAddress); } public function testGetById() diff --git a/app/code/Magento/Review/etc/di.xml b/app/code/Magento/Review/etc/di.xml index d42d3e3330c67..13f7d0b84bc50 100644 --- a/app/code/Magento/Review/etc/di.xml +++ b/app/code/Magento/Review/etc/di.xml @@ -26,4 +26,9 @@ Magento\Framework\Url + + + Magento\Customer\Model\Session\Proxy + + diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index c1d2fd3f91051..cd9794dc1757d 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -259,6 +259,9 @@ define([ // whether swatches are rendered in product list or on product page inProductList: false, + // sly-old-price block selector + slyOldPriceSelector: '.sly-old-price', + // tier prise selectors start tierPriceTemplateSelector: '#tier-prices-template', tierPriceBlockSelector: '[data-role="tier-price-block"]', @@ -837,6 +840,12 @@ define([ } ); + if (result.oldPrice.amount !== result.finalPrice.amount) { + $(this.options.slyOldPriceSelector).show(); + } else { + $(this.options.slyOldPriceSelector).hide(); + } + if (result.tierPrices.length) { if (this.options.tierPriceTemplate) { tierPriceHtml = mageTemplate( diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Attribute/CustomAttribute.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Attribute/CustomAttribute.php index 53fe49b759c64..3c324de0cabab 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Attribute/CustomAttribute.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Attribute/CustomAttribute.php @@ -102,7 +102,7 @@ private function getElementByClass($class) { $element = null; foreach (array_keys($this->classReferences) as $key) { - if (strpos($class, $key) !== false) { + if ($class == $key) { return $this->classReferences[$class]; } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/etc/di.xml index 82a7c263b1ca9..fee6efedff2e2 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/etc/di.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/etc/di.xml @@ -167,7 +167,7 @@ [name="product[%code%]"] null - + [name="product[%code%]"] datepicker diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php index 5db97cc366149..2e339e4347cf6 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php @@ -67,4 +67,40 @@ public function testGetProductFinalPriceIfOneOfChildIsDisabled() ->getFirstItem(); $this->assertEquals(20, $configurableProduct->getMinimalPrice()); } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + */ + public function testGetProductFinalPriceIfOneOfChildIsDisabledPerStore() + { + /** @var Collection $collection */ + $collection = Bootstrap::getObjectManager()->get(CollectionFactory::class) + ->create(); + $configurableProduct = $collection + ->addIdFilter([1]) + ->addMinimalPrice() + ->load() + ->getFirstItem(); + $this->assertEquals(10, $configurableProduct->getMinimalPrice()); + + $childProduct = $this->productRepository->getById(10, false, null, true); + $childProduct->setStatus(Status::STATUS_DISABLED); + + // update in default store scope + $currentStoreId = $this->storeManager->getStore()->getId(); + $defaultStore = $this->storeManager->getDefaultStoreView(); + $this->storeManager->setCurrentStore($defaultStore->getId()); + $this->productRepository->save($childProduct); + $this->storeManager->setCurrentStore($currentStoreId); + + /** @var Collection $collection */ + $collection = Bootstrap::getObjectManager()->get(CollectionFactory::class) + ->create(); + $configurableProduct = $collection + ->addIdFilter([1]) + ->addMinimalPrice() + ->load() + ->getFirstItem(); + $this->assertEquals(20, $configurableProduct->getMinimalPrice()); + } } diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionProviderTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionProviderTest.php index 50787c7962412..58df9f50f7f0c 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionProviderTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionProviderTest.php @@ -68,6 +68,38 @@ public function testGetProductsIfOneOfChildIsDisabled() $this->assertEquals(20, $lowestPriceChildrenProduct->getPrice()); } + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + */ + public function testGetProductsIfOneOfChildIsDisabledPerStore() + { + $configurableProduct = $this->productRepository->getById(1, false, null, true); + $lowestPriceChildrenProducts = $this->lowestPriceOptionsProvider->getProducts($configurableProduct); + $this->assertCount(1, $lowestPriceChildrenProducts); + $lowestPriceChildrenProduct = reset($lowestPriceChildrenProducts); + $this->assertEquals(10, $lowestPriceChildrenProduct->getPrice()); + + // load full aggregation root + $lowestPriceChildProduct = $this->productRepository->get( + $lowestPriceChildrenProduct->getSku(), + false, + null, + true + ); + $lowestPriceChildProduct->setStatus(Status::STATUS_DISABLED); + // update in default store scope + $currentStoreId = $this->storeManager->getStore()->getId(); + $defaultStore = $this->storeManager->getDefaultStoreView(); + $this->storeManager->setCurrentStore($defaultStore->getId()); + $this->productRepository->save($lowestPriceChildProduct); + $this->storeManager->setCurrentStore($currentStoreId); + + $lowestPriceChildrenProducts = $this->lowestPriceOptionsProvider->getProducts($configurableProduct); + $this->assertCount(1, $lowestPriceChildrenProducts); + $lowestPriceChildrenProduct = reset($lowestPriceChildrenProducts); + $this->assertEquals(20, $lowestPriceChildrenProduct->getPrice()); + } + /** * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php */