diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php index 380f38a5cde1a..f43bf07aca723 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php @@ -97,7 +97,6 @@ public function modifyMeta(array $meta) 'buttons' => [ [ 'text' => __('Cancel'), - 'class' => 'action-secondary', 'actions' => ['closeModal'], ], [ @@ -456,8 +455,13 @@ protected function getOptionInfo() 'dataType' => Form\Element\DataType\Text::NAME, 'formElement' => Form\Element\Select::NAME, 'componentType' => Form\Field::NAME, + 'component' => 'Magento_Bundle/js/components/bundle-input-type', + 'parentContainer' => 'product_bundle_container', + 'selections' => 'bundle_selections', + 'targetIndex' => 'is_default', 'dataScope' => 'type', 'label' => __('Input Type'), + 'sortOrder' => 20, 'options' => [ [ 'label' => __('Drop-down'), @@ -476,7 +480,12 @@ protected function getOptionInfo() 'value' => 'multi' ] ], - 'sortOrder' => 20, + 'typeMap' => [ + 'select' => 'radio', + 'radio' => 'radio', + 'checkbox' => 'checkbox', + 'multi' => 'checkbox' + ] ], ], ], @@ -533,15 +542,22 @@ protected function getBundleSelections() 'arguments' => [ 'data' => [ 'config' => [ - 'component' => 'Magento_Bundle/js/components/bundle-checkbox', + 'formElement' => Form\Element\Checkbox::NAME, 'componentType' => Form\Field::NAME, + 'component' => 'Magento_Bundle/js/components/bundle-checkbox', + 'parentContainer' => 'product_bundle_container', + 'parentSelections' => 'bundle_selections', + 'changer' => 'option_info.type', 'dataType' => Form\Element\DataType\Boolean::NAME, - 'formElement' => Form\Element\Checkbox::NAME, 'label' => __('Default'), 'dataScope' => 'is_default', 'prefer' => 'radio', - 'value' => '1', + 'value' => '0', 'sortOrder' => 50, + 'valueMap' => [ + 'false' => '0', + 'true' => '1' + ] ], ], ], diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-checkbox.js b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-checkbox.js index b075fc556ffd0..e81c2154c454b 100644 --- a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-checkbox.js +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-checkbox.js @@ -12,9 +12,9 @@ define([ return Checkbox.extend({ defaults: { clearing: false, - parentContainer: 'product_bundle_container', - parentSelections: 'bundle_selections', - changer: 'option_info.type' + parentContainer: '', + parentSelections: '', + changer: '' }, /** @@ -32,7 +32,7 @@ define([ */ initConfig: function () { this._super(); - this.imports.changeType = this.getParentName(this.parentContainer) + '.' + this.changer + ':value'; + this.imports.changeType = this.retrieveParentName(this.parentContainer) + '.' + this.changer + ':value'; return this; }, @@ -41,59 +41,52 @@ define([ * @inheritdoc */ onUpdate: function () { - if (this.prefer === 'radio' && !this.clearing) { + if (this.prefer === 'radio' && this.checked() && !this.clearing) { this.clearValues(); - } else if (this.prefer === 'radio') { - this.clearing = false; } this._super(); }, - /** - * Getter for parent name. Split string by provided parent name. - * - * @param {String} parent - parent name. - * @returns {String} - */ - getParentName: function (parent) { - return this.name.split(parent)[0] + parent; - }, - /** * Checkbox to radio type changer. * * @param {String} type - type to change. */ changeType: function (type) { - if (type === 'select') { - type = 'radio'; - } else if (type === 'multi') { - type = 'checkbox'; - } + var typeMap = registry.get(this.retrieveParentName(this.parentContainer) + '.' + this.changer).typeMap; - this.prefer = type; - this.clear(); - this.elementTmpl(this.templates[type]); - this.clearing = false; + this.prefer = typeMap[type]; + this.elementTmpl(this.templates[typeMap[type]]); }, /** * Clears values in components like this. */ clearValues: function () { - var records = registry.get(this.getParentName(this.parentSelections)), + var records = registry.get(this.retrieveParentName(this.parentSelections)), index = this.index, uid = this.uid; - this.clearing = true; records.elems.each(function (record) { record.elems.filter(function (comp) { return comp.index === index && comp.uid !== uid; }).each(function (comp) { + comp.clearing = true; comp.clear(); + comp.clearing = false; }); }); + }, + + /** + * Retrieve name for the most global parent with provided index. + * + * @param {String} parent - parent name. + * @returns {String} + */ + retrieveParentName: function (parent) { + return this.name.replace(new RegExp('^(.+?\\.)?' + parent + '\\..+'), '$1' + parent); } }); }); diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-input-type.js b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-input-type.js new file mode 100644 index 0000000000000..1f5ee5bd750c2 --- /dev/null +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-input-type.js @@ -0,0 +1,72 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/form/element/select', + 'uiRegistry' +], function (Select, registry) { + 'use strict'; + + return Select.extend({ + defaults: { + previousType: '', + parentContainer: '', + selections: '', + targetIndex: '', + typeMap: {} + }, + + /** + * @inheritdoc + */ + onUpdate: function () { + var type = this.typeMap[this.value()]; + + if (type !== this.previousType) { + this.previousType = type; + + if (type === 'radio') { + this.clearValues(); + } + } + + this._super(); + }, + + /** + * Clears values in components like this. + */ + clearValues: function () { + var records = registry.get(this.retrieveParentName(this.parentContainer) + '.' + this.selections), + checkedFound = false; + + records.elems.each(function (record) { + record.elems.filter(function (comp) { + return comp.index === this.targetIndex; + }, this).each(function (comp) { + if (comp.checked()) { + if (checkedFound) { + comp.clearing = true; + comp.clear(); + comp.clearing = false; + } + + checkedFound = true; + } + }); + }, this); + }, + + /** + * Retrieve name for the most global parent with provided index. + * + * @param {String} parent - parent name. + * @returns {String} + */ + retrieveParentName: function (parent) { + return this.name.replace(new RegExp('^(.+?\\.)?' + parent + '\\..+'), '$1' + parent); + } + }); +}); diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Button/Cancel.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Button/Cancel.php new file mode 100644 index 0000000000000..7da24ff678b90 --- /dev/null +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Button/Cancel.php @@ -0,0 +1,38 @@ + __('Cancel'), + 'data_attribute' => [ + 'mage-init' => [ + 'Magento_Ui/js/form/button-adapter' => [ + 'actions' => [ + [ + 'targetName' => 'product_form.product_form.add_attribute_modal' + . '.create_new_attribute_modal', + 'actionName' => 'toggleModal' + ] + ] + ] + ] + ], + 'on_click' => '' + ]; + } +} diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Button/Generic.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Button/Generic.php new file mode 100644 index 0000000000000..7cbc94563559e --- /dev/null +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Button/Generic.php @@ -0,0 +1,65 @@ +context = $context; + $this->registry = $registry; + } + + /** + * Generate url by route and parameters + * + * @param string $route + * @param array $params + * @return string + */ + public function getUrl($route = '', $params = []) + { + return $this->context->getUrl($route, $params); + } + + /** + * {@inheritdoc} + */ + public function getButtonData() + { + return []; + } +} diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Button/Save.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Button/Save.php new file mode 100644 index 0000000000000..7c6957f46ad54 --- /dev/null +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Button/Save.php @@ -0,0 +1,26 @@ + __('Save Attribute'), + 'class' => 'save primary', + 'data_attribute' => [ + 'mage-init' => ['button' => ['event' => 'save']], + 'form-role' => 'save', + ] + ]; + } +} diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Button/SaveInNewAttributeSet.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Button/SaveInNewAttributeSet.php new file mode 100644 index 0000000000000..8592d8142f3a4 --- /dev/null +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Button/SaveInNewAttributeSet.php @@ -0,0 +1,34 @@ + __('Save in New Attribute Set'), + 'data_attribute' => [ + 'mage-init' => [ + 'buttonAdapter' => [ + 'actions' => [ + [ + 'targetName' => 'product_attribute_add_form.product_attribute_add_form', + 'actionName' => 'saveAttributeInNewSet' + ], + ] + ] + ] + ], + 'on_click' => '' + ]; + } +} diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php index 3832adef2c4a7..132f971de4759 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php @@ -6,8 +6,25 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; -use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory; +use Magento\Catalog\Api\AttributeSetRepositoryInterface; +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Eav\Api\AttributeGroupRepositoryInterface; +use Magento\Eav\Api\AttributeManagementInterface; +use Magento\Eav\Api\AttributeRepositoryInterface; +use Magento\Eav\Api\Data\AttributeGroupInterface; +use Magento\Eav\Api\Data\AttributeGroupInterfaceFactory; +use Magento\Eav\Api\Data\AttributeInterface; +use Magento\Eav\Api\Data\AttributeSetInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Api\SortOrderBuilder; +use Magento\Framework\Exception\LocalizedException; +use Psr\Log\LoggerInterface; +/** + * Class AddAttributeToTemplate + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class AddAttributeToTemplate extends \Magento\Catalog\Controller\Adminhtml\Product { /** @@ -16,25 +33,82 @@ class AddAttributeToTemplate extends \Magento\Catalog\Controller\Adminhtml\Produ protected $resultJsonFactory; /** - * @var CollectionFactory + * @var AttributeRepositoryInterface + */ + protected $attributeRepository; + + /** + * @var AttributeSetRepositoryInterface + */ + protected $attributeSetRepository; + + /** + * @var AttributeGroupRepositoryInterface + */ + protected $attributeGroupRepository; + + /** + * @var SearchCriteriaBuilder + */ + protected $searchCriteriaBuilder; + + /** + * @var SortOrderBuilder + */ + protected $sortOrderBuilder; + + /** + * @var AttributeGroupInterfaceFactory + */ + protected $attributeGroupFactory; + + /** + * @var AttributeManagementInterfaces + */ + protected $attributeManagement; + + /** + * @var LoggerInterface */ - protected $attributeCollectionFactory; + protected $logger; /** * @param \Magento\Backend\App\Action\Context $context - * @param \Magento\Catalog\Controller\Adminhtml\Product\Builder $productBuilder + * @param Builder $productBuilder * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory - * @param CollectionFactory $attributeCollectionFactory + * @param AttributeRepositoryInterface $attributeRepository + * @param AttributeSetRepositoryInterface $attributeSetRepository + * @param AttributeGroupRepositoryInterface $attributeGroupRepository + * @param AttributeGroupInterfaceFactory $attributeGroupFactory + * @param SearchCriteriaBuilder $searchCriteriaBuilder + * @param SortOrderBuilder $sortOrderBuilder + * @param AttributeManagementInterface $attributeManagement + * @param LoggerInterface $logger + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Backend\App\Action\Context $context, \Magento\Catalog\Controller\Adminhtml\Product\Builder $productBuilder, \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory, - CollectionFactory $attributeCollectionFactory + AttributeRepositoryInterface $attributeRepository, + AttributeSetRepositoryInterface $attributeSetRepository, + AttributeGroupRepositoryInterface $attributeGroupRepository, + AttributeGroupInterfaceFactory $attributeGroupFactory, + SearchCriteriaBuilder $searchCriteriaBuilder, + SortOrderBuilder $sortOrderBuilder, + AttributeManagementInterface $attributeManagement, + LoggerInterface $logger ) { parent::__construct($context, $productBuilder); $this->resultJsonFactory = $resultJsonFactory; - $this->attributeCollectionFactory = $attributeCollectionFactory; + $this->attributeRepository = $attributeRepository; + $this->attributeSetRepository = $attributeSetRepository; + $this->attributeGroupRepository = $attributeGroupRepository; + $this->attributeGroupFactory = $attributeGroupFactory; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->sortOrderBuilder = $sortOrderBuilder; + $this->attributeManagement = $attributeManagement; + $this->logger = $logger; } /** @@ -49,56 +123,86 @@ public function execute() $response->setError(false); try { - $attributeSet = $this->_objectManager->create('Magento\Eav\Model\Entity\Attribute\Set') - ->load($request->getParam('templateId')); - - /** @var \Magento\Eav\Model\ResourceModel\Attribute\Collection $collection */ - $attributesCollection = $this->attributeCollectionFactory->create(); - - $attributesIds = $request->getParam('attributesIds'); - if ($attributesIds['excludeMode'] === 'false' && !empty($attributesIds['selected'])) { - $attributesCollection - ->addFieldToFilter('main_table.attribute_id', ['in' => $attributesIds['selected']]); - } elseif ($attributesIds['excludeMode'] === 'true') { - $attributesCollection->setExcludeSetFilter($attributeSet->getId()); - } else { - throw new \Magento\Framework\Exception\LocalizedException(__('Please, specify attributes')); - } - + /** @var AttributeSetInterface $attributeSet */ + $attributeSet = $this->attributeSetRepository->get($request->getParam('templateId')); $groupCode = $request->getParam('groupCode'); + $groupName = $request->getParam('groupName'); + $groupSortOrder = $request->getParam('groupSortOrder'); - /** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\Collection $attributeGroupCollection */ - $attributeGroupCollection = $this->_objectManager->get( - 'Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\Collection' - ); - $attributeGroupCollection->setAttributeSetFilter($attributeSet->getId()); - $attributeGroupCollection->addFilter('attribute_group_code', $groupCode); - $attributeGroupCollection->setPageSize(1); - - $attributeGroup = $attributeGroupCollection->getFirstItem(); - - if (!$attributeGroup->getId()) { - $attributeGroup->addData( - [ - 'attribute_group_code' => $groupCode, - 'attribute_set_id' => $attributeSet->getId(), - 'attribute_group_name' => $request->getParam('groupName'), - 'sort_order' => $request->getParam('groupSortOrder') - ] - ); - $attributeGroup->save(); - } + $attributeSearchCriteria = $this->getBasicAttributeSearchCriteriaBuilder()->create(); + $attributeGroupSearchCriteria = $this->searchCriteriaBuilder + ->addFilter('attribute_set_id', $attributeSet->getAttributeSetId()) + ->addFilter('attribute_group_code', $groupCode) + ->addSortOrder($this->sortOrderBuilder->setAscendingDirection()->create()) + ->setPageSize(1) + ->create(); + + try { + /** @var AttributeGroupInterface[] $attributeGroupItems */ + $attributeGroupItems = $this->attributeGroupRepository->getList($attributeGroupSearchCriteria) + ->getItems(); + + if (!$attributeGroupItems) { + throw new \Magento\Framework\Exception\NoSuchEntityException; + } - foreach ($attributesCollection as $attribute) { - $attribute->setAttributeSetId($attributeSet->getId())->loadEntityAttributeIdBySet(); - $attribute->setAttributeGroupId($attributeGroup->getId()) - ->setSortOrder('0') - ->save(); + /** @var AttributeGroupInterface $attributeGroup */ + $attributeGroup = reset($attributeGroupItems); + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + /** @var AttributeGroupInterface $attributeGroup */ + $attributeGroup = $this->attributeGroupFactory->create(); } - } catch (\Exception $e) { + + $attributeGroup->setAttributeGroupCode($groupCode); + $attributeGroup->setSortOrder($groupSortOrder); + $attributeGroup->setAttributeGroupName($groupName); + $attributeGroup->setAttributeSetId($attributeSet->getAttributeSetId()); + + $this->attributeGroupRepository->save($attributeGroup); + + /** @var AttributeInterface[] $attributesItems */ + $attributesItems = $this->attributeRepository->getList( + ProductAttributeInterface::ENTITY_TYPE_CODE, + $attributeSearchCriteria + )->getItems(); + + array_walk($attributesItems, function (AttributeInterface $attribute) use ($attributeSet, $attributeGroup) { + $this->attributeManagement->assign( + ProductAttributeInterface::ENTITY_TYPE_CODE, + $attributeSet->getAttributeSetId(), + $attributeGroup->getAttributeGroupId(), + $attribute->getAttributeCode(), + '0' + ); + }); + } catch (LocalizedException $e) { $response->setError(true); $response->setMessage($e->getMessage()); + } catch (\Exception $e) { + $this->logger->critical($e); + $response->setError(true); + $response->setMessage(__('Unable to add attribute')); } + return $this->resultJsonFactory->create()->setJsonData($response->toJson()); } + + /** + * Adding basic filters + * + * @return SearchCriteriaBuilder + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getBasicAttributeSearchCriteriaBuilder() + { + $attributeIds = (array)$this->getRequest()->getParam('attributeIds', []); + + if (empty($attributeIds['selected'])) { + throw new LocalizedException(__('Please, specify attributes')); + } + + return $this->searchCriteriaBuilder + ->addFilter('attribute_set_id', new \Zend_Db_Expr('null'), 'is') + ->addFilter('attribute_id', [$attributeIds['selected']], 'in'); + } } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php index 2d57b6148e553..384a8f92e2e35 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php @@ -9,7 +9,8 @@ namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute; -use \Magento\Framework\Exception\AlreadyExistsException; +use Magento\Framework\Exception\AlreadyExistsException; +use Magento\Framework\Controller\ResultFactory; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -46,17 +47,23 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute */ protected $groupCollectionFactory; + /** + * @var \Magento\Framework\View\LayoutFactory + */ + private $layoutFactory; + /** * @param \Magento\Backend\App\Action\Context $context * @param \Magento\Framework\Cache\FrontendInterface $attributeLabelCache * @param \Magento\Framework\Registry $coreRegistry * @param \Magento\Catalog\Model\Product\AttributeSet\BuildFactory $buildFactory + * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory * @param \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory $attributeFactory * @param \Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\ValidatorFactory $validatorFactory * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory $groupCollectionFactory * @param \Magento\Framework\Filter\FilterManager $filterManager * @param \Magento\Catalog\Helper\Product $productHelper - * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory + * @param \Magento\Framework\View\LayoutFactory $layoutFactory * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -69,7 +76,8 @@ public function __construct( \Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\ValidatorFactory $validatorFactory, \Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory $groupCollectionFactory, \Magento\Framework\Filter\FilterManager $filterManager, - \Magento\Catalog\Helper\Product $productHelper + \Magento\Catalog\Helper\Product $productHelper, + \Magento\Framework\View\LayoutFactory $layoutFactory ) { parent::__construct($context, $attributeLabelCache, $coreRegistry, $resultPageFactory); $this->buildFactory = $buildFactory; @@ -78,6 +86,7 @@ public function __construct( $this->attributeFactory = $attributeFactory; $this->validatorFactory = $validatorFactory; $this->groupCollectionFactory = $groupCollectionFactory; + $this->layoutFactory = $layoutFactory; } /** @@ -89,7 +98,6 @@ public function __construct( public function execute() { $data = $this->getRequest()->getPostValue(); - $resultRedirect = $this->resultRedirectFactory->create(); if ($data) { $setId = $this->getRequest()->getParam('set'); @@ -105,10 +113,11 @@ public function execute() ->setSkeletonId($setId) ->setName($name) ->getAttributeSet(); + } catch (AlreadyExistsException $alreadyExists) { $this->messageManager->addError(__('An attribute set named \'%1\' already exists.', $name)); - $this->messageManager->setAttributeData($data); - return $resultRedirect->setPath('catalog/*/edit', ['_current' => true]); + $this->_session->setAttributeData($data); + return $this->returnResult('catalog/*/edit', ['_current' => true], ['error' => true]); } catch (\Magento\Framework\Exception\LocalizedException $e) { $this->messageManager->addError($e->getMessage()); } catch (\Exception $e) { @@ -116,16 +125,10 @@ public function execute() } } - $redirectBack = $this->getRequest()->getParam('back', false); - /* @var $model \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ - $model = $this->attributeFactory->create(); - $attributeId = $this->getRequest()->getParam('attribute_id'); - - $attributeCode = $this->getRequest()->getParam('attribute_code'); - $frontendLabel = $this->getRequest()->getParam('frontend_label'); - $attributeCode = $attributeCode ?: $this->generateCode($frontendLabel[0]); - if (strlen($this->getRequest()->getParam('attribute_code')) > 0) { + $attributeCode = $this->getRequest()->getParam('attribute_code') + ?: $this->generateCode($this->getRequest()->getParam('frontend_label')[0]); + if (strlen($attributeCode) > 0) { $validatorAttrCode = new \Zend_Validate_Regex(['pattern' => '/^[a-z][a-z_0-9]{0,30}$/']); if (!$validatorAttrCode->isValid($attributeCode)) { $this->messageManager->addError( @@ -135,7 +138,11 @@ public function execute() $attributeCode ) ); - return $resultRedirect->setPath('catalog/*/edit', ['attribute_id' => $attributeId, '_current' => true]); + return $this->returnResult( + 'catalog/*/edit', + ['attribute_id' => $attributeId, '_current' => true], + ['error' => true] + ); } } $data['attribute_code'] = $attributeCode; @@ -148,21 +155,28 @@ public function execute() foreach ($inputType->getMessages() as $message) { $this->messageManager->addError($message); } - return $resultRedirect->setPath('catalog/*/edit', ['attribute_id' => $attributeId, '_current' => true]); + return $this->returnResult( + 'catalog/*/edit', + ['attribute_id' => $attributeId, '_current' => true], + ['error' => true] + ); } } + /* @var $model \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ + $model = $this->attributeFactory->create(); + if ($attributeId) { $model->load($attributeId); if (!$model->getId()) { $this->messageManager->addError(__('This attribute no longer exists.')); - return $resultRedirect->setPath('catalog/*/'); + return $this->returnResult('catalog/*/', [], ['error' => true]); } // entity type check if ($model->getEntityTypeId() != $this->_entityTypeId) { $this->messageManager->addError(__('We can\'t update the attribute.')); $this->_session->setAttributeData($data); - return $resultRedirect->setPath('catalog/*/'); + return $this->returnResult('catalog/*/', [], ['error' => true]); } $data['attribute_code'] = $model->getAttributeCode(); @@ -207,17 +221,23 @@ public function execute() if ($setId && $groupCode) { // For creating product attribute on product page we need specify attribute set and group $attributeSetId = $attributeSet ? $attributeSet->getId() : $setId; - $groupCollection = $attributeSet - ? $attributeSet->getGroups() - : $this->groupCollectionFactory->create()->setAttributeSetFilter($attributeSetId)->load(); - foreach ($groupCollection as $group) { - if ($group->getAttributeGroupCode() == $groupCode) { - $attributeGroupId = $group->getAttributeGroupId(); - break; - } + $groupCollection = $this->groupCollectionFactory->create() + ->setAttributeSetFilter($attributeSetId) + ->addFieldToFilter('attribute_group_code', $groupCode) + ->setPageSize(1) + ->load(); + + $group = $groupCollection->getFirstItem(); + if (!$group->getId()) { + $group->setAttributeGroupCode($groupCode); + $group->setSortOrder($this->getRequest()->getParam('groupSortOrder')); + $group->setAttributeGroupName($this->getRequest()->getParam('groupName')); + $group->setAttributeSetId($attributeSetId); + $group->save(); } + $model->setAttributeSetId($attributeSetId); - $model->setAttributeGroupId($attributeGroupId); + $model->setAttributeGroupId($group->getId()); } try { @@ -236,19 +256,55 @@ public function execute() if (!is_null($attributeSet)) { $requestParams['new_attribute_set_id'] = $attributeSet->getId(); } - $resultRedirect->setPath('catalog/product/addAttribute', $requestParams); - } elseif ($redirectBack) { - $resultRedirect->setPath('catalog/*/edit', ['attribute_id' => $model->getId(), '_current' => true]); - } else { - $resultRedirect->setPath('catalog/*/'); + return $this->returnResult('catalog/product/addAttribute', $requestParams, ['error' => false]); + } elseif ($this->getRequest()->getParam('back', false)) { + return $this->returnResult( + 'catalog/*/edit', + ['attribute_id' => $model->getId(), '_current' => true], + ['error' => false] + ); } - return $resultRedirect; + return $this->returnResult('catalog/*/', [], ['error' => false]); } catch (\Exception $e) { $this->messageManager->addError($e->getMessage()); $this->_session->setAttributeData($data); - return $resultRedirect->setPath('catalog/*/edit', ['attribute_id' => $attributeId, '_current' => true]); + return $this->returnResult( + 'catalog/*/edit', + ['attribute_id' => $attributeId, '_current' => true], + ['error' => true] + ); } } - return $resultRedirect->setPath('catalog/*/'); + return $this->returnResult('catalog/*/', [], ['error' => true]); + } + + /** + * @param string $path + * @param array $params + * @param array $response + * @return \Magento\Framework\Controller\Result\Json|\Magento\Backend\Model\View\Result\Redirect + */ + private function returnResult($path = '', array $params = [], array $response = []) + { + if ($this->isAjax()) { + $layout = $this->layoutFactory->create(); + $layout->initMessages(); + + $response['messages'] = [$layout->getMessagesBlock()->getGroupedHtml()]; + $response['params'] = $params; + return $this->resultFactory->create(ResultFactory::TYPE_JSON)->setData($response); + } + return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath($path, $params); + + } + + /** + * Define whether request is Ajax + * + * @return boolean + */ + private function isAjax() + { + return $this->getRequest()->getParam('isAjax'); } } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php index bb870c3f8ef06..e17fb5fe6ecb9 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php @@ -6,8 +6,12 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute; +use Magento\Framework\DataObject; + class Validate extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute { + const DEFAULT_MESSAGE_KEY = 'message'; + /** * @var \Magento\Framework\Controller\Result\JsonFactory */ @@ -46,7 +50,7 @@ public function __construct( */ public function execute() { - $response = new \Magento\Framework\DataObject(); + $response = new DataObject(); $response->setError(false); $attributeCode = $this->getRequest()->getParam('attribute_code'); @@ -61,16 +65,14 @@ public function execute() ); if ($attribute->getId() && !$attributeId) { - if (strlen($this->getRequest()->getParam('attribute_code'))) { - $response->setMessage( - __('An attribute with this code already exists.') - ); - } else { - $response->setMessage( - __('An attribute with the same code (%1) already exists.', $attributeCode) - ); - } + $message = strlen($this->getRequest()->getParam('attribute_code')) + ? __('An attribute with this code already exists.') + : __('An attribute with the same code (%1) already exists.', $attributeCode); + + $this->setMessageToResponse($response, [$message]); + $response->setError(true); + $response->setProductAttribute($attribute->toArray()); } if ($this->getRequest()->has('new_attribute_set_name')) { $setName = $this->getRequest()->getParam('new_attribute_set_name'); @@ -89,4 +91,20 @@ public function execute() } return $this->resultJsonFactory->create()->setJsonData($response->toJson()); } + + /** + * Set message to response object + * + * @param DataObject $response + * @param string[] $messages + * @return DataObject + */ + private function setMessageToResponse($response, $messages) + { + $messageKey = $this->getRequest()->getParam('message_key', static::DEFAULT_MESSAGE_KEY); + if ($messageKey === static::DEFAULT_MESSAGE_KEY) { + $messages = reset($messages); + } + return $response->setData($messageKey, $messages); + } } diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php index b33f10728a29f..4df01967404bb 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php @@ -156,6 +156,8 @@ public function validate($object) { $attribute = $this->getAttribute(); $priceRows = $object->getData($attribute->getName()); + $priceRows = array_filter((array)$priceRows); + if (empty($priceRows)) { return true; } @@ -243,6 +245,9 @@ public function preparePriceData(array $priceData, $productTypeId, $websiteId) $data = []; $price = $this->_catalogProductType->priceFactory($productTypeId); foreach ($priceData as $v) { + if (!array_filter($v)) { + continue; + } $key = implode('-', array_merge([$v['cust_group']], $this->_getAdditionalUniqueFields($v))); if ($v['website_id'] == $websiteId) { $data[$key] = $v; @@ -320,6 +325,8 @@ public function afterSave($object) return $this; } + $priceRows = array_filter((array)$priceRows); + $old = []; $new = []; diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/DataProvider.php b/app/code/Magento/Catalog/Model/Product/Attribute/DataProvider.php new file mode 100644 index 0000000000000..2ff9637e37a4e --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Attribute/DataProvider.php @@ -0,0 +1,224 @@ +collection = $collectionFactory->create(); + $this->storeRepository = $storeRepository; + $this->arrayManager = $arrayManager; + } + + /** + * Get data + * + * @return array + */ + public function getData() + { + return []; + } + + /** + * Get meta information + * + * @return array + */ + public function getMeta() + { + $meta = parent::getMeta(); + + $meta = $this->customizeAttributeCode($meta); + $meta = $this->customizeFrontendLabels($meta); + $meta = $this->customizeOptions($meta); + + return $meta; + } + + /** + * Customize attribute_code field + * + * @param array $meta + * @return array + */ + private function customizeAttributeCode($meta) + { + $meta['advanced_fieldset']['children'] = $this->arrayManager->set( + 'attribute_code/arguments/data/config', + [], + [ + 'notice' => __( + 'This is used internally. Make sure you don\'t use spaces or more than %1 symbols.', + EavAttribute::ATTRIBUTE_CODE_MAX_LENGTH + ), + 'validation' => [ + 'max_text_length' => EavAttribute::ATTRIBUTE_CODE_MAX_LENGTH + ] + ] + ); + return $meta; + } + + /** + * Customize frontend labels + * + * @param array $meta + * @return array + */ + private function customizeFrontendLabels($meta) + { + foreach ($this->storeRepository->getList() as $store) { + $storeId = $store->getId(); + + if (!$storeId) { + continue; + } + + $meta['manage-titles']['children'] = [ + 'frontend_label[' . $storeId . ']' => $this->arrayManager->set( + 'arguments/data/config', + [], + [ + 'formElement' => Input::NAME, + 'componentType' => Field::NAME, + 'label' => $store->getName(), + 'dataType' => Text::NAME, + 'dataScope' => 'frontend_label[' . $storeId . ']' + ] + ), + ]; + } + return $meta; + } + + /** + * Customize options + * + * @param array $meta + * @return array + */ + private function customizeOptions($meta) + { + $sortOrder = 1; + foreach ($this->storeRepository->getList() as $store) { + $storeId = $store->getId(); + + $meta['attribute_options_select_container']['children']['attribute_options_select']['children'] + ['record']['children']['value_option_' . $storeId] = $this->arrayManager->set( + 'arguments/data/config', + [], + [ + 'dataType' => 'text', + 'formElement' => 'input', + 'component' => 'Magento_Catalog/js/form/element/input', + 'template' => 'Magento_Catalog/form/element/input', + 'prefixName' => 'option.value', + 'prefixElementName' => 'option_', + 'suffixName' => (string)$storeId, + 'label' => $store->getName(), + 'sortOrder' => $sortOrder, + 'componentType' => Field::NAME, + ] + ); + $meta['attribute_options_multiselect_container']['children']['attribute_options_multiselect']['children'] + ['record']['children']['value_option_' . $storeId] = $this->arrayManager->set( + 'arguments/data/config', + [], + [ + 'dataType' => 'text', + 'formElement' => 'input', + 'component' => 'Magento_Catalog/js/form/element/input', + 'template' => 'Magento_Catalog/form/element/input', + 'prefixName' => 'option.value', + 'prefixElementName' => 'option_', + 'suffixName' => (string)$storeId, + 'label' => $store->getName(), + 'sortOrder' => $sortOrder, + 'componentType' => Field::NAME, + ] + ); + ++$sortOrder; + } + + $meta['attribute_options_select_container']['children']['attribute_options_select']['children'] + ['record']['children']['action_delete'] = $this->arrayManager->set( + 'arguments/data/config', + [], + [ + 'componentType' => 'actionDelete', + 'dataType' => 'text', + 'fit' => true, + 'sortOrder' => $sortOrder, + 'component' => 'Magento_Catalog/js/form/element/action-delete', + 'elementTmpl' => 'Magento_Catalog/form/element/action-delete', + 'template' => 'Magento_Catalog/form/element/action-delete', + 'prefixName' => 'option.delete', + 'prefixElementName' => 'option_', + ] + ); + $meta['attribute_options_multiselect_container']['children']['attribute_options_multiselect']['children'] + ['record']['children']['action_delete'] = $this->arrayManager->set( + 'arguments/data/config', + [], + [ + 'componentType' => 'actionDelete', + 'dataType' => 'text', + 'fit' => true, + 'sortOrder' => $sortOrder, + 'component' => 'Magento_Catalog/js/form/element/action-delete', + 'elementTmpl' => 'Magento_Catalog/form/element/action-delete', + 'template' => 'Magento_Catalog/form/element/action-delete', + 'prefixName' => 'option.delete', + 'prefixElementName' => 'option_', + ] + ); + return $meta; + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Attribute/Button/CancelTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Attribute/Button/CancelTest.php new file mode 100644 index 0000000000000..bf1f4adeeaec4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Attribute/Button/CancelTest.php @@ -0,0 +1,49 @@ +objectManager->getObject(Cancel::class, [ + 'context' => $this->contextMock, + 'registry' => $this->registryMock, + ]); + } + + public function testGetButtonData() + { + $this->assertEquals( + [ + 'label' => __('Cancel'), + 'data_attribute' => [ + 'mage-init' => [ + 'Magento_Ui/js/form/button-adapter' => [ + 'actions' => [ + [ + 'targetName' => 'product_form.product_form.add_attribute_modal' + . '.create_new_attribute_modal', + 'actionName' => 'toggleModal' + ] + ] + ] + ] + ], + 'on_click' => '' + ], + $this->getModel()->getButtonData() + ); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Attribute/Button/GenericTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Attribute/Button/GenericTest.php new file mode 100644 index 0000000000000..151d68f45ce84 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Attribute/Button/GenericTest.php @@ -0,0 +1,68 @@ +objectManager = new ObjectManager($this); + $this->contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->registryMock = $this->getMockBuilder(Registry::class) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @return Generic + */ + protected function getModel() + { + return $this->objectManager->getObject(Generic::class, [ + 'context' => $this->contextMock, + 'registry' => $this->registryMock, + ]); + } + + public function testGetUrl() + { + $this->contextMock->expects($this->once()) + ->method('getUrl') + ->willReturn('http://example.com'); + + $this->assertSame('http://example.com', $this->getModel()->getUrl()); + } + + public function testGetButtonData() + { + $this->assertSame([], $this->getModel()->getButtonData()); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Attribute/Button/SaveTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Attribute/Button/SaveTest.php new file mode 100644 index 0000000000000..71560c43b45c0 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Attribute/Button/SaveTest.php @@ -0,0 +1,40 @@ +objectManager->getObject(Save::class, [ + 'context' => $this->contextMock, + 'registry' => $this->registryMock, + ]); + } + + public function testGetButtonData() + { + $this->assertEquals( + [ + 'label' => __('Save Attribute'), + 'class' => 'save primary', + 'data_attribute' => [ + 'mage-init' => ['button' => ['event' => 'save']], + 'form-role' => 'save', + ] + ], + $this->getModel()->getButtonData() + ); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php new file mode 100644 index 0000000000000..4a2893620b1a3 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php @@ -0,0 +1,206 @@ +buildFactoryMock = $this->getMockBuilder(BuildFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->filterManagerMock = $this->getMockBuilder(FilterManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->productHelperMock = $this->getMockBuilder(ProductHelper::class) + ->disableOriginalConstructor() + ->getMock(); + $this->attributeFactoryMock = $this->getMockBuilder(AttributeFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->validatorFactoryMock = $this->getMockBuilder(ValidatorFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->groupCollectionFactoryMock = $this->getMockBuilder(CollectionFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->layoutFactoryMock = $this->getMockBuilder(LayoutFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->redirectMock = $this->getMockBuilder(ResultRedirect::class) + ->disableOriginalConstructor() + ->getMock(); + $this->attributeSetMock = $this->getMockBuilder(AttributeSetInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->builderMock = $this->getMockBuilder(Build::class) + ->disableOriginalConstructor() + ->getMock(); + $this->inputTypeValidatorMock = $this->getMockBuilder(InputTypeValidator::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->buildFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($this->builderMock); + $this->validatorFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($this->inputTypeValidatorMock); + } + + /** + * {@inheritdoc} + */ + protected function getModel() + { + return $this->objectManager->getObject(Save::class, [ + 'context' => $this->contextMock, + 'attributeLabelCache' => $this->attributeLabelCacheMock, + 'coreRegistry' => $this->coreRegistryMock, + 'resultPageFactory' => $this->resultPageFactoryMock, + 'buildFactory' => $this->buildFactoryMock, + 'filterManager' => $this->filterManagerMock, + 'productHelper' => $this->productHelperMock, + 'attributeFactory' => $this->attributeFactoryMock, + 'validatorFactory' => $this->validatorFactoryMock, + 'groupCollectionFactory' => $this->groupCollectionFactoryMock, + 'layoutFactory' => $this->layoutFactoryMock, + ]); + } + + public function testExecuteWithEmptyData() + { + $this->requestMock->expects($this->once()) + ->method('getPostValue') + ->willReturn([]); + $this->resultFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->redirectMock); + $this->redirectMock->expects($this->any()) + ->method('setPath') + ->willReturnSelf(); + + $this->assertInstanceOf(ResultRedirect::class, $this->getModel()->execute()); + } + + public function testExecute() + { + $data = [ + 'new_attribute_set_name' => 'Test attribute set name', + 'frontend_input' => 'test_frontend_input', + ]; + + $this->requestMock->expects($this->once()) + ->method('getPostValue') + ->willReturn($data); + $this->resultFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->redirectMock); + $this->redirectMock->expects($this->any()) + ->method('setPath') + ->willReturnSelf(); + $this->builderMock->expects($this->once()) + ->method('setEntityTypeId') + ->willReturnSelf(); + $this->builderMock->expects($this->once()) + ->method('setSkeletonId') + ->willReturnSelf(); + $this->builderMock->expects($this->once()) + ->method('setName') + ->willReturnSelf(); + $this->builderMock->expects($this->once()) + ->method('getAttributeSet') + ->willReturn($this->attributeSetMock); + $this->requestMock->expects($this->any()) + ->method('getParam') + ->willReturnMap([ + ['set', null, 1], + ['attribute_code', null, 'test_attribute_code'] + ]); + $this->inputTypeValidatorMock->expects($this->once()) + ->method('getMessages') + ->willReturn([]); + + $this->assertInstanceOf(ResultRedirect::class, $this->getModel()->execute()); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php new file mode 100644 index 0000000000000..94ba7ee6e487f --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php @@ -0,0 +1,150 @@ +resultJsonFactoryMock = $this->getMockBuilder(ResultJsonFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resultJson = $this->getMockBuilder(ResultJson::class) + ->disableOriginalConstructor() + ->getMock(); + $this->layoutFactoryMock = $this->getMockBuilder(LayoutFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->getMockForAbstractClass(); + $this->attributeMock = $this->getMockBuilder(Attribute::class) + ->disableOriginalConstructor() + ->getMock(); + $this->attributeSetMock = $this->getMockBuilder(AttributeSet::class) + ->disableOriginalConstructor() + ->getMock(); + $this->escaperMock = $this->getMockBuilder(Escaper::class) + ->disableOriginalConstructor() + ->getMock(); + $this->layoutMock = $this->getMockBuilder(LayoutInterface::class) + ->getMockForAbstractClass(); + + $this->contextMock->expects($this->any()) + ->method('getObjectManager') + ->willReturn($this->objectManagerMock); + } + + /** + * {@inheritdoc} + */ + protected function getModel() + { + return $this->objectManager->getObject(Validate::class, [ + 'context' => $this->contextMock, + 'attributeLabelCache' => $this->attributeLabelCacheMock, + 'coreRegistry' => $this->coreRegistryMock, + 'resultPageFactory' => $this->resultPageFactoryMock, + 'resultJsonFactory' => $this->resultJsonFactoryMock, + 'layoutFactory' => $this->layoutFactoryMock, + ]); + } + + public function testExecute() + { + $this->requestMock->expects($this->any()) + ->method('getParam') + ->willReturnMap([ + ['frontend_label', null, 'test_frontend_label'], + ['attribute_code', null, 'test_attribute_code'], + ['new_attribute_set_name', null, 'test_attribute_set_name'], + ]); + $this->objectManagerMock->expects($this->exactly(2)) + ->method('create') + ->willReturnMap([ + ['Magento\Catalog\Model\ResourceModel\Eav\Attribute', [], $this->attributeMock], + ['Magento\Eav\Model\Entity\Attribute\Set', [], $this->attributeSetMock] + ]); + $this->attributeMock->expects($this->once()) + ->method('loadByCode') + ->willReturnSelf(); + $this->requestMock->expects($this->once()) + ->method('has') + ->with('new_attribute_set_name') + ->willReturn(true); + $this->attributeSetMock->expects($this->once()) + ->method('setEntityTypeId') + ->willReturnSelf(); + $this->attributeSetMock->expects($this->once()) + ->method('load') + ->willReturnSelf(); + $this->attributeSetMock->expects($this->once()) + ->method('getId') + ->willReturn(false); + $this->resultJsonFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->resultJson); + $this->resultJson->expects($this->once()) + ->method('setJsonData') + ->willReturnSelf(); + + $this->assertInstanceOf(ResultJson::class, $this->getModel()->execute()); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/AttributeTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/AttributeTest.php new file mode 100644 index 0000000000000..e64b8e06a2f8e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/AttributeTest.php @@ -0,0 +1,103 @@ +objectManager = new ObjectManager($this); + $this->contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->attributeLabelCacheMock = $this->getMockBuilder(FrontendInterface::class) + ->getMockForAbstractClass(); + $this->coreRegistryMock = $this->getMockBuilder(Registry::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resultPageFactoryMock = $this->getMockBuilder(PageFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->requestMock = $this->getMockBuilder(RequestInterface::class) + ->setMethods(['getPostValue', 'has']) + ->getMockForAbstractClass(); + $this->resultFactoryMock = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->contextMock->expects($this->any()) + ->method('getRequest') + ->willReturn($this->requestMock); + $this->contextMock->expects($this->any()) + ->method('getResultFactory') + ->willReturn($this->resultFactoryMock); + } + + /** + * @return Attribute + */ + protected function getModel() + { + return $this->objectManager->getObject(Attribute::class, [ + 'context' => $this->contextMock, + 'attributeLabelCache' => $this->attributeLabelCacheMock, + 'coreRegistry' => $this->coreRegistryMock, + 'resultPageFactory' => $this->resultPageFactoryMock, + ]); + } + + public function testDispatch() + { + $this->markTestSkipped('Should be dispatched in parent'); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AttributesTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AttributesTest.php new file mode 100644 index 0000000000000..a40648d4063ba --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AttributesTest.php @@ -0,0 +1,76 @@ +urlBuilderMock = $this->getMockBuilder(UrlInterface::class) + ->getMockForAbstractClass(); + $this->registryMock = $this->getMockBuilder(Registry::class) + ->disableOriginalConstructor() + ->getMock(); + $this->authorizationMock = $this->getMockBuilder(AuthorizationInterface::class) + ->getMockForAbstractClass(); + } + + /** + * {@inheritdoc} + */ + protected function createModel() + { + return $this->objectManager->getObject(Attributes::class, [ + 'urlBuilder' => $this->urlBuilderMock, + 'registry' => $this->registryMock, + 'authorization' => $this->authorizationMock, + 'locator' => $this->locatorMock, + ]); + } + + public function testModifyData() + { + $this->assertSame($this->getSampleData(), $this->getModel()->modifyData($this->getSampleData())); + } + + public function testModifyMeta() + { + $this->registryMock->expects($this->once()) + ->method('registry') + ->with('use_wrapper') + ->willReturn(true); + $this->authorizationMock->expects($this->once()) + ->method('isAllowed') + ->with('Magento_Catalog::attributes_attributes') + ->willReturn(true); + + $this->assertArrayHasKey('add_attribute_modal', $this->getModel()->modifyMeta([])); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/SystemTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/SystemTest.php index 52c860c0c1016..c30419a5929b8 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/SystemTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/SystemTest.php @@ -68,7 +68,7 @@ public function testModifyData() $this->productMock->expects($this->once()) ->method('getId') ->willReturn($productId); - $this->productMock->expects($this->once()) + $this->productMock->expects($this->exactly(2)) ->method('getTypeId') ->willReturn(Type::TYPE_SIMPLE); $this->productMock->expects($this->once()) diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php index 7176302fd02d5..440a6da55eaae 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php @@ -592,7 +592,6 @@ protected function customizeAdvancedPricing() 'buttons' => [ [ 'text' => __('Cancel'), - 'class' => 'action-secondary', 'actions' => [ [ 'targetName' => '${ $.name }', diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php index ce0a8749caaec..d2ffbb6e64f76 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php @@ -81,7 +81,7 @@ public function modifyMeta(array $meta) { if ($name = $this->getGeneralPanelName($meta)) { $meta[$name]['children']['attribute_set_id']['arguments']['data']['config'] = [ - 'component' => 'Magento_Ui/js/form/element/ui-select', + 'component' => 'Magento_Catalog/js/components/attribute-set-select', 'disableLabel' => true, 'filterOptions' => true, 'elementTmpl' => 'ui/grid/filters/elements/ui-select', diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php index 5ac7a7c8b386f..325c62b2af183 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php @@ -10,7 +10,11 @@ use Magento\Framework\AuthorizationInterface; use Magento\Ui\Component; use Magento\Catalog\Model\Locator\LocatorInterface; +use Magento\Ui\Component\Container; +/** + * Class Attributes + */ class Attributes extends AbstractModifier { const GROUP_SORT_ORDER = 15; @@ -66,7 +70,7 @@ public function modifyData(array $data) /** * @return boolean */ - protected function canAddAttributes() + private function canAddAttributes() { $isWrapped = $this->registry->registry('use_wrapper'); if (!isset($isWrapped)) { @@ -85,33 +89,37 @@ public function modifyMeta(array $meta) return $meta; } - $general = $this->getGeneralPanelName($meta); - - if (!isset($meta[static::GROUP_CODE])) { - $meta[static::GROUP_CODE]['arguments']['data']['config'] = [ - 'label' => __('Attributes'), - 'collapsible' => true, - 'dataScope' => self::DATA_SCOPE_PRODUCT, - 'sortOrder' => $this->getNextGroupSortOrder($meta, $general, static::GROUP_SORT_ORDER), - 'componentType' => Component\Form\Fieldset::NAME - ]; + if (isset($meta[static::GROUP_CODE])) { + $meta[static::GROUP_CODE]['arguments']['data']['config']['component'] = + 'Magento_Catalog/js/components/attributes-fieldset'; } - $meta[static::GROUP_CODE]['arguments']['data']['config']['component'] = - 'Magento_Catalog/js/components/attributes-fieldset'; - $meta[static::GROUP_CODE]['arguments']['data']['config']['visible'] = - !empty($meta[static::GROUP_CODE]['children']); + $meta = $this->customizeAddAttributeModal($meta); + $meta = $this->customizeCreateAttributeModal($meta); + $meta = $this->customizeAttributesGrid($meta); + + return $meta; + } + + /** + * @param array $meta + * @return array + */ + private function customizeAddAttributeModal(array $meta) + { $meta['add_attribute_modal']['arguments']['data']['config'] = [ 'isTemplate' => false, 'componentType' => Component\Modal::NAME, 'dataScope' => '', 'provider' => 'product_form.product_form_data_source', + 'imports' => [ + 'state' => '!index=product_attribute_add_form:responseStatus' + ], 'options' => [ 'title' => __('Add Attribute'), 'buttons' => [ [ 'text' => 'Cancel', - 'class' => 'action-secondary', 'actions' => [ [ 'targetName' => '${ $.name }', @@ -131,49 +139,182 @@ public function modifyMeta(array $meta) 'closeModal' ] ] - ], + ] ], ], ]; + $meta['add_attribute_modal']['children'] = [ - 'product_attributes_grid' => [ + 'add_new_attribute_button' => [ 'arguments' => [ 'data' => [ 'config' => [ - 'component' => 'Magento_Catalog/js/components/attributes-insert-listing', - 'componentType' => Component\Container::NAME, - 'autoRender' => false, - 'dataScope' => 'product_attributes_grid', - 'externalProvider' => 'product_attributes_grid.product_attributes_grid_data_source', - 'selectionsProvider' => '${ $.ns }.${ $.ns }.product_attributes_columns.ids', - 'ns' => 'product_attributes_grid', - 'render_url' => $this->urlBuilder->getUrl('mui/index/render'), - 'immediateUpdateBySelection' => true, - 'behaviourType' => 'edit', - 'externalFilterMode' => true, - 'dataLinks' => ['imports' => false, 'exports' => true], - - 'formProvider' => 'ns = ${ $.namespace }, index = product_form', - 'groupCode' => static::GROUP_CODE, - 'groupName' => static::GROUP_NAME, - 'groupSortOrder' => static::GROUP_SORT_ORDER, - 'addAttributeUrl' => - $this->urlBuilder->getUrl('catalog/product/addAttributeToTemplate'), - 'productId' => $this->locator->getProduct()->getId(), - 'productType' => $this->locator->getProduct()->getTypeId(), - 'loading' => false, - 'imports' => [ - 'attributeSetId' => '${ $.provider }:data.product.attribute_set_id' + 'additionalClasses' => 'admin_field-complex-attributes', + 'formElement' => Container::NAME, + 'componentType' => Container::NAME, + 'content' => __('Select Attribute'), + 'label' => false, + 'template' => 'ui/form/components/complex', + ], + ], + ], + 'children' => [ + 'add_new_attribute_button' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'formElement' => Container::NAME, + 'componentType' => Container::NAME, + 'component' => 'Magento_Ui/js/form/components/button', + 'additionalClasses' => '', + 'actions' => [ + [ + 'targetName' => 'product_form.product_form.add_attribute_modal' + . '.create_new_attribute_modal', + 'actionName' => 'toggleModal', + ], + [ + 'targetName' + => 'product_form.product_form.add_attribute_modal' + . '.create_new_attribute_modal.product_attribute_add_form', + 'actionName' => 'render' + ] + ], + 'title' => __('Create New Attribute'), + 'provider' => null, + ], ], - 'exports' => [ - 'attributeSetId' => '${ $.externalProvider }:params.template_id' - ] ], ], ], ], ]; + return $meta; + } + /** + * @param array $meta + * @return array + */ + private function customizeCreateAttributeModal(array $meta) + { + $params = [ + 'group' => static::GROUP_CODE, + 'groupName' => self::GROUP_NAME, + 'groupSortOrder' => self::GROUP_SORT_ORDER, + 'store' => $this->locator->getStore()->getId(), + 'product' => $this->locator->getProduct()->getId(), + 'type' => $this->locator->getProduct()->getTypeId(), + 'set' => $this->locator->getProduct()->getAttributeSetId(), + 'message_key' => 'messages', + 'popup' => 1 + ]; + + $meta['add_attribute_modal']['children']['create_new_attribute_modal'] = [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'isTemplate' => false, + 'componentType' => Component\Modal::NAME, + 'dataScope' => 'data.new_attribute', + 'provider' => 'product_form.product_form_data_source', + 'options' => [ + 'title' => __('New Attribute') + ], + 'imports' => [ + 'state' => '!index=product_attribute_add_form:responseStatus' + ], + ] + ] + ], + 'children' => [ + 'product_attribute_add_form' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'label' => __('New Attribute'), + 'componentType' => Component\Container::NAME, + 'component' => 'Magento_Catalog/js/components/new-attribute-insert-form', + 'dataScope' => '', + 'update_url' => $this->urlBuilder->getUrl('mui/index/render'), + 'render_url' => $this->urlBuilder->getUrl( + 'mui/index/render_handle', + [ + 'handle' => 'catalog_product_attribute_edit_form', + 'buttons' => 1 + ] + ), + 'autoRender' => false, + 'ns' => 'product_attribute_add_form', + 'externalProvider' => 'product_attribute_add_form' + . '.product_attribute_add_form_data_source', + 'toolbarContainer' => '${ $.parentName }', + 'formSubmitType' => 'ajax', + 'saveUrl' => $this->urlBuilder->getUrl('catalog/product_attribute/save', $params), + 'validateUrl' => $this->urlBuilder->getUrl( + 'catalog/product_attribute/validate', + $params + ), + 'productId' => $this->locator->getProduct()->getId(), + 'productType' => $this->locator->getProduct()->getTypeId(), + 'imports' => [ + 'attributeSetId' => '${ $.provider }:data.product.attribute_set_id', + ], + 'exports' => [ + 'saveUrl' => '${ $.externalProvider }:client.urls.save', + 'validateUrl' => '${ $.externalProvider }:client.urls.beforeSave', + 'attributeSetId' => '${ $.externalProvider }:params.set', + ] + ] + ] + ] + ] + ] + ]; + return $meta; + } + + /** + * @param array $meta + * @return array + */ + private function customizeAttributesGrid(array $meta) + { + $meta['add_attribute_modal']['children']['product_attributes_grid'] = [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'component' => 'Magento_Catalog/js/components/attributes-insert-listing', + 'componentType' => Component\Container::NAME, + 'autoRender' => false, + 'dataScope' => 'product_attributes_grid', + 'externalProvider' => 'product_attributes_grid.product_attributes_grid_data_source', + 'selectionsProvider' => '${ $.ns }.${ $.ns }.product_attributes_columns.ids', + 'ns' => 'product_attributes_grid', + 'render_url' => $this->urlBuilder->getUrl('mui/index/render'), + 'immediateUpdateBySelection' => true, + 'behaviourType' => 'edit', + 'externalFilterMode' => true, + 'dataLinks' => ['imports' => false, 'exports' => true], + 'formProvider' => 'ns = ${ $.namespace }, index = product_form', + 'groupCode' => static::GROUP_CODE, + 'groupName' => static::GROUP_NAME, + 'groupSortOrder' => static::GROUP_SORT_ORDER, + 'addAttributeUrl' => + $this->urlBuilder->getUrl('catalog/product/addAttributeToTemplate'), + 'productId' => $this->locator->getProduct()->getId(), + 'productType' => $this->locator->getProduct()->getTypeId(), + 'loading' => false, + 'imports' => [ + 'attributeSetId' => '${ $.provider }:data.product.attribute_set_id' + ], + 'exports' => [ + 'attributeSetId' => '${ $.externalProvider }:params.template_id' + ] + ], + ], + ] + ]; return $meta; } } diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php index 91f5c4ceb35bd..5f5851ad7f533 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php @@ -238,9 +238,9 @@ protected function createCustomOptionsPanel() ], ], 'children' => [ - static::CONTAINER_HEADER_NAME => $this->getHeaderContainerConfig(10), - static::GRID_OPTIONS_NAME => $this->getOptionsGridConfig(20), - static::FIELD_ENABLE => $this->getEnableFieldConfig(30), + static::FIELD_ENABLE => $this->getEnableFieldConfig(10), + static::CONTAINER_HEADER_NAME => $this->getHeaderContainerConfig(20), + static::GRID_OPTIONS_NAME => $this->getOptionsGridConfig(30) ] ] ] diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php index fec81ba4aa6ab..c5041885a31c8 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php @@ -428,7 +428,6 @@ protected function getGenericModal(Phrase $title, $scope) 'buttons' => [ [ 'text' => __('Cancel'), - 'class' => 'action-secondary', 'actions' => [ 'closeModal' ] diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/System.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/System.php index 93c6ac89258c4..f4f17fb5a40ae 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/System.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/System.php @@ -71,6 +71,7 @@ public function modifyData(array $data) 'popup' => 1, 'componentJson' => 1, 'prev_set_id' => $attributeSetId, + 'type' => $this->locator->getProduct()->getTypeId() ] ); diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit_form.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit_form.xml new file mode 100644 index 0000000000000..9123ebe8fdcb9 --- /dev/null +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit_form.xml @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_form.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_form.xml index 6563db43e7706..fa9672231c70b 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_form.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_form.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> - + diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/helper/gallery.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/helper/gallery.phtml index 8353ab374709c..0a4edb4553496 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/helper/gallery.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/helper/gallery.phtml @@ -22,8 +22,6 @@ $formName = $block->getFormName();
getUploaderHtml(); ?>
-

@@ -76,9 +74,6 @@ $formName = $block->getFormName(); class="is-removed"/>
- - getFormName();
+ @@ -218,6 +236,7 @@
+
@@ -261,12 +280,15 @@ data-url="getUrl('catalog/product_gallery/upload') ?>" />
-

+
+

+
+ diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/select_attributes.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/select_attributes.phtml index 6a10c54542843..1fa312e521ff5 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/select_attributes.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/select_attributes.phtml @@ -10,7 +10,7 @@ ?>
- getAddNewAttributeButton(); ?> + getAddNewAttributeButton('product_form.product_form_data_source'); ?>

@@ -36,3 +36,9 @@ } } + diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/summary.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/summary.phtml index a93fcf5837519..63a30db901acf 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/summary.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/summary.phtml @@ -49,7 +49,8 @@ "getComponentName()?>": { "component": "Magento_ConfigurableProduct/js/variations/steps/summary", "appendTo": "getParentComponentName()?>", - "variationsComponent": "configurableVariations" + "variationsComponent": "configurableVariations", + "modalComponent": "product_form.product_form.configurableModal" } } } diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/matrix.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/matrix.phtml index 13b735d7034f4..60fb50cf5ef25 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/matrix.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/matrix.phtml @@ -263,3 +263,9 @@ $currencySymbol = $block->getCurrencySymbol(); } } + diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard-ajax.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard-ajax.phtml new file mode 100644 index 0000000000000..94ebd5cde3db1 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard-ajax.phtml @@ -0,0 +1,30 @@ +getProductMatrix(); +$attributes = $block->getProductAttributes(); + +/* @escapeNotVerified */ echo $block->getVariationWizard([ + 'attributes' => $attributes, + 'configurations' => $productMatrix, + 'configurableModal' => $block->getConfigurableModal() +]); +?> + + diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard.phtml new file mode 100644 index 0000000000000..b18bca75b7d9c --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard.phtml @@ -0,0 +1,68 @@ + +getProductMatrix(); +$attributes = $block->getProductAttributes(); +$currencySymbol = $block->getCurrencySymbol(); +?> +
+ +
+
+
+
+ + \ No newline at end of file diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/ui_component/configurable_associated_product_listing.xml b/app/code/Magento/ConfigurableProduct/view/adminhtml/ui_component/configurable_associated_product_listing.xml index e57b11e84072b..641bf7952389d 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/ui_component/configurable_associated_product_listing.xml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/ui_component/configurable_associated_product_listing.xml @@ -71,7 +71,7 @@ - configurableProductGrid + product_form.product_form.configurable_associated_product_modal.configurable_associated_product_listing selectProduct ${ $.$data.rowIndex } @@ -112,7 +112,7 @@ - + text @@ -131,7 +131,7 @@ - + textRange @@ -141,5 +141,48 @@ + + + + textRange + true + Quantity + 80 + + + + + + + text + true + Weight + 90 + + + + + + Magento\Catalog\Model\Product\Attribute\Source\Status + + select + Magento_Ui/js/grid/columns/select + true + select + Status + 100 + + + + + + + true + false + 99 + Attributes + + + diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/ui_component/product_form.xml b/app/code/Magento/ConfigurableProduct/view/adminhtml/ui_component/product_form.xml new file mode 100644 index 0000000000000..9a44e15b2feb5 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/ui_component/product_form.xml @@ -0,0 +1,14 @@ + + +
+ + + Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Button\Save + + +
diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/catalog/product/attribute.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/catalog/product/attribute.js deleted file mode 100644 index 726cf4ddfbd0c..0000000000000 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/catalog/product/attribute.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -define([ - 'jquery', - 'jquery/ui', - 'Magento_Catalog/catalog/product-attributes' -], function ($) { - 'use strict'; - - $.widget('mage.configurableAttribute', $.mage.productAttributes, { - _prepareUrl: function () { - var name = $('#configurable-attribute-selector').val(); - - return this.options.url + - (/\?/.test(this.options.url) ? '&' : '?') + - 'set=' + window.encodeURIComponent($('#attribute_set_id').val()) + - '&attribute[frontend_label]=' + - window.encodeURIComponent(name); - } - }); - - return $.mage.configurableAttribute; -}); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/advanced-pricing-handler.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/advanced-pricing-handler.js deleted file mode 100644 index 1f04719da9954..0000000000000 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/advanced-pricing-handler.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -define([ - 'jquery', - 'Magento_Catalog/catalog/type-events' -], function ($, productType) { - 'use strict'; - - return { - $initiallyDisabledAttributes: [], - $links: $('[data-ui-id=product-tabs-tab-link-advanced-pricing]'), - $tab: $('[data-tab-panel=advanced-pricing]'), - toggleDisabledAttribute: function (disabled) { - $('input,select', this.$tab).each(function (index, element) { - if (!$.inArray(element, this.$initiallyDisabledAttributes)) { - $(element).attr('disabled', disabled); - } - }); - }, - init: function () { - $(document).on('changeTypeProduct', this._initType.bind(this)); - this._setInitialState(); - this._initType(); - }, - _setInitialState: function () { - if (this.$initiallyDisabledAttributes.length == 0) { - this.$initiallyDisabledAttributes = $('input:disabled,select:disabled', this.$tab).toArray(); - } - }, - _initType: function () { - var isConfigurable = productType.type.current === 'configurable'; - - if (isConfigurable) { - this.$links.hide(); - } else { - this.$links.show(); - } - - this.toggleDisabledAttribute(isConfigurable); - } - }; -}); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/associated-product-insert-listing.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/associated-product-insert-listing.js new file mode 100644 index 0000000000000..1d4819546fba3 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/associated-product-insert-listing.js @@ -0,0 +1,290 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'underscore', + 'Magento_Ui/js/form/components/insert-listing' +], function (_, insertListing) { + 'use strict'; + + return insertListing.extend({ + defaults: { + gridInitialized: false, + paramsUpdated: false, + showMassActionColumn: true, + currentProductId: 0, + dataScopeAssociatedProduct: 'data.associated_product_ids', + typeGrid: '', + product: {}, + rowIndexForChange: undefined, + changeProductData: [], + modules: { + productsProvider: '${ $.productsProvider }', + productsColumns: '${ $.productsColumns }', + productsMassAction: '${ $.productsMassAction }', + modalWithGrid: '${ $.modalWithGrid }' + }, + exports: { + externalProviderParams: '${ $.externalProvider }:params' + }, + links: { + changeProductData: '${ $.provider }:${ $.changeProductProvider }' + }, + listens: { + '${ $.externalProvider }:params': '_setFilters _setVisibilityMassActionColumn', + '${ $.productsProvider }:data': '_handleManualGridOpening', + '${ $.productsMassAction }:selected': '_handleManualGridSelect' + } + }, + + /** + * Initialize observables. + * + * @returns {Object} Chainable. + */ + initObservable: function () { + this._super().observe( + 'changeProductData' + ); + + return this; + }, + + /** + * Get ids of used products. + * + * @returns {Array} + */ + getUsedProductIds: function () { + return this.source.get(this.dataScopeAssociatedProduct); + }, + + /** + * Request for render content. + * + * @returns {Object} + */ + doRender: function (showMassActionColumn, typeGrid) { + this.typeGrid = typeGrid; + this.showMassActionColumn = showMassActionColumn; + + if (this.gridInitialized) { + this.paramsUpdated = false; + this._setFilters(this.externalProviderParams); + this._setVisibilityMassActionColumn(); + } + + return this.render(); + }, + + /** + * Show grid with assigned product. + * + * @returns {Object} + */ + showGridAssignProduct: function () { + this.product = {}; + this.rowIndexForChange = undefined; + + return this.doRender(true, 'assignProduct'); + }, + + /** + * Show grid with changed product. + * + * @param {String} rowIndex + * @param {String} product + */ + showGridChangeProduct: function (rowIndex, product) { + this.rowIndexForChange = rowIndex; + this.product = product; + this.doRender(false, 'changeProduct'); + }, + + /** + * Select product. + * + * @param {String} rowIndex + */ + selectProduct: function (rowIndex) { + this.changeProductData({ + rowIndex: this.rowIndexForChange, + product: this.productsProvider().data.items[rowIndex] + }); + this.modalWithGrid().closeModal(); + }, + + /** + * Set visibility state for mass action column + * + * @private + */ + _setVisibilityMassActionColumn: function () { + this.productsMassAction(function (massActionComponent) { + this.productsColumns().elems().each(function (rowElement) { + rowElement.disableAction = this.showMassActionColumn; + }, this); + massActionComponent.visible = this.showMassActionColumn; + }.bind(this)); + }, + + /** + * Set filters. + * + * @param {Object} params + * @private + */ + _setFilters: function (params) { + var filterModifier = {}, + attrCodes, + usedProductIds, + attributes; + + if (!this.paramsUpdated) { + this.gridInitialized = true; + this.paramsUpdated = true; + + attrCodes = this._getAttributesCodes(), + usedProductIds = this.getUsedProductIds(); + + if (this.currentProductId) { + usedProductIds.push(this.currentProductId); + } + + filterModifier['entity_id'] = { + 'condition_type': 'nin', value: usedProductIds + }; + attrCodes.each(function (code) { + filterModifier[code] = { + 'condition_type': 'notnull' + }; + }); + + if (this.typeGrid === 'changeProduct') { + attributes = JSON.parse(this.product.attributes); + + filterModifier = _.extend(filterModifier, _.mapObject(attributes, function (value) { + return { + 'condition_type': 'eq', + 'value': value + }; + })); + + params.filters = attributes; + } + + params['attributes_codes'] = attrCodes; + + this.set('externalProviderParams', params); + this.set('externalFiltersModifier', filterModifier); + } + }, + + /** + * Get attribute codes. + * + * @returns {Array} + * @private + */ + _getAttributesCodes: function () { + var attrCodes = this.source.get('data.attribute_codes'); + + return attrCodes ? attrCodes : []; + }, + + /** + * Get product variations. + * + * @returns {Array} + * @private + */ + _getProductVariations: function () { + var matrix = this.source.get('data.configurable-matrix'); + + return matrix ? matrix : []; + }, + + /** + * Handle manual grid after opening + * @private + */ + _handleManualGridOpening: function (data) { + if (data.items.length && this.typeGrid === 'assignProduct') { + this.productsColumns().elems().each(function (rowElement) { + rowElement.disableAction = true; + }); + + this._disableRows(data.items); + } + }, + + /** + * Handle manual selection. + * + * @param {Array} selected + * @private + */ + _handleManualGridSelect: function (selected) { + var selectedRows, + selectedVariationKeys; + + if (this.typeGrid === 'assignProduct') { + selectedRows = _.filter(this.productsProvider().data.items, function (row) { + return selected.indexOf(row['entity_id']) !== -1; + }); + selectedVariationKeys = _.values(this._getVariationKeyMap(selectedRows)); + this._disableRows(this.productsProvider().data.items, selectedVariationKeys, selected); + } + }, + + /** + * Disable rows in grid for products with the same variation key + * + * @param {Array} items + * @param {Array} selectedVariationKeys + * @param {Array} selected + * @private + */ + _disableRows: function (items, selectedVariationKeys, selected) { + selectedVariationKeys = selectedVariationKeys === undefined ? [] : selectedVariationKeys; + selected = selected === undefined ? [] : selected; + this.productsMassAction(function (massaction) { + var configurableVariationKeys = _.union( + selectedVariationKeys, + _.pluck(this._getProductVariations(), 'variationKey') + ), + variationKeyMap = this._getVariationKeyMap(items), + rowsForDisable = _.keys(_.pick( + variationKeyMap, + function (variationKey) { + return configurableVariationKeys.indexOf(variationKey) !== -1; + } + )); + + massaction.disabled(_.difference(rowsForDisable, selected)); + }.bind(this)); + }, + + /** + * Get variation key map used in manual grid. + * + * @param {Array} items + * @returns {Array} [{entity_id: variation-key}, ...] + * @private + */ + _getVariationKeyMap: function (items) { + var variationKeyMap = {}; + + _.each(items, function (row) { + variationKeyMap[row['entity_id']] = _.values( + _.pick(row, this._getAttributesCodes()) + ).sort().join('-'); + + }, this); + + return variationKeyMap; + } + }); +}); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/container-configurable-handler.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/container-configurable-handler.js new file mode 100644 index 0000000000000..4f109fb978312 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/container-configurable-handler.js @@ -0,0 +1,69 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'uiComponent' +], function (Element) { + 'use strict'; + + return Element.extend({ + defaults: { + listens: { + '${ $.provider }:data.is_downloadable': 'handleProductType' + }, + links: { + isDownloadable: '${ $.provider }:data.is_downloadable' + }, + modules: { + createConfigurableButton: '${$.createConfigurableButton}' + } + }, + + /** + * Invokes initialize method of parent class, + * contains initialization logic + */ + initialize: function () { + this._super(); + this.handleProductType(this.isDownloadable, true); + + return this; + }, + + /** + * Calls 'initObservable' of parent + * + * @returns {Object} Chainable. + */ + initObservable: function () { + this._super() + .observe(['content']); + + return this; + }, + + /** + * Change content for container and visibility for button + * + * @param {String} isDownloadable + * @param {Boolean} onlyContent + */ + handleProductType: function (isDownloadable, onlyContent) { + if (isDownloadable === '1') { + this.content(this.content2); + + if (onlyContent !== true) { + this.createConfigurableButton().visible(false); + } + } else { + this.content(this.content1); + + if (onlyContent !== true) { + this.createConfigurableButton().visible(true); + } + } + } + }); +}); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/custom-options-price-type.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/custom-options-price-type.js new file mode 100644 index 0000000000000..7a2c19047da59 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/custom-options-price-type.js @@ -0,0 +1,76 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'underscore', + 'Magento_Ui/js/form/element/select' +], function (_, Select) { + 'use strict'; + + return Select.extend({ + defaults: { + isConfigurable: false, + isFiltered: null, + defaultOptions: null, + filteredOptions: null, + bannedOptions: [] + }, + + /** + * Updates options. + * + * @param {Boolean} variationsEmpty + * @returns {Boolean} + */ + updateOptions: function (variationsEmpty) { + var isFiltered = this.isConfigurable || !variationsEmpty, + value; + + if (this.isFiltered !== isFiltered) { + value = this.value(); + + this.options(isFiltered ? this.getFilteredOptions() : this.getDefaultOptions()); + this.value(value); + } + + return isFiltered; + }, + + /** + * Get default list of options. + * + * @returns {Array} + */ + getDefaultOptions: function () { + if (this.defaultOptions === null) { + this.defaultOptions = this.options(); + } + + return this.defaultOptions; + }, + + /** + * Get filtered list of options. + * + * @returns {Array} + */ + getFilteredOptions: function () { + var defaultOptions; + + if (this.filteredOptions === null) { + defaultOptions = this.getDefaultOptions(); + this.filteredOptions = []; + + _.each(defaultOptions, function (option) { + if (this.bannedOptions.indexOf(option.value) === -1) { + this.filteredOptions.push(option); + } + }, this); + } + + return this.filteredOptions; + } + }); +}); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/custom-options-warning.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/custom-options-warning.js new file mode 100644 index 0000000000000..f6a9a00f2a527 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/custom-options-warning.js @@ -0,0 +1,30 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/form/components/html' +], function (Html) { + 'use strict'; + + return Html.extend({ + defaults: { + isConfigurable: false + }, + + /** + * Updates component visibility state. + * + * @param {Boolean} variationsEmpty + * @returns {Boolean} + */ + updateVisibility: function (variationsEmpty) { + var isVisible = this.isConfigurable || !variationsEmpty; + + this.visible(isVisible); + + return isVisible; + } + }); +}); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js new file mode 100644 index 0000000000000..89f18d6945a02 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js @@ -0,0 +1,480 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'underscore', + 'uiRegistry', + 'Magento_Ui/js/dynamic-rows/dynamic-rows' +], function (_, registry, dynamicRows) { + 'use strict'; + + return dynamicRows.extend({ + defaults: { + actionsListOpened: false, + canEditField: 'canEdit', + newProductField: 'newProduct', + dataScopeAssociatedProduct: 'data.associated_product_ids', + dataProviderFromGrid: '', + dataProviderChangeFromGrid: '', + insertDataFromGrid: [], + changeDataFromGrid: [], + dataProviderFromWizard: '', + insertDataFromWizard: [], + map: null, + isEmpty: true, + cacheGridData: [], + unionInsertData: [], + deleteProperty: false, + dataLength: 0, + identificationProperty: 'id', + 'attribute_set_id': '', + listens: { + 'insertDataFromGrid': 'processingInsertDataFromGrid', + 'insertDataFromWizard': 'processingInsertDataFromWizard', + 'unionInsertData': 'processingUnionInsertData', + 'changeDataFromGrid': 'processingChangeDataFromGrid', + 'isEmpty': 'changeVisibility' + }, + imports: { + 'attribute_set_id': '${$.provider}:data.product.attribute_set_id' + }, + 'exports': { + 'attribute_set_id': '${$.provider}:data.new-variations-attribute-set-id' + }, + modules: { + modalWithGrid: '${ $.modalWithGrid }', + gridWithProducts: '${ $.gridWithProducts}' + } + }, + + /** + * Invokes initialize method of parent class, + * contains initialization logic + */ + initialize: function () { + this._super() + .changeVisibility(this.isEmpty()); + + return this; + }, + + /** + * Change visibility + * + * When isEmpty = true, then visbible = false + * + * @param {Boolean} isEmpty + */ + changeVisibility: function (isEmpty) { + this.visible(!isEmpty); + }, + + /** + * Open modal with grid. + * + * @param {String} rowIndex + */ + openModalWithGrid: function (rowIndex) { + var productSource = this.source.get(this.dataScope + '.' + this.index + '.' + rowIndex), + product = { + 'id': productSource.id, + 'attributes': productSource['configurable_attribute'] + }; + + this.modalWithGrid().openModal(); + this.gridWithProducts().showGridChangeProduct(rowIndex, product); + }, + + /** + * Initialize children + * + * @returns {Object} Chainable. + */ + initChildren: function () { + var tmpArray = []; + + this.recordData.each(function (recordData) { + tmpArray.push(recordData); + }, this); + + this.unionInsertData(tmpArray); + + return this; + }, + + /** + * Delete record + * + * @param {Number} index - row index + */ + deleteRecord: function (index) { + var tmpArray; + + this.reRender = false; + tmpArray = this.unionInsertData(); + tmpArray.splice(index, 1); + + if (!tmpArray.length) { + this.source.set('data.attributes', []); + } + + this.unionInsertData(tmpArray); + this.reRender = true; + }, + + /** + * Generate associated products + */ + generateAssociatedProducts: function () { + var productsIds = []; + + this.unionInsertData().each(function (data) { + if (data.id !== null) { + productsIds.push(data.id); + } + }); + + this.source.set(this.dataScopeAssociatedProduct, productsIds); + }, + + /** + * Calls 'initObservable' of parent + * + * @returns {Object} Chainable. + */ + initObservable: function () { + this._super() + .observe([ + 'insertDataFromGrid', 'unionInsertData', 'isEmpty', 'actionsListOpened' + ]); + + return this; + }, + + /** + * Process union insert data. + * + * @param {Array} data + */ + processingUnionInsertData: function (data) { + var dataInc = 0, + diff = 0, + dataCount, + elemsCount, + lastRecord; + + this.source.remove(this.dataScope + '.' + this.index); + this.isEmpty(data.length === 0); + + _.each(data, function (row) { + _.each(row, function (value, key) { + var path = this.dataScope + '.' + this.index + '.' + dataInc + '.' + key; + + this.source.set(path, value); + }, this); + + ++dataInc; + }, this); + + // Render + dataCount = data.length; + elemsCount = this.elems().length; + + if (dataCount > elemsCount) { + for (diff = dataCount - elemsCount; diff > 0; diff--) { + this.addChild(data, false); + } + } else { + for (diff = elemsCount - dataCount; diff > 0; diff--) { + lastRecord = + _.findWhere(this.elems(), { + index: this.recordIterator - 1 + }) || + _.findWhere(this.elems(), { + index: (this.recordIterator - 1).toString() + }); + lastRecord.destroy(); + --this.recordIterator; + } + } + + this.generateAssociatedProducts(); + }, + + /** + * Parsed data + * + * @param {Array} data - array with data + * about selected records + */ + processingInsertDataFromGrid: function (data) { + var changes, + tmpArray; + + if (!data.length) { + return; + } + + tmpArray = this.unionInsertData(); + + changes = this._checkGridData(data); + this.cacheGridData = data; + + changes.each(function (changedObject) { + var mappedData = this.mappingValue(changedObject); + + mappedData[this.canEditField] = 0; + mappedData[this.newProductField] = 0; + mappedData.variationKey = this._getVariationKey(changedObject); + mappedData['configurable_attribute'] = this._getConfigurableAttribute(changedObject); + tmpArray.push(mappedData); + }, this); + + this.unionInsertData(tmpArray); + }, + + /** + * Process changes from grid. + * + * @param {Object} data + */ + processingChangeDataFromGrid: function (data) { + var tmpArray = this.unionInsertData(), + mappedData = this.mappingValue(data.product); + + mappedData[this.canEditField] = 0; + mappedData[this.newProductField] = 0; + mappedData.variationKey = this._getVariationKey(data.product); + mappedData['configurable_attribute'] = this._getConfigurableAttribute(data.product); + tmpArray[data.rowIndex] = mappedData; + + this.unionInsertData(tmpArray); + }, + + /** + * Get variation key. + * + * @param {Object} data + * @returns {String} + * @private + */ + _getVariationKey: function (data) { + var attrCodes = this.source.get('data.attribute_codes'), + key = []; + + attrCodes.each(function (code) { + key.push(data[code]); + }); + + return key.sort().join('-'); + }, + + /** + * Get configurable attribute. + * + * @param {Object} data + * @returns {String} + * @private + */ + _getConfigurableAttribute: function (data) { + var attrCodes = this.source.get('data.attribute_codes'), + confAttrs = {}; + + attrCodes.each(function (code) { + confAttrs[code] = data[code]; + }); + + return JSON.stringify(confAttrs); + }, + + /** + * Process data insertion from wizard + * + * @param {Object} data + */ + processingInsertDataFromWizard: function (data) { + var tmpArray = this.unionInsertData(), + productIdsToDelete = this.source.get(this.dataScopeAssociatedProduct), + index, + product = {}; + + tmpArray = this.unsetArrayItem( + tmpArray, + { + id: null + } + ); + + _.each(data, function (row) { + var attributesText; + + if (row.productId) { + index = _.indexOf(productIdsToDelete, row.productId); + + if (index > -1) { + productIdsToDelete.splice(index, 1); + tmpArray = this.unsetArrayItem( + tmpArray, + { + id: row.productId + } + ); + } + } + + attributesText = ''; + _.each(row.options, function (attribute) { + if (attributesText) { + attributesText += ', '; + } + attributesText += attribute['attribute_label'] + ': ' + attribute.label; + }, this); + + product = { + 'id': row.productId, + 'product_link': row.productUrl, + 'name': row.name, + 'sku': row.sku, + 'status': row.status, + 'price': row.price, + 'price_currency': row.priceCurrency, + 'price_string': row.priceCurrency + row.price, + 'weight': row.weight, + 'qty': row.quantity, + 'variationKey': row.variationKey, + 'configurable_attribute': row.attribute, + 'thumbnail_image': row.images.preview, + 'media_gallery': row['media_gallery'], + 'swatch_image': row['swatch_image'], + 'small_image': row['small_image'], + 'thumbnail': row.thumbnail, + 'attributes': attributesText + }; + product[this.canEditField] = row.editable; + product[this.newProductField] = row.newProduct; + + tmpArray.push(product); + }, this); + + _.each(productIdsToDelete, function (id) { + tmpArray = this.unsetArrayItem( + tmpArray, + { + id: id + } + ); + }, this); + + this.unionInsertData(tmpArray); + }, + + /** + * Remove array items matching condition. + * + * @param {Array} data + * @param {Object} condition + * @returns {Array} + */ + unsetArrayItem: function (data, condition) { + var objs = _.where(data, condition); + + _.each(objs, function (obj) { + var index = _.indexOf(data, obj); + + if (index > -1) { + data.splice(index, 1); + } + }); + + return data; + }, + + /** + * Check changed records + * + * @param {Array} data - array with records data + * @returns {Array} Changed records + */ + _checkGridData: function (data) { + var cacheLength = this.cacheGridData.length, + curData = data.length, + max = cacheLength > curData ? this.cacheGridData : data, + changes = [], + obj = {}; + + max.each(function (record, index) { + obj[this.map.id] = record[this.map.id]; + + if (!_.where(this.cacheGridData, obj).length) { + changes.push(data[index]); + } + }, this); + + return changes; + }, + + /** + * Mapped value + */ + mappingValue: function (data) { + var result = {}; + + _.each(this.map, function (prop, index) { + result[index] = data[prop]; + }); + + return result; + }, + + /** + * Toggle actions list. + * + * @param {Number} rowIndex + * @returns {Object} Chainable. + */ + toggleActionsList: function (rowIndex) { + var state = false; + + if (rowIndex !== this.actionsListOpened()) { + state = rowIndex; + } + this.actionsListOpened(state); + + return this; + }, + + /** + * Close action list. + * + * @param {Number} rowIndex + * @returns {Object} Chainable + */ + closeList: function (rowIndex) { + if (this.actionsListOpened() === rowIndex) { + this.actionsListOpened(false); + } + + return this; + }, + + /** + * Toggle product status. + * + * @param {Number} rowIndex + */ + toggleStatusProduct: function (rowIndex) { + var tmpArray = this.unionInsertData(), + status = parseInt(tmpArray[rowIndex].status, 10); + + if (status === 1) { + tmpArray[rowIndex].status = 2; + } else { + tmpArray[rowIndex].status = 1; + } + + this.unionInsertData(tmpArray); + } + }); +}); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/file-uploader.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/file-uploader.js new file mode 100644 index 0000000000000..faeb48f3893d9 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/file-uploader.js @@ -0,0 +1,82 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'Magento_Ui/js/form/element/file-uploader' +], function (Element) { + 'use strict'; + + return Element.extend({ + processedFile: {}, + actionsListOpened: false, + defaults: { + fileInputName: '' + }, + + /** + * Initialize observables. + * + * @returns {Object} Chainable. + */ + initObservable: function () { + this._super().observe(['processedFile', 'actionsListOpened']); + + return this; + }, + + /** + * Adds provided file to the files list. + * + * @param {Object} file + * @returns {Object} Chainable. + */ + addFile: function (file) { + this.processedFile(this.processFile(file)); + + this.value(this.processedFile().file); + + return this; + }, + + /** + * Toggle actions list. + * + * @returns {Object} Chainable. + */ + toggleActionsList: function () { + if (this.actionsListOpened()) { + this.actionsListOpened(false); + } else { + this.actionsListOpened(true); + } + + return this; + }, + + /** + * Close action list. + * + * @returns {Object} Chainable + */ + closeList: function () { + if (this.actionsListOpened()) { + this.actionsListOpened(false); + } + + return this; + }, + + /** + * Delete Image + * + * @returns {Object} Chainable + */ + deleteImage: function () { + this.processedFile({}); + this.value(null); + + return this; + } + }); +}); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/modal-configurable.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/modal-configurable.js new file mode 100644 index 0000000000000..7ba004072c26e --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/modal-configurable.js @@ -0,0 +1,30 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/modal/modal-component' +], function (Modal) { + 'use strict'; + + return Modal.extend({ + defaults: { + modules: { + form: '${ $.formName }', + targetWizard: '${ $.wizardName }' + } + }, + + /** + * Open modal + */ + openModal: function () { + this.form().validate(); + + if (this.form().source.get('params.invalid') === false) { + this._super(); + } + } + }); +}); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/configurable-type-handler.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/configurable-type-handler.js index 3a77ac24ba6f9..fc61e624f7f4e 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/configurable-type-handler.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/configurable-type-handler.js @@ -5,13 +5,11 @@ define([ 'jquery', 'Magento_Catalog/catalog/type-events', - 'Magento_ConfigurableProduct/js/advanced-pricing-handler', - 'Magento_ConfigurableProduct/js/options/price-type-handler', 'collapsible', 'Magento_Ui/js/modal/modal', 'mage/translate', 'domReady!' -], function ($, productType, advancedPricingHandler, priceTypeHandler) { +], function ($, productType) { 'use strict'; return { @@ -84,7 +82,9 @@ define([ * @private */ _initType: function () { - var suggestContainer = $('#product-template-suggest-container .action-dropdown > .action-toggle'); + + /*var suggestContainer = $('#product-template-suggest-container .action-dropdown > .action-toggle'); + if (productType.type.current === 'configurable') { this._setElementDisabled(suggestContainer.addClass('disabled'), true); @@ -98,12 +98,15 @@ define([ this._setElementDisabled($('#inventory_stock_availability'), true); this._setElementDisabled($('#qty'), false, true); } + */ - if (['simple', 'virtual', 'configurable'].indexOf(productType.type.current) < 0) { + /*if (['simple', 'virtual', 'configurable'].indexOf(productType.type.current) < 0) { this.hide(); } else { this.show(); - } + }*/ + + this.show(); }, /** @@ -114,12 +117,13 @@ define([ this.$block = $(data.blockId + ' input[name="attributes[]"]'); this.hasVariations = data.hasVariations; - advancedPricingHandler.init(); - priceTypeHandler.init(); + //advancedPricingHandler.init(); + //priceTypeHandler.init(); - if (productType.type.init === 'configurable' && !this.hasVariations) { + /*if (productType.type.init === 'configurable' && !this.hasVariations) { $(document).trigger('setTypeProduct', 'simple'); - } + }*/ + $(document).trigger('setTypeProduct', 'simple'); this.bindAll(); this._initType(); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/options/price-type-handler.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/options/price-type-handler.js index d0d0314443b5d..d486723fc46d0 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/options/price-type-handler.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/options/price-type-handler.js @@ -2,7 +2,7 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ - +/* define([ 'jquery', 'Magento_Catalog/catalog/type-events', @@ -80,3 +80,4 @@ define([ } }; }); +*/ \ No newline at end of file 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 0a1088393c04f..6955bb511f606 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 @@ -15,7 +15,8 @@ define([ return Component.extend({ defaults: { modules: { - variationsComponent: '${ $.variationsComponent }' + variationsComponent: '${ $.variationsComponent }', + modalComponent: '${ $.modalComponent }' }, notificationMessage: { text: null, @@ -41,7 +42,7 @@ define([ variations: [], generateGrid: function (variations, getSectionValue) { var productSku = this.variationsComponent().getProductValue('sku'), - productPrice = this.variationsComponent().getProductValue('price'), + productPrice = this.variationsComponent().getProductPrice(), productWeight = this.variationsComponent().getProductValue('weight'), variationsKeys = [], gridExisting = [], @@ -159,7 +160,7 @@ define([ }, force: function () { this.variationsComponent().render(this.variations, this.attributes()); - $('[data-role=step-wizard-dialog]').trigger('closeModal'); + this.modalComponent().closeModal(); }, back: function () { } diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js index 878b0459fb0e9..f88d60b5fe9f4 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js @@ -9,8 +9,10 @@ define([ 'jquery', 'ko', 'underscore', - 'Magento_Ui/js/modal/alert' -], function (Component, $, ko, _, alert) { + 'Magento_Ui/js/modal/alert', + 'uiRegistry', + 'mage/translate' +], function (Component, $, ko, _, alert, registry, $t) { 'use strict'; function UserException(message) { @@ -23,77 +25,53 @@ define([ defaults: { opened: false, attributes: [], + usedAttributes: [], + attributeCodes: [], + attributesData: {}, productMatrix: [], variations: [], + formSaveParams: [], productAttributes: [], + disabledAttributes: [], fullAttributes: [], rowIndexToEdit: false, productAttributesMap: null, + value: [], modules: { - associatedProductGrid: '${ $.configurableProductGrid }' + associatedProductGrid: '${ $.configurableProductGrid }', + wizardButtonElement: '${ $.wizardModalButtonName }', + formElement: '${ $.formName }', + attributeSetHandlerModal: '${ $.attributeSetHandler }', + messageBoxElement: 'ns = ${ $.ns }, index = affectedAttributeSetError' + }, + imports: { + attributeSetName: '${ $.provider }:configurableNewAttributeSetName', + attributeSetId: '${ $.provider }:configurableExistingAttributeSetId', + attributeSetSelection: '${ $.provider }:configurableAffectedAttributeSet', + productPrice: '${ $.provider }:data.product.price' + }, + links: { + value: '${ $.provider }:${ $.dataScopeVariations }', + usedAttributes: '${ $.provider }:${ $.dataScopeAttributes }', + attributesData: '${ $.provider }:${ $.dataScopeAttributesData }', + attributeCodes: '${ $.provider }:${ $.dataScopeAttributeCodes }', + skeletonAttributeSet: '${ $.provider }:data.new-variations-attribute-set-id' } }, initialize: function () { this._super(); - if (this.variations.length) { - this.render(this.variations, this.productAttributes); - } + this.changeButtonWizard(); this.initProductAttributesMap(); + this.disableConfigurableAttributes(this.productAttributes); }, initObservable: function () { - this._super().observe('actions opened attributes productMatrix'); + this._super().observe( + 'actions opened attributes productMatrix value usedAttributes attributesData attributeCodes' + ); return this; }, - showGrid: function (rowIndex) { - var product = this.productMatrix()[rowIndex], - attributes = JSON.parse(product.attribute), - filterModifier = product.productId ? { - 'entity_id': { - 'condition_type': 'neq', value: product.productId - } - } : {}; - this.rowIndexToEdit = rowIndex; - - filterModifier = _.extend(filterModifier, _.mapObject(attributes, function (value) { - return { - 'condition_type': 'eq', - 'value': value - }; - })); - this.associatedProductGrid().open( - { - 'filters': attributes, - 'filters_modifier': filterModifier - }, - 'changeProduct', - false - ); - }, - changeProduct: function (newProducts) { - var oldProduct = this.productMatrix()[this.rowIndexToEdit], - newProduct = this._makeProduct(_.extend(oldProduct, newProducts[0])); - this.productAttributesMap[this.getVariationKey(newProduct.options)] = newProduct.productId; - this.productMatrix.splice(this.rowIndexToEdit, 1, newProduct); - }, - appendProducts: function (newProducts) { - this.productMatrix.push.apply( - this.productMatrix, - _.map( - newProducts, - _.wrap( - this._makeProduct.bind(this), - function (func, product) { - var newProduct = func(product); - this.productAttributesMap[this.getVariationKey(newProduct.options)] = newProduct.productId; - - return newProduct; - }.bind(this) - ) - ) - ); - }, _makeProduct: function (product) { var productId = product['entity_id'] || product.productId || null, attributes = _.pick(product, this.attributes.pluck('code')), @@ -155,25 +133,95 @@ define([ return result; }, - getAttributeRowName: function (attribute, field) { - return 'product[configurable_attributes_data][' + attribute.id + '][' + field + ']'; - }, - getOptionRowName: function (attribute, option, field) { - return 'product[configurable_attributes_data][' + attribute.id + '][values][' + - option.value + '][' + field + ']'; - }, render: function (variations, attributes) { this.changeButtonWizard(); this.populateVariationMatrix(variations); this.attributes(attributes); - this.initImageUpload(); this.disableConfigurableAttributes(attributes); - this.showPrice(); + this.handleValue(variations); + this.handleAttributes(); }, changeButtonWizard: function () { - var $button = $('[data-action=open-steps-wizard] [data-role=button-label]'); - $button.text($button.attr('data-edit-label')); + if (this.variations.length) { + this.wizardButtonElement().title(this.wizardModalButtonTitle); + } }, + handleValue: function (variations) { + var tmpArray = []; + + + _.each(variations, function (variation) { + var attributes = _.reduce(variation.options, function (memo, option) { + var attribute = {}; + attribute[option['attribute_code']] = option.value; + + return _.extend(memo, attribute); + }, {}); + var gallery = {images: {}}; + var defaultImage = null; + + _.each(variation.images.images, function (image) { + gallery.images[image.file_id] = { + position: image.position, + file: image.file, + disabled: image.disabled, + label: '' + }; + if (image.position == 1) { + defaultImage = image.file; + } + }, this); + + tmpArray.push(_.extend(variation, { + productId: variation.productId || null, + name: variation.name || variation.sku, + priceCurrency: this.currencySymbol, + weight: variation.weight, + attribute: JSON.stringify(attributes), + variationKey: this.getVariationKey(variation.options), + editable: variation.editable === undefined ? 0 : 1, + productUrl: this.buildProductUrl(variation.productId), + status: variation.status === undefined ? 1 : parseInt(variation.status, 10), + newProduct: variation.productId ? 0 : 1, + media_gallery: gallery, + swatch_image: defaultImage, + small_image: defaultImage, + thumbnail: defaultImage, + image: defaultImage + })); + }, this); + + this.value(tmpArray); + }, + handleAttributes: function () { + var tmpArray = [], codesArray = [], tmpOptions = {}, option = {}, position = 0, values = {}; + + _.each(this.attributes(), function (attribute) { + tmpArray.push(attribute.id); + codesArray.push(attribute.code); + values = {}; + _.each(attribute.chosen, function (row) { + values[row.value] = { + "include": "1", + "value_index": row.value + }; + }, this); + option = { + "attribute_id": attribute.id, + "code": attribute.code, + "label": attribute.label, + "position": position, + "values": values + }; + tmpOptions[attribute.id] = option; + position++; + }, this); + + this.attributesData(tmpOptions); + this.usedAttributes(tmpArray); + this.attributeCodes(codesArray); + }, + /** * Get attributes options @@ -193,6 +241,7 @@ define([ _.each(variations, function (variation) { var attributes = _.reduce(variation.options, function (memo, option) { var attribute = {}; + attribute[option['attribute_code']] = option.value; return _.extend(memo, attribute); @@ -212,52 +261,6 @@ define([ buildProductUrl: function (productId) { return this.productUrl.replace('%id%', productId); }, - removeProduct: function (rowIndex) { - this.opened(false); - var removedProduct = this.productMatrix.splice(rowIndex, 1); - delete this.productAttributesMap[this.getVariationKey(removedProduct[0].options)]; - - if (this.productMatrix().length === 0) { - this.attributes.each(function (attribute) { - $('[data-attribute-code="' + attribute.code + '"] select').removeProp('disabled'); - }); - } - this.showPrice(); - }, - toggleProduct: function (rowIndex) { - var product, row, productChanged = {}; - - if (this.productMatrix()[rowIndex].editable) { - row = $('[data-row-number=' + rowIndex + ']'); - _.each(['name','sku','qty','weight','price'], function (column) { - productChanged[column] = $( - 'input[type=text]', - row.find($('[data-column="%s"]'.replace('%s', column))) - ).val(); - }); - } - product = this.productMatrix.splice(rowIndex, 1)[0]; - product = _.extend(product, productChanged); - product.status = +!product.status; - this.productMatrix.splice(rowIndex, 0, product); - }, - toggleList: function (rowIndex) { - var state = false; - - if (rowIndex !== this.opened()) { - state = rowIndex; - } - this.opened(state); - - return this; - }, - closeList: function (rowIndex) { - if (this.opened() === rowIndex()) { - this.opened(false); - } - - return this; - }, getVariationKey: function (options) { return _.pluck(options, 'value').sort().join('-'); }, @@ -272,131 +275,156 @@ define([ }.bind(this)); } }, + disableConfigurableAttributes: function (attributes) { + var element; + + _.each(this.disabledAttributes, function (attribute) { + registry.get('index = ' + attribute).disabled(false); + }); + this.disabledAttributes = []; + + _.each(attributes, function (attribute) { + element = registry.get('index = ' + attribute.code); + if (!_.isUndefined(element)) { + element.disabled(true); + this.disabledAttributes.push(attribute.code); + } + }, this); + }, /** - * Is show preview image - * @see use in matrix.phtml - * @function - * @event - * @param {object} variation - * @returns {*|boolean} + * Get currency symbol + * @returns {*} */ - isShowPreviewImage: function (variation) { - return variation.images.preview && (!variation.editable || variation.images.file); + getCurrencySymbol: function () { + return this.currencySymbol; }, - generateImageGallery: function (variation) { - var gallery = [], - imageFields = ['position', 'file', 'disabled', 'label']; - _.each(variation.images.images, function (image) { - _.each(imageFields, function (field) { - gallery.push( - '' - ); - }, this); - _.each(image.galleryTypes, function (imageType) { - gallery.push( - '' - ); - }, this); + + /** + * Chose action for the form save button + */ + saveFormHandler: function() { + if (this.checkForNewAttributes()) { + this.formSaveParams = arguments; + this.attributeSetHandlerModal().openModal(); + } else { + this.formElement().save(arguments[0], arguments[1]); + } + }, + + /** + * Check for newly added attributes + * @returns {Boolean} + */ + checkForNewAttributes: function () { + var element, newAttributes = false; + + _.each(this.source.get('data.attribute_codes'), function (attribute) { + element = registry.get('index = ' + attribute); + + if (_.isUndefined(element)) { + newAttributes = true; + } }, this); - return gallery.join('\n'); + return newAttributes; + }, + + /** + * New attributes handler + * @returns {Boolean} + */ + addNewAttributeSetHandler: function() { + this.formElement().validate(); + + if (this.formElement().source.get('params.invalid') === false) { + var choosenAttributeSetOption = this.attributeSetSelection; + + if (choosenAttributeSetOption === 'new') { + this.createNewAttributeSet(); + return false; + } + + if (choosenAttributeSetOption === 'existing') { + this.set( + 'skeletonAttributeSet', + this.attributeSetId + ); + } + + this.closeDialogAndProcessForm(); + return true; + } }, - initImageUpload: function () { - require([ - 'mage/template', - 'jquery/file-uploader', - 'mage/mage', - 'mage/translate', - 'domReady!' - ], function (mageTemplate) { - - var matrix = $('[data-role=product-variations-matrix]'); - matrix.find('[data-action=upload-image]').find('[name=image]').each(function () { - var imageColumn = $(this).closest('[data-column=image]'); - - if (imageColumn.find('[data-role=image]').length) { - imageColumn.find('[data-toggle=dropdown]').dropdown().show(); + + /** + * Handles new attribute set creation + * @returns {Boolean} + */ + createNewAttributeSet: function() { + var ns = this.formElement().ns, + messageBoxElement = registry.get('index = ' + ns + '.affectedAttributeSetError'); + + messageBoxElement.visible(false); + + $.ajax({ + type: 'POST', + url: this.attributeSetCreationUrl, + data: { + gotoEdit: 1, + attribute_set_name: this.attributeSetName, + skeleton_set: this.skeletonAttributeSet, + return_session_messages_only: 1 + }, + dataType: 'json', + showLoader: true, + context: this + }) + + .success(function (data) { + if (!data.error) { + this.set( + 'skeletonAttributeSet', + data.id + ); + messageBoxElement.content(data.messages); + messageBoxElement.visible(true); + this.closeDialogAndProcessForm(); + } else { + messageBoxElement.content(data.messages); + messageBoxElement.visible(true); + } + + return false; + }) + + .error(function (xhr) { + if (xhr.statusText === 'abort') { + return; } - $(this).fileupload({ - dataType: 'json', - dropZone: $(this).closest('[data-role=row]'), - acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, - done: function (event, data) { - var tmpl, parentElement, uploaderControl, imageElement; - - if (!data.result) { - return; - } - - if (!data.result.error) { - parentElement = $(event.target).closest('[data-column=image]'); - uploaderControl = parentElement.find('[data-action=upload-image]'); - imageElement = parentElement.find('[data-role=image]'); - - if (imageElement.length) { - imageElement.attr('src', data.result.url); - } else { - tmpl = mageTemplate(matrix.find('[data-template-for=variation-image]').html()); - - $(tmpl({ - data: data.result - })).prependTo(uploaderControl); - } - parentElement.find('[name$="[image]"]').val(data.result.file); - parentElement.find('[data-toggle=dropdown]').dropdown().show(); - } else { - alert({ - content: $.mage.__('We don\'t recognize or support this file extension type.') - }); - } - }, - start: function (event) { - $(event.target).closest('[data-action=upload-image]').addClass('loading'); - }, - stop: function (event) { - $(event.target).closest('[data-action=upload-image]').removeClass('loading'); - } + + alert({ + content: $t('Something went wrong.') }); }); - matrix.find('[data-action=no-image]').click(function (event) { - var parentElement = $(event.target).closest('[data-column=image]'); - parentElement.find('[data-role=image]').remove(); - parentElement.find('[name$="[image]"]').val(''); - parentElement.find('[data-toggle=dropdown]').trigger('close.dropdown').hide(); - }); - }); - }, - disableConfigurableAttributes: function (attributes) { - $('[data-attribute-code] select.disabled-configurable-elements') - .removeClass('disabled-configurable-elements') - .prop('disabled', false); - _.each(attributes, function (attribute) { - $('[data-attribute-code="' + attribute.code + '"] select') - .addClass('disabled-configurable-elements') - .prop('disabled', true); - }); + + return false; }, - showPrice: function () { - var priceContainer = $('[id="attribute-price-container"]'); - if (this.productMatrix().length !== 0) { - priceContainer.hide(); - priceContainer.find('input').prop('disabled', true); - } else { - priceContainer.show(); - priceContainer.find('input').prop('disabled', false); - } + + /** + * Closes attribute set handler modal and process product form + */ + closeDialogAndProcessForm: function() { + this.attributeSetHandlerModal().closeModal(); + this.formElement().save(this.formSaveParams[0], this.formSaveParams[1]); }, /** - * Get currency symbol + * Retrieves product price * @returns {*} */ - getCurrencySymbol: function () { - return this.currencySymbol; + getProductPrice: function() { + return this.productPrice; } }); }); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/components/actions-list.html b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/components/actions-list.html new file mode 100644 index 0000000000000..934acac25f1c4 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/components/actions-list.html @@ -0,0 +1,44 @@ + +
+ + +
\ No newline at end of file diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/components/cell-html.html b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/components/cell-html.html new file mode 100644 index 0000000000000..541836b7b74c5 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/components/cell-html.html @@ -0,0 +1,13 @@ + +
+ + +
\ No newline at end of file diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/components/cell-status.html b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/components/cell-status.html new file mode 100644 index 0000000000000..1011fe6f31fde --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/components/cell-status.html @@ -0,0 +1,13 @@ + +
+ + +
\ No newline at end of file diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/components/file-uploader.html b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/components/file-uploader.html new file mode 100644 index 0000000000000..ec4669ed843c1 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/components/file-uploader.html @@ -0,0 +1,44 @@ + + +
+
+ + + + + + +
+ + + +
+
+ +
+
+ +
diff --git a/app/code/Magento/Downloadable/view/adminhtml/web/js/components/is-downloadable-handler.js b/app/code/Magento/Downloadable/view/adminhtml/web/js/components/is-downloadable-handler.js index f372cda0405ce..9003055c8b459 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/web/js/components/is-downloadable-handler.js +++ b/app/code/Magento/Downloadable/view/adminhtml/web/js/components/is-downloadable-handler.js @@ -10,8 +10,7 @@ define([ return Element.extend({ defaults: { listens: { - disabled: 'changeVisibility', - checked: 'changeVisibility' + disabled: 'changeVisibility' }, modules: { samplesFieldset: '${ $.samplesFieldset }', @@ -32,6 +31,16 @@ define([ this.linksFieldset().visible(false); } } + }, + + /** + * Handle checked state changes for checkbox / radio button. + * + * @param {Boolean} newChecked + */ + onCheckedChanged: function (newChecked) { + this.changeVisibility(); + this._super(newChecked); } }); }); diff --git a/app/code/Magento/Eav/Api/Data/AttributeGroupInterface.php b/app/code/Magento/Eav/Api/Data/AttributeGroupInterface.php index 9c02bda805670..1bc89df8d03d0 100644 --- a/app/code/Magento/Eav/Api/Data/AttributeGroupInterface.php +++ b/app/code/Magento/Eav/Api/Data/AttributeGroupInterface.php @@ -10,10 +10,12 @@ interface AttributeGroupInterface extends ExtensibleDataInterface { const GROUP_ID = 'attribute_group_id'; - const GROUP_NAME = 'attribute_group_name'; - const ATTRIBUTE_SET_ID = 'attribute_set_id'; + const SORT_ORDER = 'sort_order'; + const DEFAULT_ID = 'default_id'; + const ATTRIBUTE_GROUP_CODE = 'attribute_group_code'; + const SCOPE_CODE = 'tab_group_code'; /** * Retrieve id @@ -60,6 +62,66 @@ public function getAttributeSetId(); */ public function setAttributeSetId($attributeSetId); + /** + * Retrieve sort order + * + * @return int + */ + public function getSortOrder(); + + /** + * Set sort order + * + * @param int $sortOrder + * @return $this + */ + public function setSortOrder($sortOrder); + + /** + * Retrieve default ID + * + * @return int + */ + public function getDefaultId(); + + /** + * Set default ID + * + * @param int $defaultId + * @return $this + */ + public function setDefaultId($defaultId); + + /** + * Retrieve attribute group code + * + * @return string + */ + public function getAttributeGroupCode(); + + /** + * Set attribute group code + * + * @param string $attributeGroupCode + * @return $this + */ + public function setAttributeGroupCode($attributeGroupCode); + + /** + * Retrieve scope code + * + * @return string + */ + public function getScopeCode(); + + /** + * Set scope code + * + * @param string $scopeCode + * @return $this + */ + public function setScopeCode($scopeCode); + /** * Retrieve existing extension attributes object. * diff --git a/app/code/Magento/Eav/Model/Adminhtml/Attribute/Validation/Rules/Options.php b/app/code/Magento/Eav/Model/Adminhtml/Attribute/Validation/Rules/Options.php new file mode 100644 index 0000000000000..737cbea0f9b83 --- /dev/null +++ b/app/code/Magento/Eav/Model/Adminhtml/Attribute/Validation/Rules/Options.php @@ -0,0 +1,30 @@ + '', 'label' => '