diff --git a/app/code/Magento/Backup/Model/Fs/Collection.php b/app/code/Magento/Backup/Model/Fs/Collection.php index 888a072a57d66..4e5c009b7b0f9 100644 --- a/app/code/Magento/Backup/Model/Fs/Collection.php +++ b/app/code/Magento/Backup/Model/Fs/Collection.php @@ -110,7 +110,10 @@ protected function _generateRow($filename) $row[$key] = $value; } $row['size'] = $this->_varDirectory->stat($this->_varDirectory->getRelativePath($filename))['size']; - $row['id'] = $row['time'] . '_' . $row['type']; + if (isset($row['display_name']) && $row['display_name'] == '') { + $row['display_name'] = 'WebSetupWizard'; + } + $row['id'] = $row['time'] . '_' . $row['type'] . (isset($row['display_name']) ? $row['display_name'] : ''); return $row; } } diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php index 8afc9e369d772..441fb60f55ec4 100644 --- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php @@ -85,7 +85,6 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity */ protected $_indexValueAttributes = [ 'status', - 'gift_message_available', ]; /** diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 5c4a19efba143..970b8a1aaea0f 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -196,7 +196,6 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity protected $_indexValueAttributes = [ 'status', 'tax_class_id', - 'gift_message_available', ]; /** diff --git a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php index 08de47e9bb074..cf3e2ac4a5191 100644 --- a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php +++ b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php @@ -8,12 +8,15 @@ namespace Magento\CatalogWidget\Block\Product; +use Magento\Framework\DataObject\IdentityInterface; +use Magento\Widget\Block\BlockInterface; + /** * Catalog Products List widget block * Class ProductsList * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implements \Magento\Widget\Block\BlockInterface +class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implements BlockInterface, IdentityInterface { /** * Default value for products count that will be shown @@ -327,7 +330,16 @@ public function getPagerHtml() */ public function getIdentities() { - return [\Magento\Catalog\Model\Product::CACHE_TAG]; + $identities = []; + if ($this->getProductCollection()) { + foreach ($this->getProductCollection() as $product) { + if ($product instanceof IdentityInterface) { + $identities = array_merge($identities, $product->getIdentities()); + } + } + } + + return $identities ?: [\Magento\Catalog\Model\Product::CACHE_TAG]; } /** diff --git a/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php b/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php index 7ee5b3448d340..36c6553476b16 100644 --- a/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php +++ b/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php @@ -261,21 +261,10 @@ public function testCreateCollection($pagerEnable, $productsCount, $productsPerP $this->collectionFactory->expects($this->once())->method('create')->willReturn($collection); $this->productsList->setData('conditions_encoded', 'some_serialized_conditions'); - $conditions = $this->getMockBuilder('\Magento\Rule\Model\Condition\Combine') - ->setMethods(['collectValidatedAttributes']) - ->disableOriginalConstructor() - ->getMock(); - $conditions->expects($this->once())->method('collectValidatedAttributes') - ->with($collection) - ->willReturnSelf(); - $this->builder->expects($this->once())->method('attachConditionToCollection') - ->with($collection, $conditions) + ->with($collection, $this->getConditionsForCollection($collection)) ->willReturnSelf(); - $this->rule->expects($this->once())->method('loadPost')->willReturnSelf(); - $this->rule->expects($this->once())->method('getConditions')->willReturn($conditions); - if ($productsPerPage) { $this->productsList->setData('products_per_page', $productsPerPage); } else { @@ -333,7 +322,44 @@ public function testShowPager() public function testGetIdentities() { - $this->assertEquals([\Magento\Catalog\Model\Product::CACHE_TAG], $this->productsList->getIdentities()); + $collection = $this->getMockBuilder('\Magento\Catalog\Model\ResourceModel\Product\Collection') + ->setMethods([ + 'addAttributeToSelect', + 'getIterator', + ])->disableOriginalConstructor() + ->getMock(); + + $product = $this->getMock('Magento\Framework\DataObject\IdentityInterface', ['getIdentities']); + $notProduct = $this->getMock('NotProduct', ['getIdentities']); + $product->expects($this->once())->method('getIdentities')->willReturn(['product_identity']); + $collection->expects($this->once())->method('getIterator')->willReturn( + new \ArrayIterator([$product, $notProduct]) + ); + $this->productsList->setData('product_collection', $collection); + + $this->assertEquals( + ['product_identity'], + $this->productsList->getIdentities() + ); + } + + /** + * @param $collection + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function getConditionsForCollection($collection) + { + $conditions = $this->getMockBuilder('\Magento\Rule\Model\Condition\Combine') + ->setMethods(['collectValidatedAttributes']) + ->disableOriginalConstructor() + ->getMock(); + $conditions->expects($this->once())->method('collectValidatedAttributes') + ->with($collection) + ->willReturnSelf(); + + $this->rule->expects($this->once())->method('loadPost')->willReturnSelf(); + $this->rule->expects($this->once())->method('getConditions')->willReturn($conditions); + return $conditions; } public function testGetTitle() diff --git a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Order/Create/Sidebar.php b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Order/Create/Sidebar.php new file mode 100644 index 0000000000000..0cff9d736a250 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Order/Create/Sidebar.php @@ -0,0 +1,51 @@ +getProduct()->getTypeId() == Configurable::TYPE_CODE) { + return ''; + } + return $proceed($item); + } + + /** + * Check whether product configuration is required before adding to order + * + * @param \Magento\Sales\Block\Adminhtml\Order\Create\Sidebar\AbstractSidebar $subject + * @param \Closure $proceed + * @param string $productType + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundIsConfigurationRequired( + \Magento\Sales\Block\Adminhtml\Order\Create\Sidebar\AbstractSidebar $subject, + \Closure $proceed, + $productType + ) { + if ($productType == Configurable::TYPE_CODE) { + return true; + } + return $proceed($productType); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php index 9a40931c05d02..7ba330f5a060c 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php @@ -29,7 +29,10 @@ class VariationHandler /** @var \Magento\Catalog\Model\ProductFactory */ protected $productFactory; - /** @var \Magento\CatalogInventory\Api\StockConfigurationInterface */ + /** + * @var \Magento\CatalogInventory\Api\StockConfigurationInterface + * @deprecated + */ protected $stockConfiguration; /** @@ -168,14 +171,10 @@ protected function fillSimpleProductData( $keysFilter = ['item_id', 'product_id', 'stock_id', 'type_id', 'website_id']; $postData['stock_data'] = array_diff_key((array)$parentProduct->getStockData(), array_flip($keysFilter)); - $postData['stock_data']['manage_stock'] = $postData['quantity_and_stock_status']['qty'] === '' ? 0 : 1; if (!isset($postData['stock_data']['is_in_stock'])) { $stockStatus = $parentProduct->getQuantityAndStockStatus(); $postData['stock_data']['is_in_stock'] = $stockStatus['is_in_stock']; } - $configDefaultValue = $this->stockConfiguration->getManageStock($product->getStoreId()); - $postData['stock_data']['use_config_manage_stock'] = $postData['stock_data']['manage_stock'] == - $configDefaultValue ? 1 : 0; $postData = $this->processMediaGallery($product, $postData); $postData['status'] = isset($postData['status']) ? $postData['status'] 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 7efc0794f11b8..026fed5e29db5 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/VariationHandlerTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/VariationHandlerTest.php @@ -254,11 +254,6 @@ public function testGenerateSimpleProducts() $parentProductMock->expects($this->once()) ->method('getQuantityAndStockStatus') ->willReturn(['is_in_stock' => 1]); - $newSimpleProductMock->expects($this->once())->method('getStoreId')->willReturn('store_id'); - $this->stockConfiguration->expects($this->once()) - ->method('getManageStock') - ->with('store_id') - ->willReturn(1); $newSimpleProductMock->expects($this->once())->method('addData')->willReturnSelf(); $parentProductMock->expects($this->once())->method('getWebsiteIds')->willReturn('website_id'); $newSimpleProductMock->expects($this->once())->method('setWebsiteIds')->with('website_id')->willReturnSelf(); diff --git a/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml b/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml index 3fe4a122c2b35..90c5612f5e507 100644 --- a/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml @@ -16,6 +16,9 @@ + + + Magento\Catalog\Model\System\Config\Source\InputtypeFactory diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/summary.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/summary.js index 3439f8d9f2177..de2c7aa1db53f 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/summary.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/summary.js @@ -81,6 +81,7 @@ define([ options: options, images: images, sku: sku, + name: sku, quantity: quantity, price: price, productId: productId, @@ -91,6 +92,7 @@ define([ if (productId) { variation.sku = product.sku; variation.weight = product.weight; + variation.name = product.name; gridExisting.push(this.prepareRowForGrid(variation)); } else { gridNew.push(this.prepareRowForGrid(variation)); diff --git a/app/code/Magento/GiftMessage/Setup/InstallData.php b/app/code/Magento/GiftMessage/Setup/InstallData.php index baed68bf23483..26d356335f8c1 100644 --- a/app/code/Magento/GiftMessage/Setup/InstallData.php +++ b/app/code/Magento/GiftMessage/Setup/InstallData.php @@ -94,7 +94,7 @@ public function install(ModuleDataSetupInterface $setup, ModuleContextInterface 'label' => 'Allow Gift Message', 'input' => 'select', 'class' => '', - 'source' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean', + 'source' => 'Magento\Catalog\Model\Product\Attribute\Source\Boolean', 'global' => true, 'visible' => true, 'required' => false, diff --git a/app/code/Magento/GiftMessage/Setup/UpgradeData.php b/app/code/Magento/GiftMessage/Setup/UpgradeData.php index a73009a36baf7..9429c27e2ab5d 100644 --- a/app/code/Magento/GiftMessage/Setup/UpgradeData.php +++ b/app/code/Magento/GiftMessage/Setup/UpgradeData.php @@ -35,19 +35,20 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface { $setup->startSetup(); + /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */ + $categorySetup = $this->categorySetupFactory->create(['setup' => $setup]); + $entityTypeId = $categorySetup->getEntityTypeId(Product::ENTITY); + $attributeSetId = $categorySetup->getAttributeSetId($entityTypeId, 'Default'); + $attribute = $categorySetup->getAttribute($entityTypeId, 'gift_message_available'); + if (version_compare($context->getVersion(), '2.0.1', '<')) { - /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */ - $categorySetup = $this->categorySetupFactory->create(['setup' => $setup]); + $groupName = 'Gift Options'; if (!$categorySetup->getAttributeGroup(Product::ENTITY, 'Default', $groupName)) { $categorySetup->addAttributeGroup(Product::ENTITY, 'Default', $groupName, 60); } - $entityTypeId = $categorySetup->getEntityTypeId(Product::ENTITY); - $attributeSetId = $categorySetup->getAttributeSetId($entityTypeId, 'Default'); - $attribute = $categorySetup->getAttribute($entityTypeId, 'gift_message_available'); - $categorySetup->addAttributeToGroup( $entityTypeId, $attributeSetId, @@ -57,6 +58,16 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface ); } + if (version_compare($context->getVersion(), '2.1.0', '<')) { + + $categorySetup->updateAttribute( + $entityTypeId, + $attribute['attribute_id'], + 'source_model', + 'Magento\Catalog\Model\Product\Attribute\Source\Boolean' + ); + } + $setup->endSetup(); } } diff --git a/app/code/Magento/GiftMessage/composer.json b/app/code/Magento/GiftMessage/composer.json index 4e6f44a608188..e24d985833eb5 100644 --- a/app/code/Magento/GiftMessage/composer.json +++ b/app/code/Magento/GiftMessage/composer.json @@ -9,7 +9,6 @@ "magento/module-sales": "100.1.*", "magento/module-backend": "100.1.*", "magento/module-customer": "100.1.*", - "magento/module-eav": "100.1.*", "magento/module-quote": "100.1.*", "magento/framework": "100.1.*", "magento/module-ui": "100.1.*" diff --git a/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php b/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php index 3080145bfcb09..02ff894df7c1f 100644 --- a/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php +++ b/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php @@ -202,7 +202,7 @@ public function getAssociatedProducts($product) $collection = $this->getAssociatedProductCollection( $product )->addAttributeToSelect( - ['name', 'price'] + ['name', 'price', 'special_price', 'special_from_date', 'special_to_date'] )->addFilterByRequiredOptions()->setPositionOrder()->addStoreFilter( $this->getStoreFilter($product) )->addAttributeToFilter( diff --git a/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Link/RelationPersister.php b/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Link/RelationPersister.php new file mode 100644 index 0000000000000..f83b34ec3b30d --- /dev/null +++ b/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Link/RelationPersister.php @@ -0,0 +1,84 @@ +relationProcessor = $relationProcessor; + $this->linkFactory = $linkFactory; + } + + /** + * Save grouped products to product relation table + * + * @param Link $subject + * @param \Closure $proceed + * @param int $parentId + * @param array $data + * @param int $typeId + * @return Link + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundSaveProductLinks(Link $subject, \Closure $proceed, $parentId, $data, $typeId) + { + $result = $proceed($parentId, $data, $typeId); + if ($typeId == \Magento\GroupedProduct\Model\ResourceModel\Product\Link::LINK_TYPE_GROUPED) { + foreach ($data as $linkData) { + $this->relationProcessor->addRelation( + $parentId, + $linkData['product_id'] + ); + } + } + return $result; + } + + /** + * Remove grouped products from product relation table + * + * @param Link $subject + * @param \Closure $proceed + * @param int $linkId + * @return Link + */ + public function aroundDeleteProductLink(Link $subject, \Closure $proceed, $linkId) + { + /** @var \Magento\Catalog\Model\ProductLink\Link $link */ + $link = $this->linkFactory->create(); + $subject->load($link, $linkId, $subject->getIdFieldName()); + $result = $proceed($linkId); + if ($link->getLinkTypeId() == \Magento\GroupedProduct\Model\ResourceModel\Product\Link::LINK_TYPE_GROUPED) { + $this->relationProcessor->removeRelations( + $link->getProductId(), + $link->getLinkedProductId() + ); + } + return $result; + } +} diff --git a/app/code/Magento/GroupedProduct/Setup/UpgradeData.php b/app/code/Magento/GroupedProduct/Setup/UpgradeData.php new file mode 100644 index 0000000000000..83dab45c4287a --- /dev/null +++ b/app/code/Magento/GroupedProduct/Setup/UpgradeData.php @@ -0,0 +1,58 @@ +relationProcessor = $relationProcessor; + } + + /** + * {@inheritdoc} + */ + public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + if (version_compare($context->getVersion(), '2.0.1', '<')) { + $connection = $setup->getConnection(); + $select = $connection->select() + ->from( + $this->relationProcessor->getTable('catalog_product_link'), + ['product_id', 'linked_product_id'] + ) + ->where('link_type_id = ?', Link::LINK_TYPE_GROUPED); + + $connection->query( + $connection->insertFromSelect( + $select, $this->relationProcessor->getMainTable(), + ['parent_id', 'child_id'], + AdapterInterface::INSERT_IGNORE + ) + ); + } + + $setup->endSetup(); + } +} diff --git a/app/code/Magento/GroupedProduct/Test/Unit/Model/ResourceModel/Product/Link/RelationPersisterTest.php b/app/code/Magento/GroupedProduct/Test/Unit/Model/ResourceModel/Product/Link/RelationPersisterTest.php new file mode 100644 index 0000000000000..d11539945b0e8 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Unit/Model/ResourceModel/Product/Link/RelationPersisterTest.php @@ -0,0 +1,96 @@ +getMockBuilder(LinkFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $this->relationProcessor = $this->getMockBuilder(Relation::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->link = $this->getMockBuilder(Link::class) + ->setMethods(['getLinkTypeId', 'getProductId', 'getLinkedProductId']) + ->disableOriginalConstructor() + ->getMock(); + + $linkFactory->expects($this->any())->method('create')->willReturn($this->link); + + $this->object = new RelationPersister( + $this->relationProcessor, + $linkFactory + ); + } + + public function testAroundSaveProductLinks() + { + $subject = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Link::class) + ->disableOriginalConstructor() + ->getMock(); + $this->relationProcessor->expects($this->once())->method('addRelation')->with(2, 10); + $this->assertEquals($subject, $this->object->aroundSaveProductLinks( + $subject, + function() use ($subject) { return $subject; }, + 2, + [['product_id' => 10]], + 3 + )); + } + + public function testAroundDeleteProductLink() + { + $subject = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Link::class) + ->disableOriginalConstructor() + ->getMock(); + $subject->expects($this->any())->method('getIdFieldName')->willReturn('id'); + $subject->expects($this->once())->method('load')->with($this->link, 155, 'id'); + + $this->link->expects($this->any()) + ->method('getLinkTypeId') + ->willReturn(\Magento\GroupedProduct\Model\ResourceModel\Product\Link::LINK_TYPE_GROUPED); + $this->link->expects($this->any()) + ->method('getProductId') + ->willReturn(12); + $this->link->expects($this->any()) + ->method('getLinkedProductId') + ->willReturn(13); + + $this->relationProcessor->expects($this->once())->method('removeRelations')->with(12, 13); + $this->assertEquals( + $subject, + $this->object->aroundDeleteProductLink( + $subject, + function() use ($subject) { return $subject; }, + 155 + ) + ); + + } +} diff --git a/app/code/Magento/GroupedProduct/etc/di.xml b/app/code/Magento/GroupedProduct/etc/di.xml index aa1fb0d98796a..459198a2bb338 100644 --- a/app/code/Magento/GroupedProduct/etc/di.xml +++ b/app/code/Magento/GroupedProduct/etc/di.xml @@ -77,6 +77,9 @@ + + + diff --git a/app/code/Magento/GroupedProduct/etc/module.xml b/app/code/Magento/GroupedProduct/etc/module.xml index ca5b06993a88a..5a03ee430ea1b 100644 --- a/app/code/Magento/GroupedProduct/etc/module.xml +++ b/app/code/Magento/GroupedProduct/etc/module.xml @@ -6,7 +6,7 @@ */ --> - + diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Save.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Save.php index 6e81434275856..463279367e04f 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Save.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Save.php @@ -27,8 +27,12 @@ public function execute() $filterManager = $this->_objectManager->get('Magento\Framework\Filter\FilterManager'); if ($isNew) { $statusCode = $data['status'] = $filterManager->stripTags($data['status']); + } + $data['label'] = $filterManager->stripTags($data['label']); + if (!isset($data['store_labels'])) { + $data['store_labels'] = []; } - $data['label'] = $filterManager->stripTags($data['label']); + foreach ($data['store_labels'] as &$label) { $label = $filterManager->stripTags($label); } diff --git a/app/code/Magento/Theme/Model/Design/Config/Validator.php b/app/code/Magento/Theme/Model/Design/Config/Validator.php new file mode 100644 index 0000000000000..7f1b95670c8e6 --- /dev/null +++ b/app/code/Magento/Theme/Model/Design/Config/Validator.php @@ -0,0 +1,109 @@ +templateFactory = $templateFactory; + $this->fields = $fields; + } + + /** + * Validate if design configuration has recursive references + * + * @param DesignConfigInterface $designConfig + * + * @throws LocalizedException + * @return void + */ + public function validate(DesignConfigInterface $designConfig) + { + /** @var DesignConfigDataInterface[] $designConfigData */ + $designConfigData = $designConfig->getExtensionAttributes()->getDesignConfigData(); + $elements = []; + foreach ($designConfigData as $designElement) { + if (!in_array($designElement->getFieldConfig()['field'], $this->fields)) { + continue; + } + /* Save mapping between field names and config paths */ + $elements[$designElement->getFieldConfig()['field']] = [ + 'config_path' => $designElement->getPath(), + 'value' => $designElement->getValue() + ]; + } + + foreach ($elements as $name => $data) { + // Load template object by configured template id + $template = $this->templateFactory->create(); + $template->emulateDesign($designConfig->getScopeId()); + $templateId = $data['value']; + if (is_numeric($templateId)) { + $template->load($templateId); + } else { + $template->loadDefault($templateId); + } + $text = $template->getTemplateText(); + $template->revertDesign(); + // Check if template body has a reference to the same config path + if (preg_match_all(Template::CONSTRUCTION_TEMPLATE_PATTERN, $text, $constructions, PREG_SET_ORDER)) { + foreach ($constructions as $construction) { + $configPath = isset($construction[2]) ? $construction[2] : ''; + $params = $this->getParameters($configPath); + if (isset($params['config_path']) && $params['config_path'] == $data['config_path']) { + throw new LocalizedException( + __( + "Incorrect configuration for %templateName. Template body has a reference to itself", + ["templateName" => $name] + ) + ); + }; + } + } + } + } + + /** + * Return associative array of parameters. + * + * @param string $value raw parameters + * @return array + */ + private function getParameters($value) + { + $tokenizer = new ParameterTokenizer(); + $tokenizer->setString($value); + $params = $tokenizer->tokenize(); + return $params; + } +} diff --git a/app/code/Magento/Theme/Model/DesignConfigRepository.php b/app/code/Magento/Theme/Model/DesignConfigRepository.php index 8645b2b0ffbb4..afebd188d4a9c 100644 --- a/app/code/Magento/Theme/Model/DesignConfigRepository.php +++ b/app/code/Magento/Theme/Model/DesignConfigRepository.php @@ -24,6 +24,13 @@ class DesignConfigRepository implements DesignConfigRepositoryInterface /** @var ConfigStorage */ protected $configStorage; + /** + * Design config validator + * + * @var \Magento\Theme\Model\Design\Config\Validator + */ + private $validator; + /** * @param ConfigStorage $configStorage * @param ReinitableConfigInterface $reinitableConfig @@ -39,6 +46,23 @@ public function __construct( $this->configStorage = $configStorage; } + /** + * Get config validator + * + * @return Design\Config\Validator + * + * @deprecated + */ + private function getValidator() + { + if (null === $this->validator) { + $this->validator =\Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Theme\Model\Design\Config\Validator::class + ); + } + return $this->validator; + } + /** * @inheritDoc */ @@ -58,6 +82,8 @@ public function save(DesignConfigInterface $designConfig) throw new LocalizedException(__('Can not save empty config')); } + $this->getValidator()->validate($designConfig); + $this->configStorage->save($designConfig); $this->reinitableConfig->reinit(); $this->reindexGrid(); diff --git a/app/code/Magento/Theme/Test/Unit/Model/Config/ValidatorTest.php b/app/code/Magento/Theme/Test/Unit/Model/Config/ValidatorTest.php new file mode 100644 index 0000000000000..2e58a5a2f10bd --- /dev/null +++ b/app/code/Magento/Theme/Test/Unit/Model/Config/ValidatorTest.php @@ -0,0 +1,123 @@ +templateFactoryMock = $this->getMockBuilder(\Magento\Framework\Mail\TemplateInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManagerHelper->getObject( + \Magento\Theme\Model\Design\Config\Validator::class, + [ + "templateFactory" => $this->templateFactoryMock, + "fields" => ["email_header_template", "no_reference"] + ] + ); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Incorrect configuration for email_header_template. Template body has a reference to + */ + public function testValidateHasRecursiveReference() + { + $fieldConfig = [ + 'path' => 'design/email/header_template', + 'fieldset' => 'other_settings/email', + 'field' => 'email_header_template' + ]; + + $designConfigMock = $this->getMockBuilder(\Magento\Theme\Api\Data\DesignConfigInterface::class) + ->getMock(); + $designConfigExtensionMock = + $this->getMockBuilder(\Magento\Theme\Api\Data\DesignConfigExtensionInterface::class) + ->setMethods(['getDesignConfigData']) + ->getMock(); + $designElementMock = $this->getMockBuilder(\Magento\Theme\Model\Data\Design\Config\Data::class) + ->disableOriginalConstructor() + ->getMock(); + + $designConfigMock->expects($this->once()) + ->method('getExtensionAttributes') + ->willReturn($designConfigExtensionMock); + $designConfigExtensionMock->expects($this->once()) + ->method('getDesignConfigData') + ->willReturn([$designElementMock]); + $designElementMock->expects($this->any())->method('getFieldConfig')->willReturn($fieldConfig); + $designElementMock->expects($this->once())->method('getPath')->willReturn($fieldConfig['path']); + $designElementMock->expects($this->once())->method('getValue')->willReturn($fieldConfig['field']); + + $templateMock = $this->getMockBuilder(\Magento\Email\Model\TemplateInterface::class) + ->setMethods(['getTemplateText', 'emulateDesign', 'loadDefault', 'revertDesign']) + ->getMock(); + + $this->templateFactoryMock->expects($this->once())->method('create')->willReturn($templateMock); + $templateMock->expects($this->once())->method('getTemplateText')->willReturn( + file_get_contents(__DIR__ . '/_files/template_fixture.html') + ); + + $this->model->validate($designConfigMock); + } + + public function testValidateNoRecursiveReference() + { + $fieldConfig = [ + 'path' => 'no/reference', + 'fieldset' => 'no/reference', + 'field' => 'no_reference' + ]; + + $designConfigMock = $this->getMockBuilder(\Magento\Theme\Api\Data\DesignConfigInterface::class) + ->getMock(); + $designConfigExtensionMock = + $this->getMockBuilder(\Magento\Theme\Api\Data\DesignConfigExtensionInterface::class) + ->setMethods(['getDesignConfigData']) + ->getMock(); + $designElementMock = $this->getMockBuilder(\Magento\Theme\Model\Data\Design\Config\Data::class) + ->disableOriginalConstructor() + ->getMock(); + + $designConfigMock->expects($this->once()) + ->method('getExtensionAttributes') + ->willReturn($designConfigExtensionMock); + $designConfigExtensionMock->expects($this->once()) + ->method('getDesignConfigData') + ->willReturn([$designElementMock]); + $designElementMock->expects($this->any())->method('getFieldConfig')->willReturn($fieldConfig); + $designElementMock->expects($this->once())->method('getPath')->willReturn($fieldConfig['path']); + $designElementMock->expects($this->once())->method('getValue')->willReturn($fieldConfig['field']); + + $templateMock = $this->getMockBuilder(\Magento\Email\Model\TemplateInterface::class) + ->setMethods(['getTemplateText', 'emulateDesign', 'loadDefault', 'revertDesign']) + ->getMock(); + + $this->templateFactoryMock->expects($this->once())->method('create')->willReturn($templateMock); + $templateMock->expects($this->once())->method('getTemplateText')->willReturn( + file_get_contents(__DIR__ . '/_files/template_fixture.html') + ); + + $this->model->validate($designConfigMock); + } +} diff --git a/app/code/Magento/Theme/Test/Unit/Model/Config/_files/template_fixture.html b/app/code/Magento/Theme/Test/Unit/Model/Config/_files/template_fixture.html new file mode 100644 index 0000000000000..b29ebf62b1f0a --- /dev/null +++ b/app/code/Magento/Theme/Test/Unit/Model/Config/_files/template_fixture.html @@ -0,0 +1,31 @@ +{{template config_path="design/email/header_template"}} + +

{{trans "%name," name=$customer.name}}

+

{{trans "Welcome to %store_name." store_name=$store.getFrontendName()}}

+

+ {{trans + 'To sign in to our site, use these credentials during checkout or on the My Account page:' + + customer_url=$this.getUrl($store,'customer/account/',[_nosid:1]) + |raw}} +

+
    +
  • {{trans "Email:"}} {{var customer.email}}
  • +
  • {{trans "Password:"}} {{trans "Password you set when creating account"}}
  • +
+

+ {{trans + 'Forgot your account password? Click here to reset it.' + + reset_url="$this.getUrl($store,'customer/account/createPassword/',[_query:[id:$customer.id,token:$customer.rp_token],_nosid:1])" + |raw}} +

+

{{trans "When you sign in to your account, you will be able to:"}}

+
    +
  • {{trans "Proceed through checkout faster"}}
  • +
  • {{trans "Check the status of orders"}}
  • +
  • {{trans "View past orders"}}
  • +
  • {{trans "Store alternative addresses (for shipping to multiple family members and friends)"}}
  • +
+ +{{template config_path="design/email/footer_template"}} diff --git a/app/code/Magento/Theme/Test/Unit/Model/DesignConfigRepositoryTest.php b/app/code/Magento/Theme/Test/Unit/Model/DesignConfigRepositoryTest.php index 09c533d56a091..9cd13fc30f989 100644 --- a/app/code/Magento/Theme/Test/Unit/Model/DesignConfigRepositoryTest.php +++ b/app/code/Magento/Theme/Test/Unit/Model/DesignConfigRepositoryTest.php @@ -7,6 +7,7 @@ use Magento\Theme\Model\Data\Design\Config; use Magento\Theme\Model\DesignConfigRepository; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; class DesignConfigRepositoryTest extends \PHPUnit_Framework_TestCase { @@ -34,6 +35,11 @@ class DesignConfigRepositoryTest extends \PHPUnit_Framework_TestCase /** @var DesignConfigRepository */ protected $repository; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $validator; + public function setUp() { $this->configStorage = $this->getMock('Magento\Theme\Model\Design\Config\Storage', [], [], '', false); @@ -71,10 +77,24 @@ public function setUp() '', false ); - $this->repository = new DesignConfigRepository( - $this->configStorage, - $this->reinitableConfig, - $this->indexerRegistry + + $this->validator = $this->getMock( + \Magento\Theme\Model\Design\Config\Validator::class, + [], + [], + '', + false, + false + ); + $objectManagerHelper = new ObjectManager($this); + $this->repository = $objectManagerHelper->getObject( + DesignConfigRepository::class, + [ + 'configStorage' => $this->configStorage, + 'reinitableConfig' => $this->reinitableConfig, + 'indexerRegistry' => $this->indexerRegistry, + 'validator' => $this->validator + ] ); } @@ -97,6 +117,7 @@ public function testSave() ->willReturn($this->indexer); $this->indexer->expects($this->once()) ->method('reindexAll'); + $this->validator->expects($this->once())->method('validate')->with($this->designConfig); $this->assertSame($this->designConfig, $this->repository->save($this->designConfig)); } diff --git a/app/code/Magento/Theme/etc/adminhtml/di.xml b/app/code/Magento/Theme/etc/adminhtml/di.xml index 0b03d146cb108..0c043c2e36bac 100644 --- a/app/code/Magento/Theme/etc/adminhtml/di.xml +++ b/app/code/Magento/Theme/etc/adminhtml/di.xml @@ -33,4 +33,12 @@
+ + + + email_header_template + email_footer_template + + + diff --git a/app/code/Magento/User/Block/User/Edit.php b/app/code/Magento/User/Block/User/Edit.php index 40b719bdffc08..bd69d23b01afd 100644 --- a/app/code/Magento/User/Block/User/Edit.php +++ b/app/code/Magento/User/Block/User/Edit.php @@ -53,7 +53,7 @@ protected function _construct() $objId = $this->getRequest()->getParam($this->_objectId); if (!empty($objId)) { - $deleteConfirmMsg = __("Are you sure you want to revoke the users\\\\'s tokens?"); + $deleteConfirmMsg = __("Are you sure you want to revoke the user\'s tokens?"); $this->addButton( 'invalidate', [ diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/Block/CreateAdmin.php b/dev/tests/functional/tests/app/Magento/Install/Test/Block/CreateAdmin.php index d446cf9abe6e4..3205f17a63d8d 100644 --- a/dev/tests/functional/tests/app/Magento/Install/Test/Block/CreateAdmin.php +++ b/dev/tests/functional/tests/app/Magento/Install/Test/Block/CreateAdmin.php @@ -21,7 +21,7 @@ class CreateAdmin extends Form * * @var string */ - protected $next = "[ng-click*='next']"; + protected $next = "[ng-click*='validateCredentials']"; /** * First field selector diff --git a/dev/tests/functional/tests/app/Magento/Upgrade/Test/TestCase/UpgradeSystemTest.php b/dev/tests/functional/tests/app/Magento/Upgrade/Test/TestCase/UpgradeSystemTest.php index cfdd69bfa0ace..f5933fa91e51d 100644 --- a/dev/tests/functional/tests/app/Magento/Upgrade/Test/TestCase/UpgradeSystemTest.php +++ b/dev/tests/functional/tests/app/Magento/Upgrade/Test/TestCase/UpgradeSystemTest.php @@ -81,8 +81,9 @@ public function test( . "(-{$preReleaseVersion}(\\.{$preReleaseVersion})*)?" . "(\\+{$buildVersion}(\\.{$buildVersion})*)?{$suffix}/"; - if (preg_match($versionPattern, $version, $out)) { - $version = array_shift($out); + if (preg_match($versionPattern, $version)) { + preg_match("/(.*){$suffix}/", $version, $matches); + $version = $matches[1]; } else { $this->fail( "Provided version format does not comply with semantic versioning specification. Got '{$version}'" diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/VariationHandlerTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/VariationHandlerTest.php index 2957a9de06c14..68b445ed80da3 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/VariationHandlerTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/VariationHandlerTest.php @@ -8,37 +8,36 @@ namespace Magento\ConfigurableProduct\Model\Product; +use Magento\TestFramework\Helper\Bootstrap; + /** * @magentoAppIsolation enabled * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php */ class VariationHandlerTest extends \PHPUnit_Framework_TestCase { - /** - * Object under test - * - * @var \Magento\ConfigurableProduct\Model\Product\VariationHandler - */ - protected $_model; + /** @var \Magento\ConfigurableProduct\Model\Product\VariationHandler */ + private $_model; - /** - * @var \Magento\Catalog\Model\Product - */ - protected $_product; + /** @var \Magento\Catalog\Model\Product */ + private $_product; + + /** @var \Magento\CatalogInventory\Api\StockRegistryInterface */ + private $stockRegistry; protected function setUp() { - $this->_product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $this->_product = Bootstrap::getObjectManager()->create( 'Magento\Catalog\Model\Product' ); $this->_product->load(1); - // fixture - $this->_model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $this->_model = Bootstrap::getObjectManager()->create( 'Magento\ConfigurableProduct\Model\Product\VariationHandler' ); // prevent fatal errors by assigning proper "singleton" of type instance to the product $this->_product->setTypeInstance($this->_model); + $this->stockRegistry = Bootstrap::getObjectManager()->get('Magento\CatalogInventory\Api\StockRegistryInterface'); } /** @@ -52,8 +51,9 @@ public function testGenerateSimpleProducts($productsData) $generatedProducts = $this->_model->generateSimpleProducts($this->_product, $productsData); $this->assertEquals(3, count($generatedProducts)); foreach ($generatedProducts as $productId) { + $stockItem = $this->stockRegistry->getStockItem($productId); /** @var $product \Magento\Catalog\Model\Product */ - $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $product = Bootstrap::getObjectManager()->create( 'Magento\Catalog\Model\Product' ); $product->load($productId); @@ -61,6 +61,7 @@ public function testGenerateSimpleProducts($productsData) $this->assertNotNull($product->getSku()); $this->assertNotNull($product->getPrice()); $this->assertNotNull($product->getWeight()); + $this->assertEquals('1', $stockItem->getIsInStock()); } } @@ -71,14 +72,12 @@ public function testGenerateSimpleProducts($productsData) */ public function testGenerateSimpleProductsWithPartialData($productsData) { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - /** @var \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry */ - $stockRegistry = $objectManager->get('Magento\CatalogInventory\Api\StockRegistryInterface'); $this->_product->setNewVariationsAttributeSetId(4); $generatedProducts = $this->_model->generateSimpleProducts($this->_product, $productsData); + $parentStockItem = $this->stockRegistry->getStockItem($this->_product->getId()); foreach ($generatedProducts as $productId) { - $stockItem = $stockRegistry->getStockItem($productId); - $this->assertEquals('0', $stockItem->getManageStock()); + $stockItem = $this->stockRegistry->getStockItem($productId); + $this->assertEquals($parentStockItem->getManageStock(), $stockItem->getManageStock()); $this->assertEquals('1', $stockItem->getIsInStock()); } } diff --git a/dev/tests/integration/testsuite/Magento/Email/Model/_files/email_template.php b/dev/tests/integration/testsuite/Magento/Email/Model/_files/email_template.php new file mode 100644 index 0000000000000..7b78046a4e307 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Email/Model/_files/email_template.php @@ -0,0 +1,19 @@ +create('Magento\Email\Model\Template'); +$template->setId(1); +$template->setOptions(['area' => 'test area', 'store' => 1]); +$template->setData( + [ + 'template_text' => + file_get_contents(__DIR__ . '/template_fixture.html') + ] +); +$template->setTemplateCode('fixture'); +$template->save(); diff --git a/dev/tests/integration/testsuite/Magento/Email/Model/_files/template_fixture.html b/dev/tests/integration/testsuite/Magento/Email/Model/_files/template_fixture.html new file mode 100644 index 0000000000000..7259962619a33 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Email/Model/_files/template_fixture.html @@ -0,0 +1,29 @@ +{{template config_path="design/email/header_template"}} + +

{{trans "%name," name=$customer.name}}

+

{{trans "Welcome to %store_name." store_name=$store.getFrontendName()}}

+

+ {{trans + 'To sign in to our site, use these credentials during checkout or on the My Account page:' + + customer_url=$this.getUrl($store,'customer/account/',[_nosid:1]) + |raw}} +

+ +

+ {{trans + 'Forgot your account password? Click here to reset it.' + + reset_url="$this.getUrl($store,'customer/account/createPassword/',[_query:[id:$customer.id,token:$customer.rp_token],_nosid:1])" + |raw}} +

+

{{trans "When you sign in to your account, you will be able to:"}}

+ diff --git a/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php b/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php new file mode 100644 index 0000000000000..2b40b621c7180 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php @@ -0,0 +1,72 @@ +get('\Magento\Catalog\Api\ProductRepositoryInterface'); + /** @var $product \Magento\Catalog\Model\Product */ + $product = $productRepository->get('grouped-product'); + + $this->assertEquals(10, $product->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue()); + } + + /** + * @magentoDataFixture Magento/GroupedProduct/_files/product_grouped.php + * @magentoAppIsolation enabled + */ + public function testFinalPriceWithTearPrice() + { + $productRepository = Bootstrap::getObjectManager() + ->get('\Magento\Catalog\Api\ProductRepositoryInterface'); + /** @var ProductTierPriceInterface $tierPrice */ + $tierPrice = Bootstrap::getObjectManager()->create(ProductTierPriceInterface::class); + $tierPrice->setQty(1); + $tierPrice->setCustomerGroupId(\Magento\Customer\Model\GroupManagement::CUST_GROUP_ALL); + $tierPrice->setValue(5); + + /** @var $simpleProduct \Magento\Catalog\Api\Data\ProductInterface */ + $simpleProduct = $productRepository->get('simple'); + $simpleProduct->setTierPrices([ + $tierPrice + ]); + $productRepository->save($simpleProduct); + + /** @var $product \Magento\Catalog\Model\Product */ + $product = $productRepository->get('grouped-product'); + $this->assertEquals(5, $product->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue()); + } + + /** + * @magentoDataFixture Magento/GroupedProduct/_files/product_grouped.php + * @magentoAppIsolation enabled + */ + public function testFinalPriceWithSpecialPrice() + { + $productRepository = Bootstrap::getObjectManager() + ->get('\Magento\Catalog\Api\ProductRepositoryInterface'); + + /** @var $simpleProduct \Magento\Catalog\Api\Data\ProductInterface */ + $simpleProduct = $productRepository->get('simple'); + $simpleProduct->setCustomAttribute('special_price', 6); + $productRepository->save($simpleProduct); + + /** @var $product \Magento\Catalog\Model\Product */ + $product = $productRepository->get('grouped-product'); + $this->assertEquals(6, $product->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Theme/Model/Config/ValidatorTest.php b/dev/tests/integration/testsuite/Magento/Theme/Model/Config/ValidatorTest.php new file mode 100644 index 0000000000000..669671bf0c7c5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Theme/Model/Config/ValidatorTest.php @@ -0,0 +1,107 @@ +get('Magento\Framework\App\AreaList') + ->getArea(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE) + ->load(\Magento\Framework\App\Area::PART_CONFIG); + $objectManager->get('Magento\Framework\App\State') + ->setAreaCode(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE); + + $this->model = $objectManager->get('Magento\Theme\Model\Design\Config\Validator'); + } + + /** + * @magentoDataFixture Magento/Email/Model/_files/email_template.php + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Incorrect configuration for email_header_template. Template body has a reference to + */ + public function testValidateHasRecursiveReference() + { + $fieldConfig = [ + 'path' => 'design/email/header_template', + 'fieldset' => 'other_settings/email', + 'field' => 'email_header_template' + ]; + + $designConfigMock = $this->getMockBuilder('Magento\Theme\Api\Data\DesignConfigInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $designConfigExtensionMock = $this->getMockBuilder('Magento\Theme\Api\Data\DesignConfigExtensionInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $designElementMock = $this->getMockBuilder('Magento\Theme\Model\Data\Design\Config\Data') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $designConfigMock->expects($this->once()) + ->method('getExtensionAttributes') + ->willReturn($designConfigExtensionMock); + $designConfigExtensionMock->expects($this->once()) + ->method('getDesignConfigData') + ->willReturn([$designElementMock]); + $designElementMock->expects($this->any())->method('getFieldConfig')->willReturn($fieldConfig); + $designElementMock->expects($this->once())->method('getPath')->willReturn($fieldConfig['path']); + $designElementMock->expects($this->once())->method('getValue')->willReturn(1); + + $this->model->validate($designConfigMock); + } + + /** + * @magentoDataFixture Magento/Email/Model/_files/email_template.php + */ + public function testValidateNoRecursiveReference() + { + $fieldConfig = [ + 'path' => 'design/email/footer_template', + 'fieldset' => 'other_settings/email', + 'field' => 'email_footer_template' + ]; + + $designConfigMock = $this->getMockBuilder('Magento\Theme\Api\Data\DesignConfigInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $designConfigExtensionMock = $this->getMockBuilder('Magento\Theme\Api\Data\DesignConfigExtensionInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $designElementMock = $this->getMockBuilder('Magento\Theme\Model\Data\Design\Config\Data') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $designConfigMock->expects($this->once()) + ->method('getExtensionAttributes') + ->willReturn($designConfigExtensionMock); + $designConfigExtensionMock->expects($this->once()) + ->method('getDesignConfigData') + ->willReturn([$designElementMock]); + $designElementMock->expects($this->any())->method('getFieldConfig')->willReturn($fieldConfig); + $designElementMock->expects($this->once())->method('getPath')->willReturn($fieldConfig['path']); + $designElementMock->expects($this->once())->method('getValue')->willReturn(1); + + $this->model->validate($designConfigMock); + } +} diff --git a/lib/internal/Magento/Framework/App/ObjectManagerFactory.php b/lib/internal/Magento/Framework/App/ObjectManagerFactory.php index 1b84ed9bb008b..ddff84be4a177 100644 --- a/lib/internal/Magento/Framework/App/ObjectManagerFactory.php +++ b/lib/internal/Magento/Framework/App/ObjectManagerFactory.php @@ -111,7 +111,7 @@ public function create(array $arguments) { $writeFactory = new \Magento\Framework\Filesystem\Directory\WriteFactory($this->driverPool); $generatedFiles = new GeneratedFiles($this->directoryList, $writeFactory); - $generatedFiles->regenerate(); + $generatedFiles->cleanGeneratedFiles(); $deploymentConfig = $this->createDeploymentConfig($this->directoryList, $this->configFilePool, $arguments); $arguments = array_merge($deploymentConfig->get(), $arguments); diff --git a/lib/internal/Magento/Framework/Code/GeneratedFiles.php b/lib/internal/Magento/Framework/Code/GeneratedFiles.php index ec6e43b8c67f1..75d5ff4b4b73b 100644 --- a/lib/internal/Magento/Framework/Code/GeneratedFiles.php +++ b/lib/internal/Magento/Framework/Code/GeneratedFiles.php @@ -5,7 +5,6 @@ */ namespace Magento\Framework\Code; -use Magento\Framework\Config\Data\ConfigData; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Config\File\ConfigFilePool; use Magento\Framework\Filesystem\Directory\WriteFactory; @@ -48,67 +47,127 @@ public function __construct(DirectoryList $directoryList, WriteFactory $writeFac * Clean generated code and DI configuration * * @return void + * + * @deprecated + * @see \Magento\Framework\Code\GeneratedFiles::cleanGeneratedFiles */ public function regenerate() + { + $this->cleanGeneratedFiles(); + } + + /** + * Clean var/generation, var/di and var/cache + * + * @return void + */ + public function cleanGeneratedFiles() { if ($this->write->isExist(self::REGENERATE_FLAG)) { + + $enabledCacheTypes = []; + //TODO: to be removed in scope of MAGETWO-53476 - //clean cache $deploymentConfig = $this->directoryList->getPath(DirectoryList::CONFIG); $configPool = new ConfigFilePool(); $envPath = $deploymentConfig . '/' . $configPool->getPath(ConfigFilePool::APP_ENV); if ($this->write->isExist($this->write->getRelativePath($envPath))) { - $this->saveCacheStatus($envPath); + $enabledCacheTypes = $this->getEnabledCacheTypes(); + $this->disableAllCacheTypes(); } //TODO: Till here + $cachePath = $this->write->getRelativePath($this->directoryList->getPath(DirectoryList::CACHE)); $generationPath = $this->write->getRelativePath($this->directoryList->getPath(DirectoryList::GENERATION)); $diPath = $this->write->getRelativePath($this->directoryList->getPath(DirectoryList::DI)); + // Clean var/generation dir if ($this->write->isDirectory($generationPath)) { $this->write->delete($generationPath); } + + // Clean var/di if ($this->write->isDirectory($diPath)) { $this->write->delete($diPath); } + + // Clean var/cache if ($this->write->isDirectory($cachePath)) { $this->write->delete($cachePath); } - //add to queue - $this->write->delete(self::REGENERATE_FLAG); + $this->enableCacheTypes($enabledCacheTypes); } } /** - * Read Cache types from env.php and write to a json file. + * Create flag for cleaning up var/generation, var/di and var/cache directories for subsequent + * regeneration of this content * - * @param string $envPath * @return void */ - private function saveCacheStatus($envPath) + public function requestRegeneration() + { + $this->write->touch(self::REGENERATE_FLAG); + } + + /** + * Reads Cache configuration from env.php and returns indexed array containing all the enabled cache types. + * + * @return string[] + */ + private function getEnabledCacheTypes() { - $cacheData = include $envPath; - - if (isset($cacheData['cache_types'])) { - $enabledCacheTypes = $cacheData['cache_types']; - $enabledCacheTypes = array_filter($enabledCacheTypes, function ($value) { - return $value; - }); - if (!empty($enabledCacheTypes)) { - $varDir = $this->directoryList->getPath(DirectoryList::VAR_DIR); - $this->write->writeFile( - $this->write->getRelativePath($varDir) . '/.cachestates.json', - json_encode($enabledCacheTypes) - ); - $cacheTypes = array_keys($cacheData['cache_types']); + $enabledCacheTypes = []; + $envPath = $this->getEnvPath(); + if ($this->write->isReadable($this->write->getRelativePath($envPath))) { + $envData = include $envPath; + if (isset($envData['cache_types'])) { + $cacheStatus = $envData['cache_types']; + $enabledCacheTypes = array_filter($cacheStatus, function ($value) { + return $value; + }); + $enabledCacheTypes = array_keys($enabledCacheTypes); + } + } + return $enabledCacheTypes; + } + + + /** + * Returns path to env.php file + * + * @return string + * @throws \Exception + */ + private function getEnvPath() + { + $deploymentConfig = $this->directoryList->getPath(DirectoryList::CONFIG); + $configPool = new ConfigFilePool(); + $envPath = $deploymentConfig . '/' . $configPool->getPath(ConfigFilePool::APP_ENV); + return $envPath; + } + + /** + * Disables all cache types by updating env.php. + * + * @return void + */ + private function disableAllCacheTypes() + { + $envPath = $this->getEnvPath(); + if ($this->write->isWritable($this->write->getRelativePath($envPath))) { + $envData = include $envPath; + + if (isset($envData['cache_types'])) { + $cacheTypes = array_keys($envData['cache_types']); foreach ($cacheTypes as $cacheType) { - $cacheData['cache_types'][$cacheType] = 0; + $envData['cache_types'][$cacheType] = 0; } $formatter = new PhpFormatter(); - $contents = $formatter->format($cacheData); + $contents = $formatter->format($envData); $this->write->writeFile($this->write->getRelativePath($envPath), $contents); if (function_exists('opcache_invalidate')) { @@ -121,12 +180,34 @@ private function saveCacheStatus($envPath) } /** - * Create flag for regeneration of code and di + * Enables apppropriate cache types in app/etc/env.php based on the passed in $cacheTypes array + * TODO: to be removed in scope of MAGETWO-53476 + * + * @param string[] * * @return void */ - public function requestRegeneration() + private function enableCacheTypes($cacheTypes) { - $this->write->touch(self::REGENERATE_FLAG); + if (empty($cacheTypes)) { + return; + } + $envPath = $this->getEnvPath(); + if ($this->write->isReadable($this->write->getRelativePath($envPath))) { + $envData = include $envPath; + foreach ($cacheTypes as $cacheType) { + if (isset($envData['cache_types'][$cacheType])) { + $envData['cache_types'][$cacheType] = 1; + } + } + + $formatter = new PhpFormatter(); + $contents = $formatter->format($envData); + + $this->write->writeFile($this->write->getRelativePath($envPath), $contents); + if (function_exists('opcache_invalidate')) { + opcache_invalidate($this->write->getAbsolutePath($envPath)); + } + } } } diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/GeneratedFilesTest.php b/lib/internal/Magento/Framework/Code/Test/Unit/GeneratedFilesTest.php index 4fef046786871..5d5f7178ed909 100644 --- a/lib/internal/Magento/Framework/Code/Test/Unit/GeneratedFilesTest.php +++ b/lib/internal/Magento/Framework/Code/Test/Unit/GeneratedFilesTest.php @@ -45,9 +45,9 @@ protected function setUp() * @param array $getPathMap * @param array $isDirectoryMap * @param array $deleteMap - * @dataProvider regenerateDataProvider + * @dataProvider cleanGeneratedFilesDataProvider */ - public function testRegenerate($getPathMap, $isDirectoryMap, $deleteMap) + public function testCleanGeneratedFiles($getPathMap, $isDirectoryMap, $deleteMap) { $this->writeInterface @@ -62,20 +62,21 @@ public function testRegenerate($getPathMap, $isDirectoryMap, $deleteMap) $this->writeInterface->expects($this->any())->method('getRelativePath')->willReturnMap($getPathMap); $this->writeInterface->expects($this->any())->method('isDirectory')->willReturnMap($isDirectoryMap); $this->writeInterface->expects($this->exactly(1))->method('delete')->willReturnMap($deleteMap); - $this->model->regenerate(); + $this->model->cleanGeneratedFiles(); } /** * @return array */ - public function regenerateDataProvider() + public function cleanGeneratedFilesDataProvider() { $pathToGeneration = 'path/to/generation'; $pathToDi = 'path/to/di'; $pathToCache = 'path/to/di'; $pathToConfig = 'path/to/config'; - $getPathMap = [[DirectoryList::GENERATION, $pathToGeneration], + $getPathMap = [ + [DirectoryList::GENERATION, $pathToGeneration], [DirectoryList::DI, $pathToDi], [DirectoryList::CACHE, $pathToCache], [DirectoryList::CONFIG, $pathToConfig], @@ -98,7 +99,7 @@ public function regenerateDataProvider() ]; } - public function testRegenerateWithNoFlag() + public function testCleanGeneratedFilesWithNoFlag() { $this->writeInterface ->expects($this->once()) @@ -108,7 +109,7 @@ public function testRegenerateWithNoFlag() $this->directoryList->expects($this->never())->method('getPath'); $this->writeInterface->expects($this->never())->method('getPath'); $this->writeInterface->expects($this->never())->method('delete'); - $this->model->regenerate(); + $this->model->cleanGeneratedFiles(); } public function testRequestRegeneration() diff --git a/setup/config/di.config.php b/setup/config/di.config.php index 9c73aae475358..81ddbde7adb1e 100644 --- a/setup/config/di.config.php +++ b/setup/config/di.config.php @@ -21,6 +21,7 @@ 'Magento\Setup\Controller\Environment', 'Magento\Setup\Controller\DependencyCheck', 'Magento\Setup\Controller\DatabaseCheck', + 'Magento\Setup\Controller\ValidateAdminCredentials', 'Magento\Setup\Controller\AddDatabase', 'Magento\Setup\Controller\WebConfiguration', 'Magento\Setup\Controller\CustomizeYourStore', diff --git a/setup/pub/magento/setup/create-admin-account.js b/setup/pub/magento/setup/create-admin-account.js index ec9fdf9c6b248..dd8357dd9a568 100644 --- a/setup/pub/magento/setup/create-admin-account.js +++ b/setup/pub/magento/setup/create-admin-account.js @@ -5,14 +5,14 @@ 'use strict'; angular.module('create-admin-account', ['ngStorage']) - .controller('createAdminAccountController', ['$scope', '$state', '$localStorage', function ($scope, $state, $localStorage) { + .controller('createAdminAccountController', ['$scope', '$state', '$localStorage', '$http', function ($scope, $state, $localStorage, $http) { $scope.admin = { 'passwordStatus': { class: 'none', label: 'None' } }; - + $scope.passwordStatusChange = function () { if (angular.isUndefined($scope.admin.password)) { return; @@ -41,6 +41,25 @@ angular.module('create-admin-account', ['ngStorage']) $scope.admin = $localStorage.admin; } + $scope.validateCredentials = function () { + var data = { + 'db': $localStorage.db, + 'admin': $localStorage.admin, + 'store': $localStorage.store, + 'config': $localStorage.config + }; + $http.post('index.php/validate-admin-credentials', data) + .success(function (data) { + $scope.validateCredentials.result = data; + if ($scope.validateCredentials.result.success) { + $scope.nextState(); + } + }) + .error(function (data) { + $scope.validateCredentials.failed = data; + }); + }; + $scope.$on('nextState', function () { $localStorage.admin = $scope.admin; }); diff --git a/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php b/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php index 17d86ef9bad58..577e787bdd5f2 100644 --- a/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php +++ b/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php @@ -5,13 +5,9 @@ */ namespace Magento\Setup\Console\Command; -use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Backend\Console\Command\AbstractCacheManageCommand; -use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Setup\ConsoleLogger; use Magento\Setup\Model\InstallerFactory; use Magento\Setup\Model\ObjectManagerProvider; -use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -34,9 +30,7 @@ class UpgradeCommand extends AbstractSetupCommand private $installerFactory; /** - * Object Manager - * - * @var ObjectManagerProvider + * @var \Magento\Setup\Model\ObjectManagerProvider; */ private $objectManagerProvider; @@ -79,9 +73,9 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $areaCode = 'setup'; /** @var \Magento\Framework\ObjectManagerInterface $objectManager */ $objectManager = $this->objectManagerProvider->get(); - $areaCode = 'setup'; /** @var \Magento\Framework\App\State $appState */ $appState = $objectManager->get('Magento\Framework\App\State'); $appState->setAreaCode($areaCode); @@ -98,40 +92,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln('Please re-run Magento compile command'); } - return $this->enableCaches($objectManager, $output); - } - - /** - * Enables cache if cachestates exists - * TODO: to be removed in scope of MAGETWO-53476 - * - * @param \Magento\Framework\ObjectManagerInterface $objectManager - * @param \Symfony\Component\Console\Output\OutputInterface $output - * @return int - */ - private function enableCaches($objectManager, $output) - { - $writeFactory = $objectManager->get('Magento\Framework\Filesystem\Directory\WriteFactory'); - $write = $writeFactory->create(BP); - /** @var \Magento\Framework\App\Filesystem\DirectoryList $dirList */ - $dirList = $objectManager->get('Magento\Framework\App\Filesystem\DirectoryList'); - - $pathToCacheStatus = $write->getRelativePath($dirList->getPath(DirectoryList::VAR_DIR) . '/.cachestates.json'); - - if ($write->isExist($pathToCacheStatus)) { - $params = array_keys(json_decode($write->readFile($pathToCacheStatus), true)); - $command = $this->getApplication()->find('cache:enable'); - - $arguments = ['command' => 'cache:enable', AbstractCacheManageCommand::INPUT_KEY_TYPES => $params ]; - $returnCode = $command->run(new ArrayInput($arguments), $output); - - $write->delete($pathToCacheStatus); - if (isset($returnCode) && $returnCode > 0) { - $message = ' Error occured during upgrade. Error code: ' . $returnCode . ''; - $output->writeln($message); - return \Magento\Framework\Console\Cli::RETURN_FAILURE; - } - } return \Magento\Framework\Console\Cli::RETURN_SUCCESS; } } diff --git a/setup/src/Magento/Setup/Controller/Install.php b/setup/src/Magento/Setup/Controller/Install.php index 647ba4e59f7d0..5b3507911ef4e 100644 --- a/setup/src/Magento/Setup/Controller/Install.php +++ b/setup/src/Magento/Setup/Controller/Install.php @@ -6,21 +6,18 @@ namespace Magento\Setup\Controller; -use Magento\Setup\Model\AdminAccount; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Config\ConfigOptionsListConstants as SetupConfigOptionsList; -use Magento\Backend\Setup\ConfigOptionsList as BackendConfigOptionsList; +use Magento\SampleData; use Magento\Setup\Model\Installer; use Magento\Setup\Model\Installer\ProgressFactory; use Magento\Setup\Model\InstallerFactory; -use Magento\Setup\Model\StoreConfigurationDataMapper as UserConfig; +use Magento\Setup\Model\RequestDataConverter; use Magento\Setup\Model\WebLogger; use Zend\Json\Json; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\JsonModel; use Zend\View\Model\ViewModel; -use Magento\Setup\Console\Command\InstallCommand; -use Magento\SampleData; -use Magento\Framework\App\DeploymentConfig; /** * Install controller @@ -54,6 +51,11 @@ class Install extends AbstractActionController */ private $deploymentConfig; + /** + * @var RequestDataConverter + */ + private $requestDataConverter; + /** * Default Constructor * @@ -62,19 +64,22 @@ class Install extends AbstractActionController * @param ProgressFactory $progressFactory * @param \Magento\Framework\Setup\SampleData\State $sampleDataState * @param \Magento\Framework\App\DeploymentConfig $deploymentConfig + * @param RequestDataConverter $requestDataConverter */ public function __construct( WebLogger $logger, InstallerFactory $installerFactory, ProgressFactory $progressFactory, \Magento\Framework\Setup\SampleData\State $sampleDataState, - DeploymentConfig $deploymentConfig + DeploymentConfig $deploymentConfig, + RequestDataConverter $requestDataConverter ) { $this->log = $logger; $this->installer = $installerFactory->create($logger); $this->progressFactory = $progressFactory; $this->sampleDataState = $sampleDataState; $this->deploymentConfig = $deploymentConfig; + $this->requestDataConverter = $requestDataConverter; } /** @@ -91,7 +96,6 @@ public function indexAction() * Index Action * * @return JsonModel - * @SuppressWarnings(PHPMD.NPathComplexity) */ public function startAction() { @@ -99,11 +103,9 @@ public function startAction() $json = new JsonModel; try { $this->checkForPriorInstall(); - $data = array_merge( - $this->importDeploymentConfigForm(), - $this->importUserConfigForm(), - $this->importAdminUserForm() - ); + $content = $this->getRequest()->getContent(); + $source = $content ? $source = Json::decode($content, Json::TYPE_ARRAY) : []; + $data = $this->requestDataConverter->convert($source); $this->installer->install($data); $json->setVariable( 'key', @@ -168,102 +170,4 @@ private function checkForPriorInstall() throw new \Magento\Setup\Exception('Magento application is already installed.'); } } - - /** - * Maps data from request to format of deployment config model - * - * @return array - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - private function importDeploymentConfigForm() - { - $content = $this->getRequest()->getContent(); - $source = []; - if ($content) { - $source = Json::decode($content, Json::TYPE_ARRAY); - } - - $result = []; - $result[SetupConfigOptionsList::INPUT_KEY_DB_HOST] = isset($source['db']['host']) ? $source['db']['host'] : ''; - $result[SetupConfigOptionsList::INPUT_KEY_DB_NAME] = isset($source['db']['name']) ? $source['db']['name'] : ''; - $result[SetupConfigOptionsList::INPUT_KEY_DB_USER] = isset($source['db']['user']) ? $source['db']['user'] :''; - $result[SetupConfigOptionsList::INPUT_KEY_DB_PASSWORD] = - isset($source['db']['password']) ? $source['db']['password'] : ''; - $result[SetupConfigOptionsList::INPUT_KEY_DB_PREFIX] = - isset($source['db']['tablePrefix']) ? $source['db']['tablePrefix'] : ''; - $result[BackendConfigOptionsList::INPUT_KEY_BACKEND_FRONTNAME] = isset($source['config']['address']['admin']) - ? $source['config']['address']['admin'] : ''; - $result[SetupConfigOptionsList::INPUT_KEY_ENCRYPTION_KEY] = isset($source['config']['encrypt']['key']) - ? $source['config']['encrypt']['key'] : null; - $result[SetupConfigOptionsList::INPUT_KEY_SESSION_SAVE] = isset($source['config']['sessionSave']['type']) - ? $source['config']['sessionSave']['type'] : SetupConfigOptionsList::SESSION_SAVE_FILES; - $result[Installer::ENABLE_MODULES] = isset($source['store']['selectedModules']) - ? implode(',', $source['store']['selectedModules']) : ''; - $result[Installer::DISABLE_MODULES] = isset($source['store']['allModules']) - ? implode(',', array_diff($source['store']['allModules'], $source['store']['selectedModules'])) : ''; - return $result; - } - - /** - * Maps data from request to format of user config model - * - * @return array - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - private function importUserConfigForm() - { - $result = []; - $source = []; - $content = $this->getRequest()->getContent(); - if ($content) { - $source = Json::decode($content, Json::TYPE_ARRAY); - } - if (isset($source['config']['address']['base_url']) && !empty($source['config']['address']['base_url'])) { - $result[UserConfig::KEY_BASE_URL] = $source['config']['address']['base_url']; - } - $result[UserConfig::KEY_USE_SEF_URL] = isset($source['config']['rewrites']['allowed']) - ? $source['config']['rewrites']['allowed'] : ''; - $result[UserConfig::KEY_IS_SECURE] = isset($source['config']['https']['front']) - ? $source['config']['https']['front'] : ''; - $result[UserConfig::KEY_IS_SECURE_ADMIN] = isset($source['config']['https']['admin']) - ? $source['config']['https']['admin'] : ''; - $result[UserConfig::KEY_BASE_URL_SECURE] = (isset($source['config']['https']['front']) - || isset($source['config']['https']['admin'])) - ? $source['config']['https']['text'] : ''; - $result[UserConfig::KEY_LANGUAGE] = isset($source['store']['language']) - ? $source['store']['language'] : ''; - $result[UserConfig::KEY_TIMEZONE] = isset($source['store']['timezone']) - ? $source['store']['timezone'] : ''; - $result[UserConfig::KEY_CURRENCY] = isset($source['store']['currency']) - ? $source['store']['currency'] : ''; - $result[InstallCommand::INPUT_KEY_USE_SAMPLE_DATA] = isset($source['store']['useSampleData']) - ? $source['store']['useSampleData'] : ''; - $result[InstallCommand::INPUT_KEY_CLEANUP_DB] = isset($source['store']['cleanUpDatabase']) - ? $source['store']['cleanUpDatabase'] : ''; - return $result; - } - - /** - * Maps data from request to format of admin account model - * - * @return array - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - private function importAdminUserForm() - { - $result = []; - $source = []; - $content = $this->getRequest()->getContent(); - if ($content) { - $source = Json::decode($content, Json::TYPE_ARRAY); - } - $result[AdminAccount::KEY_USER] = isset($source['admin']['username']) ? $source['admin']['username'] : ''; - $result[AdminAccount::KEY_PASSWORD] = isset($source['admin']['password']) ? $source['admin']['password'] : ''; - $result[AdminAccount::KEY_EMAIL] = isset($source['admin']['email']) ? $source['admin']['email'] : ''; - $result[AdminAccount::KEY_FIRST_NAME] = $result[AdminAccount::KEY_USER]; - $result[AdminAccount::KEY_LAST_NAME] = $result[AdminAccount::KEY_USER]; - return $result; - } } diff --git a/setup/src/Magento/Setup/Controller/ValidateAdminCredentials.php b/setup/src/Magento/Setup/Controller/ValidateAdminCredentials.php new file mode 100644 index 0000000000000..6833c1fba0c11 --- /dev/null +++ b/setup/src/Magento/Setup/Controller/ValidateAdminCredentials.php @@ -0,0 +1,61 @@ +adminCredentialsValidator = $adminCredentialsValidator; + $this->requestDataConverter = $requestDataConverter; + } + + /** + * Validate admin credentials. + * + * @return JsonModel + */ + public function indexAction() + { + try { + $content = $this->getRequest()->getContent(); + $source = $content ? $source = Json::decode($content, Json::TYPE_ARRAY) : []; + $data = $this->requestDataConverter->convert($source); + $this->adminCredentialsValidator->validate($data); + return new JsonModel(['success' => true]); + } catch (\Exception $e) { + return new JsonModel(['success' => false, 'error' => $e->getMessage()]); + } + } +} diff --git a/setup/src/Magento/Setup/Model/AdminAccount.php b/setup/src/Magento/Setup/Model/AdminAccount.php index cd9362f8a0d47..6605052cede1e 100644 --- a/setup/src/Magento/Setup/Model/AdminAccount.php +++ b/setup/src/Magento/Setup/Model/AdminAccount.php @@ -137,22 +137,22 @@ private function saveAdminUser() * @return void * @throws \Exception If the username and email do not both match data provided to install */ - private function validateUserMatches($username, $email) + public function validateUserMatches($username, $email) { if ((strcasecmp($email, $this->data[self::KEY_EMAIL]) == 0) && (strcasecmp($username, $this->data[self::KEY_USER]) != 0)) { // email matched but username did not throw new \Exception( - 'An existing user has the given email but different username. ' . self::KEY_USER . - ' and ' . self::KEY_EMAIL . ' both need to match an existing user or both be new.' + 'An existing user has the given email but different username. ' + . 'Username and email both need to match an existing user or both be new.' ); } if ((strcasecmp($username, $this->data[self::KEY_USER]) == 0) && (strcasecmp($email, $this->data[self::KEY_EMAIL]) != 0)) { // username matched but email did not throw new \Exception( - 'An existing user has the given username but different email. ' . self::KEY_USER . - ' and ' . self::KEY_EMAIL . ' both need to match an existing user or both be new.' + 'An existing user has the given username but different email. ' + . 'Username and email both need to match an existing user or both be new.' ); } } diff --git a/setup/src/Magento/Setup/Model/RequestDataConverter.php b/setup/src/Magento/Setup/Model/RequestDataConverter.php new file mode 100644 index 0000000000000..bd924c16f3da0 --- /dev/null +++ b/setup/src/Magento/Setup/Model/RequestDataConverter.php @@ -0,0 +1,117 @@ +convertDeploymentConfigForm($source), + $this->convertUserConfigForm($source), + $this->convertAdminUserForm($source) + ); + return $result; + } + + /** + * Convert data from request to format of deployment config model + * + * @param array $source + * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + private function convertDeploymentConfigForm(array $source) + { + $result = []; + $result[SetupConfigOptionsList::INPUT_KEY_DB_HOST] = isset($source['db']['host']) ? $source['db']['host'] : ''; + $result[SetupConfigOptionsList::INPUT_KEY_DB_NAME] = isset($source['db']['name']) ? $source['db']['name'] : ''; + $result[SetupConfigOptionsList::INPUT_KEY_DB_USER] = isset($source['db']['user']) ? $source['db']['user'] : ''; + $result[SetupConfigOptionsList::INPUT_KEY_DB_PASSWORD] = + isset($source['db']['password']) ? $source['db']['password'] : ''; + $result[SetupConfigOptionsList::INPUT_KEY_DB_PREFIX] = + isset($source['db']['tablePrefix']) ? $source['db']['tablePrefix'] : ''; + $result[BackendConfigOptionsList::INPUT_KEY_BACKEND_FRONTNAME] = isset($source['config']['address']['admin']) + ? $source['config']['address']['admin'] : ''; + $result[SetupConfigOptionsList::INPUT_KEY_ENCRYPTION_KEY] = isset($source['config']['encrypt']['key']) + ? $source['config']['encrypt']['key'] : null; + $result[SetupConfigOptionsList::INPUT_KEY_SESSION_SAVE] = isset($source['config']['sessionSave']['type']) + ? $source['config']['sessionSave']['type'] : SetupConfigOptionsList::SESSION_SAVE_FILES; + $result[Installer::ENABLE_MODULES] = isset($source['store']['selectedModules']) + ? implode(',', $source['store']['selectedModules']) : ''; + $result[Installer::DISABLE_MODULES] = isset($source['store']['allModules']) + ? implode(',', array_diff($source['store']['allModules'], $source['store']['selectedModules'])) : ''; + return $result; + } + + /** + * Convert data from request to format of user config model + * + * @param array $source + * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + private function convertUserConfigForm(array $source) + { + $result = []; + if (isset($source['config']['address']['base_url']) && !empty($source['config']['address']['base_url'])) { + $result[UserConfig::KEY_BASE_URL] = $source['config']['address']['base_url']; + } + $result[UserConfig::KEY_USE_SEF_URL] = isset($source['config']['rewrites']['allowed']) + ? $source['config']['rewrites']['allowed'] : ''; + $result[UserConfig::KEY_IS_SECURE] = isset($source['config']['https']['front']) + ? $source['config']['https']['front'] : ''; + $result[UserConfig::KEY_IS_SECURE_ADMIN] = isset($source['config']['https']['admin']) + ? $source['config']['https']['admin'] : ''; + $result[UserConfig::KEY_BASE_URL_SECURE] = (isset($source['config']['https']['front']) + || isset($source['config']['https']['admin'])) + ? $source['config']['https']['text'] : ''; + $result[UserConfig::KEY_LANGUAGE] = isset($source['store']['language']) + ? $source['store']['language'] : ''; + $result[UserConfig::KEY_TIMEZONE] = isset($source['store']['timezone']) + ? $source['store']['timezone'] : ''; + $result[UserConfig::KEY_CURRENCY] = isset($source['store']['currency']) + ? $source['store']['currency'] : ''; + $result[InstallCommand::INPUT_KEY_USE_SAMPLE_DATA] = isset($source['store']['useSampleData']) + ? $source['store']['useSampleData'] : ''; + $result[InstallCommand::INPUT_KEY_CLEANUP_DB] = isset($source['store']['cleanUpDatabase']) + ? $source['store']['cleanUpDatabase'] : ''; + return $result; + } + + /** + * Convert data from request to format of admin account model + * + * @param array $source + * @return array + */ + private function convertAdminUserForm(array $source) + { + $result = []; + $result[AdminAccount::KEY_USER] = isset($source['admin']['username']) ? $source['admin']['username'] : ''; + $result[AdminAccount::KEY_PASSWORD] = isset($source['admin']['password']) ? $source['admin']['password'] : ''; + $result[AdminAccount::KEY_EMAIL] = isset($source['admin']['email']) ? $source['admin']['email'] : ''; + $result[AdminAccount::KEY_FIRST_NAME] = $result[AdminAccount::KEY_USER]; + $result[AdminAccount::KEY_LAST_NAME] = $result[AdminAccount::KEY_USER]; + return $result; + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/UpgradeCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/UpgradeCommandTest.php index 9066201ace7b3..17d78718e4bf4 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/UpgradeCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/UpgradeCommandTest.php @@ -28,21 +28,11 @@ public function testExecute() $installer->expects($this->at(2))->method('installDataFixtures'); $installerFactory->expects($this->once())->method('create')->willReturn($installer); - $pathToCacheStatus = '/path/to/cachefile'; - $writeFactory = $this->getMock('\Magento\Framework\Filesystem\Directory\WriteFactory', [], [], '', false); - $write = $this->getMock('\Magento\Framework\Filesystem\Directory\Write', [], [], '', false); - $write->expects($this->once())->method('isExist')->with('/path/to/cachefile')->willReturn(false); - $write->expects($this->once())->method('getRelativePath')->willReturn($pathToCacheStatus); - - $writeFactory->expects($this->once())->method('create')->willReturn($write); - $directoryList = $this->getMock('\Magento\Framework\App\Filesystem\DirectoryList', [], [], '', false); - $objectManager->expects($this->exactly(4)) + $objectManager->expects($this->exactly(2)) ->method('get') ->will($this->returnValueMap([ ['Magento\Framework\App\State', $state], - ['Magento\Framework\ObjectManager\ConfigLoaderInterface', $configLoader], - ['Magento\Framework\Filesystem\Directory\WriteFactory', $writeFactory], - ['Magento\Framework\App\Filesystem\DirectoryList', $directoryList], + ['Magento\Framework\ObjectManager\ConfigLoaderInterface', $configLoader] ])); $commandTester = new CommandTester(new UpgradeCommand($installerFactory, $objectManagerProvider)); diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/InstallTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/InstallTest.php index 121621075a138..55666ffef059c 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/InstallTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/InstallTest.php @@ -7,6 +7,7 @@ namespace Magento\Setup\Test\Unit\Controller; use \Magento\Setup\Controller\Install; +use Magento\Setup\Model\RequestDataConverter; class InstallTest extends \PHPUnit_Framework_TestCase { @@ -25,6 +26,11 @@ class InstallTest extends \PHPUnit_Framework_TestCase */ private $progressFactory; + /** + * @var \PHPUnit_Framework_MockObject_MockObject|RequestDataConverter + */ + private $requestDataConverter; + /** * @var Install */ @@ -48,6 +54,7 @@ public function setUp() $this->progressFactory = $this->getMock('\Magento\Setup\Model\Installer\ProgressFactory', [], [], '', false); $this->sampleDataState = $this->getMock('\Magento\Framework\Setup\SampleData\State', [], [], '', false); $this->deploymentConfig = $this->getMock('\Magento\Framework\App\DeploymentConfig', [], [], '', false); + $this->requestDataConverter = $this->getMock(RequestDataConverter::class, [], [], '', false); $installerFactory->expects($this->once())->method('create')->with($this->webLogger) ->willReturn($this->installer); @@ -56,7 +63,8 @@ public function setUp() $installerFactory, $this->progressFactory, $this->sampleDataState, - $this->deploymentConfig + $this->deploymentConfig, + $this->requestDataConverter ); } diff --git a/setup/src/Magento/Setup/Validator/AdminCredentialsValidator.php b/setup/src/Magento/Setup/Validator/AdminCredentialsValidator.php new file mode 100644 index 0000000000000..b487e11a63fe1 --- /dev/null +++ b/setup/src/Magento/Setup/Validator/AdminCredentialsValidator.php @@ -0,0 +1,87 @@ +connectionFactory = $connectionFactory; + $this->adminAccountFactory = $adminAccountFactory; + $this->setupFactory = $setupFactory; + } + + /** + * Validate admin user name and email. + * + * @param array $data + * @return void + * @throws \Exception + */ + public function validate(array $data) + { + try { + $dbConnection = $this->connectionFactory->create([ + ConfigOption::KEY_NAME => $data[ConfigOption::INPUT_KEY_DB_NAME], + ConfigOption::KEY_HOST => $data[ConfigOption::INPUT_KEY_DB_HOST], + ConfigOption::KEY_USER => $data[ConfigOption::INPUT_KEY_DB_USER], + ConfigOption::KEY_PASSWORD => $data[ConfigOption::INPUT_KEY_DB_PASSWORD], + ConfigOption::KEY_PREFIX => $data[ConfigOption::INPUT_KEY_DB_PREFIX] + ]); + + $userName = $data[AdminAccount::KEY_USER]; + $userEmail = $data[AdminAccount::KEY_EMAIL]; + $userTable = $dbConnection->getTableName('admin_user'); + $result = $dbConnection->fetchRow( + "SELECT user_id, username, email FROM {$userTable} WHERE username = :username OR email = :email", + ['username' => $userName, 'email' => $userEmail] + ); + $setup = $this->setupFactory->create(); + $adminAccount = $this->adminAccountFactory->create( + $setup, + [AdminAccount::KEY_USER => $userName, AdminAccount::KEY_EMAIL => $userEmail] + ); + } catch (\Exception $e) { + return; + } + + if (!empty($result)) { + $adminAccount->validateUserMatches($result['username'], $result['email']); + } + } +} diff --git a/setup/view/magento/setup/create-admin-account.phtml b/setup/view/magento/setup/create-admin-account.phtml index 8c08d127aac65..35c0c911b184c 100644 --- a/setup/view/magento/setup/create-admin-account.phtml +++ b/setup/view/magento/setup/create-admin-account.phtml @@ -29,7 +29,7 @@ $passwordWizard = sprintf( @@ -41,6 +41,25 @@ $passwordWizard = sprintf(

{{$state.current.header}}

+
+ Credentials validation successfully passed. +
+
+ {{validateCredentials.result.error}} +
+
+ {{validateCredentials.failed}} +
+

Create a new Admin account to manage your store.