diff --git a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php index f9bc1bb9617d77523ca9eeeb949ccbaee607311c..a89178f6ed141fac175be54475f265be81d2e559 100644 --- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php +++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php @@ -931,13 +931,13 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac $select = $connection->select(); if ($attribute->getBackend()->getType() === 'static') { $value = $object->getData($attribute->getAttributeCode()); - $bind = ['attribute_code' => trim($value)]; + $bind = ['value' => trim($value)]; $select->from( $this->getEntityTable(), $this->getEntityIdField() )->where( - $attribute->getAttributeCode() . ' = :attribute_code' + $attribute->getAttributeCode() . ' = :value' ); } else { $value = $object->getData($attribute->getAttributeCode()); @@ -950,7 +950,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac ]; $select->from( $attribute->getBackend()->getTable(), - $attribute->getBackend()->getEntityIdField() + $object->getResource()->getLinkField() )->where( 'attribute_id = :attribute_id' )->where( diff --git a/app/code/Magento/Eav/Model/ResourceModel/UpdateHandler.php b/app/code/Magento/Eav/Model/ResourceModel/UpdateHandler.php index c2f390e90e5d6e5330ca2f3db99bb80074da12a7..2e486e4bfa7c045696bf396d9e6cd22ba4a6f349 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/UpdateHandler.php +++ b/app/code/Magento/Eav/Model/ResourceModel/UpdateHandler.php @@ -134,6 +134,7 @@ class UpdateHandler if ((!array_key_exists($attribute->getAttributeCode(), $snapshot) || $snapshot[$attribute->getAttributeCode()] === false) && array_key_exists($attribute->getAttributeCode(), $data) + && $data[$attribute->getAttributeCode()] !== false && !$attribute->isValueEmpty($data[$attribute->getAttributeCode()]) ) { $this->attributePersistor->registerInsert( @@ -147,6 +148,7 @@ class UpdateHandler if (array_key_exists($attribute->getAttributeCode(), $snapshot) && $snapshot[$attribute->getAttributeCode()] !== false && array_key_exists($attribute->getAttributeCode(), $data) + && $data[$attribute->getAttributeCode()] !== false && $snapshot[$attribute->getAttributeCode()] != $data[$attribute->getAttributeCode()] && !$attribute->isValueEmpty($data[$attribute->getAttributeCode()]) ) { diff --git a/app/code/Magento/Ui/Component/AbstractComponent.php b/app/code/Magento/Ui/Component/AbstractComponent.php index 0fac243a21da0670817825040d99b372f3ad4ad0..d61acfb312adc2ca66019596c3b20b3943df0774 100644 --- a/app/code/Magento/Ui/Component/AbstractComponent.php +++ b/app/code/Magento/Ui/Component/AbstractComponent.php @@ -106,6 +106,10 @@ abstract class AbstractComponent extends DataObject implements UiComponentInterf $this->getContext()->addActions($this->getData('actions'), $this); } + if ($this->hasData('html_blocks')) { + $this->getContext()->addHtmlBlocks($this->getData('html_blocks'), $this); + } + if ($this->hasData('buttons')) { $this->getContext()->addButtons($this->getData('buttons'), $this); } diff --git a/app/code/Magento/Ui/Component/Control/ActionPool.php b/app/code/Magento/Ui/Component/Control/ActionPool.php index 6ccb8393648dc0337a7acc66d6f4022acfbce878..47548541f11b49dcc2f620ec6b4b29aa6b4297a2 100644 --- a/app/code/Magento/Ui/Component/Control/ActionPool.php +++ b/app/code/Magento/Ui/Component/Control/ActionPool.php @@ -117,6 +117,21 @@ class ActionPool implements ActionPoolInterface } } + /** + * Add html block + * + * @param string $type + * @param string $name + * @param array $arguments + * @return void + */ + public function addHtmlBlock($type, $name = '', array $arguments = []) + { + $toolbar = $this->getToolbar(); + $container = $this->context->getPageLayout()->createBlock($type, $name, $arguments); + $toolbar->setChild($name, $container); + } + /** * Create button container * diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/insert-form.js b/app/code/Magento/Ui/view/base/web/js/form/components/insert-form.js index 6b70d1dd9f27abafcfd10e5bd4aa807621fd1823..f882d407165d6d4bc5081de4bdbf462b5ad8052a 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/insert-form.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/insert-form.js @@ -89,7 +89,7 @@ define([ /** @inheritdoc*/ destroyInserted: function () { if (this.isRendered) { - this.externalForm().destroy(); + this.externalForm().delegate('destroy'); this.removeActions(); this.responseStatus(undefined); this.responseData = {}; diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/insert.js b/app/code/Magento/Ui/view/base/web/js/form/components/insert.js index 403ed424602d5961bc860087f3c282e30a42bdaa..820b6612e728ee009ff15a5279d4f8f6a7ab7be4 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/insert.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/insert.js @@ -123,13 +123,15 @@ define([ return this; } + self.previousParams = params || {}; + $.async({ component: this.name, ctx: '.' + this.contentSelector }, function (el) { self.contentEl = $(el); self.startRender = true; - params = _.extend(params || {}, self.params); + params = _.extend({}, self.params, params || {}); request = self.requestData(params, self.renderSettings); request .done(self.onRender) diff --git a/app/code/Magento/Ui/view/base/web/js/modal/modal-component.js b/app/code/Magento/Ui/view/base/web/js/modal/modal-component.js index 7c4263a0c1ae9f70a473adedd00ebaa06e9ea952..e67c5403fb5af97878b6707b6ba0609a110cb51a 100644 --- a/app/code/Magento/Ui/view/base/web/js/modal/modal-component.js +++ b/app/code/Magento/Ui/view/base/web/js/modal/modal-component.js @@ -73,9 +73,11 @@ define([ * @returns {Object} Chainable. */ initSelector: function () { + var modalClass = this.name.replace(/\./g, '_'); + this.contentSelector = '.' + this.modalClass; - this.options.modalClass = this.name.replace(/\./g, '_'); - this.rootSelector = '.' + this.options.modalClass; + this.options.modalClass = this.options.modalClass + ' ' + modalClass; + this.rootSelector = '.' + modalClass; return this; }, diff --git a/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/_module.less index 74c2b190d33cb26aedccdd7eb4acab8476be0309..89c9c3bda87ab1af228d8b57b3e628026932f894 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/_module.less @@ -5,3 +5,4 @@ @import 'module/_scheduled-changes.less'; @import 'module/_staging-data-tooltip.less'; +@import 'module/_scheduled-changes-modal.less'; diff --git a/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/module/_scheduled-changes-modal.less b/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/module/_scheduled-changes-modal.less new file mode 100644 index 0000000000000000000000000000000000000000..e7e66437bc8ba0471b2ba0f36be9de4d67686699 --- /dev/null +++ b/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/module/_scheduled-changes-modal.less @@ -0,0 +1,31 @@ +// /** +// * Copyright © 2015 Magento. All rights reserved. +// * See COPYING.txt for license details. +// */ + +// +// Scheduled changes modal +// _____________________________________________ + +// +// Slide modal panel with store switcher +// --------------------------------------------- + +.scheduled-changes-modal-slide { + .page-actions { + .lib-vendor-prefix-display(flex); + width: 100%; + + .store-switcher { + .lib-vendor-prefix-order(-1); + -ms-flex: 1; + -webkit-flex: 1; + flex: 1; + + .admin__action-dropdown { + font-size: @action-dropdown__font-size; + letter-spacing: -.025em; + } + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php index a799dc0865bbdcf5f6876fb72241eed1b4ea5072..3c08f8155bce84785cb1b617caad09e86ec9c408 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php @@ -147,7 +147,58 @@ class CategoryTest extends \Magento\TestFramework\TestCase\AbstractBackendContro public function saveActionDataProvider() { return [ - //'default values' removed from here. Should be fixed in MAGETWO-49481 + 'default values' => [ + [ + 'id' => '2', + 'entity_id' => '2', + 'path' => '1/2', + 'url_key' => 'default-category', + 'is_anchor' => false, + 'use_default' => [ + 'name' => 1, + 'is_active' => 1, + 'thumbnail' => 1, + 'description' => 1, + 'image' => 1, + 'meta_title' => 1, + 'meta_keywords' => 1, + 'meta_description' => 1, + 'include_in_menu' => 1, + 'display_mode' => 1, + 'landing_page' => 1, + 'available_sort_by' => 1, + 'default_sort_by' => 1, + 'filter_price_range' => 1, + 'custom_apply_to_products' => 1, + 'custom_design' => 1, + 'custom_design_from' => 1, + 'custom_design_to' => 1, + 'page_layout' => 1, + 'custom_layout_update' => 1, + ], + ], + [ + 'name' => false, + 'default_sort_by' => false, + 'display_mode' => false, + 'meta_title' => false, + 'custom_design' => false, + 'page_layout' => false, + 'is_active' => false, + 'include_in_menu' => false, + 'landing_page' => false, + 'is_anchor' => false, + 'custom_apply_to_products' => false, + 'available_sort_by' => false, + 'description' => false, + 'meta_keywords' => false, + 'meta_description' => false, + 'custom_layout_update' => false, + 'custom_design_from' => false, + 'custom_design_to' => false, + 'filter_price_range' => false + ], + ], 'custom values' => [ [ 'id' => '2', @@ -163,7 +214,7 @@ class CategoryTest extends \Magento\TestFramework\TestCase\AbstractBackendContro 'url_key' => 'default-category', 'display_mode' => 'PRODUCTS', 'landing_page' => '1', - 'is_anchor' => '1', + 'is_anchor' => true, 'custom_apply_to_products' => '0', 'custom_design' => 'Magento/blank', 'custom_design_from' => '5/21/2015', @@ -232,7 +283,7 @@ class CategoryTest extends \Magento\TestFramework\TestCase\AbstractBackendContro 'url_key' => 'default-category', 'display_mode' => 'PRODUCTS', 'landing_page' => '1', - 'is_anchor' => '1', + 'is_anchor' => true, 'custom_apply_to_products' => '0', 'custom_design' => 'Magento/blank', 'custom_design_from' => '5/29/2015', diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php index e3e6c2588736c9c073def5dd495c80f4cbc9658b..e3f2afad0c0759cd731ec33ca272cefc1814033a 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php @@ -37,7 +37,6 @@ class ProductTest extends \PHPUnit_Framework_TestCase $this->_model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( 'Magento\Catalog\Model\Product' ); - $this->markTestSkipped('Test skipped due to changes that appear after MAGETWO-45654'); } public static function tearDownAfterClass() @@ -434,6 +433,47 @@ class ProductTest extends \PHPUnit_Framework_TestCase } } + /** + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/Catalog/_files/products_with_unique_input_attribute.php + */ + public function testValidateUniqueInputAttributeValue() + { + /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */ + $attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get('Magento\Catalog\Model\ResourceModel\Eav\Attribute') + ->loadByCode(\Magento\Catalog\Model\Product::ENTITY, 'unique_input_attribute'); + $this->_model->setTypeId( + 'simple' + )->setAttributeSetId( + 4 + )->setName( + 'Simple Product with non-unique value' + )->setSku( + 'some product SKU' + )->setPrice( + 10 + )->setMetaTitle( + 'meta title' + )->setData( + $attribute->getAttributeCode(), + 'unique value' + )->setVisibility( + \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH + )->setStatus( + \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED + )->setCollectExceptionMessages( + true + ); + + $validationResult = $this->_model->validate(); + $this->assertCount(1, $validationResult); + $this->assertContains( + 'The value of attribute "' . $attribute->getDefaultFrontendLabel() . '" must be unique', + $validationResult + ); + } + /** * @magentoDataFixture Magento/Catalog/_files/product_simple.php * @magentoAppIsolation enabled diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_unique_input_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_unique_input_attribute.php new file mode 100644 index 0000000000000000000000000000000000000000..5d7b8b244d4a9ab05c56eb75ef00f6d1936fff6e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_unique_input_attribute.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +require __DIR__ . '/unique_input_attribute.php'; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = $objectManager->create('Magento\Catalog\Model\Product'); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setName('Simple Product with unique input attribute') + ->setSku('simple product with unique input attribute') + ->setPrice(10) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setWebsiteIds([1]) + ->setStockData(['qty' => 100, 'is_in_stock' => 1]) + ->setData($attribute->getAttributeCode(), 'unique value') + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/unique_input_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/unique_input_attribute.php new file mode 100644 index 0000000000000000000000000000000000000000..d357473de9ef1fdc89b9ed5ed9d74570820e588d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/unique_input_attribute.php @@ -0,0 +1,42 @@ +<?php +/** + * "Input" fixture of product EAV attribute. + * + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var \Magento\Eav\Model\Entity\Type $entityType */ +$entityType = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Eav\Model\Entity\Type'); +$entityType->loadByCode('catalog_product'); +$defaultSetId = $entityType->getDefaultAttributeSetId(); +/** @var \Magento\Eav\Model\Entity\Attribute\Set $defaultSet */ +$defaultSet = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + 'Magento\Eav\Model\Entity\Attribute\Set' +); +$defaultSet->load($defaultSetId); +$defaultGroupId = $defaultSet->getDefaultGroupId(); + +/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ +$attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + 'Magento\Catalog\Model\ResourceModel\Eav\Attribute' +); +$attribute->setAttributeCode( + 'unique_input_attribute' +)->setEntityTypeId( + $entityType->getEntityTypeId() +)->setAttributeGroupId( + $defaultGroupId +)->setAttributeSetId( + $defaultSetId +)->setFrontendInput( + 'text' +)->setFrontendLabel( + 'Unique Input Attribute' +)->setBackendType( + 'varchar' +)->setIsUserDefined( + 1 +)->setIsUnique( + 1 +)->save(); diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php b/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php index 3e23ff74b22eccf5477fd87e749ed6f0cd427819..a054442fd0370e3077324712d6d3fc488fd82bec 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php @@ -298,6 +298,19 @@ class Context implements ContextInterface return $sortOrderA - $sortOrderB; } + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function addHtmlBlocks(array $htmlBlocks, UiComponentInterface $component) + { + if (!empty($htmlBlocks)) { + foreach ($htmlBlocks as $htmlBlock => $blockData) { + $this->actionPool->addHtmlBlock($blockData['type'], $blockData['name'], $blockData['arguments']); + } + } + } + /** * Getting requested accept type * diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/ContextInterface.php b/lib/internal/Magento/Framework/View/Element/UiComponent/ContextInterface.php index 97d1f70707c7f450fd9c7f307ebe64ca330c54c1..cf78e6ea447a252631e74a55b6bf43660564cf2e 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/ContextInterface.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/ContextInterface.php @@ -122,6 +122,15 @@ interface ContextInterface */ public function addButtons(array $buttons, UiComponentInterface $component); + /** + * Add html block in the actions toolbar + * + * @param array $htmlBlocks + * @param UiComponentInterface $component + * @return void + */ + public function addHtmlBlocks(array $htmlBlocks, UiComponentInterface $component); + /** * Get render engine * diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/Control/ActionPoolInterface.php b/lib/internal/Magento/Framework/View/Element/UiComponent/Control/ActionPoolInterface.php index 225bfe0dcf81b7faef0e797b4629bc7c0ec7a32e..89d12c3451e84736fd5e979d1d1c93ad25c5c106 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/Control/ActionPoolInterface.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/Control/ActionPoolInterface.php @@ -46,4 +46,14 @@ interface ActionPoolInterface * @return bool|BlockInterface */ public function getToolbar(); + + /** + * Add html block + * + * @param string $type + * @param string $name + * @param array $arguments + * @return void + */ + public function addHtmlBlock($type, $name = '', array $arguments = []); }