diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 7297f3a6c22991215efa0b07ab37087ff7dd9bf9..0eb01d6a252ea20107b04aae6250cbd31ab979c3 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -198,7 +198,7 @@ <resource>Magento_Config::config_general</resource> <group id="country" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Country Options</label> - <field id="allow" translate="label" type="multiselect" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="allow" translate="label" type="multiselect" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> <label>Allow Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php index 7ba330f5a060c615b74bcb6cba86e5116e36f767..321870b96d32abf13c48fdc0975efc9ba5248f17 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php @@ -148,10 +148,14 @@ class VariationHandler \Magento\Catalog\Model\Product $parentProduct, $postData ) { + $typeId = isset($postData['weight']) && !empty($postData['weight']) + ? ProductType::TYPE_SIMPLE + : ProductType::TYPE_VIRTUAL; + $product->setStoreId( \Magento\Store\Model\Store::DEFAULT_STORE_ID )->setTypeId( - $postData['weight'] ? ProductType::TYPE_SIMPLE : ProductType::TYPE_VIRTUAL + $typeId )->setAttributeSetId( $parentProduct->getNewVariationsAttributeSetId() ); diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/VariationHandlerTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/VariationHandlerTest.php index fb991879bbca391abc170e55c061762ab6aa4f2c..62f5a8089e350d247a75595d24214a08745acf47 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/VariationHandlerTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/VariationHandlerTest.php @@ -8,6 +8,7 @@ namespace Magento\ConfigurableProduct\Test\Unit\Model\Product; +use Magento\Catalog\Model\Product\Type; use Magento\ConfigurableProduct\Model\Product\VariationHandler; /** @@ -162,23 +163,30 @@ class VariationHandlerTest extends \PHPUnit_Framework_TestCase /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @dataProvider dataProviderTestGenerateSimpleProducts + * @param int|string|null $weight + * @param string $typeId */ - public function testGenerateSimpleProducts() + public function testGenerateSimpleProducts($weight, $typeId) { $productsData = [ - 6 => - [ - 'image' => 'image.jpg', - 'name' => 'config-red', - 'configurable_attribute' => '{"new_attr":"6"}', - 'sku' => 'config-red', - 'quantity_and_stock_status' => - [ - 'qty' => '', - ], - 'weight' => '333', - ] + [ + 'image' => 'image.jpg', + 'name' => 'config-red', + 'configurable_attribute' => '{"new_attr":"6"}', + 'sku' => 'config-red', + 'quantity_and_stock_status' => + [ + 'qty' => '', + ], + ] ]; + + // Do not add 'weight' attribute if it's value is null! + if ($weight !== null) { + $productsData[0]['weight'] = $weight; + } + $stockData = [ 'manage_stock' => '0', 'use_config_enable_qty_increments' => '1', @@ -218,7 +226,7 @@ class VariationHandlerTest extends \PHPUnit_Framework_TestCase ) ->disableOriginalConstructor() ->getMock(); - $productTypeMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Type::class) + $productTypeMock = $this->getMockBuilder(Type::class) ->setMethods(['getSetAttributes']) ->disableOriginalConstructor() ->getMock(); @@ -236,7 +244,7 @@ class VariationHandlerTest extends \PHPUnit_Framework_TestCase ->willReturn('new_attr_set_id'); $this->productFactoryMock->expects($this->once())->method('create')->willReturn($newSimpleProductMock); $newSimpleProductMock->expects($this->once())->method('setStoreId')->with(0)->willReturnSelf(); - $newSimpleProductMock->expects($this->once())->method('setTypeId')->with('simple')->willReturnSelf(); + $newSimpleProductMock->expects($this->once())->method('setTypeId')->with($typeId)->willReturnSelf(); $newSimpleProductMock->expects($this->once()) ->method('setAttributeSetId') ->with('new_attr_set_id') @@ -265,6 +273,27 @@ class VariationHandlerTest extends \PHPUnit_Framework_TestCase $this->assertEquals(['product_id'], $this->model->generateSimpleProducts($parentProductMock, $productsData)); } + /** + * @return array + */ + public function dataProviderTestGenerateSimpleProducts() + { + return [ + [ + 'weight' => 333, + 'type_id' => Type::TYPE_SIMPLE, + ], + [ + 'weight' => '', + 'type_id' => Type::TYPE_VIRTUAL, + ], + [ + 'weight' => null, + 'type_id' => Type::TYPE_VIRTUAL, + ], + ]; + } + public function testProcessMediaGalleryWithImagesAndGallery() { $this->product->expects($this->atLeastOnce())->method('getMediaGallery')->with('images')->willReturn([]); diff --git a/app/code/Magento/Customer/Model/Customer/DataProvider.php b/app/code/Magento/Customer/Model/Customer/DataProvider.php index 2c1f3bdb2388c8cca9b227c9fdb0131fb8f008ec..2f46459a794eca284e91c1dddf3c2e39def0cd9f 100644 --- a/app/code/Magento/Customer/Model/Customer/DataProvider.php +++ b/app/code/Magento/Customer/Model/Customer/DataProvider.php @@ -7,9 +7,12 @@ namespace Magento\Customer\Model\Customer; use Magento\Customer\Api\AddressMetadataInterface; use Magento\Customer\Api\CustomerMetadataInterface; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Api\Data\CustomerInterface; use Magento\Customer\Model\Attribute; use Magento\Customer\Model\FileProcessor; use Magento\Customer\Model\FileProcessorFactory; +use Magento\Customer\Model\ResourceModel\Address\Attribute\Source\CountryWithWebsites; use Magento\Eav\Api\Data\AttributeInterface; use Magento\Eav\Model\Config; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; @@ -55,6 +58,16 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider */ protected $loadedData; + /** + * @var CountryWithWebsites + */ + private $countryWithWebsiteSource; + + /** + * @var \Magento\Customer\Model\Config\Share + */ + private $shareConfig; + /** * EAV attribute properties to fetch from meta storage * @var array @@ -117,6 +130,7 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider * @param FileProcessorFactory $fileProcessorFactory * @param array $meta * @param array $data + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( $name, @@ -234,6 +248,7 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider * @param Attribute $attribute * @param array $customerData * @return array + * @SuppressWarnings(PHPMD.NPathComplexity) */ private function getFileUploaderData( Type $entityType, @@ -292,6 +307,7 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider $this->processFrontendInput($attribute, $meta); $code = $attribute->getAttributeCode(); + // use getDataUsingMethod, since some getters are defined and apply additional processing of returning value foreach ($this->metaProperties as $metaName => $origName) { $value = $attribute->getDataUsingMethod($origName); @@ -304,7 +320,12 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider } if ($attribute->usesSource()) { - $meta[$code]['arguments']['data']['config']['options'] = $attribute->getSource()->getAllOptions(); + if ($code == AddressInterface::COUNTRY_ID) { + $meta[$code]['arguments']['data']['config']['options'] = $this->getCountryWithWebsiteSource() + ->getAllOptions(); + } else { + $meta[$code]['arguments']['data']['config']['options'] = $attribute->getSource()->getAllOptions(); + } } $rules = $this->eavValidationRules->build($attribute, $meta[$code]['arguments']['data']['config']); @@ -315,9 +336,61 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider $this->overrideFileUploaderMetadata($entityType, $attribute, $meta[$code]['arguments']['data']['config']); } + + $this->processWebsiteMeta($meta); return $meta; } + /** + * Retrieve Country With Websites Source + * + * @deprecated + * @return CountryWithWebsites + */ + private function getCountryWithWebsiteSource() + { + if (!$this->countryWithWebsiteSource) { + $this->countryWithWebsiteSource = ObjectManager::getInstance()->get(CountryWithWebsites::class); + } + + return $this->countryWithWebsiteSource; + } + + /** + * Retrieve Customer Config Share + * + * @deprecated + * @return \Magento\Customer\Model\Config\Share + */ + private function getShareConfig() + { + if (!$this->shareConfig) { + $this->shareConfig = ObjectManager::getInstance()->get(\Magento\Customer\Model\Config\Share::class); + } + + return $this->shareConfig; + } + + /** + * Add global scope parameter and filter options to website meta + * + * @param array $meta + * @return void + */ + private function processWebsiteMeta(&$meta) + { + if (isset($meta[CustomerInterface::WEBSITE_ID]) && $this->getShareConfig()->isGlobalScope()) { + $meta[CustomerInterface::WEBSITE_ID]['arguments']['data']['config']['isGlobalScope'] = 1; + } + + if (isset($meta[AddressInterface::COUNTRY_ID]) && !$this->getShareConfig()->isGlobalScope()) { + $meta[AddressInterface::COUNTRY_ID]['arguments']['data']['config']['filterBy'] = [ + 'target' => '${ $.provider }:data.customer.website_id', + 'field' => 'website_ids' + ]; + } + } + /** * Override file uploader UI component metadata * diff --git a/app/code/Magento/Customer/Model/Plugin/AllowedCountries.php b/app/code/Magento/Customer/Model/Plugin/AllowedCountries.php new file mode 100644 index 0000000000000000000000000000000000000000..157643bdfe5cc070b24b6ed4fe7ce19c6458ba37 --- /dev/null +++ b/app/code/Magento/Customer/Model/Plugin/AllowedCountries.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Model\Plugin; + +use Magento\Customer\Model\Config\Share; +use Magento\Store\Api\Data\WebsiteInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Class AllowedCountries + */ +class AllowedCountries +{ + /** + * @var \Magento\Customer\Model\Config\Share + */ + private $shareConfig; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param Share $share + * @param StoreManagerInterface $storeManager + */ + public function __construct( + Share $share, + StoreManagerInterface $storeManager + ) { + $this->shareConfig = $share; + $this->storeManager = $storeManager; + } + + /** + * Retrieve all allowed countries or specific by scope depends on customer share setting + * + * @param \Magento\Directory\Model\AllowedCountries $subject + * @param string | null $filter + * @param string $scope + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeGetAllowedCountries( + \Magento\Directory\Model\AllowedCountries $subject, + $scope = ScopeInterface::SCOPE_WEBSITE, + $scopeCode = null + ) { + if ($this->shareConfig->isGlobalScope()) { + //Check if we have shared accounts - than merge all website allowed countries + $scopeCode = array_map(function (WebsiteInterface $website) { + return $website->getId(); + }, $this->storeManager->getWebsites()); + $scope = ScopeInterface::SCOPE_WEBSITES; + } + + return [$scope, $scopeCode]; + } +} diff --git a/app/code/Magento/Customer/Model/ResourceModel/Address/Attribute/Source/Country.php b/app/code/Magento/Customer/Model/ResourceModel/Address/Attribute/Source/Country.php index 72a18afa07a27ae7653c849fbbbc44c55badacb0..13d23d91880aac2ba0c122e88a1e02352f3f9137 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/Address/Attribute/Source/Country.php +++ b/app/code/Magento/Customer/Model/ResourceModel/Address/Attribute/Source/Country.php @@ -11,6 +11,15 @@ */ namespace Magento\Customer\Model\ResourceModel\Address\Attribute\Source; +use Magento\Checkout\Model\Session; +use Magento\Framework\App\ObjectManager; +use Magento\Store\Api\StoreResolverInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Class Country. + * @package Magento\Customer\Model\ResourceModel\Address\Attribute\Source + */ class Country extends \Magento\Eav\Model\Entity\Attribute\Source\Table { /** @@ -18,6 +27,11 @@ class Country extends \Magento\Eav\Model\Entity\Attribute\Source\Table */ protected $_countriesFactory; + /** + * @var StoreResolverInterface + */ + private $storeResolver; + /** * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\CollectionFactory $attrOptionCollectionFactory * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\OptionFactory $attrOptionFactory @@ -41,7 +55,7 @@ class Country extends \Magento\Eav\Model\Entity\Attribute\Source\Table { if (!$this->_options) { $this->_options = $this->_createCountriesCollection()->loadByStore( - $this->getAttribute()->getStoreId() + $this->getStoreResolver()->getCurrentStoreId() )->toOptionArray(); } return $this->_options; @@ -54,4 +68,18 @@ class Country extends \Magento\Eav\Model\Entity\Attribute\Source\Table { return $this->_countriesFactory->create(); } + + /** + * Retrieve Store Resolver + * @deprecated + * @return StoreResolverInterface + */ + private function getStoreResolver() + { + if (!$this->storeResolver) { + $this->storeResolver = ObjectManager::getInstance()->get(StoreResolverInterface::class); + } + + return $this->storeResolver; + } } diff --git a/app/code/Magento/Customer/Model/ResourceModel/Address/Attribute/Source/CountryWithWebsites.php b/app/code/Magento/Customer/Model/ResourceModel/Address/Attribute/Source/CountryWithWebsites.php new file mode 100644 index 0000000000000000000000000000000000000000..16ceee8a3f28dd91145915cefefaab842ab50c6e --- /dev/null +++ b/app/code/Magento/Customer/Model/ResourceModel/Address/Attribute/Source/CountryWithWebsites.php @@ -0,0 +1,117 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * Customer country with website specified attribute source + * + * @author Magento Core Team <core@magentocommerce.com> + */ +namespace Magento\Customer\Model\ResourceModel\Address\Attribute\Source; + +use Magento\Customer\Model\Config\Share; +use Magento\Directory\Model\AllowedCountries; +use Magento\Store\Model\ScopeInterface; + +class CountryWithWebsites extends \Magento\Eav\Model\Entity\Attribute\Source\Table +{ + /** + * @var \Magento\Directory\Model\ResourceModel\Country\CollectionFactory + */ + private $countriesFactory; + + /** + * @var \Magento\Directory\Model\AllowedCountries + */ + private $allowedCountriesReader; + + /** + * @var array + */ + private $options; + + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + + /** + * @var Share + */ + private $shareConfig; + + /** + * CountryWithWebsites constructor. + * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\CollectionFactory $attrOptionCollectionFactory + * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\OptionFactory $attrOptionFactory + * @param \Magento\Directory\Model\ResourceModel\Country\CollectionFactory $countriesFactory + * @param AllowedCountries $allowedCountriesReader + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param Share $shareConfig + */ + public function __construct( + \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\CollectionFactory $attrOptionCollectionFactory, + \Magento\Eav\Model\ResourceModel\Entity\Attribute\OptionFactory $attrOptionFactory, + \Magento\Directory\Model\ResourceModel\Country\CollectionFactory $countriesFactory, + \Magento\Directory\Model\AllowedCountries $allowedCountriesReader, + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Magento\Customer\Model\Config\Share $shareConfig + ) { + $this->countriesFactory = $countriesFactory; + $this->allowedCountriesReader = $allowedCountriesReader; + $this->storeManager = $storeManager; + $this->shareConfig = $shareConfig; + parent::__construct($attrOptionCollectionFactory, $attrOptionFactory); + } + + /** + * Retrieve all options + * + * @return array + */ + public function getAllOptions() + { + if (!$this->options) { + $allowedCountries = []; + $websiteIds = []; + + if (!$this->shareConfig->isGlobalScope()) { + foreach ($this->storeManager->getWebsites() as $website) { + $countries = $this->allowedCountriesReader + ->getAllowedCountries(ScopeInterface::SCOPE_WEBSITE, $website->getId()); + $allowedCountries = array_merge($allowedCountries, $countries); + + foreach ($countries as $countryCode) { + $websiteIds[$countryCode][] = $website->getId(); + } + } + } else { + $allowedCountries = $this->allowedCountriesReader->getAllowedCountries(); + } + + $this->options = $this->createCountriesCollection() + ->addFieldToFilter('country_id', ['in' => $allowedCountries]) + ->toOptionArray(); + + foreach ($this->options as &$option) { + if (isset($websiteIds[$option['value']])) { + $option['website_ids'] = $websiteIds[$option['value']]; + } + } + } + + return $this->options; + } + + /** + * Create Countries Collection with all countries + * + * @return \Magento\Directory\Model\ResourceModel\Country\Collection + */ + private function createCountriesCollection() + { + return $this->countriesFactory->create(); + } +} diff --git a/app/code/Magento/Customer/Setup/UpgradeData.php b/app/code/Magento/Customer/Setup/UpgradeData.php index 4c4452f1a4c81d1ec4e2f8031518cf87049f7c35..c03a1dec02d80e2ff01dc83ac2fa6a5ff156552c 100644 --- a/app/code/Magento/Customer/Setup/UpgradeData.php +++ b/app/code/Magento/Customer/Setup/UpgradeData.php @@ -7,11 +7,16 @@ namespace Magento\Customer\Setup; use Magento\Customer\Model\Customer; +use Magento\Directory\Model\AllowedCountries; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Encryption\Encryptor; use Magento\Framework\Indexer\IndexerRegistry; +use Magento\Framework\Setup\SetupInterface; use Magento\Framework\Setup\UpgradeDataInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; /** * @codeCoverageIgnore @@ -26,6 +31,11 @@ class UpgradeData implements UpgradeDataInterface */ protected $customerSetupFactory; + /** + * @var AllowedCountries + */ + private $allowedCountriesReader; + /** * @var IndexerRegistry */ @@ -36,6 +46,11 @@ class UpgradeData implements UpgradeDataInterface */ protected $eavConfig; + /** + * @var StoreManagerInterface + */ + private $storeManager; + /** * @param CustomerSetupFactory $customerSetupFactory * @param IndexerRegistry $indexerRegistry @@ -106,12 +121,140 @@ class UpgradeData implements UpgradeDataInterface $this->upgradeCustomerPasswordResetlinkExpirationPeriodConfig($setup); } + if (version_compare($context->getVersion(), '2.0.9', '<')) { + $setup->getConnection()->beginTransaction(); + + try { + $this->migrateStoresAllowedCountriesToWebsite($setup); + $setup->getConnection()->commit(); + } catch (\Exception $e) { + $setup->getConnection()->rollBack(); + throw $e; + } + } + $indexer = $this->indexerRegistry->get(Customer::CUSTOMER_GRID_INDEXER_ID); $indexer->reindexAll(); $this->eavConfig->clear(); $setup->endSetup(); } + /** + * Retrieve Store Manager + * + * @deprecated + * @return StoreManagerInterface + */ + private function getStoreManager() + { + if (!$this->storeManager) { + $this->storeManager = ObjectManager::getInstance()->get(StoreManagerInterface::class); + } + + return $this->storeManager; + } + + /** + * Retrieve Allowed Countries Reader + * + * @deprecated + * @return AllowedCountries + */ + private function getAllowedCountriesReader() + { + if (!$this->allowedCountriesReader) { + $this->allowedCountriesReader = ObjectManager::getInstance()->get(AllowedCountries::class); + } + + return $this->allowedCountriesReader; + } + + /** + * Merge allowed countries between different scopes + * + * @param array $countries + * @param array $newCountries + * @param string $identifier + * @return array + */ + private function mergeAllowedCountries(array $countries, array $newCountries, $identifier) + { + if (!isset($countries[$identifier])) { + $countries[$identifier] = $newCountries; + } else { + $countries[$identifier] = + array_replace($countries[$identifier], $newCountries); + } + + return $countries; + } + + /** + * Retrieve countries not depending on global scope + * + * @param string $scope + * @param int $scopeCode + * @return array + */ + private function getAllowedCountries($scope, $scopeCode) + { + $reader = $this->getAllowedCountriesReader(); + return $reader->makeCountriesUnique($reader->getCountriesFromConfig($scope, $scopeCode)); + } + + /** + * Merge allowed countries from stores to websites + * + * @param SetupInterface $setup + * @return void + */ + private function migrateStoresAllowedCountriesToWebsite(SetupInterface $setup) + { + $allowedCountries = []; + //Process Websites + foreach ($this->getStoreManager()->getStores() as $store) { + $allowedCountries = $this->mergeAllowedCountries( + $allowedCountries, + $this->getAllowedCountries(ScopeInterface::SCOPE_STORE, $store->getId()), + $store->getWebsiteId() + ); + } + //Process stores + foreach ($this->getStoreManager()->getWebsites() as $website) { + $allowedCountries = $this->mergeAllowedCountries( + $allowedCountries, + $this->getAllowedCountries(ScopeInterface::SCOPE_WEBSITE, $website->getId()), + $website->getId() + ); + } + + $connection = $setup->getConnection(); + + //Remove everything from stores scope + $connection->delete( + $setup->getTable('core_config_data'), + [ + 'path = ?' => AllowedCountries::ALLOWED_COUNTRIES_PATH, + 'scope = ?' => ScopeInterface::SCOPE_STORES + ] + ); + + //Update websites + foreach ($allowedCountries as $scopeId => $countries) { + $connection->update( + $setup->getTable('core_config_data'), + [ + 'value' => implode(',', $countries) + ], + [ + 'path = ?' => AllowedCountries::ALLOWED_COUNTRIES_PATH, + 'scope_id = ?' => $scopeId, + 'scope = ?' => ScopeInterface::SCOPE_WEBSITES + ] + ); + } + } + /** * @param array $entityAttributes * @param CustomerSetup $customerSetup diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php index 5e022dd8feddb252146f43fff55a1c1b716dad49..e594432b5bd66358a2ed13f3d93d41f26f73a7bd 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php @@ -6,6 +6,8 @@ namespace Magento\Customer\Test\Unit\Model\Customer; use Magento\Customer\Api\CustomerMetadataInterface; +use Magento\Customer\Model\Config\Share; +use Magento\Customer\Model\ResourceModel\Address\Attribute\Source\CountryWithWebsites; use Magento\Eav\Model\Config; use Magento\Eav\Model\Entity\Type; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; @@ -103,6 +105,7 @@ class DataProviderTest extends \PHPUnit_Framework_TestCase public function testGetAttributesMetaWithOptions(array $expected) { $helper = new ObjectManager($this); + /** @var \Magento\Customer\Model\Customer\DataProvider $dataProvider */ $dataProvider = $helper->getObject( \Magento\Customer\Model\Customer\DataProvider::class, [ @@ -227,6 +230,29 @@ class DataProviderTest extends \PHPUnit_Framework_TestCase ], ], ], + 'country_id' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'dataType' => 'frontend_input', + 'formElement' => 'frontend_input', + 'options' => 'test-options', + 'visible' => 'is_visible', + 'required' => 'is_required', + 'label' => __('frontend_label'), + 'sortOrder' => 'sort_order', + 'notice' => 'note', + 'default' => 'default_value', + 'size' => 'multiline_count', + 'componentType' => Field::NAME, + 'filterBy' => [ + 'target' => '${ $.provider }:data.customer.website_id', + 'field' => 'website_ids' + ] + ], + ], + ], + ] ], ], ] @@ -298,7 +324,7 @@ class DataProviderTest extends \PHPUnit_Framework_TestCase $typeAddressMock->expects($this->once()) ->method('getAttributeCollection') - ->willReturn($this->getAttributeMock()); + ->willReturn($this->getAttributeMock('address')); return $typeAddressMock; } @@ -306,7 +332,7 @@ class DataProviderTest extends \PHPUnit_Framework_TestCase /** * @return AbstractAttribute[]|\PHPUnit_Framework_MockObject_MockObject[] */ - protected function getAttributeMock() + protected function getAttributeMock($type = 'customer') { $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) ->setMethods(['getAttributeCode', 'getDataUsingMethod', 'usesSource', 'getSource']) @@ -365,10 +391,66 @@ class DataProviderTest extends \PHPUnit_Framework_TestCase [$attributeMock, $this->logicalNot($this->isEmpty()), []], [$attributeBooleanMock, $this->logicalNot($this->isEmpty()), []], ]); + $mocks = [$attributeMock, $attributeBooleanMock]; + + if ($type == "address") { + $mocks[] = $this->getCountryAttrMock(); + } + return $mocks; + } + + private function getCountryAttrMock() + { + $countryByWebsiteMock = $this->getMockBuilder(CountryWithWebsites::class) + ->disableOriginalConstructor() + ->getMock(); + $countryByWebsiteMock->expects($this->any()) + ->method('getAllOptions') + ->willReturn('test-options'); + $shareMock = $this->getMockBuilder(Share::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManagerInterface::class); + $objectManagerMock->expects($this->any()) + ->method('get') + ->willReturnMap([ + [CountryWithWebsites::class, $countryByWebsiteMock], + [Share::class, $shareMock], + ]); + \Magento\Framework\App\ObjectManager::setInstance($objectManagerMock); + $countryAttrMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) + ->setMethods(['getAttributeCode', 'getDataUsingMethod', 'usesSource', 'getSource']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $countryAttrMock->expects($this->exactly(2)) + ->method('getAttributeCode') + ->willReturn('country_id'); - return [$attributeMock, $attributeBooleanMock]; + $countryAttrMock->expects($this->any()) + ->method('getDataUsingMethod') + ->willReturnCallback( + function ($origName) { + return $origName; + } + ); + $countryAttrMock->expects($this->any()) + ->method('getLabel') + ->willReturn(__('frontend_label')); + $countryAttrMock->expects($this->any()) + ->method('usesSource') + ->willReturn(true); + $countryAttrMock->expects($this->any()) + ->method('getSource') + ->willReturn(null); + + return $countryAttrMock; } + /** + * @return void + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ public function testGetData() { $customer = $this->getMockBuilder(\Magento\Customer\Model\Customer::class) @@ -478,6 +560,10 @@ class DataProviderTest extends \PHPUnit_Framework_TestCase ); } + /** + * @return void + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ public function testGetDataWithCustomerFormData() { $customerId = 11; @@ -591,6 +677,10 @@ class DataProviderTest extends \PHPUnit_Framework_TestCase $this->assertEquals([$customerId => $customerFormData], $dataProvider->getData()); } + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @return void + */ public function testGetDataWithCustomAttributeImage() { $customerId = 1; @@ -812,6 +902,10 @@ class DataProviderTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedData, $dataProvider->getData()); } + /** + * @return void + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ public function testGetAttributesMetaWithCustomAttributeImage() { $maxFileSize = 1000; diff --git a/app/code/Magento/Customer/Test/Unit/Model/Plugin/AllowedCountriesTest.php b/app/code/Magento/Customer/Test/Unit/Model/Plugin/AllowedCountriesTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e332d5deca626fb198669feb23011aa0e92a3be1 --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Model/Plugin/AllowedCountriesTest.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Test\Unit\Model\Plugin; + +use Magento\Customer\Model\Config\Share; +use Magento\Customer\Model\Plugin\AllowedCountries; +use Magento\Store\Api\Data\WebsiteInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; + +class AllowedCountriesTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Customer\Model\Config\Share | \PHPUnit_Framework_MockObject_MockObject + */ + private $shareConfig; + + /** + * @var StoreManagerInterface | \PHPUnit_Framework_MockObject_MockObject + */ + private $storeManager; + + /** @var AllowedCountries */ + private $plugin; + + public function setUp() + { + $this->shareConfig = $this->getMockBuilder(Share::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeManager = $this->getMock(StoreManagerInterface::class); + + $this->plugin = new AllowedCountries($this->shareConfig, $this->storeManager); + } + + public function testGetAllowedCountriesWithGlobalScope() + { + $expectedFilter = 1; + $expectedScope = ScopeInterface::SCOPE_WEBSITES; + + $this->shareConfig->expects($this->once()) + ->method('isGlobalScope') + ->willReturn(true); + $originalAllowedCountriesMock = $this->getMockBuilder(\Magento\Directory\Model\AllowedCountries::class) + ->disableOriginalConstructor() + ->getMock(); + $websiteMock = $this->getMock(WebsiteInterface::class); + $websiteMock->expects($this->once()) + ->method('getId') + ->willReturn($expectedFilter); + $this->storeManager->expects($this->once()) + ->method('getWebsites') + ->willReturn([$websiteMock]); + + $this->assertEquals( + [$expectedScope, [$expectedFilter]], + $this->plugin->beforeGetAllowedCountries($originalAllowedCountriesMock) + ); + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/Address/Attribute/Source/CountryWithWebsitesTest.php b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/Address/Attribute/Source/CountryWithWebsitesTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c91070b139ad6700cee3d032bde087359ff9ad3e --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/Address/Attribute/Source/CountryWithWebsitesTest.php @@ -0,0 +1,120 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Test\Unit\Model\ResourceModel\Address\Attribute\Source; + +use Magento\Customer\Model\Config\Share; +use Magento\Customer\Model\ResourceModel\Address\Attribute\Source\CountryWithWebsites; +use Magento\Directory\Model\AllowedCountries; +use Magento\Framework\Data\Collection\AbstractDb; +use Magento\Store\Api\Data\WebsiteInterface; +use Magento\Store\Model\StoreManagerInterface; + +class CountryWithWebsitesTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Directory\Model\ResourceModel\Country\CollectionFactory | \PHPUnit_Framework_MockObject_MockObject + */ + private $countriesFactoryMock; + + /** + * @var \Magento\Directory\Model\AllowedCountries | \PHPUnit_Framework_MockObject_MockObject + */ + private $allowedCountriesMock; + + /** + * @var \Magento\Store\Model\StoreManagerInterface | \PHPUnit_Framework_MockObject_MockObject + */ + private $storeManagerMock; + + /** + * @var CountryWithWebsites + */ + private $countryByWebsite; + + /** + * @var Share | \PHPUnit_Framework_MockObject_MockObject + */ + private $shareConfigMock; + + public function setUp() + { + $this->countriesFactoryMock = + $this->getMockBuilder(\Magento\Directory\Model\ResourceModel\Country\CollectionFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->allowedCountriesMock = $this->getMockBuilder(AllowedCountries::class) + ->disableOriginalConstructor() + ->getMock(); + $eavCollectionFactoryMock = + $this->getMockBuilder(\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\CollectionFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $optionsFactoryMock = + $this->getMockBuilder(\Magento\Eav\Model\ResourceModel\Entity\Attribute\OptionFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeManagerMock = $this->getMock(StoreManagerInterface::class); + $this->shareConfigMock = $this->getMockBuilder(Share::class) + ->disableOriginalConstructor() + ->getMock(); + $this->countryByWebsite = new CountryWithWebsites( + $eavCollectionFactoryMock, + $optionsFactoryMock, + $this->countriesFactoryMock, + $this->allowedCountriesMock, + $this->storeManagerMock, + $this->shareConfigMock + ); + } + + public function testGetAllOptions() + { + $website1 = $this->getMock(WebsiteInterface::class); + $website2 = $this->getMock(WebsiteInterface::class); + + $website1->expects($this->atLeastOnce()) + ->method('getId') + ->willReturn(1); + $website2->expects($this->atLeastOnce()) + ->method('getId') + ->willReturn(2); + $this->storeManagerMock->expects($this->once()) + ->method('getWebsites') + ->willReturn([$website1, $website2]); + $collectionMock = $this->getMockBuilder(AbstractDb::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->allowedCountriesMock->expects($this->exactly(2)) + ->method('getAllowedCountries') + ->withConsecutive( + ['website', 1], + ['website', 2] + ) + ->willReturnMap([ + ['website', 1, ['AM' => 'AM']], + ['website', 2, ['AM' => 'AM', 'DZ' => 'DZ']] + ]); + $this->countriesFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($collectionMock); + $collectionMock->expects($this->once()) + ->method('addFieldToFilter') + ->with('country_id', ['in' => ['AM' => 'AM', 'DZ' => 'DZ']]) + ->willReturnSelf(); + $collectionMock->expects($this->once()) + ->method('toOptionArray') + ->willReturn([ + ['value' => 'AM', 'label' => 'UZ'] + ]); + + $this->assertEquals([ + ['value' => 'AM', 'label' => 'UZ', 'website_ids' => [1, 2]] + ], $this->countryByWebsite->getAllOptions()); + } +} diff --git a/app/code/Magento/Customer/etc/di.xml b/app/code/Magento/Customer/etc/di.xml index ebde958f75bc1f678aafd7ccd677c1536f76ef69..b45d00ad0cdddcc1c46645569b92c7848feb8cff 100644 --- a/app/code/Magento/Customer/etc/di.xml +++ b/app/code/Magento/Customer/etc/di.xml @@ -311,6 +311,9 @@ <type name="Magento\Customer\Api\CustomerRepositoryInterface"> <plugin name="transactionWrapper" type="Magento\Customer\Model\Plugin\CustomerRepository\TransactionWrapper" sortOrder="-1"/> </type> + <type name="Magento\Directory\Model\AllowedCountries"> + <plugin name="customerAllowedCountries" type="Magento\Customer\Model\Plugin\AllowedCountries"/> + </type> <type name="Magento\Framework\App\Action\AbstractAction"> <plugin name="customerNotification" type="Magento\Customer\Model\Plugin\CustomerNotification"/> </type> diff --git a/app/code/Magento/Customer/etc/module.xml b/app/code/Magento/Customer/etc/module.xml index 085320e80d2c68e20fd832c21626da841ad264e1..fd8307fc36657d8aacc419b2ad48006969cf68f0 100644 --- a/app/code/Magento/Customer/etc/module.xml +++ b/app/code/Magento/Customer/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Customer" setup_version="2.0.8"> + <module name="Magento_Customer" setup_version="2.0.9"> <sequence> <module name="Magento_Eav"/> <module name="Magento_Directory"/> diff --git a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml index fdb9c3f317a4cb04fa1b9908ccde4ca98874ceb3..61d2900af4bcbc19be1417c99d1d3ecb3f9bdf30 100644 --- a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml +++ b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml @@ -98,7 +98,11 @@ <item name="config" xsi:type="array"> <item name="dataType" xsi:type="string">number</item> <item name="formElement" xsi:type="string">select</item> + <item name="component" xsi:type="string">Magento_Ui/js/form/element/website</item> <item name="source" xsi:type="string">customer</item> + <item name="imports" xsi:type="array"> + <item name="customerId" xsi:type="string">${ $.provider }:data.customer.entity_id</item> + </item> <item name="validation" xsi:type="array"> <item name="required-entry" xsi:type="boolean">true</item> </item> @@ -373,10 +377,14 @@ <item name="config" xsi:type="array"> <item name="dataType" xsi:type="string">text</item> <item name="formElement" xsi:type="string">select</item> + <item name="component" xsi:type="string">Magento_Ui/js/form/element/country</item> <item name="source" xsi:type="string">address</item> <item name="validation" xsi:type="array"> <item name="required-entry" xsi:type="boolean">true</item> </item> + <item name="imports" xsi:type="array"> + <item name="default" xsi:type="string">${ $.provider }:data.customer.website_id</item> + </item> </item> </argument> </field> diff --git a/app/code/Magento/Directory/Model/AllowedCountries.php b/app/code/Magento/Directory/Model/AllowedCountries.php new file mode 100644 index 0000000000000000000000000000000000000000..9c8b0c51c2e0ee280527858d8bb9639bf4908385 --- /dev/null +++ b/app/code/Magento/Directory/Model/AllowedCountries.php @@ -0,0 +1,150 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Directory\Model; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Data\Collection\AbstractDb; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Class CountryHandler + */ +class AllowedCountries +{ + const ALLOWED_COUNTRIES_PATH = 'general/country/allow'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @return void + */ + public function __construct( + ScopeConfigInterface $scopeConfig, + StoreManagerInterface $storeManager + ) { + $this->scopeConfig = $scopeConfig; + $this->storeManager = $storeManager; + } + + /** + * Retrieve all allowed countries for scope or scopes + * + * @param string | null $scopeCode + * @param string $scope + * @return array + */ + public function getAllowedCountries( + $scope = ScopeInterface::SCOPE_WEBSITE, + $scopeCode = null + ) { + if (empty($scopeCode)) { + $scopeCode = $this->getDefaultScopeCode($scope); + } + + switch ($scope) { + case ScopeInterface::SCOPE_WEBSITES: + case ScopeInterface::SCOPE_STORES: + $allowedCountries = []; + foreach ($scopeCode as $singleFilter) { + $allowedCountries = array_merge( + $allowedCountries, + $this->getCountriesFromConfig($this->getSingleScope($scope), $singleFilter) + ); + } + break; + default: + $allowedCountries = $this->getCountriesFromConfig($scope, $scopeCode); + } + + return $this->makeCountriesUnique($allowedCountries); + } + + /** + * Resolve scope code by scope + * + * @throws \InvalidArgumentException + * @param string $scope + * @return array|int + */ + private function getDefaultScopeCode($scope) + { + switch ($scope) { + case ScopeInterface::SCOPE_WEBSITE: + return $this->storeManager->getWebsite()->getId(); + case ScopeInterface::SCOPE_STORE: + return $this->storeManager->getStore()->getId(); + case ScopeInterface::SCOPE_GROUP: + return $this->storeManager->getGroup()->getId(); + case ScopeInterface::SCOPE_WEBSITES: + return [$this->storeManager->getWebsite()->getId()]; + case ScopeInterface::SCOPE_STORES: + return [$this->storeManager->getStore()->getId()]; + default: + throw new \InvalidArgumentException("Invalid scope is specified"); + } + } + + /** + * Return Unique Countries by merging them by keys + * + * @param array $allowedCountries + * @return array + */ + public function makeCountriesUnique(array $allowedCountries) + { + return array_combine($allowedCountries, $allowedCountries); + } + + /** + * Takes countries from Countries Config data + * + * @param string $scope + * @param int $scopeCode + * @return array + */ + public function getCountriesFromConfig($scope, $scopeCode) + { + return explode( + ',', + (string) $this->scopeConfig->getValue( + self::ALLOWED_COUNTRIES_PATH, + $scope, + $scopeCode + ) + ); + } + + /** + * Return Single Scope + * + * @param string $scope + * @return string + */ + private function getSingleScope($scope) + { + if ($scope == ScopeInterface::SCOPE_WEBSITES) { + return ScopeInterface::SCOPE_WEBSITE; + } + + if ($scope == ScopeInterface::SCOPE_STORES) { + return ScopeInterface::SCOPE_STORE; + } + + return $scope; + } +} diff --git a/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php b/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php index 70dbfe9b2b9bcb2b3ffa2944a36e5dd93cce2b57..1093e41b6da61037732eb40c29c40dfea78cb671 100644 --- a/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php +++ b/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php @@ -10,6 +10,9 @@ * Directory Country Resource Collection */ namespace Magento\Directory\Model\ResourceModel\Country; +use Magento\Directory\Model\AllowedCountries; +use Magento\Framework\App\ObjectManager; +use Magento\Store\Model\ScopeInterface; /** * Class Collection @@ -53,6 +56,11 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab */ protected $helperData; + /** + * @var AllowedCountries + */ + private $allowedCountriesReader; + /** * @var string[] */ @@ -118,6 +126,21 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab $this->_init(\Magento\Directory\Model\Country::class, \Magento\Directory\Model\ResourceModel\Country::class); } + /** + * Return Allowed Countries reader + * + * @deprecated + * @return \Magento\Directory\Model\AllowedCountries + */ + private function getAllowedCountriesReader() + { + if (!$this->allowedCountriesReader) { + $this->allowedCountriesReader = ObjectManager::getInstance()->get(AllowedCountries::class); + } + + return $this->allowedCountriesReader; + } + /** * Load allowed countries for current store * @@ -126,16 +149,13 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab */ public function loadByStore($store = null) { - $allowCountries = explode(',', - (string)$this->_scopeConfig->getValue( - 'general/country/allow', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - $store - ) - ); - if (!empty($allowCountries)) { - $this->addFieldToFilter("country_id", ['in' => $allowCountries]); + $allowedCountries = $this->getAllowedCountriesReader() + ->getAllowedCountries(ScopeInterface::SCOPE_STORE, $store); + + if (!empty($allowedCountries)) { + $this->addFieldToFilter("country_id", ['in' => $allowedCountries]); } + return $this; } diff --git a/app/code/Magento/Directory/Test/Unit/Model/AllowedCountriesTest.php b/app/code/Magento/Directory/Test/Unit/Model/AllowedCountriesTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f5ed44753c43c40305f14895a60b7932818cdea0 --- /dev/null +++ b/app/code/Magento/Directory/Test/Unit/Model/AllowedCountriesTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Directory\Test\Unit\Model; + +use Magento\Directory\Model\AllowedCountries; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Data\Collection\AbstractDb; +use Magento\Store\Api\Data\WebsiteInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; + +class AllowedCountriesTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject | ScopeConfigInterface + */ + private $scopeConfigMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject | StoreManagerInterface + */ + private $storeManagerMock; + + /** + * @var AllowedCountries + */ + private $allowedCountriesReader; + + public function setUp() + { + $this->scopeConfigMock = $this->getMock(ScopeConfigInterface::class); + $this->storeManagerMock = $this->getMock(StoreManagerInterface::class); + + $this->allowedCountriesReader = new AllowedCountries( + $this->scopeConfigMock, + $this->storeManagerMock + ); + } + + public function testGetAllowedCountriesWithEmptyFilter() + { + $website1 = $this->getMock(WebsiteInterface::class); + $website1->expects($this->once()) + ->method('getId') + ->willReturn(1); + $this->storeManagerMock->expects($this->once()) + ->method('getWebsite') + ->willReturn($website1); + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->with(AllowedCountries::ALLOWED_COUNTRIES_PATH, 'website', 1) + ->willReturn('AM'); + + $this->assertEquals(['AM' => 'AM'], $this->allowedCountriesReader->getAllowedCountries()); + } + + public function testGetAllowedCountries() + { + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->with(AllowedCountries::ALLOWED_COUNTRIES_PATH, 'website', 1) + ->willReturn('AM'); + + $this->assertEquals( + ['AM' => 'AM'], + $this->allowedCountriesReader->getAllowedCountries(ScopeInterface::SCOPE_WEBSITE, true) + ); + } +} diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Address.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Address.php index bcaca17ad3e1f24a71dc6dc54a0e1614a5a2ccb3..c421f511c1671987f49344b5badb7cf5a169d2e1 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Address.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Address.php @@ -5,6 +5,8 @@ */ namespace Magento\Sales\Block\Adminhtml\Order\Create\Form; +use Magento\Backend\Model\Session\Quote; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Data\Form\Element\AbstractElement; use Magento\Framework\Pricing\PriceCurrencyInterface; @@ -75,6 +77,16 @@ class Address extends \Magento\Sales\Block\Adminhtml\Order\Create\Form\AbstractF */ protected $addressMapper; + /** + * @var \Magento\Directory\Model\ResourceModel\Country\Collection + */ + private $countriesCollection; + + /** + * @var \Magento\Backend\Model\Session\Quote + */ + private $backendQuoteSession; + /** * Constructor * @@ -263,7 +275,7 @@ class Address extends \Magento\Sales\Block\Adminhtml\Order\Create\Form\AbstractF $this->directoryHelper->getDefaultCountry($this->getStore()) ); } - + $this->processCountryOptions($this->_form->getElement('country_id')); // Set custom renderer for VAT field if needed $vatIdElement = $this->_form->getElement('vat_id'); if ($vatIdElement && $this->getDisplayVatValidationButton() !== false) { @@ -279,6 +291,49 @@ class Address extends \Magento\Sales\Block\Adminhtml\Order\Create\Form\AbstractF return $this; } + /** + * @param \Magento\Framework\Data\Form\Element\AbstractElement $countryElement + * @return void + */ + private function processCountryOptions(\Magento\Framework\Data\Form\Element\AbstractElement $countryElement) + { + $storeId = $this->getBackendQuoteSession()->getStoreId(); + $options = $this->getCountriesCollection() + ->loadByStore($storeId) + ->toOptionArray(); + + $countryElement->setValues($options); + } + + /** + * Retrieve Directiry Countries collection + * @deprecated + * @return \Magento\Directory\Model\ResourceModel\Country\Collection + */ + private function getCountriesCollection() + { + if (!$this->countriesCollection) { + $this->countriesCollection = ObjectManager::getInstance() + ->get(\Magento\Directory\Model\ResourceModel\Country\Collection::class); + } + + return $this->countriesCollection; + } + + /** + * Retrieve Backend Quote Session + * @deprecated + * @return Quote + */ + private function getBackendQuoteSession() + { + if (!$this->backendQuoteSession) { + $this->backendQuoteSession = ObjectManager::getInstance()->get(Quote::class); + } + + return $this->backendQuoteSession; + } + /** * Add additional data to form element * diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js index 51f5973372d6910b6fcdd5c36baf68428a389453..449418f07c100c91e139231c704bb28fd8c9ae82 100644 --- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js +++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js @@ -316,9 +316,11 @@ define([ }, this); } - this.pagesChanged[this.currentPage()] = - !compareArrays(this.defaultPagesState[this.currentPage()], this.arrayFilter(this.getChildItems())); - this.changed(_.some(this.pagesChanged)); + if (this.defaultPagesState[this.currentPage()]) { + this.pagesChanged[this.currentPage()] = + !compareArrays(this.defaultPagesState[this.currentPage()], this.arrayFilter(this.getChildItems())); + this.changed(_.some(this.pagesChanged)); + } }, /** diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/country.js b/app/code/Magento/Ui/view/base/web/js/form/element/country.js new file mode 100644 index 0000000000000000000000000000000000000000..c72d02c595bb8d8d4185f8bc9969cfa3a3bc5940 --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/js/form/element/country.js @@ -0,0 +1,48 @@ +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'underscore', + 'uiRegistry', + './select' +], function (_, registry, Select) { + 'use strict'; + + return Select.extend({ + defaults: { + imports: { + update: '${ $.parentName }.website_id:value' + } + }, + + /** + * Filters 'initialOptions' property by 'field' and 'value' passed, + * calls 'setOptions' passing the result to it + * + * @param {*} value + * @param {String} field + */ + filter: function (value, field) { + var result; + + if (!field) { //validate field, if we are on update + field = this.filterBy.field; + } + + this._super(value, field); + result = _.filter(this.initialOptions, function (item) { + + if (item[field]) { + return ~item[field].indexOf(value); + } + + return false; + }); + + this.setOptions(result); + } + }); +}); + 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 b8cbdcb655a1e7b351adb92d529ff6ae3a3c378b..6414b90ad02eedb2bdf7bfed86ebdab892de0647 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 @@ -56,16 +56,20 @@ define([ */ filter: function (value, field) { var country = registry.get(this.parentName + '.' + 'country_id'), + option; + + if (country) { option = country.indexedOptions[value]; - this._super(value, field); + this._super(value, field); - if (option && option['is_region_visible'] === false) { - // hide select and corresponding text input field if region must not be shown for selected country - this.setVisible(false); + if (option && option['is_region_visible'] === false) { + // hide select and corresponding text input field if region must not be shown for selected country + this.setVisible(false); - if (this.customEntry) { - this.toggleInput(false); + if (this.customEntry) { + this.toggleInput(false); + } } } } diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/website.js b/app/code/Magento/Ui/view/base/web/js/form/element/website.js new file mode 100644 index 0000000000000000000000000000000000000000..095e2c4740305dbae338e8e3946e82404683b0dc --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/js/form/element/website.js @@ -0,0 +1,33 @@ +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'underscore', + 'uiRegistry', + './select' +], function (_, registry, Select) { + 'use strict'; + + return Select.extend({ + defaults: { + customerId: null, + isGlobalScope: 0 + }, + + /** + * Website component constructor. + * @returns {exports} + */ + initialize: function () { + this._super(); + + if (this.customerId || this.isGlobalScope) { + this.disable(true); + } + + return this; + } + }); +}); diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses.php index 947aef9e82cdc3e113a8cb0d13fa01060c529a5b..2a8ecbf80c4165af5a4cfdb385d02acec65d030d 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses.php @@ -20,6 +20,7 @@ use Magento\Customer\Test\Fixture\Address; /** * Customer addresses edit block. + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Addresses extends Tab { @@ -37,6 +38,8 @@ class Addresses extends Tab */ protected $addressSelector = "//li[address[contains(.,'%s')]]"; + protected $countriesSelector = "//*/select[@name='address[new_%d][country_id]']/option"; + /** * Delete Address button. * @@ -240,6 +243,34 @@ class Addresses extends Tab return $addressTab->isVisible(); } + /** + * Retrieve list of all countries + * @param int $addressNumber + * @return array + */ + public function getCountriesList($addressNumber) + { + $this->openCustomerAddress($addressNumber); + /** @var SimpleElement $element */ + $options = $this->_rootElement->getElements( + sprintf($this->countriesSelector, $addressNumber - 1), + Locator::SELECTOR_XPATH + ); + $data = []; + /** @var SimpleElement $option */ + foreach ($options as $option) { + if ($option->isVisible()) { + $value = $option->getValue(); + + if ($value != "") { + $data[] = $value; + } + } + } + + return $data; + } + /** * Click delete customer address button. * diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertChangingWebsiteChangeCountries.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertChangingWebsiteChangeCountries.php new file mode 100644 index 0000000000000000000000000000000000000000..395dac1326dc39536a6b6a69214d74b04e7d2c33 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertChangingWebsiteChangeCountries.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Test\Constraint; + +use Magento\Customer\Test\Fixture\Customer; +use Magento\Customer\Test\Page\Adminhtml\CustomerIndexNew; +use Magento\Mtf\Constraint\AbstractConstraint; + +/** + * Assert required fields on customer form. + */ +class AssertChangingWebsiteChangeCountries extends AbstractConstraint +{ + /** + * Assert required fields on customer form. + * + * @param CustomerIndexNew $customerNewPage + * @param array $expectedRequiredFields + * @return void + */ + public function processAssert( + CustomerIndexNew $customerIndexNew, + Customer $customer, + $expectedList + ) { + $customerIndexNew->getCustomerForm() + ->openTab('account_information'); + $customerIndexNew->getCustomerForm()->fillCustomer($customer); + $customerIndexNew->getCustomerForm() + ->openTab('addresses'); + $tab = $customerIndexNew->getCustomerForm() + ->getTab('addresses'); + $countriesList = $tab->getCountriesList(1); + sort($countriesList); + sort($expectedList); + \PHPUnit_Framework_Assert::assertEquals( + $countriesList, + $expectedList, + 'Wrong country list is displayed.' + ); + } + + /** + * Return string representation of object. + * + * @return string + */ + public function toString() + { + return 'All required fields on customer form are highlighted.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php index bf3ae060a6d1b008aab6a94181008aa24149a08c..9d04e9afc68f44ecf450cbf6f97fc3909b76fc15 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php @@ -6,6 +6,11 @@ namespace Magento\Customer\Test\TestCase; +use Magento\Config\Test\Fixture\ConfigData; +use Magento\Customer\Test\Constraint\AssertChangingWebsiteChangeCountries; +use Magento\Framework\App\ObjectManager; +use Magento\Mtf\Fixture\FixtureFactory; +use Magento\Mtf\Fixture\FixtureInterface; use Magento\Mtf\TestCase\Injectable; use Magento\Customer\Test\Fixture\Address; use Magento\Customer\Test\Fixture\Customer; @@ -37,6 +42,14 @@ class CreateCustomerBackendEntityTest extends Injectable */ protected $customer; + /** + * @var Address + */ + private $address; + + /** @var array */ + private $allowedCountriesData = []; + /** * Customer index page. * @@ -51,6 +64,9 @@ class CreateCustomerBackendEntityTest extends Injectable */ protected $pageCustomerIndexNew; + /** @var FixtureFactory */ + private $fixtureFactory; + /** * Inject customer pages. * @@ -60,10 +76,12 @@ class CreateCustomerBackendEntityTest extends Injectable */ public function __inject( CustomerIndex $pageCustomerIndex, - CustomerIndexNew $pageCustomerIndexNew + CustomerIndexNew $pageCustomerIndexNew, + \Magento\Mtf\Fixture\FixtureFactory $fixtureFactory ) { $this->pageCustomerIndex = $pageCustomerIndex; $this->pageCustomerIndexNew = $pageCustomerIndexNew; + $this->fixtureFactory = $fixtureFactory; } /** @@ -74,12 +92,141 @@ class CreateCustomerBackendEntityTest extends Injectable * @param Address $address * @return void */ - public function test(Customer $customer, $customerAction, Address $address = null) - { - // Steps + public function test( + Customer $customer, + $customerAction, + Address $address = null, + array $steps = [], + array $beforeActionCallback = [] + ) { + ///Process initialize steps + foreach ($steps as $methodName => $stepData) { + if (method_exists($this, $methodName)) { + call_user_func_array([$this, $methodName], $stepData); + } + } + $this->pageCustomerIndex->open(); $this->pageCustomerIndex->getPageActionsBlock()->addNew(); $this->pageCustomerIndexNew->getCustomerForm()->fillCustomer($customer, $address); + $this->address = $address; + $this->customer = $customer; + + foreach ($beforeActionCallback as $methodName) { + if (method_exists($this, $methodName)) { + call_user_func([$this, $methodName]); + } + } + $this->pageCustomerIndexNew->getPageActionsBlock()->$customerAction(); } + + /** + * Assert that allowed countries renders in correct way. + * @return void + */ + protected function assertAllowedCountries() + { + /** @var \Magento\Customer\Test\Constraint\AssertChangingWebsiteChangeCountries $assert */ + $assert = $this->objectManager->get( + \Magento\Customer\Test\Constraint\AssertChangingWebsiteChangeCountries::class + ); + + foreach ($this->allowedCountriesData as $dataPerWebsite) { + $customerWithWebsite = $this->fixtureFactory->createByCode( + 'customer', + [ + 'data' => [ + 'website_id' => $dataPerWebsite['website']->getName() + ] + ] + ); + $assert->processAssert( + $this->pageCustomerIndexNew, + $customerWithWebsite, + $dataPerWebsite['countries'] + ); + } + + $this->pageCustomerIndexNew->getCustomerForm()->openTab('account_information'); + $this->pageCustomerIndexNew->getCustomerForm()->fillCustomer($this->customer); + $this->pageCustomerIndexNew->getCustomerForm()->openTab('addresses'); + $this->pageCustomerIndexNew->getCustomerForm()->getTab('addresses')->updateAddresses($this->address); + } + + /** + * @return \Magento\Store\Test\Fixture\Website + */ + private function createWebsiteFixture() + { + /** @var \Magento\Store\Test\Fixture\Website $websiteFixture */ + $websiteFixture = $this->fixtureFactory->createByCode('website', ['dataset' => 'custom_website']); + $websiteFixture->persist(); + $storeGroupFixture = $this->fixtureFactory->createByCode( + 'storeGroup', + [ + 'data' => [ + 'website_id' => [ + 'fixture' => $websiteFixture + ], + 'root_category_id' => [ + 'dataset' => 'default_category' + ], + 'name' => 'Store_Group_%isolation%', + ] + ] + ); + $storeGroupFixture->persist(); + /** @var \Magento\Store\Test\Fixture\Store $storeFixture */ + $storeFixture = $this->fixtureFactory->createByCode( + 'store', + [ + 'data' => [ + 'website_id' => $websiteFixture->getWebsiteId(), + 'group_id' => [ + 'fixture' => $storeGroupFixture + ], + 'is_active' => true, + 'name' => 'Store_%isolation%', + 'code' => 'store_%isolation%' + ] + ] + ); + $storeFixture->persist(); + + return $websiteFixture; + } + + /** + * @param array $countryList + */ + protected function configureAllowedCountries(array $countryList = []) + { + foreach ($countryList as $countries) { + $websiteFixture = $this->createWebsiteFixture(); + /** @var FixtureInterface $configFixture */ + $configFixture = $this->fixtureFactory->createByCode( + 'configData', + [ + 'data' => [ + 'general/country/allow' => [ + 'value' => $countries + ], + 'scope' => [ + 'fixture' => $websiteFixture, + 'scope_type' => 'website', + 'website_id' => $websiteFixture->getWebsiteId(), + 'set_level' => 'website', + ] + ] + ] + ); + + $configFixture->persist(); + $this->allowedCountriesData[] = [ + 'website' => $websiteFixture, + 'countries' => explode(",", $countries) + ]; + } + } } diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml index 248b26561af3a568250a72e6beba60c73c38f50c..14e3a9642d1a5d649536d0b84070c8ae3e9210a6 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml @@ -130,5 +130,31 @@ </data> <constraint name="Magento\Customer\Test\Constraint\AssertCustomerBackendRequiredFields" /> </variation> + <variation name="CreateCustomerBackendEntityTestVariation10" summary="Create customer with 2 websites and with different allowed countries."> + <data name="customerAction" xsi:type="string">save</data> + <data name="customer/data/website_id" xsi:type="string">Main Website</data> + <data name="customer/data/group_id/dataset" xsi:type="string">General</data> + <data name="customer/data/firstname" xsi:type="string">John%isolation%</data> + <data name="customer/data/lastname" xsi:type="string">Doe%isolation%</data> + <data name="customer/data/email" xsi:type="string">JohnDoe%isolation%@example.com</data> + <data name="address/data/company" xsi:type="string">Magento</data> + <data name="address/data/country_id" xsi:type="string">Bangladesh</data> + <data name="address/data/street" xsi:type="string">Chmielna 113</data> + <data name="address/data/city" xsi:type="string">Bielsko-Biala</data> + <data name="address/data/postcode" xsi:type="string">43-310</data> + <data name="address/data/telephone" xsi:type="string">799885616</data> + <data name="steps" xsi:type="array"> + <item name="configureAllowedCountries" xsi:type="array"> + <item name="countries" xsi:type="array"> + <item name="0" xsi:type="string">AS,BM</item> + <item name="1" xsi:type="string">BD,BB,AF</item> + </item> + </item> + </data> + <data name="beforeActionCallback" xsi:type="array"> + <item name="assertAllowedCountries" xsi:type="string">assertAllowedCountries</item> + </data> + <constraint name="Magento\Customer\Test\Constraint\AssertCustomerSuccessSaveMessage" /> + </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/Fixture/Store/GroupId.php b/dev/tests/functional/tests/app/Magento/Store/Test/Fixture/Store/GroupId.php index 8b508168c674cca0248f7a316b4640e6ac924066..93bf1a9001cbfcc4825afee549e3873d1d623ae9 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/Fixture/Store/GroupId.php +++ b/dev/tests/functional/tests/app/Magento/Store/Test/Fixture/Store/GroupId.php @@ -39,6 +39,9 @@ class GroupId extends DataSource } $this->storeGroup = $storeGroup; $this->data = $storeGroup->getWebsiteId() . "/" . $storeGroup->getName(); + } elseif (isset($data['fixture'])) { + $this->storeGroup = $data['fixture']; + $this->data = $this->storeGroup->getWebsiteId() . "/" . $this->storeGroup->getName(); } } diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/Fixture/StoreGroup/WebsiteId.php b/dev/tests/functional/tests/app/Magento/Store/Test/Fixture/StoreGroup/WebsiteId.php index 9ad26d88d278d37c51338897c153102399662da7..985b4a91c35f8412eb614b1903a5e56ab8a410e1 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/Fixture/StoreGroup/WebsiteId.php +++ b/dev/tests/functional/tests/app/Magento/Store/Test/Fixture/StoreGroup/WebsiteId.php @@ -39,6 +39,9 @@ class WebsiteId extends DataSource } $this->website = $website; $this->data = $website->getName(); + } elseif (isset($data['fixture'])) { + $this->website = $data['fixture']; + $this->data = $this->website->getName(); } } diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js index 94de561da986dc5336c7e1a835fccfe869651518..4964e6bb14e703f3a0a11a84ab1af78c7e9cfe6e 100755 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js @@ -136,6 +136,10 @@ define([ } }; + if (!settings.style_formats) { + settings.theme_advanced_buttons1 = settings.theme_advanced_buttons1.replace(',styleselect', ''); + } + // Set the document base URL if (this.config.document_base_url) { settings.document_base_url = this.config.document_base_url;