diff --git a/.htaccess b/.htaccess index a0d1710a8aa55..1636ce4bc50cb 100644 --- a/.htaccess +++ b/.htaccess @@ -58,6 +58,32 @@ + + +############################################ +## adjust memory limit + + php_value memory_limit 768M + php_value max_execution_time 18000 + +############################################ +## disable automatic session start +## before autoload was initialized + + php_flag session.auto_start off + +############################################ +## enable resulting html compression + + #php_flag zlib.output_compression on + +########################################### +## disable user agent verification to not break multiple image upload + + php_flag suhosin.session.cryptua off + + + ########################################### ## disable POST processing to not break multiple image upload diff --git a/app/code/Magento/Backend/Block/Widget/Form.php b/app/code/Magento/Backend/Block/Widget/Form.php index 02ef3b1135009..3f258a48ca383 100644 --- a/app/code/Magento/Backend/Block/Widget/Form.php +++ b/app/code/Magento/Backend/Block/Widget/Form.php @@ -187,7 +187,7 @@ protected function _setFieldset($attributes, $fieldset, $exclude = []) $fieldType, [ 'name' => $attribute->getAttributeCode(), - 'label' => $attribute->getFrontend()->getLabel(), + 'label' => $attribute->getFrontend()->getLocalizedLabel(), 'class' => $attribute->getFrontend()->getClass(), 'required' => $attribute->getIsRequired(), 'note' => $attribute->getNote() diff --git a/app/code/Magento/Backend/Block/Widget/Tabs.php b/app/code/Magento/Backend/Block/Widget/Tabs.php index e7b21aa01abdc..0354554e3e2fb 100644 --- a/app/code/Magento/Backend/Block/Widget/Tabs.php +++ b/app/code/Magento/Backend/Block/Widget/Tabs.php @@ -111,6 +111,9 @@ public function addTabAfter($tabId, $tab, $afterTabId) */ public function addTab($tabId, $tab) { + if (empty($tabId)) { + throw new \Exception(__('Please correct the tab configuration and try again. Tab Id should be not empry')); + } if (is_array($tab)) { $this->_tabs[$tabId] = new \Magento\Framework\DataObject($tab); } elseif ($tab instanceof \Magento\Framework\DataObject) { diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/System/Account/SaveTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/System/Account/SaveTest.php index a68d23ab22c1a..d86dd0eb5e1bc 100644 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/System/Account/SaveTest.php +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/System/Account/SaveTest.php @@ -9,88 +9,105 @@ class SaveTest extends \PHPUnit_Framework_TestCase { - /** @var \Magento\Backend\Controller\Adminhtml\System\Account */ - protected $_controller; - - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\RequestInterface */ - protected $_requestMock; - - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\ResponseInterface */ - protected $_responseMock; - - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\ObjectManager\ObjectManager */ - protected $_objectManagerMock; - - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Message\ManagerInterface */ - protected $_messagesMock; - - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Backend\Helper\Data */ - protected $_helperMock; - - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Backend\Model\Auth\Session */ - protected $_authSessionMock; - - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\User\Model\User */ - protected $_userMock; - - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Validator\locale */ - protected $_validatorMock; - - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Backend\Model\Locale\Manager */ - protected $_managerMock; - - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\TranslateInterface */ - protected $_translatorMock; + /** + * @var \Magento\Backend\Controller\Adminhtml\System\Account\Save + */ + protected $controller; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\RequestInterface + */ + protected $requestMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\ResponseInterface + */ + protected $responseMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\ObjectManager\ObjectManager + */ + protected $objectManagerMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Message\ManagerInterface + */ + protected $messagesMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Backend\Helper\Data + */ + protected $helperMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Backend\Model\Auth\Session + */ + protected $authSessionMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\User\Model\User + */ + protected $userMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Validator\locale + */ + protected $validatorMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Backend\Model\Locale\Manager + */ + protected $managerMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\TranslateInterface + */ + protected $translatorMock; protected function setUp() { - $this->_requestMock = $this->getMockBuilder('Magento\Framework\App\Request\Http') + $frontControllerMock = $this->getMockBuilder('Magento\Framework\App\FrontController') + ->disableOriginalConstructor() + ->getMock(); + + $this->requestMock = $this->getMockBuilder('Magento\Framework\App\Request\Http') ->disableOriginalConstructor()->setMethods(['getOriginalPathInfo']) ->getMock(); - $this->_responseMock = $this->getMockBuilder('Magento\Framework\App\Response\Http') + $this->responseMock = $this->getMockBuilder('Magento\Framework\App\Response\Http') ->disableOriginalConstructor() ->setMethods([]) ->getMock(); - $this->_objectManagerMock = $this->getMockBuilder('Magento\Framework\ObjectManager\ObjectManager') + $this->objectManagerMock = $this->getMockBuilder('Magento\Framework\ObjectManager\ObjectManager') ->disableOriginalConstructor() ->setMethods(['get', 'create']) ->getMock(); - $frontControllerMock = $this->getMockBuilder('Magento\Framework\App\FrontController') - ->disableOriginalConstructor() - ->getMock(); - - $this->_helperMock = $this->getMockBuilder('Magento\Backend\Helper\Data') + $this->helperMock = $this->getMockBuilder('Magento\Backend\Helper\Data') ->disableOriginalConstructor() ->setMethods(['getUrl']) ->getMock(); - $this->_messagesMock = $this->getMockBuilder('Magento\Framework\Message\Manager') + $this->messagesMock = $this->getMockBuilder('Magento\Framework\Message\Manager') ->disableOriginalConstructor() ->setMethods(['addSuccess']) ->getMockForAbstractClass(); - - $this->_authSessionMock = $this->getMockBuilder('Magento\Backend\Model\Auth\Session') + $this->authSessionMock = $this->getMockBuilder('Magento\Backend\Model\Auth\Session') ->disableOriginalConstructor() ->setMethods(['getUser']) ->getMock(); - - $this->_userMock = $this->getMockBuilder('Magento\User\Model\User') + $this->userMock = $this->getMockBuilder('Magento\User\Model\User') ->disableOriginalConstructor() ->setMethods( ['load', 'save', 'sendPasswordResetNotificationEmail', 'verifyIdentity', '__sleep', '__wakeup'] ) ->getMock(); - - $this->_validatorMock = $this->getMockBuilder('Magento\Framework\Validator\Locale') + $this->validatorMock = $this->getMockBuilder('Magento\Framework\Validator\Locale') ->disableOriginalConstructor() ->setMethods(['isValid']) ->getMock(); - - $this->_managerMock = $this->getMockBuilder('Magento\Backend\Model\Locale\Manager') + $this->managerMock = $this->getMockBuilder('Magento\Backend\Model\Locale\Manager') ->disableOriginalConstructor() ->setMethods(['switchBackendInterfaceLocale']) ->getMock(); - - $this->_translatorMock = $this->getMockBuilder('Magento\Framework\TranslateInterface') + $this->translatorMock = $this->getMockBuilder('Magento\Framework\TranslateInterface') ->disableOriginalConstructor() ->getMock(); @@ -107,19 +124,19 @@ protected function setUp() ->willReturn($resultRedirect); $contextMock = $this->getMock('Magento\Backend\App\Action\Context', [], [], '', false); - $contextMock->expects($this->any())->method('getRequest')->willReturn($this->_requestMock); - $contextMock->expects($this->any())->method('getResponse')->willReturn($this->_responseMock); - $contextMock->expects($this->any())->method('getObjectManager')->willReturn($this->_objectManagerMock); + $contextMock->expects($this->any())->method('getRequest')->willReturn($this->requestMock); + $contextMock->expects($this->any())->method('getResponse')->willReturn($this->responseMock); + $contextMock->expects($this->any())->method('getObjectManager')->willReturn($this->objectManagerMock); $contextMock->expects($this->any())->method('getFrontController')->willReturn($frontControllerMock); - $contextMock->expects($this->any())->method('getHelper')->willReturn($this->_helperMock); - $contextMock->expects($this->any())->method('getMessageManager')->willReturn($this->_messagesMock); - $contextMock->expects($this->any())->method('getTranslator')->willReturn($this->_translatorMock); + $contextMock->expects($this->any())->method('getHelper')->willReturn($this->helperMock); + $contextMock->expects($this->any())->method('getMessageManager')->willReturn($this->messagesMock); + $contextMock->expects($this->any())->method('getTranslator')->willReturn($this->translatorMock); $contextMock->expects($this->once())->method('getResultFactory')->willReturn($resultFactory); $args = ['context' => $contextMock]; $testHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->_controller = $testHelper->getObject('Magento\Backend\Controller\Adminhtml\System\Account\Save', $args); + $this->controller = $testHelper->getObject('Magento\Backend\Controller\Adminhtml\System\Account\Save', $args); } public function testSaveAction() @@ -136,69 +153,50 @@ public function testSaveAction() \Magento\Backend\Block\System\Account\Edit\Form::IDENTITY_VERIFICATION_PASSWORD_FIELD => 'current_password', ]; - $testedMessage = 'You saved the account.'; - - $this->_authSessionMock->expects($this->any())->method('getUser')->will($this->returnValue($this->_userMock)); - - $this->_userMock->expects($this->any())->method('load')->will($this->returnSelf()); - $this->_validatorMock->expects( - $this->once() - )->method( - 'isValid' - )->with( - $this->equalTo($requestParams['interface_locale']) - )->will( - $this->returnValue(true) - ); - $this->_managerMock->expects($this->any())->method('switchBackendInterfaceLocale'); - - $this->_objectManagerMock->expects( - $this->at(0) - )->method( - 'get' - )->with( - $this->equalTo('Magento\Backend\Model\Auth\Session') - )->will( - $this->returnValue($this->_authSessionMock) - ); - $this->_objectManagerMock->expects( - $this->at(1) - )->method( - 'create' - )->with( - $this->equalTo('Magento\User\Model\User') - )->will( - $this->returnValue($this->_userMock) - ); - $this->_objectManagerMock->expects( - $this->at(2) - )->method( - 'get' - )->with( - $this->equalTo('Magento\Framework\Validator\Locale') - )->will( - $this->returnValue($this->_validatorMock) - ); - $this->_objectManagerMock->expects( - $this->at(3) - )->method( - 'get' - )->with( - $this->equalTo('Magento\Backend\Model\Locale\Manager') - )->will( - $this->returnValue($this->_managerMock) - ); - - $this->_userMock->setUserId($userId); - - $this->_userMock->expects($this->once())->method('save'); - $this->_userMock->expects($this->once())->method('verifyIdentity')->will($this->returnValue(true)); - $this->_userMock->expects($this->once())->method('sendPasswordResetNotificationEmail'); - - $this->_requestMock->setParams($requestParams); - - $this->_messagesMock->expects($this->once())->method('addSuccess')->with($this->equalTo($testedMessage)); - - $this->_controller->executeInternal(); + $testedMessage = __('You saved the account.'); + + $this->authSessionMock->expects($this->any()) + ->method('getUser') + ->willReturn($this->userMock); + $this->userMock->expects($this->any()) + ->method('load') + ->will($this->returnSelf()); + $this->validatorMock->expects($this->once()) + ->method('isValid') + ->with($this->equalTo($requestParams['interface_locale'])) + ->willReturn(true); + $this->managerMock->expects($this->any()) + ->method('switchBackendInterfaceLocale'); + $this->objectManagerMock->expects($this->at(0)) + ->method('get') + ->with($this->equalTo('Magento\Backend\Model\Auth\Session')) + ->willReturn($this->authSessionMock); + $this->objectManagerMock->expects($this->at(1)) + ->method('create') + ->with($this->equalTo('Magento\User\Model\User')) + ->willReturn($this->userMock); + $this->objectManagerMock->expects($this->at(2)) + ->method('get') + ->with($this->equalTo('Magento\Framework\Validator\Locale')) + ->willReturn($this->validatorMock); + $this->objectManagerMock->expects($this->at(3)) + ->method('get') + ->with($this->equalTo('Magento\Backend\Model\Locale\Manager')) + ->willReturn($this->managerMock); + $this->userMock->expects($this->once()) + ->method('save'); + $this->userMock->expects($this->once()) + ->method('verifyIdentity') + ->willReturn(true); + $this->userMock->expects($this->once()) + ->method('sendPasswordResetNotificationEmail'); + $this->messagesMock->expects($this->once()) + ->method('addSuccess') + ->with($this->equalTo($testedMessage)); + + $this->userMock->setUserId($userId); + $this->requestMock->setParams($requestParams); + + $this->controller->executeInternal(); } } diff --git a/app/code/Magento/Backend/i18n/en_US.csv b/app/code/Magento/Backend/i18n/en_US.csv index 7c6b4dab5992d..82284a014c7e3 100644 --- a/app/code/Magento/Backend/i18n/en_US.csv +++ b/app/code/Magento/Backend/i18n/en_US.csv @@ -606,6 +606,12 @@ Tags,Tags Options,Options "Magento Admin","Magento Admin" "Community Edition","Community Edition" +"Last Orders","Last Orders" +"Last Search Terms","Last Search Terms" +"Top Search Terms","Top Search Terms" +"Your Password","Your Password" +"You saved the account.","You saved the account." +"Current User Identity Verification","Current User Identity Verification" Marketing,Marketing Communications,Communications "SEO & Search","SEO & Search" diff --git a/app/code/Magento/Catalog/Api/Data/ProductWebsiteLinkInterface.php b/app/code/Magento/Catalog/Api/Data/ProductWebsiteLinkInterface.php new file mode 100644 index 0000000000000..08c8f6b970583 --- /dev/null +++ b/app/code/Magento/Catalog/Api/Data/ProductWebsiteLinkInterface.php @@ -0,0 +1,39 @@ +getProduct(); - $config = []; if (!$this->hasOptions()) { $config = [ 'productId' => $product->getId(), diff --git a/app/code/Magento/Catalog/Block/Product/View/Options.php b/app/code/Magento/Catalog/Block/Product/View/Options.php index befca9212fccc..e23b253872e62 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options.php @@ -162,7 +162,7 @@ protected function _getPriceConfiguration($option) $data = [ 'prices' => [ 'oldPrice' => [ - 'amount' => $optionPrice, + 'amount' => $this->pricingHelper->currency($option->getRegularPrice(), false, false), 'adjustments' => [], ], 'basePrice' => [ @@ -208,7 +208,6 @@ public function getJsonConfig() $config = []; foreach ($this->getOptions() as $option) { /* @var $option \Magento\Catalog\Model\Product\Option */ - $priceValue = 0; if ($option->getGroupByType() == \Magento\Catalog\Model\Product\Option::OPTION_GROUP_SELECT) { $tmpPriceValues = []; foreach ($option->getValues() as $value) { diff --git a/app/code/Magento/Catalog/Model/Layer/Search/CollectionFilter.php b/app/code/Magento/Catalog/Model/Layer/Search/CollectionFilter.php index 958751ae02b38..2ae363436b6fe 100644 --- a/app/code/Magento/Catalog/Model/Layer/Search/CollectionFilter.php +++ b/app/code/Magento/Catalog/Model/Layer/Search/CollectionFilter.php @@ -63,7 +63,6 @@ public function filter( ->addTaxPercents() ->addStoreFilter() ->addUrlRewrite() - ->setVisibility($this->productVisibility->getVisibleInSearchIds()) - ->setOrder('relevance', Select::SQL_DESC); + ->setVisibility($this->productVisibility->getVisibleInSearchIds()); } } diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 63045d6d5e26f..2687760e6b13f 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -117,12 +117,19 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements */ protected $_errors = []; + /** + * Product option factory + * + * @var Product\OptionFactory + */ + protected $optionFactory; + /** * Product option * * @var Product\Option */ - protected $_optionInstance; + protected $optionInstance; /** * @var array @@ -337,7 +344,7 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements * @param Product\Link $productLink * @param Product\Configuration\Item\OptionFactory $itemOptionFactory * @param \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory - * @param Product\Option $catalogProductOption + * @param Product\OptionFactory $catalogProductOptionFactory * @param Product\Visibility $catalogProductVisibility * @param Product\Attribute\Source\Status $catalogProductStatus * @param Product\Media\Config $catalogProductMediaConfig @@ -376,7 +383,7 @@ public function __construct( Product\Link $productLink, \Magento\Catalog\Model\Product\Configuration\Item\OptionFactory $itemOptionFactory, \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory, - \Magento\Catalog\Model\Product\Option $catalogProductOption, + \Magento\Catalog\Model\Product\OptionFactory $catalogProductOptionFactory, \Magento\Catalog\Model\Product\Visibility $catalogProductVisibility, \Magento\Catalog\Model\Product\Attribute\Source\Status $catalogProductStatus, \Magento\Catalog\Model\Product\Media\Config $catalogProductMediaConfig, @@ -405,7 +412,7 @@ public function __construct( $this->metadataService = $metadataService; $this->_itemOptionFactory = $itemOptionFactory; $this->_stockItemFactory = $stockItemFactory; - $this->_optionInstance = $catalogProductOption; + $this->optionFactory = $catalogProductOptionFactory; $this->_catalogProductVisibility = $catalogProductVisibility; $this->_catalogProductStatus = $catalogProductStatus; $this->_catalogProductMediaConfig = $catalogProductMediaConfig; @@ -1889,7 +1896,11 @@ public function getWeight() */ public function getOptionInstance() { - return $this->_optionInstance; + if (!isset($this->optionInstance)) { + $this->optionInstance = $this->optionFactory->create(); + $this->optionInstance->setProduct($this); + } + return $this->optionInstance; } /** diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Group.php b/app/code/Magento/Catalog/Model/Product/Attribute/Group.php index ba141dc370cd4..4fb0b521c22c7 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Group.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Group.php @@ -18,13 +18,15 @@ class Group extends \Magento\Eav\Model\Entity\Attribute\Group protected $_attributeCollectionFactory; /** + * Group constructor. * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry * @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory * @param AttributeValueFactory $customAttributeFactory + * @param \Magento\Framework\Filter\Translit $translitFilter * @param \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $attributeCollectionFactory - * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource - * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection + * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource + * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection * @param array $data */ public function __construct( @@ -32,6 +34,7 @@ public function __construct( \Magento\Framework\Registry $registry, \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory, AttributeValueFactory $customAttributeFactory, + \Magento\Framework\Filter\Translit $translitFilter, \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $attributeCollectionFactory, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, @@ -43,6 +46,7 @@ public function __construct( $registry, $extensionFactory, $customAttributeFactory, + $translitFilter, $resource, $resourceCollection, $data diff --git a/app/code/Magento/Catalog/Model/Product/Option.php b/app/code/Magento/Catalog/Model/Product/Option.php index 8b981d6f0b86c..cf672e0f29f26 100644 --- a/app/code/Magento/Catalog/Model/Product/Option.php +++ b/app/code/Magento/Catalog/Model/Product/Option.php @@ -4,15 +4,16 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile namespace Magento\Catalog\Model\Product; +use Magento\Catalog\Api\Data\ProductCustomOptionInterface; use Magento\Catalog\Api\Data\ProductCustomOptionValuesInterface; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ResourceModel\Product\Option\Value\Collection; use Magento\Catalog\Pricing\Price\BasePrice; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Model\AbstractExtensibleModel; /** * Catalog product option model @@ -25,8 +26,7 @@ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.ExcessivePublicCount) */ -class Option extends \Magento\Framework\Model\AbstractExtensibleModel - implements \Magento\Catalog\Api\Data\ProductCustomOptionInterface +class Option extends AbstractExtensibleModel implements ProductCustomOptionInterface { const OPTION_GROUP_TEXT = 'text'; @@ -77,31 +77,31 @@ class Option extends \Magento\Framework\Model\AbstractExtensibleModel /** * @var Product */ - protected $_product; + protected $product; /** * @var array */ - protected $_options = []; + protected $options = []; /** * @var array */ - protected $_values = null; + protected $values = null; /** * Catalog product option value * * @var Option\Value */ - protected $_productOptionValue; + protected $productOptionValue; /** * Product option factory * * @var \Magento\Catalog\Model\Product\Option\Type\Factory */ - protected $_optionFactory; + protected $optionTypeFactory; /** * @var \Magento\Framework\Stdlib\StringUtils @@ -140,8 +140,8 @@ public function __construct( \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [] ) { - $this->_productOptionValue = $productOptionValue; - $this->_optionFactory = $optionFactory; + $this->productOptionValue = $productOptionValue; + $this->optionTypeFactory = $optionFactory; $this->validatorPool = $validatorPool; $this->string = $string; parent::__construct( @@ -182,7 +182,7 @@ protected function _construct() */ public function addValue(Option\Value $value) { - $this->_values[$value->getId()] = $value; + $this->values[$value->getId()] = $value; return $this; } @@ -194,8 +194,8 @@ public function addValue(Option\Value $value) */ public function getValueById($valueId) { - if (isset($this->_values[$valueId])) { - return $this->_values[$valueId]; + if (isset($this->values[$valueId])) { + return $this->values[$valueId]; } return null; @@ -206,7 +206,7 @@ public function getValueById($valueId) */ public function getValues() { - return $this->_values; + return $this->values; } /** @@ -216,7 +216,7 @@ public function getValues() */ public function getValueInstance() { - return $this->_productOptionValue; + return $this->productOptionValue; } /** @@ -227,7 +227,7 @@ public function getValueInstance() */ public function addOption($option) { - $this->_options[] = $option; + $this->options[] = $option; return $this; } @@ -238,7 +238,7 @@ public function addOption($option) */ public function getOptions() { - return $this->_options; + return $this->options; } /** @@ -249,7 +249,7 @@ public function getOptions() */ public function setOptions($options) { - $this->_options = $options; + $this->options = $options; return $this; } @@ -260,7 +260,7 @@ public function setOptions($options) */ public function unsetOptions() { - $this->_options = []; + $this->options = []; return $this; } @@ -271,7 +271,7 @@ public function unsetOptions() */ public function getProduct() { - return $this->_product; + return $this->product; } /** @@ -282,7 +282,7 @@ public function getProduct() */ public function setProduct(Product $product = null) { - $this->_product = $product; + $this->product = $product; return $this; } @@ -294,7 +294,7 @@ public function setProduct(Product $product = null) */ public function getGroupByType($type = null) { - if (is_null($type)) { + if ($type === null) { $type = $this->getType(); } $optionGroupsToTypes = [ @@ -324,7 +324,7 @@ public function groupFactory($type) { $group = $this->getGroupByType($type); if (!empty($group)) { - return $this->_optionFactory->create( + return $this->optionTypeFactory->create( 'Magento\Catalog\Model\Product\Option\Type\\' . $this->string->upperCaseWords($group) ); } @@ -451,24 +451,24 @@ public function getPrice($flag = false) /** * Delete prices of option * - * @param int $option_id + * @param int $optionId * @return $this */ - public function deletePrices($option_id) + public function deletePrices($optionId) { - $this->getResource()->deletePrices($option_id); + $this->getResource()->deletePrices($optionId); return $this; } /** * Delete titles of option * - * @param int $option_id + * @param int $optionId * @return $this */ - public function deleteTitles($option_id) + public function deleteTitles($optionId) { - $this->getResource()->deleteTitles($option_id); + $this->getResource()->deleteTitles($optionId); return $this; } @@ -480,7 +480,8 @@ public function deleteTitles($option_id) */ public function getProductOptionCollection(Product $product) { - $collection = $this->getCollection()->addFieldToFilter( + $collection = clone $this->getCollection(); + $collection->addFieldToFilter( 'product_id', $product->getId() )->addTitleToResult( @@ -519,12 +520,12 @@ public function getValuesCollection() * Get collection of values by given option ids * * @param array $optionIds - * @param int $store_id + * @param int $storeId * @return Collection */ - public function getOptionValuesByOptionId($optionIds, $store_id) + public function getOptionValuesByOptionId($optionIds, $storeId) { - $collection = $this->_productOptionValue->getValuesByOption($optionIds, $this->getId(), $store_id); + $collection = $this->productOptionValue->getValuesByOption($optionIds, $this->getId(), $storeId); return $collection; } @@ -563,7 +564,7 @@ public function getSearchableData($productId, $storeId) protected function _clearData() { $this->_data = []; - $this->_values = null; + $this->values = null; return $this; } @@ -574,8 +575,8 @@ protected function _clearData() */ protected function _clearReferences() { - if (!empty($this->_values)) { - foreach ($this->_values as $value) { + if (!empty($this->values)) { + foreach ($this->values as $value) { $value->unsetOption(); } } @@ -848,7 +849,7 @@ public function setImageSizeY($imageSizeY) */ public function setValues(array $values = null) { - $this->_values = $values; + $this->values = $values; return $this; } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Value.php b/app/code/Magento/Catalog/Model/Product/Option/Value.php index 863c3a374b1cd..9a84ec2292183 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Value.php @@ -238,6 +238,21 @@ public function getPrice($flag = false) return $this->_getData(self::KEY_PRICE); } + /** + * Return regular price. + * + * @return float|int + */ + public function getRegularPrice() + { + if ($this->getPriceType() == self::TYPE_PERCENT) { + $basePrice = $this->getOption()->getProduct()->getPriceInfo()->getPrice('regular_price')->getAmount()->getValue(); + $price = $basePrice * ($this->_getData(self::KEY_PRICE) / 100); + return $price; + } + return $this->_getData(self::KEY_PRICE); + } + /** * Enter description here... * diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index ccd62fb3d3361..42923ef4310df 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -286,7 +286,12 @@ protected function initializeProductData(array $productData, $createNew) if ($createNew) { $product = $this->productFactory->create(); if ($this->storeManager->hasSingleStore()) { - $product->setWebsiteIds([$this->storeManager->getStore(true)->getWebsite()->getId()]); + $product->setWebsiteIds([$this->storeManager->getStore(true)->getWebsiteId()]); + } elseif (isset($productData['store_id']) + && !empty($productData['store_id']) + && $this->storeManager->getStore($productData['store_id']) + ) { + $product->setWebsiteIds([$this->storeManager->getStore($productData['store_id'])->getWebsiteId()]); } } else { unset($this->instances[$productData['sku']]); diff --git a/app/code/Magento/Catalog/Model/ProductWebsiteLink.php b/app/code/Magento/Catalog/Model/ProductWebsiteLink.php new file mode 100644 index 0000000000000..614cd579281f1 --- /dev/null +++ b/app/code/Magento/Catalog/Model/ProductWebsiteLink.php @@ -0,0 +1,51 @@ +_get(self::KEY_SKU); + } + + /** + * {@inheritdoc} + */ + public function getWebsiteId() + { + return $this->_get(self::WEBSITE_ID); + } + + /** + * @param string $sku + * @return $this + */ + public function setSku($sku) + { + return $this->setData(self::KEY_SKU, $sku); + } + + /** + * {@inheritdoc} + */ + public function setWebsiteId($websiteId) + { + return $this->setData(self::WEBSITE_ID, $websiteId); + } +} diff --git a/app/code/Magento/Catalog/Model/ProductWebsiteLinkRepository.php b/app/code/Magento/Catalog/Model/ProductWebsiteLinkRepository.php new file mode 100644 index 0000000000000..4e24fc5ef9c9b --- /dev/null +++ b/app/code/Magento/Catalog/Model/ProductWebsiteLinkRepository.php @@ -0,0 +1,84 @@ +productRepository = $productRepository; + } + + /** + * {@inheritdoc} + */ + public function save(ProductWebsiteLinkInterface $productWebsiteLink) + { + if (!$productWebsiteLink->getWebsiteId()) { + throw new InputException(__('There are not websites for assign to product')); + } + $product = $this->productRepository->get($productWebsiteLink->getSku()); + $product->setWebsiteIds(array_merge($product->getWebsiteIds(), [$productWebsiteLink->getWebsiteId()])); + try { + $product->save(); + } catch (\Exception $e) { + throw new CouldNotSaveException( + __( + 'Could not assign product "%1" to websites "%2"', + $product->getId(), + $productWebsiteLink->getWebsiteId() + ), + $e + ); + } + return true; + } + + /** + * {@inheritdoc} + */ + public function delete(ProductWebsiteLinkInterface $productLink) + { + return $this->deleteById($productLink->getSku(), $productLink->getSku()); + } + + /** + * {@inheritdoc} + */ + public function deleteById($sku, $websiteId) + { + $product = $this->productRepository->get($sku); + $product->setWebsiteIds(array_diff($product->getWebsiteIds(), [$websiteId])); + + try { + $product->save(); + } catch (\Exception $e) { + throw new CouldNotSaveException( + __( + 'Could not save product "%1" with websites %2', + $product->getId(), + implode(', ', $product->getWebsiteIds()) + ), + $e + ); + } + return true; + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Media.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Media.php index 0ac7a8c1a7bf6..96fa2009c64fe 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Media.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Media.php @@ -298,42 +298,30 @@ public function deleteGalleryValueInStore($valueId, $entityId, $storeId) * @param array $newFiles * @param int $originalProductId * @param int $newProductId - * @return $this + * @return array */ public function duplicate($attributeId, $newFiles, $originalProductId, $newProductId) { - $mainTableAlias = $this->getMainTableAlias(); + $mediaGalleryEntities = $this->loadMediaGalleryEntities($attributeId, $originalProductId); - $select = $this->getConnection()->select()->from( - [$mainTableAlias => $this->getMainTable()], - ['value_id', 'value'] - )->joinInner( - ['entity' => $this->getTable(self::GALLERY_VALUE_TO_ENTITY_TABLE)], - $mainTableAlias . '.value_id = entity.value_id', - ['entity_id' => 'entity_id'] - )->where( - 'attribute_id = ?', - $attributeId - )->where( - 'entity.entity_id = ?', - $originalProductId - ); - - $valueIdMap = []; // Duplicate main entries of gallery - foreach ($this->getConnection()->fetchAll($select) as $row) { + $valueIdMap = []; + foreach ($mediaGalleryEntities as $row) { + $valueId = $row['value_id']; $data = [ 'attribute_id' => $attributeId, - 'value' => isset($newFiles[$row['value_id']]) ? $newFiles[$row['value_id']] : $row['value'], + 'media_type' => $row['media_type'], + 'disabled' => $row['disabled'], + 'value' => isset($newFiles[$valueId]) ? $newFiles[$valueId] : $row['value'], ]; + $valueIdMap[$valueId] = $this->insertGallery($data); + $this->bindValueToEntity($valueIdMap[$valueId], $newProductId); + } - $valueIdMap[$row['value_id']] = $this->insertGallery($data); - $this->bindValueToEntity($valueIdMap[$row['value_id']], $newProductId); - } if (count($valueIdMap) == 0) { - return $this; + return []; } // Duplicate per store gallery values @@ -345,12 +333,55 @@ public function duplicate($attributeId, $newFiles, $originalProductId, $newProdu ); foreach ($this->getConnection()->fetchAll($select) as $row) { - unset($row['record_id']); - $row['entity_id'] = $newProductId; $row['value_id'] = $valueIdMap[$row['value_id']]; + unset($row['record_id']); $this->insertGalleryValueInStore($row); + $this->bindValueToEntity($row['value_id'], $newProductId); } - return $this; + return $valueIdMap; + } + + /** + * @param array $valueIds + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function loadMediaGalleryEntitiesbyId($valueIds) + { + $select = $this->getConnection()->select()->from( + $this->getMainTable() + )->where( + 'value_id IN(?)', + $valueIds + ); + + return $this->getConnection()->fetchAll($select); + } + + /** + * @param int $attributeId + * @param int $productId + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function loadMediaGalleryEntities($attributeId, $productId) + { + $mainTableAlias = $this->getMainTableAlias(); + $select = $this->getConnection()->select()->from( + [$mainTableAlias => $this->getMainTable()] + )->joinInner( + ['entity' => $this->getTable(self::GALLERY_VALUE_TO_ENTITY_TABLE)], + $mainTableAlias . '.value_id = entity.value_id', + ['entity_id' => 'entity_id'] + )->where( + 'attribute_id = ?', + $attributeId + )->where( + 'entity.entity_id = ?', + $productId + ); + + return $this->getConnection()->fetchAll($select); } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php index 0df889441d262..8b433296b328c 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php @@ -224,6 +224,20 @@ protected function _getWebsiteDateTable() * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ protected function _prepareFinalPriceData($entityIds = null) + { + return $this->prepareFinalPriceDataForType($entityIds, $this->getTypeId()); + } + + /** + * Prepare products default final price in temporary index table + * + * @param int|array $entityIds the entity ids limitation + * @param string|null $type product type, all if null + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + protected function prepareFinalPriceDataForType($entityIds, $type) { $this->_prepareDefaultFinalPriceTable(); @@ -260,11 +274,12 @@ protected function _prepareFinalPriceData($entityIds = null) 'tp.entity_id = e.entity_id AND tp.website_id = cw.website_id' . ' AND tp.customer_group_id = cg.customer_group_id', [] - )->where( - 'e.type_id = ?', - $this->getTypeId() ); + if ($type !== null) { + $select->where('e.type_id = ?', $type); + } + // add enable products limitation $statusCond = $connection->quoteInto( '=?', @@ -299,10 +314,10 @@ protected function _prepareFinalPriceData($entityIds = null) $select->columns( [ - 'orig_price' => $price, - 'price' => $finalPrice, - 'min_price' => $finalPrice, - 'max_price' => $finalPrice, + 'orig_price' => $connection->getIfNullSql($price, 0), + 'price' => $connection->getIfNullSql($finalPrice, 0), + 'min_price' => $connection->getIfNullSql($finalPrice, 0), + 'max_price' => $connection->getIfNullSql($finalPrice, 0), 'tier_price' => new \Zend_Db_Expr('tp.min_price'), 'base_tier' => new \Zend_Db_Expr('tp.min_price'), ] @@ -554,9 +569,10 @@ protected function _applyCustomOption() /** * Mode Final Prices index to primary temporary index table * + * @param int[]|null $entityIds * @return $this */ - protected function _movePriceDataToIndexTable() + protected function _movePriceDataToIndexTable($entityIds = null) { $columns = [ 'entity_id' => 'entity_id', @@ -574,6 +590,10 @@ protected function _movePriceDataToIndexTable() $table = $this->_getDefaultFinalPriceTable(); $select = $connection->select()->from($table, $columns); + if ($entityIds !== null) { + $select->where('entity_id in (?)', count($entityIds) > 0 ? $entityIds : 0); + } + $query = $select->insertFromSelect($this->getIdxTable(), [], false); $connection->query($query); diff --git a/app/code/Magento/Catalog/Setup/UpgradeData.php b/app/code/Magento/Catalog/Setup/UpgradeData.php index c59635383719f..accd781b59984 100644 --- a/app/code/Magento/Catalog/Setup/UpgradeData.php +++ b/app/code/Magento/Catalog/Setup/UpgradeData.php @@ -40,31 +40,6 @@ public function __construct(CategorySetupFactory $categorySetupFactory) public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); - if (version_compare($context->getVersion(), '2.0.1') < 0) { - /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */ - $categorySetup = $this->categorySetupFactory->create(['setup' => $setup]); - - $entityTypeId = $categorySetup->getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY); - $attributeSetId = $categorySetup->getDefaultAttributeSetId($entityTypeId); - - $attributeGroup = $categorySetup->getAttributeGroup( - $entityTypeId, - $attributeSetId, - 'Images', - 'attribute_group_name' - ); - if (isset($attributeGroup['attribute_group_name']) && $attributeGroup['attribute_group_name'] == 'Images') { - // update General Group - $categorySetup->updateAttributeGroup( - $entityTypeId, - $attributeSetId, - $attributeGroup['attribute_group_id'], - 'attribute_group_name', - 'Images and Videos' - ); - } - } - if ($context->getVersion() && version_compare($context->getVersion(), '2.0.1') < 0 ) { @@ -139,6 +114,12 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface 'Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection' ); } + + if (version_compare($context->getVersion(), '2.0.3') < 0) { + /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */ + $categorySetup = $this->categorySetupFactory->create(['setup' => $setup]); + $categorySetup->updateAttribute(3, 51, 'default_value', 1); + } $setup->endSetup(); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php index c52c96470fd63..e80bbd8682628 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php @@ -250,6 +250,15 @@ public function setUp() ->setMethods(['setProduct', 'saveOptions', '__wakeup', '__sleep']) ->disableOriginalConstructor()->getMock(); + $optionFactory = $this->getMock( + 'Magento\Catalog\Model\Product\OptionFactory', + ['create'], + [], + '', + false + ); + $optionFactory->expects($this->any())->method('create')->willReturn($this->optionInstanceMock); + $this->resource = $this->getMockBuilder('Magento\Catalog\Model\ResourceModel\Product') ->disableOriginalConstructor() ->getMock(); @@ -347,7 +356,7 @@ public function setUp() 'catalogProductType' => $this->productTypeInstanceMock, 'productFlatIndexerProcessor' => $this->productFlatProcessor, 'productPriceIndexerProcessor' => $this->productPriceProcessor, - 'catalogProductOption' => $this->optionInstanceMock, + 'catalogProductOptionFactory' => $optionFactory, 'storeManager' => $storeManager, 'resource' => $this->resource, 'registry' => $this->registry, @@ -1241,6 +1250,14 @@ public function testGetOptions() $optionInstanceMock = $this->getMockBuilder('Magento\Catalog\Model\Product\Option') ->disableOriginalConstructor() ->getMock(); + $optionFactory = $this->getMock( + 'Magento\Catalog\Model\Product\OptionFactory', + ['create'], + [], + '', + false + ); + $optionFactory->expects($this->any())->method('create')->willReturn($optionInstanceMock); $joinProcessorMock = $this->getMockBuilder('Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface') ->disableOriginalConstructor() ->getMock(); @@ -1249,7 +1266,7 @@ public function testGetOptions() $productModel = $this->objectManagerHelper->getObject( 'Magento\Catalog\Model\Product', [ - 'catalogProductOption' => $optionInstanceMock, + 'catalogProductOptionFactory' => $optionFactory, 'joinProcessor' => $joinProcessorMock ] ); diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index 7bd251153314e..3ffee93604698 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -459,6 +459,8 @@ + + diff --git a/app/code/Magento/Catalog/etc/module.xml b/app/code/Magento/Catalog/etc/module.xml index ee04bbc7431f4..e5bc1089fc9f8 100644 --- a/app/code/Magento/Catalog/etc/module.xml +++ b/app/code/Magento/Catalog/etc/module.xml @@ -6,7 +6,7 @@ */ --> - + diff --git a/app/code/Magento/Catalog/etc/webapi.xml b/app/code/Magento/Catalog/etc/webapi.xml index 24492cbc019b9..d447b748a9ef4 100644 --- a/app/code/Magento/Catalog/etc/webapi.xml +++ b/app/code/Magento/Catalog/etc/webapi.xml @@ -385,4 +385,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Catalog/i18n/en_US.csv b/app/code/Magento/Catalog/i18n/en_US.csv index 87bcbd26408da..b692356a48f6e 100644 --- a/app/code/Magento/Catalog/i18n/en_US.csv +++ b/app/code/Magento/Catalog/i18n/en_US.csv @@ -633,3 +633,24 @@ Set,Set none,none Overview,Overview "Field ""%1"" was not found in DO ""%2"".","Field ""%1"" was not found in DO ""%2""." +"Is Active","Is Active" +"URL Key","URL Key" +"Description","Description" +"Image","Image" +"Page Title","Page Title" +"Meta Keywords","Meta Keywords" +"Meta Description","Meta Description" +"Include in Navigation Menu","Include in Navigation Menu" +"Display Mode","Display Mode" +"CMS Block","CMS Block" +"Is Anchor","Is Anchor" +"Available Product Listing Sort By","Available Product Listing Sort By" +"Default Product Listing Sort By","Default Product Listing Sort By" +"Layered Navigation Price Step","Layered Navigation Price Step" +"Use Parent Category Settings","Use Parent Category Settings" +"Apply To Products","Apply To Products" +"Active From","Active From" +"Active To","Active To" +"Page Layout","Page Layout" +"Custom Layout Update","Custom Layout Update" +"Name","Name" diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js index de62ea60219a7..2d02bf2b97f69 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js @@ -38,7 +38,7 @@ define([ var findElement = function (data) { return $container.find('.image:not(.image-placeholder)').filter(function () { - if(!$(this).data('image')) { + if (!$(this).data('image')) { return false; } return $(this).data('image').file === data.file; diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/edit.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/edit.js index b56225bc6ea29..51fed1ebfec00 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/edit.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/edit.js @@ -20,7 +20,7 @@ define([ var params = {}; var fields = $('category_edit_form').getElementsBySelector('input', 'select'); - for(var i=0;i_storeManager->getWebsites(true); foreach ($websites as $website) { + // Continue if website has no store to be able to create catalog rule for website without store + if ($website->getDefaultStore() === null) { + continue; + } $map[$website->getId()] = $website->getDefaultStore()->getId(); } return $map; diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Dynamic/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Dynamic/DataProvider.php index b486feeafb601..cedd874dbdee0 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Dynamic/DataProvider.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Dynamic/DataProvider.php @@ -135,7 +135,10 @@ public function getAggregation( $column = $select->getPart(Select::COLUMNS)[0]; $select->reset(Select::COLUMNS); $rangeExpr = new \Zend_Db_Expr( - $this->connection->quoteInto('(FLOOR(' . $column[1] . ' / ? ) + 1)', $range) + $this->connection->getIfNullSql( + $this->connection->quoteInto('FLOOR(' . $column[1] . ' / ? ) + 1', $range), + 1 + ) ); $select diff --git a/app/code/Magento/CatalogSearch/Model/Advanced.php b/app/code/Magento/CatalogSearch/Model/Advanced.php index 277e092dec291..ec96c0deb3aa2 100644 --- a/app/code/Magento/CatalogSearch/Model/Advanced.php +++ b/app/code/Magento/CatalogSearch/Model/Advanced.php @@ -166,7 +166,6 @@ public function __construct( public function addFilters($values) { $attributes = $this->getAttributes(); - $hasConditions = false; $allConditions = []; foreach ($attributes as $attribute) { @@ -225,7 +224,7 @@ public function addFilters($values) if ($allConditions) { $this->_registry->register('advanced_search_conditions', $allConditions); $this->getProductCollection()->addFieldsToFilter($allConditions); - } elseif (!$hasConditions) { + } else { throw new LocalizedException(__('Please specify at least one search term.')); } diff --git a/app/code/Magento/CatalogSearch/Model/Advanced/Request/Builder.php b/app/code/Magento/CatalogSearch/Model/Advanced/Request/Builder.php index 5e0eb83f4ebab..138916f0767ad 100644 --- a/app/code/Magento/CatalogSearch/Model/Advanced/Request/Builder.php +++ b/app/code/Magento/CatalogSearch/Model/Advanced/Request/Builder.php @@ -27,7 +27,7 @@ public function bindRequestValue($attributeCode, $attributeValue) } elseif (!is_array($attributeValue)) { $this->bind($attributeCode, $attributeValue); } elseif (isset($attributeValue['like'])) { - $this->bind($attributeCode, trim($attributeValue['like'], '%')); + $this->bind($attributeCode, $attributeValue['like']); } elseif (isset($attributeValue['in'])) { $this->bind($attributeCode, $attributeValue['in']); } elseif (isset($attributeValue['in_set'])) { diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Plugin/Product.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Plugin/Product.php index 04c89e9ecf23d..ad68fe0899878 100644 --- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Plugin/Product.php +++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Plugin/Product.php @@ -11,24 +11,38 @@ class Product extends AbstractPlugin /** * Reindex on product save * - * @param \Magento\Catalog\Model\Product $product - * @return \Magento\Catalog\Model\Product + * @param \Magento\Catalog\Model\ResourceModel\Product $productResource + * @param \Closure $proceed + * @param \Magento\Framework\Model\AbstractModel $product + * @return \Magento\Catalog\Model\ResourceModel\Product + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterSave(\Magento\Catalog\Model\Product $product) - { + public function aroundSave( + \Magento\Catalog\Model\ResourceModel\Product $productResource, + \Closure $proceed, + \Magento\Framework\Model\AbstractModel $product + ) { + $result = $proceed($product); $this->reindexRow($product->getId()); - return $product; + return $result; } /** * Reindex on product delete * - * @param \Magento\Catalog\Model\Product $product - * @return \Magento\Catalog\Model\Product + * @param \Magento\Catalog\Model\ResourceModel\Product $productResource + * @param \Closure $proceed + * @param \Magento\Framework\Model\AbstractModel $product + * @return \Magento\Catalog\Model\ResourceModel\Product + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterDelete(\Magento\Catalog\Model\Product $product) - { + public function aroundDelete( + \Magento\Catalog\Model\ResourceModel\Product $productResource, + \Closure $proceed, + \Magento\Framework\Model\AbstractModel $product + ) { + $result = $proceed($product); $this->reindexRow($product->getId()); - return $product; + return $result; } } diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced.php index 4dafffbf8dfab..8f4b8de898f2e 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced.php @@ -81,7 +81,7 @@ public function prepareCondition($attribute, $value) } else { if (strlen($value) > 0) { if (in_array($attribute->getBackendType(), ['varchar', 'text', 'static'])) { - $condition = ['like' => '%' . $value . '%']; // text search + $condition = ['like' => $value]; // text search } else { $condition = $value; } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Advanced/Request/BuilderTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Advanced/Request/BuilderTest.php index 2b7788e245be3..0c3377d2b13c6 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Advanced/Request/BuilderTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Advanced/Request/BuilderTest.php @@ -182,7 +182,7 @@ public function testCreate() ]; $this->requestBuilder->bindRequestValue('from_to', ['from' => 10, 'to' => 20]); $this->requestBuilder->bindRequestValue('not_array', 130); - $this->requestBuilder->bindRequestValue('like', ['like' => '%search_text%']); + $this->requestBuilder->bindRequestValue('like', ['like' => 'search_text']); $this->requestBuilder->bindRequestValue('in', ['in' => 23]); $this->requestBuilder->bindRequestValue('in_set', ['in_set' => [12, 23, 34, 45]]); $this->requestBuilder->setRequestName($requestName); diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/ProductTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/ProductTest.php index 49f37b71f1a10..cb6c83ea3dc45 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/ProductTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/ProductTest.php @@ -16,10 +16,20 @@ class ProductTest extends \PHPUnit_Framework_TestCase protected $indexerMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Product + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\ResourceModel\Product */ protected $subjectMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Product + */ + protected $productMock; + + /** + * @var \Closure + */ + protected $proceed; + /** * @var \Magento\Framework\Indexer\IndexerRegistry|\PHPUnit_Framework_MockObject_MockObject */ @@ -32,8 +42,8 @@ class ProductTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->subjectMock = $this->getMock('Magento\Catalog\Model\Product', [], [], '', false); - + $this->productMock = $this->getMock('Magento\Catalog\Model\Product', [], [], '', false); + $this->subjectMock = $this->getMock('Magento\Catalog\Model\ResourceModel\Product', [], [], '', false); $this->indexerMock = $this->getMockForAbstractClass( 'Magento\Framework\Indexer\IndexerInterface', [], @@ -43,7 +53,6 @@ protected function setUp() true, ['getId', 'getState', '__wakeup'] ); - $this->indexerRegistryMock = $this->getMock( 'Magento\Framework\Indexer\IndexerRegistry', ['get'], @@ -52,6 +61,10 @@ protected function setUp() false ); + $this->proceed = function () { + return $this->subjectMock; + }; + $this->model = new Product($this->indexerRegistryMock); } @@ -61,9 +74,12 @@ public function testAfterSaveNonScheduled() $this->indexerMock->expects($this->once())->method('reindexRow')->with(1); $this->prepareIndexer(); - $this->subjectMock->expects($this->once())->method('getId')->will($this->returnValue(1)); + $this->productMock->expects($this->once())->method('getId')->will($this->returnValue(1)); - $this->assertEquals($this->subjectMock, $this->model->afterSave($this->subjectMock)); + $this->assertEquals( + $this->subjectMock, + $this->model->aroundSave($this->subjectMock, $this->proceed, $this->productMock) + ); } public function testAfterSaveScheduled() @@ -72,9 +88,12 @@ public function testAfterSaveScheduled() $this->indexerMock->expects($this->never())->method('reindexRow'); $this->prepareIndexer(); - $this->subjectMock->expects($this->once())->method('getId')->will($this->returnValue(1)); + $this->productMock->expects($this->once())->method('getId')->will($this->returnValue(1)); - $this->assertEquals($this->subjectMock, $this->model->afterSave($this->subjectMock)); + $this->assertEquals( + $this->subjectMock, + $this->model->aroundSave($this->subjectMock, $this->proceed, $this->productMock) + ); } public function testAfterDeleteNonScheduled() @@ -83,9 +102,12 @@ public function testAfterDeleteNonScheduled() $this->indexerMock->expects($this->once())->method('reindexRow')->with(1); $this->prepareIndexer(); - $this->subjectMock->expects($this->once())->method('getId')->will($this->returnValue(1)); + $this->productMock->expects($this->once())->method('getId')->will($this->returnValue(1)); - $this->assertEquals($this->subjectMock, $this->model->afterDelete($this->subjectMock)); + $this->assertEquals( + $this->subjectMock, + $this->model->aroundDelete($this->subjectMock, $this->proceed, $this->productMock) + ); } public function testAfterDeleteScheduled() @@ -94,9 +116,12 @@ public function testAfterDeleteScheduled() $this->indexerMock->expects($this->never())->method('reindexRow'); $this->prepareIndexer(); - $this->subjectMock->expects($this->once())->method('getId')->will($this->returnValue(1)); + $this->productMock->expects($this->once())->method('getId')->will($this->returnValue(1)); - $this->assertEquals($this->subjectMock, $this->model->afterDelete($this->subjectMock)); + $this->assertEquals( + $this->subjectMock, + $this->model->aroundDelete($this->subjectMock, $this->proceed, $this->productMock) + ); } protected function prepareIndexer() diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/AdvancedTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/AdvancedTest.php index 94bc0686cd20a..93c29b5078720 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/AdvancedTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/AdvancedTest.php @@ -54,7 +54,7 @@ public function prepareConditionDataProvider() { return [ ['string', 'string', 'string'], - ['varchar', 'string', ['like' => '%string%']], + ['varchar', 'string', ['like' => 'string']], ['varchar', ['test'], ['in_set' => ['test']]], ['select', ['test'], ['in' => ['test']]], ['range', ['from' => 1], ['from' => 1]], diff --git a/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml b/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml index 64c40be45f2dd..cef7804d7c308 100644 --- a/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml +++ b/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml @@ -27,11 +27,6 @@ validate-digits - - - validate-digits - Enter "0" to enable layered navigation for any number of results. - diff --git a/app/code/Magento/CatalogSearch/etc/config.xml b/app/code/Magento/CatalogSearch/etc/config.xml index 8ff9c9f527dad..c4183bb54245d 100644 --- a/app/code/Magento/CatalogSearch/etc/config.xml +++ b/app/code/Magento/CatalogSearch/etc/config.xml @@ -15,7 +15,6 @@ mysql 1 128 - 0 diff --git a/app/code/Magento/CatalogSearch/etc/di.xml b/app/code/Magento/CatalogSearch/etc/di.xml index adc76b143fa3e..815f2b223f04e 100644 --- a/app/code/Magento/CatalogSearch/etc/di.xml +++ b/app/code/Magento/CatalogSearch/etc/di.xml @@ -33,7 +33,7 @@ - + diff --git a/app/code/Magento/CatalogSearch/etc/adminhtml/events.xml b/app/code/Magento/CatalogSearch/etc/events.xml similarity index 100% rename from app/code/Magento/CatalogSearch/etc/adminhtml/events.xml rename to app/code/Magento/CatalogSearch/etc/events.xml diff --git a/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlPathGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlPathGenerator.php index d978d726a58c7..69b955cf78a39 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlPathGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlPathGenerator.php @@ -73,7 +73,9 @@ public function getUrlPath($category) return $category->getUrlPath(); } if ($this->isNeedToGenerateUrlPathForParent($category)) { - $parentPath = $this->getUrlPath($this->categoryRepository->get($category->getParentId())); + $parentPath = $this->getUrlPath( + $this->categoryRepository->get($category->getParentId(), $category->getStoreId()) + ); $path = $parentPath === '' ? $path : $parentPath . '/' . $path; } return $path; @@ -141,7 +143,7 @@ public function getCanonicalUrlPath($category) * @param \Magento\Catalog\Model\Category $category * @return string */ - public function generateUrlKey($category) + public function getUrlKey($category) { $urlKey = $category->getUrlKey(); return $category->formatUrlKey($urlKey === '' || $urlKey === null ? $category->getName() : $urlKey); diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php index 90e65b9094f5e..7656610422d4a 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php @@ -114,7 +114,7 @@ public function getCanonicalUrlPath($product, $category = null) * @param \Magento\Catalog\Model\Product $product * @return string */ - public function generateUrlKey($product) + public function getUrlKey($product) { return $product->getUrlKey() === false ? false : $this->prepareProductUrlKey($product); } diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php index e72b5df170485..1a903432cc1ee 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php @@ -7,9 +7,11 @@ use Magento\Catalog\Model\Category; use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; +use Magento\CatalogUrlRewrite\Service\V1\StoreViewService; use Magento\Framework\Event\Observer; use Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider; use Magento\Framework\Event\ObserverInterface; +use Magento\Store\Model\Store; class CategoryUrlPathAutogeneratorObserver implements ObserverInterface { @@ -19,16 +21,22 @@ class CategoryUrlPathAutogeneratorObserver implements ObserverInterface /** @var \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider */ protected $childrenCategoriesProvider; + /** @var StoreViewService */ + protected $storeViewService; + /** * @param CategoryUrlPathGenerator $categoryUrlPathGenerator * @param ChildrenCategoriesProvider $childrenCategoriesProvider + * @param \Magento\CatalogUrlRewrite\Service\V1\StoreViewService $storeViewService */ public function __construct( CategoryUrlPathGenerator $categoryUrlPathGenerator, - ChildrenCategoriesProvider $childrenCategoriesProvider + ChildrenCategoriesProvider $childrenCategoriesProvider, + StoreViewService $storeViewService ) { $this->categoryUrlPathGenerator = $categoryUrlPathGenerator; $this->childrenCategoriesProvider = $childrenCategoriesProvider; + $this->storeViewService = $storeViewService; } /** @@ -40,7 +48,7 @@ public function execute(\Magento\Framework\Event\Observer $observer) /** @var Category $category */ $category = $observer->getEvent()->getCategory(); if ($category->getUrlKey() !== false) { - $category->setUrlKey($this->categoryUrlPathGenerator->generateUrlKey($category)) + $category->setUrlKey($this->categoryUrlPathGenerator->getUrlKey($category)) ->setUrlPath($this->categoryUrlPathGenerator->getUrlPath($category)); if (!$category->isObjectNew()) { $category->getResource()->saveAttribute($category, 'url_path'); @@ -57,10 +65,48 @@ public function execute(\Magento\Framework\Event\Observer $observer) */ protected function updateUrlPathForChildren(Category $category) { - foreach ($this->childrenCategoriesProvider->getChildren($category, true) as $childCategory) { - $childCategory->unsUrlPath(); - $childCategory->setUrlPath($this->categoryUrlPathGenerator->getUrlPath($childCategory)); - $childCategory->getResource()->saveAttribute($childCategory, 'url_path'); + $children = $this->childrenCategoriesProvider->getChildren($category, true); + + if ($this->isGlobalScope($category->getStoreId())) { + foreach ($children as $child) { + foreach ($category->getStoreIds() as $storeId) { + if ($this->storeViewService->doesEntityHaveOverriddenUrlPathForStore( + $storeId, + $child->getId(), + Category::ENTITY + )) { + $child->setStoreId($storeId); + $this->updateUrlPathForCategory($child); + } + } + } + } else { + foreach ($children as $child) { + $child->setStoreId($category->getStoreId()); + $this->updateUrlPathForCategory($child); + } } } + + /** + * Check is global scope + * + * @param int|null $storeId + * @return bool + */ + protected function isGlobalScope($storeId) + { + return null === $storeId || $storeId == Store::DEFAULT_STORE_ID; + } + + /** + * @param Category $category + * @return void + */ + protected function updateUrlPathForCategory(Category $category) + { + $category->unsUrlPath(); + $category->setUrlPath($this->categoryUrlPathGenerator->getUrlPath($category)); + $category->getResource()->saveAttribute($category, 'url_path'); + } } diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/ProductUrlKeyAutogeneratorObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/ProductUrlKeyAutogeneratorObserver.php index 5a3cd8bb47a0a..525cc1568a77c 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/ProductUrlKeyAutogeneratorObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/ProductUrlKeyAutogeneratorObserver.php @@ -31,6 +31,6 @@ public function execute(\Magento\Framework\Event\Observer $observer) { /** @var Product $product */ $product = $observer->getEvent()->getProduct(); - $product->setUrlKey($this->productUrlPathGenerator->generateUrlKey($product)); + $product->setUrlKey($this->productUrlPathGenerator->getUrlKey($product)); } } diff --git a/app/code/Magento/CatalogUrlRewrite/Service/V1/StoreViewService.php b/app/code/Magento/CatalogUrlRewrite/Service/V1/StoreViewService.php index 958e882c46013..71e33a7411101 100644 --- a/app/code/Magento/CatalogUrlRewrite/Service/V1/StoreViewService.php +++ b/app/code/Magento/CatalogUrlRewrite/Service/V1/StoreViewService.php @@ -46,14 +46,43 @@ public function __construct( */ public function doesEntityHaveOverriddenUrlKeyForStore($storeId, $entityId, $entityType) { - $attribute = $this->eavConfig->getAttribute($entityType, 'url_key'); + return $this->doesEntityHaveOverriddenUrlAttributeForStore($storeId, $entityId, $entityType, 'url_key'); + } + + /** + * Check that entity has overridden url path for specific store + * + * @param int $storeId + * @param int $entityId + * @param string $entityType + * @throws \InvalidArgumentException + * @return bool + */ + public function doesEntityHaveOverriddenUrlPathForStore($storeId, $entityId, $entityType) + { + return $this->doesEntityHaveOverriddenUrlAttributeForStore($storeId, $entityId, $entityType, 'url_path'); + } + + /** + * Check that entity has overridden url attribute for specific store + * + * @param int $storeId + * @param int $entityId + * @param string $entityType + * @param mixed $attributeName + * @throws \InvalidArgumentException + * @return bool + */ + protected function doesEntityHaveOverriddenUrlAttributeForStore($storeId, $entityId, $entityType, $attributeName) + { + $attribute = $this->eavConfig->getAttribute($entityType, $attributeName); if (!$attribute) { throw new \InvalidArgumentException(sprintf('Cannot retrieve attribute for entity type "%s"', $entityType)); } $select = $this->connection->select() - ->from($attribute->getBackendTable(), 'store_id') - ->where('attribute_id = ?', $attribute->getId()) - ->where('entity_id = ?', $entityId); + ->from($attribute->getBackendTable(), 'store_id') + ->where('attribute_id = ?', $attribute->getId()) + ->where('entity_id = ?', $entityId); return in_array($storeId, $this->connection->fetchCol($select)); } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php index 94dc7f9c33ae2..41300634caaa4 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php @@ -235,7 +235,7 @@ public function testGetCanonicalUrlPath() /** * @return array */ - public function generateUrlKeyDataProvider() + public function getUrlKeyDataProvider() { return [ ['url-key', null, 'url-key'], @@ -244,17 +244,17 @@ public function generateUrlKeyDataProvider() } /** - * @dataProvider generateUrlKeyDataProvider - * @param string $urlKey - * @param string $name + * @dataProvider getUrlKeyDataProvider + * @param string|null|bool $urlKey + * @param string|null|bool $name * @param string $result */ - public function testGenerateUrlKey($urlKey, $name, $result) + public function testGetUrlKey($urlKey, $name, $result) { $this->category->expects($this->once())->method('getUrlKey')->will($this->returnValue($urlKey)); $this->category->expects($this->any())->method('getName')->will($this->returnValue($name)); $this->category->expects($this->once())->method('formatUrlKey')->will($this->returnArgument(0)); - $this->assertEquals($result, $this->categoryUrlPathGenerator->generateUrlKey($this->category)); + $this->assertEquals($result, $this->categoryUrlPathGenerator->getUrlKey($this->category)); } } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php index 7b733303bf4d4..fe4895c721bb2 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php @@ -85,11 +85,11 @@ public function getUrlPathDataProvider() /** * @dataProvider getUrlPathDataProvider - * @param $urlKey - * @param $productName - * @param $result + * @param string|null|bool $urlKey + * @param string|null|bool $productName + * @param string $result */ - public function testGenerateUrlPath($urlKey, $productName, $result) + public function testGetUrlPath($urlKey, $productName, $result) { $this->product->expects($this->once())->method('getData')->with('url_path') ->will($this->returnValue(null)); @@ -101,19 +101,21 @@ public function testGenerateUrlPath($urlKey, $productName, $result) } /** - * @param $productUrlKey - * @param $expectedUrlKey - * - * @dataProvider generateUrlKeyDataProvider + * @param string|bool $productUrlKey + * @param string|bool $expectedUrlKey + * @dataProvider getUrlKeyDataProvider */ - public function testGenerateUrlKey($productUrlKey, $expectedUrlKey) + public function testGetUrlKey($productUrlKey, $expectedUrlKey) { $this->product->expects($this->any())->method('getUrlKey')->will($this->returnValue($productUrlKey)); $this->product->expects($this->any())->method('formatUrlKey')->will($this->returnValue($productUrlKey)); - $this->assertEquals($expectedUrlKey, $this->productUrlPathGenerator->generateUrlKey($this->product)); + $this->assertEquals($expectedUrlKey, $this->productUrlPathGenerator->getUrlKey($this->product)); } - public function generateUrlKeyDataProvider() + /** + * @return array + */ + public function getUrlKeyDataProvider() { return [ 'URL Key use default' => [false, false], @@ -121,17 +123,10 @@ public function generateUrlKeyDataProvider() ]; } - public function testGetUrlPath() - { - $this->product->expects($this->once())->method('getData')->with('url_path') - ->will($this->returnValue('url-path')); - $this->product->expects($this->never())->method('getUrlKey'); - - $this->assertEquals('url-path', $this->productUrlPathGenerator->getUrlPath($this->product, null)); - } - /** - * + * @param string|null|bool $storedUrlKey + * @param string|null|bool $productName + * @param string $expectedUrlKey * @dataProvider getUrlPathDefaultUrlKeyDataProvider */ public function testGetUrlPathDefaultUrlKey($storedUrlKey, $productName, $expectedUrlKey) @@ -144,13 +139,15 @@ public function testGetUrlPathDefaultUrlKey($storedUrlKey, $productName, $expect $this->assertEquals($expectedUrlKey, $this->productUrlPathGenerator->getUrlPath($this->product, null)); } + /** + * @return array + */ public function getUrlPathDefaultUrlKeyDataProvider() { return [ ['default-store-view-url-key', null, 'default-store-view-url-key'], [false, 'default-store-view-product-name', 'default-store-view-product-name'] ]; - } public function testGetUrlPathWithCategory() diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php index 8088fcf653a18..e400fe924c686 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php @@ -24,6 +24,16 @@ class CategoryUrlPathAutogeneratorObserverTest extends \PHPUnit_Framework_TestCa /** @var \PHPUnit_Framework_MockObject_MockObject */ protected $category; + /** + * @var \Magento\CatalogUrlRewrite\Service\V1\StoreViewService|\PHPUnit_Framework_MockObject_MockObject + */ + protected $storeViewService; + + /** + * @var \Magento\Catalog\Model\ResourceModel\Category|\PHPUnit_Framework_MockObject_MockObject + */ + protected $categoryResource; + protected function setUp() { $this->observer = $this->getMock( @@ -33,13 +43,15 @@ protected function setUp() '', false ); + $this->categoryResource = $this->getMock('Magento\Catalog\Model\ResourceModel\Category', [], [], '', false); $this->category = $this->getMock( 'Magento\Catalog\Model\Category', - ['setUrlKey', 'setUrlPath', 'dataHasChangedFor', 'isObjectNew', 'getResource', 'getUrlKey'], + ['setUrlKey', 'setUrlPath', 'dataHasChangedFor', 'isObjectNew', 'getResource', 'getUrlKey', 'getStoreId'], [], '', false ); + $this->category->expects($this->any())->method('getResource')->willReturn($this->categoryResource); $this->observer->expects($this->any())->method('getEvent')->willReturnSelf(); $this->observer->expects($this->any())->method('getCategory')->willReturn($this->category); $this->categoryUrlPathGenerator = $this->getMock( @@ -53,11 +65,20 @@ protected function setUp() 'Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider' ); + $this->storeViewService = $this->getMock( + 'Magento\CatalogUrlRewrite\Service\V1\StoreViewService', + [], + [], + '', + false + ); + $this->categoryUrlPathAutogeneratorObserver = (new ObjectManagerHelper($this))->getObject( 'Magento\CatalogUrlRewrite\Observer\CategoryUrlPathAutogeneratorObserver', [ 'categoryUrlPathGenerator' => $this->categoryUrlPathGenerator, - 'childrenCategoriesProvider' => $this->childrenCategoriesProvider + 'childrenCategoriesProvider' => $this->childrenCategoriesProvider, + 'storeViewService' => $this->storeViewService, ] ); } @@ -65,7 +86,7 @@ protected function setUp() public function testSetCategoryUrlAndCategoryPath() { $this->category->expects($this->once())->method('getUrlKey')->willReturn('category'); - $this->categoryUrlPathGenerator->expects($this->once())->method('generateUrlKey')->willReturn('urk_key'); + $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlKey')->willReturn('urk_key'); $this->category->expects($this->once())->method('setUrlKey')->with('urk_key')->willReturnSelf(); $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlPath')->willReturn('url_path'); $this->category->expects($this->once())->method('setUrlPath')->with('url_path')->willReturnSelf(); @@ -74,7 +95,7 @@ public function testSetCategoryUrlAndCategoryPath() $this->categoryUrlPathAutogeneratorObserver->execute($this->observer); } - public function testExecuteWithoutGeneration() + public function testExecuteWithoutUrlKeyAndUrlPathUpdating() { $this->category->expects($this->once())->method('getUrlKey')->willReturn(false); $this->category->expects($this->never())->method('setUrlKey'); @@ -82,30 +103,103 @@ public function testExecuteWithoutGeneration() $this->categoryUrlPathAutogeneratorObserver->execute($this->observer); } - public function testUpdateUrlPathForChildren() + public function testUrlKeyAndUrlPathUpdating() { - $this->category->expects($this->once())->method('getUrlKey')->willReturn('category'); - $this->category->expects($this->once())->method('setUrlKey')->willReturnSelf(); - $this->category->expects($this->once())->method('setUrlPath')->willReturnSelf(); + $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlKey')->with($this->category) + ->willReturn('url_key'); + $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlPath')->with($this->category) + ->willReturn('url_path'); + + $this->category->expects($this->once())->method('getUrlKey')->willReturn('not_formatted_url_key'); + $this->category->expects($this->once())->method('setUrlKey')->with('url_key')->willReturnSelf(); + $this->category->expects($this->once())->method('setUrlPath')->with('url_path')->willReturnSelf(); + // break code execution + $this->category->expects($this->once())->method('isObjectNew')->willReturn(true); + + $this->categoryUrlPathAutogeneratorObserver->execute($this->observer); + } + + public function testUrlPathAttributeNoUpdatingIfCategoryIsNew() + { + $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlKey')->willReturn('url_key'); + $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn('url_path'); + + $this->category->expects($this->any())->method('getUrlKey')->willReturn('not_formatted_url_key'); + $this->category->expects($this->any())->method('setUrlKey')->willReturnSelf(); + $this->category->expects($this->any())->method('setUrlPath')->willReturnSelf(); + + $this->category->expects($this->once())->method('isObjectNew')->willReturn(true); + $this->categoryResource->expects($this->never())->method('saveAttribute'); + + $this->categoryUrlPathAutogeneratorObserver->execute($this->observer); + } + + public function testUrlPathAttributeUpdating() + { + $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlKey')->willReturn('url_key'); + $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn('url_path'); + + $this->category->expects($this->any())->method('getUrlKey')->willReturn('not_formatted_url_key'); + $this->category->expects($this->any())->method('setUrlKey')->willReturnSelf(); + $this->category->expects($this->any())->method('setUrlPath')->willReturnSelf(); $this->category->expects($this->once())->method('isObjectNew')->willReturn(false); - $this->category->expects($this->once())->method('dataHasChangedFor')->with('url_path')->willReturn(true); - $categoryResource = $this->getMockBuilder('Magento\Catalog\Model\ResourceModel\Category') - ->disableOriginalConstructor()->getMock(); - $this->category->expects($this->once())->method('getResource')->willReturn($categoryResource); - $categoryResource->expects($this->once())->method('saveAttribute')->with($this->category, 'url_path'); + $this->categoryResource->expects($this->once())->method('saveAttribute')->with($this->category, 'url_path'); + + // break code execution + $this->category->expects($this->once())->method('dataHasChangedFor')->with('url_path')->willReturn(false); + + $this->categoryUrlPathAutogeneratorObserver->execute($this->observer); + } + + public function testChildrenUrlPathAttributeNoUpdatingIfParentUrlPathIsNotChanged() + { + $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlKey')->willReturn('url_key'); + $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn('url_path'); + + $this->categoryResource->expects($this->once())->method('saveAttribute')->with($this->category, 'url_path'); + + $this->category->expects($this->any())->method('getUrlKey')->willReturn('not_formatted_url_key'); + $this->category->expects($this->any())->method('setUrlKey')->willReturnSelf(); + $this->category->expects($this->any())->method('setUrlPath')->willReturnSelf(); + $this->category->expects($this->once())->method('isObjectNew')->willReturn(false); + // break code execution + $this->category->expects($this->once())->method('dataHasChangedFor')->with('url_path')->willReturn(false); + + $this->categoryUrlPathAutogeneratorObserver->execute($this->observer); + } + + public function testChildrenUrlPathAttributeUpdatingForSpecificStore() + { + $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlKey')->willReturn('generated_url_key'); + $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn('generated_url_path'); + + $this->category->expects($this->any())->method('getUrlKey')->willReturn('not_formatted_url_key'); + $this->category->expects($this->any())->method('setUrlKey')->willReturnSelf(); + $this->category->expects($this->any())->method('setUrlPath')->willReturnSelf(); + $this->category->expects($this->any())->method('isObjectNew')->willReturn(false); + $this->category->expects($this->any())->method('dataHasChangedFor')->willReturn(true); + // only for specific store + $this->category->expects($this->atLeastOnce())->method('getStoreId')->willReturn(1); + + $childCategoryResource = $this->getMockBuilder('Magento\Catalog\Model\ResourceModel\Category') + ->disableOriginalConstructor()->getMock(); $childCategory = $this->getMockBuilder('Magento\Catalog\Model\Category') - ->setMethods(['getUrlPath', 'setUrlPath', 'getResource', 'unsUrlPath']) + ->setMethods([ + 'getUrlPath', + 'setUrlPath', + 'getResource', + 'getStore', + 'getStoreId', + 'setStoreId' + ]) ->disableOriginalConstructor()->getMock(); + $childCategory->expects($this->any())->method('getResource')->willReturn($childCategoryResource); + $childCategory->expects($this->once())->method('setStoreId')->with(1); $this->childrenCategoriesProvider->expects($this->once())->method('getChildren')->willReturn([$childCategory]); - $childCategoryResource = $this->getMockBuilder('Magento\Catalog\Model\ResourceModel\Category') - ->disableOriginalConstructor()->getMock(); - $childCategory->expects($this->once())->method('unsUrlPath')->willReturnSelf(); - $childCategory->expects($this->once())->method('getResource')->willReturn($childCategoryResource); + $childCategory->expects($this->once())->method('setUrlPath')->with('generated_url_path')->willReturnSelf(); $childCategoryResource->expects($this->once())->method('saveAttribute')->with($childCategory, 'url_path'); - $childCategory->expects($this->once())->method('setUrlPath')->with('category-url_path')->willReturnSelf(); - $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn('category-url_path'); $this->categoryUrlPathAutogeneratorObserver->execute($this->observer); } diff --git a/app/code/Magento/Checkout/Model/Cart.php b/app/code/Magento/Checkout/Model/Cart.php index 9cc015b2271cd..470af073fef90 100644 --- a/app/code/Magento/Checkout/Model/Cart.php +++ b/app/code/Magento/Checkout/Model/Cart.php @@ -324,6 +324,7 @@ protected function _getProductRequest($requestInfo) if (!$request->hasQty()) { $request->setQty(1); } + !$request->hasFormKey() ?: $request->unsFormKey(); return $request; } diff --git a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php index d0cc39ceaafe1..8e59581f35196 100644 --- a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php +++ b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php @@ -62,11 +62,6 @@ class DefaultConfigProvider implements ConfigProviderInterface */ private $httpContext; - /** - * @var CurrencyManager - */ - private $currencyManager; - /** * @var QuoteRepository */ @@ -174,7 +169,6 @@ class DefaultConfigProvider implements ConfigProviderInterface * @param CustomerSession $customerSession * @param CustomerUrlManager $customerUrlManager * @param HttpContext $httpContext - * @param CurrencyManager $currencyManager * @param QuoteRepository $quoteRepository * @param QuoteItemRepository $quoteItemRepository * @param ShippingMethodManager $shippingMethodManager @@ -205,7 +199,6 @@ public function __construct( CustomerSession $customerSession, CustomerUrlManager $customerUrlManager, HttpContext $httpContext, - CurrencyManager $currencyManager, QuoteRepository $quoteRepository, QuoteItemRepository $quoteItemRepository, ShippingMethodManager $shippingMethodManager, @@ -233,7 +226,6 @@ public function __construct( $this->customerSession = $customerSession; $this->customerUrlManager = $customerUrlManager; $this->httpContext = $httpContext; - $this->currencyManager = $currencyManager; $this->quoteRepository = $quoteRepository; $this->quoteItemRepository = $quoteItemRepository; $this->shippingMethodManager = $shippingMethodManager; @@ -282,7 +274,7 @@ public function getConfig() ); $output['basePriceFormat'] = $this->localeFormat->getPriceFormat( null, - $this->currencyManager->getDefaultCurrency() + $this->checkoutSession->getQuote()->getBaseCurrencyCode() ); $output['postCodes'] = $this->postCodesConfig->getPostCodes(); $output['imageData'] = $this->imageProvider->getImages($quoteId); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js index f503e5be641c3..052e79cbc7fce 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -10,9 +10,10 @@ define([ 'Magento_Customer/js/customer-data', 'Magento_Ui/js/modal/alert', 'Magento_Ui/js/modal/confirm', + 'Magento_Customer/js/customer-data', "jquery/ui", "mage/decorate" -], function($, authenticationPopup, customerData, alert, confirm){ +], function($, authenticationPopup, customerData, alert, confirm, customerData){ $.widget('mage.sidebar', { options: { @@ -21,8 +22,19 @@ define([ }, scrollHeight: 0, - _create: function() { + /** + * Create sidebar. + * @private + */ + _create: function () { + var self = this; + this._initContent(); + customerData.get('cart').subscribe(function () { + $(self.options.targetElement).trigger('contentUpdated'); + self._calcHeight(); + self._isOverflowed(); + }); }, _initContent: function() { @@ -67,6 +79,9 @@ define([ event.stopPropagation(); self._updateItemQty($(event.currentTarget)); }; + events['focusout ' + this.options.item.qty] = function(event) { + self._validateQty($(event.currentTarget)); + }; this._on(this.element, events); this._calcHeight(); @@ -95,7 +110,6 @@ define([ if (this._isValidQty(itemQty, elem.val())) { $('#update-cart-item-' + itemId).show('fade', 300); } else if (elem.val() == 0) { - elem.val(itemQty); this._hideItemButton(elem); } else { this._hideItemButton(elem); @@ -115,6 +129,18 @@ define([ && (changed - 0 > 0); }, + /** + * @param {Object} elem + * @private + */ + _validateQty: function(elem) { + var itemQty = elem.data('item-qty'); + + if (!this._isValidQty(itemQty, elem.val())) { + elem.val(itemQty); + } + }, + _hideItemButton: function(elem) { var itemId = elem.data('cart-item'); $('#update-cart-item-' + itemId).hide('fade', 300); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js index b69365ebc0f17..0b3290b3c3960 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js @@ -145,6 +145,9 @@ define( } else { this.source.set('params.invalid', false); this.source.trigger(this.dataScopePrefix + '.data.validate'); + if (this.source.get(this.dataScopePrefix + '.custom_attributes')) { + this.source.trigger(this.dataScopePrefix + '.custom_attributes.data.validate'); + }; if (!this.source.get('params.invalid')) { var addressData = this.source.get(this.dataScopePrefix), diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js b/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js index 7c4025a51c2e5..68f02b5d57224 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js @@ -72,12 +72,6 @@ define([ this.isLoading(addToCartCalls > 0); sidebarInitialized = false; initSidebar(); - - /**TODO: Extra options support. Should be refactored after MAGETWO-43159. */ - setInterval(function(){ - minicart.trigger('contentUpdated'); - }, 500); - }, this); $('[data-block="minicart"]').on('contentLoading', function(event) { addToCartCalls++; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js index 562266434dd89..e650f6269c910 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js @@ -201,7 +201,7 @@ define( } }, - validateShippingInformation: function() { + validateShippingInformation: function () { var shippingAddress, addressData, loginFormSelector = 'form[data-role=email-with-possible-login]', @@ -224,6 +224,9 @@ define( if (this.isFormInline) { this.source.set('params.invalid', false); this.source.trigger('shippingAddress.data.validate'); + if (this.source.get('shippingAddress.custom_attributes')) { + this.source.trigger('shippingAddress.custom_attributes.data.validate'); + }; if (this.source.get('params.invalid') || !quote.shippingMethod().method_code || !quote.shippingMethod().carrier_code diff --git a/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php index fce1979115e66..356f9b2f2b8cd 100644 --- a/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php @@ -184,15 +184,15 @@ public function getJsonConfig() 'optionPrices' => $this->getOptionPrices(), 'prices' => [ 'oldPrice' => [ - 'amount' => $this->_registerJsPrice($this->_convertPrice($regularPrice->getAmount()->getValue())), + 'amount' => $this->_registerJsPrice($regularPrice->getAmount()->getValue()), ], 'basePrice' => [ 'amount' => $this->_registerJsPrice( - $this->_convertPrice($finalPrice->getAmount()->getBaseAmount()) + $finalPrice->getAmount()->getBaseAmount() ), ], 'finalPrice' => [ - 'amount' => $this->_registerJsPrice($this->_convertPrice($finalPrice->getAmount()->getValue())), + 'amount' => $this->_registerJsPrice($finalPrice->getAmount()->getValue()), ], ], 'productId' => $currentProduct->getId(), @@ -223,17 +223,17 @@ protected function getOptionPrices() [ 'oldPrice' => [ 'amount' => $this->_registerJsPrice( - $this->_convertPrice($priceInfo->getPrice('regular_price')->getAmount()->getValue()) + $priceInfo->getPrice('regular_price')->getAmount()->getValue() ), ], 'basePrice' => [ 'amount' => $this->_registerJsPrice( - $this->_convertPrice($priceInfo->getPrice('final_price')->getAmount()->getBaseAmount()) + $priceInfo->getPrice('final_price')->getAmount()->getBaseAmount() ), ], 'finalPrice' => [ 'amount' => $this->_registerJsPrice( - $this->_convertPrice($priceInfo->getPrice('final_price')->getAmount()->getValue()) + $priceInfo->getPrice('final_price')->getAmount()->getValue() ), ] ]; @@ -251,25 +251,4 @@ protected function _registerJsPrice($price) { return str_replace(',', '.', $price); } - - /** - * Convert price from default currency to current currency - * - * @param float $price - * @param bool $round - * @return float - */ - protected function _convertPrice($price, $round = false) - { - if (empty($price)) { - return 0; - } - - $price = $this->priceCurrency->convert($price); - if ($round) { - $price = $this->priceCurrency->round($price); - } - - return $price; - } } diff --git a/app/code/Magento/ConfigurableProduct/Helper/Data.php b/app/code/Magento/ConfigurableProduct/Helper/Data.php index 91dd722424219..8ab8a52a9ee65 100644 --- a/app/code/Magento/ConfigurableProduct/Helper/Data.php +++ b/app/code/Magento/ConfigurableProduct/Helper/Data.php @@ -40,7 +40,7 @@ public function getGalleryImages(\Magento\Catalog\Api\Data\ProductInterface $pro { $images = $product->getMediaGalleryImages(); if ($images instanceof \Magento\Framework\Data\Collection) { - foreach ($images as &$image) { + foreach ($images as $image) { /** @var $image \Magento\Catalog\Model\Product\Image */ $image->setData( 'small_image_url', diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php index bd74b867c8610..aeae1374a9813 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php @@ -7,8 +7,6 @@ */ namespace Magento\ConfigurableProduct\Model\Product\Type\Configurable; -use Magento\Framework\Pricing\PriceCurrencyInterface; - class Price extends \Magento\Catalog\Model\Product\Type\Price { /** @@ -24,17 +22,27 @@ public function getFinalPrice($qty, $product) return $product->getCalculatedFinalPrice(); } if ($product->getCustomOption('simple_product')) { - $simpleProduct = $product->getCustomOption('simple_product')->getProduct(); - $product->setSelectedConfigurableOption($simpleProduct); - $priceInfo = $simpleProduct->getPriceInfo(); + return parent::getFinalPrice($qty, $product->getCustomOption('simple_product')->getProduct()); } else { $priceInfo = $product->getPriceInfo(); + $finalPrice = $priceInfo->getPrice('final_price')->getAmount()->getValue(); + $finalPrice = $this->_applyOptionsPrice($product, $qty, $finalPrice); + $finalPrice = max(0, $finalPrice); + $product->setFinalPrice($finalPrice); + + return $finalPrice; } - $finalPrice = $priceInfo->getPrice('final_price')->getAmount()->getValue(); - $finalPrice = $this->_applyOptionsPrice($product, $qty, $finalPrice); - $finalPrice = max(0, $finalPrice); - $product->setFinalPrice($finalPrice); + } - return $finalPrice; + /** + * {@inheritdoc} + */ + public function getPrice($product) + { + if ($product->getCustomOption('simple_product')) { + return $product->getCustomOption('simple_product')->getProduct()->getPrice(); + } else { + return 0; + } } } diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php index 814508cd62959..84395f7f5b138 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php @@ -48,14 +48,46 @@ public function reindexEntity($entityIds) protected function reindex($entityIds = null) { if ($this->hasEntity() || !empty($entityIds)) { - $this->_prepareFinalPriceData($entityIds); + if (!empty($entityIds)) { + $allEntityIds = $this->getRelatedProducts($entityIds); + $this->prepareFinalPriceDataForType($allEntityIds, null); + } else { + $this->_prepareFinalPriceData($entityIds); + } $this->_applyCustomOption(); - $this->_applyConfigurableOption(); - $this->_movePriceDataToIndexTable(); + $this->_applyConfigurableOption($entityIds); + $this->_movePriceDataToIndexTable($entityIds); } return $this; } + /** + * Get related product + * + * @param int[] $entityIds + * @return int[] + */ + private function getRelatedProducts($entityIds) + { + $select = $this->getConnection()->select()->union( + [ + $this->getConnection()->select() + ->from($this->getTable('catalog_product_super_link'), 'parent_id') + ->where('parent_id in (?)', $entityIds), + $this->getConnection()->select() + ->from($this->getTable('catalog_product_super_link'), 'product_id') + ->where('parent_id in (?)', $entityIds), + $this->getConnection()->select() + ->from($this->getTable('catalog_product_super_link'), 'product_id') + ->where('product_id in (?)', $entityIds), + ] + ); + return array_map( + 'intval', + $this->getConnection()->fetchCol($select) + ); + } + /** * Retrieve table name for custom option temporary aggregation data * diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/BasePrice.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/BasePrice.php deleted file mode 100644 index 76f2ef5cdd900..0000000000000 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/BasePrice.php +++ /dev/null @@ -1,68 +0,0 @@ -product->getSelectedConfigurableOption(); - $productId = $selectedConfigurableOption ? $selectedConfigurableOption->getId() : $this->product->getId(); - if (!isset($this->values[$productId])) { - $this->value = null; - if (!$selectedConfigurableOption) { - $this->values[$productId] = parent::getValue(); - } else { - if (false !== $this->getMinimumAdditionalPrice()) { - $this->values[$productId] = $this->getMinimumAdditionalPrice(); - } else { - $this->values[$productId] = parent::getValue(); - } - } - } - return $this->values[$productId]; - } - - /** - * @return bool|float - */ - protected function getMinimumAdditionalPrice() - { - if (null === $this->minimumAdditionalPrice) { - $priceCodes = [ - \Magento\Catalog\Pricing\Price\SpecialPrice::PRICE_CODE, - \Magento\Catalog\Pricing\Price\TierPrice::PRICE_CODE, - ]; - $this->minimumAdditionalPrice = false; - foreach ($priceCodes as $priceCode) { - $price = $this->product->getPriceInfo()->getPrice($priceCode); - if ($price instanceof BasePriceProviderInterface && $price->getValue() !== false) { - $this->minimumAdditionalPrice = min( - $price->getValue(), - $this->minimumAdditionalPrice ?: $price->getValue() - ); - } - } - } - return $this->minimumAdditionalPrice; - } -} diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php index d0f2461c96a3e..a4fe3d76da22b 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php @@ -37,22 +37,22 @@ public function __construct( } /** - * @param \Magento\Framework\Pricing\SaleableInterface $product + * @param \Magento\Framework\Pricing\SaleableInterface|\Magento\Catalog\Model\Product $product * @return float + * @throws \Magento\Framework\Exception\LocalizedException */ public function resolvePrice(\Magento\Framework\Pricing\SaleableInterface $product) { - $selectedConfigurableOption = $product->getSelectedConfigurableOption(); - if ($selectedConfigurableOption) { - $price = $this->priceResolver->resolvePrice($selectedConfigurableOption); - } else { - $price = null; - foreach ($this->configurable->getUsedProducts($product) as $subProduct) { - $productPrice = $this->priceResolver->resolvePrice($subProduct); - $price = $price ? min($price, $productPrice) : $productPrice; - } + $price = null; + foreach ($this->configurable->getUsedProducts($product) as $subProduct) { + $productPrice = $this->priceResolver->resolvePrice($subProduct); + $price = $price ? min($price, $productPrice) : $productPrice; } - $priceInCurrentCurrency = $this->priceCurrency->convertAndRound($price); - return $priceInCurrentCurrency ? (float)$priceInCurrentCurrency : false; + if (!$price) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Configurable product "%1" do not have sub-products', $product->getName()) + ); + } + return (float)$price; } } diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php index de5ea0dcb0716..936698cef3567 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php @@ -61,20 +61,19 @@ public function __construct( */ public function getValue() { - $selectedConfigurableOption = $this->product->getSelectedConfigurableOption(); - $productId = $selectedConfigurableOption ? $selectedConfigurableOption->getId() : $this->product->getId(); - if (!isset($this->values[$productId])) { - $this->values[$productId] = $this->priceResolver->resolvePrice($this->product); + if (!isset($this->values[$this->product->getId()])) { + $this->values[$this->product->getId()] = $this->priceResolver->resolvePrice($this->product); } - return $this->values[$productId]; + return $this->values[$this->product->getId()]; } + /** * {@inheritdoc} */ public function getAmount() { - return $this->getMinRegularAmount($this->product); + return $this->getMinRegularAmount(); } /** @@ -116,7 +115,6 @@ public function getMinRegularAmount() $this->minRegularAmount = $this->doGetMinRegularAmount() ?: false; } return $this->minRegularAmount; - } /** diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/FinalPrice.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/FinalPrice.php index 3d7cfd251d54e..834df6ffd429a 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/FinalPrice.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/FinalPrice.php @@ -34,28 +34,15 @@ public function __construct( $this->priceResolver = $priceResolver; } - /** - * {@inheritdoc} - */ - public function getAmount() - { - if ($this->product->getSelectedConfigurableOption()) { - $this->amount = null; - } - return parent::getAmount(); - } - /** * {@inheritdoc} */ public function getValue() { - $selectedConfigurableOption = $this->product->getSelectedConfigurableOption(); - $productId = $selectedConfigurableOption ? $selectedConfigurableOption->getId() : $this->product->getId(); - if (!isset($this->values[$productId])) { - $this->values[$productId] = $this->priceResolver->resolvePrice($this->product); + if (!isset($this->values[$this->product->getId()])) { + $this->values[$this->product->getId()] = $this->priceResolver->resolvePrice($this->product); } - return $this->values[$productId]; + return $this->values[$this->product->getId()]; } } diff --git a/app/code/Magento/ConfigurableProduct/Setup/InstallData.php b/app/code/Magento/ConfigurableProduct/Setup/InstallData.php index 1d6705846db6e..8b29c11bc2b8f 100644 --- a/app/code/Magento/ConfigurableProduct/Setup/InstallData.php +++ b/app/code/Magento/ConfigurableProduct/Setup/InstallData.php @@ -47,7 +47,6 @@ public function install(ModuleDataSetupInterface $setup, ModuleContextInterface 'minimal_price', 'msrp', 'msrp_display_actual_price_type', - 'price', 'special_price', 'special_from_date', 'special_to_date', diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php index 7d14671ec0d7d..7757b43562442 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php @@ -31,11 +31,7 @@ public function testGetFinalPrice() $qty = 1; $configurableProduct = $this->getMockBuilder('Magento\Catalog\Model\Product') ->disableOriginalConstructor() - ->setMethods(['getCustomOption', 'setSelectedConfigurableOption', 'setFinalPrice', '__wakeUp']) - ->getMock(); - $childProduct = $this->getMockBuilder('Magento\Catalog\Model\Product') - ->disableOriginalConstructor() - ->setMethods(['getPriceInfo', '__wakeUp']) + ->setMethods(['getCustomOption', 'getPriceInfo', 'setFinalPrice', '__wakeUp']) ->getMock(); $customOption = $this->getMockBuilder('Magento\Catalog\Model\Product\Configuration\Item\Option') ->disableOriginalConstructor() @@ -52,24 +48,14 @@ public function testGetFinalPrice() ->disableOriginalConstructor() ->getMock(); - $configurableProduct->expects($this->at(0)) - ->method('getCustomOption') - ->with('simple_product') - ->willReturn($customOption); - $configurableProduct->expects($this->at(1)) + $configurableProduct->expects($this->any()) ->method('getCustomOption') - ->with('simple_product') - ->willReturn($customOption); - $customOption->expects($this->once())->method('getProduct')->willReturn($childProduct); - $configurableProduct->expects($this->once()) - ->method('setSelectedConfigurableOption') - ->with($childProduct) - ->willReturnSelf(); - $childProduct->expects($this->once())->method('getPriceInfo')->willReturn($priceInfo); + ->willReturnMap([['simple_product', false], ['option_ids', false]]); + $customOption->expects($this->never())->method('getProduct'); + $configurableProduct->expects($this->once())->method('getPriceInfo')->willReturn($priceInfo); $priceInfo->expects($this->once())->method('getPrice')->with('final_price')->willReturn($price); $price->expects($this->once())->method('getAmount')->willReturn($amount); $amount->expects($this->once())->method('getValue')->willReturn($finalPrice); - $configurableProduct->expects($this->at(3))->method('getCustomOption')->with('option_ids')->willReturn(false); $configurableProduct->expects($this->once())->method('setFinalPrice')->with($finalPrice)->willReturnSelf(); $this->assertEquals($finalPrice, $this->model->getFinalPrice($qty, $configurableProduct)); diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index ff1173c2e0908..e2463e52daa81 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -70,7 +70,6 @@ Magento\ConfigurableProduct\Pricing\Price\ConfigurableRegularPrice Magento\ConfigurableProduct\Pricing\Price\FinalPrice - Magento\ConfigurableProduct\Pricing\Price\BasePrice Magento\Catalog\Pricing\Price\Pool diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/bulk.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/bulk.js index 499962f8e59f8..c01f1a8c874f2 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/bulk.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/bulk.js @@ -14,7 +14,8 @@ define([ 'mage/template', 'Magento_Ui/js/modal/alert', 'jquery/file-uploader', - 'mage/translate' + 'mage/translate', + 'Magento_ConfigurableProduct/js/variations/variations' ], function (Component, $, ko, _, Collapsible, mageTemplate, alert) { 'use strict'; @@ -55,7 +56,7 @@ define([ type: ko.observable('none'), value: ko.observable(), attribute: ko.observable(), - currencySymbol: this.variationsComponent().getCurrencySymbol() + currencySymbol: '' }, quantity: { label: 'quantity', @@ -64,6 +65,11 @@ define([ attribute: ko.observable() } }); + + this.variationsComponent(function (variationsComponent) { + this.sections().price.currencySymbol = variationsComponent.getCurrencySymbol() + }.bind(this)); + this.makeOptionSections = function () { this.images = new self.makeImages(null); this.price = self.price; diff --git a/app/code/Magento/Deploy/Model/Deployer.php b/app/code/Magento/Deploy/Model/Deployer.php index 60480c220e23c..c63bed567703b 100644 --- a/app/code/Magento/Deploy/Model/Deployer.php +++ b/app/code/Magento/Deploy/Model/Deployer.php @@ -312,12 +312,11 @@ private function deployFile($filePath, $area, $themePath, $locale, $module) } } - $logMessage = "Processing file '$filePath' for area '$area', theme '$themePath', locale '$locale'"; - if ($module) { - $logMessage .= ", module '$module'"; - } - if ($this->output->isVeryVerbose()) { + $logMessage = "Processing file '$filePath' for area '$area', theme '$themePath', locale '$locale'"; + if ($module) { + $logMessage .= ", module '$module'"; + } $this->output->writeln($logMessage); } diff --git a/app/code/Magento/Developer/i18n/en_US.csv b/app/code/Magento/Developer/i18n/en_US.csv index e69de29bb2d1d..1cf1f79536095 100644 --- a/app/code/Magento/Developer/i18n/en_US.csv +++ b/app/code/Magento/Developer/i18n/en_US.csv @@ -0,0 +1,40 @@ +"Front-end development workflow","Front-end development workflow" +"Workflow type","Workflow type" +"Not available in production mode","Not available in production mode" +"Developer Client Restrictions","Developer Client Restrictions" +"Allowed IPs (comma separated)","Allowed IPs (comma separated)" +"Leave empty for access from any location.","Leave empty for access from any location." +"Debug","Debug" +"Enabled Template Path Hints for Storefront","Enabled Template Path Hints for Storefront" +"Enabled Template Path Hints for Admin","Enabled Template Path Hints for Admin" +"Add Block Names to Hints","Add Block Names to Hints" +"Template Settings","Template Settings" +"Allow Symlinks","Allow Symlinks" +"Warning! Enabling this feature is not recommended on production environments because it represents a potential security risk.","Warning! Enabling this feature is not recommended on production environments because it represents a potential security risk." +"Minify Html","Minify Html" +"Translate Inline","Translate Inline" +"Enabled for Storefront","Enabled for Storefront" +"Enabled for Admin","Enabled for Admin" +"Translate, blocks and other output caches should be disabled for both Storefront and Admin inline translations.","Translate, blocks and other output caches should be disabled for both Storefront and Admin inline translations." +"JavaScript Settings","JavaScript Settings" +"Enable Javascript Bundling","Enable Javascript Bundling" +"Merge JavaScript Files","Merge JavaScript Files" +"Minify JavaScript Files","Minify JavaScript Files" +"Translation Strategy","Translation Strategy" +"Dictionary (Translation on Storefront side)","Dictionary (Translation on Storefront side)" +"Embedded (Translation on Admin side)","Embedded (Translation on Admin side)" +"Log JS Errors to Session Storage","Log JS Errors to Session Storage" +"Log JS Errors to Session Storage Key","Log JS Errors to Session Storage Key" +"CSS Settings","CSS Settings" +"Merge CSS Files","Merge CSS Files" +"Minify CSS Files","Minify CSS Files" +"Image Processing Settings","Image Processing Settings" +"Image Adapter","Image Adapter" +"Static Files Settings","Static Files Settings" +"Sign Static Files","Sign Static Files" +"Grid Settings","Grid Settings" +"Asynchronous indexing","Asynchronous indexing" +"Client side less compilation","Client side less compilation" +"Server side less compilation","Server side less compilation" +"Disable","Disable" +"Enable","Enable" diff --git a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php index fb92f2bc3fffd..ee184dff0e6d5 100644 --- a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php +++ b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php @@ -157,27 +157,13 @@ public function getPurchasedSeparatelyAttribute() } /** - * Retrieve Purchased Separately HTML select + * Get Links can be purchased separately value for current product * - * @return string + * @return bool */ - public function getPurchasedSeparatelySelect() + public function isProductLinksCanBePurchasedSeparately() { - $select = $this->getLayout()->createBlock( - 'Magento\Framework\View\Element\Html\Select' - )->setName( - 'product[links_purchased_separately]' - )->setId( - 'downloadable_link_purchase_type' - )->setOptions( - $this->_sourceModel->toOptionArray() - )->setValue( - $this->getProduct()->getLinksPurchasedSeparately() - )->setClass( - 'admin__control-select' - ); - - return $select->getHtml(); + return (bool) $this->getProduct()->getData('links_purchased_separately'); } /** diff --git a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable.phtml b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable.phtml index dac2edad7be49..dd2cf918d704e 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable.phtml +++ b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable.phtml @@ -25,17 +25,6 @@ require([ // var uploaderTemplate = '
' + - '
' + - '' + - '<%= data.name %>' + - ' ' + - '(<%- data.size %>)' + - '' + - '
' + - '
' + - '
' + - '
' + - '
' + '
' + '
' + '<%- data.percent %>% <%- data.uploaded %> / <%- data.total %>' + diff --git a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/links.phtml b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/links.phtml index cf43aa7d3f4f6..909ab56b20049 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/links.phtml +++ b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/links.phtml @@ -34,7 +34,34 @@
isSingleStoreMode() ? ' data-config-scope="' . __('[GLOBAL]') . '"' : ''; ?>>
- getPurchasedSeparatelySelect()?> +
@@ -357,9 +384,9 @@ require([ } }, togglePriceFields : function(){ - var toogleTo = $('downloadable_link_purchase_type').value; + var toogleTo = jQuery('#link-switcher1').is(':checked'); var disableFlag = true; - if (toogleTo == '1') { + if (toogleTo) { disableFlag = false; } $$('.link-prices[type="text"]').each(function(elm){ @@ -442,9 +469,10 @@ require([ })(jQuery); }; - - if ($('downloadable_link_purchase_type')) { - Event.observe('downloadable_link_purchase_type', 'change', linkItems.togglePriceFields.bind()); + if (jQuery('input[name="product[links_purchased_separately]"]')) { + jQuery('input[name="product[links_purchased_separately]"]').on('change', function () { + linkItems.togglePriceFields.bind() + }); } if($('add_link_item')) { diff --git a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php index c52d87b51b419..f26e634b865aa 100644 --- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php +++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php @@ -1513,7 +1513,7 @@ protected function _saveAttribute($object, $attribute, $value) 'value' => $this->_prepareValueForSave($value, $attribute), ]; - if (!$this->getEntityTable()) { + if (!$this->getEntityTable() || $this->getEntityTable() == \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE) { $data['entity_type_id'] = $object->getEntityTypeId(); } diff --git a/app/code/Magento/Eav/Model/Entity/Attribute.php b/app/code/Magento/Eav/Model/Entity/Attribute.php index e26146bc9aa23..2c8cc458caa90 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute.php @@ -242,7 +242,7 @@ public function beforeSave() ) ) { throw new LocalizedException( - __('An attribute code must be fewer than %1 characters.', self::ATTRIBUTE_CODE_MAX_LENGTH) + __('An attribute code must not be more than %1 characters.', self::ATTRIBUTE_CODE_MAX_LENGTH) ); } diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php b/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php index 65644c94f2605..943cf2d3530de 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php @@ -70,7 +70,7 @@ public function getInputType() } /** - * Retrieve lable + * Retrieve label * * @return string */ @@ -84,6 +84,16 @@ public function getLabel() return $label; } + /** + * Retrieve localized label + * + * @return \Magento\Framework\Phrase + */ + public function getLocalizedLabel() + { + return __($this->getLabel()); + } + /** * Retrieve attribute value * diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Group.php b/app/code/Magento/Eav/Model/Entity/Attribute/Group.php index b8c123718f89f..b0603a54ebc93 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Group.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Group.php @@ -6,6 +6,8 @@ namespace Magento\Eav\Model\Entity\Attribute; +use Magento\Framework\Api\AttributeValueFactory; + /** * @author Magento Core Team * @@ -23,6 +25,43 @@ class Group extends \Magento\Framework\Model\AbstractExtensibleModel implements \Magento\Eav\Api\Data\AttributeGroupInterface { + /** + * @var \Magento\Framework\Filter\Translit + */ + private $translitFilter; + + /** + * @param \Magento\Framework\Model\Context $context + * @param \Magento\Framework\Registry $registry + * @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory + * @param AttributeValueFactory $customAttributeFactory + * @param \Magento\Framework\Filter\Translit $translitFilter + * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource + * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection + * @param array $data + */ + public function __construct( + \Magento\Framework\Model\Context $context, + \Magento\Framework\Registry $registry, + \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory, + AttributeValueFactory $customAttributeFactory, + \Magento\Framework\Filter\Translit $translitFilter, + \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, + \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, + array $data = [] + ) { + parent::__construct( + $context, + $registry, + $extensionFactory, + $customAttributeFactory, + $resource, + $resourceCollection, + $data + ); + $this->translitFilter = $translitFilter; + } + /** * Resource initialization * @@ -66,7 +105,14 @@ public function beforeSave() if (!$this->getAttributeGroupCode()) { $groupName = $this->getAttributeGroupName(); if ($groupName) { - $attributeGroupCode = trim(preg_replace('/[^a-z0-9]+/', '-', strtolower($groupName)), '-'); + $attributeGroupCode = trim( + preg_replace( + '/[^a-z0-9]+/', + '-', + $this->translitFilter->filter(strtolower($groupName)) + ), + '-' + ); if (empty($attributeGroupCode)) { // in the following code md5 is not used for security purposes $attributeGroupCode = md5($groupName); diff --git a/app/code/Magento/Eav/Setup/EavSetup.php b/app/code/Magento/Eav/Setup/EavSetup.php index a1dba20110f86..d1d0452fe7b77 100644 --- a/app/code/Magento/Eav/Setup/EavSetup.php +++ b/app/code/Magento/Eav/Setup/EavSetup.php @@ -527,6 +527,14 @@ public function addAttributeGroup($entityTypeId, $setId, $name, $sortOrder = nul if ($sortOrder === null) { $data['sort_order'] = $this->getAttributeGroupSortOrder($entityTypeId, $setId, $sortOrder); } + if (empty($data['attribute_group_code'])) { + $attributeGroupCode = trim(preg_replace('/[^a-z0-9]+/', '-', strtolower($name)), '-'); + if (empty($attributeGroupCode)) { + // in the following code md5 is not used for security purposes + $attributeGroupCode = md5($name); + } + $data['attribute_group_code'] = $attributeGroupCode; + } $this->setup->getConnection()->insert($this->setup->getTable('eav_attribute_group'), $data); } @@ -728,7 +736,7 @@ private function _validateAttributeData($data) ) ) { throw new LocalizedException( - __('An attribute code must be fewer than %1 characters.', $attributeCodeMaxLength) + __('An attribute code must not be more than %1 characters.', $attributeCodeMaxLength) ); } diff --git a/app/code/Magento/Eav/Setup/InstallSchema.php b/app/code/Magento/Eav/Setup/InstallSchema.php index 24c041c7e2c05..75ae06b1a3076 100644 --- a/app/code/Magento/Eav/Setup/InstallSchema.php +++ b/app/code/Magento/Eav/Setup/InstallSchema.php @@ -895,7 +895,7 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con 'attribute_group_code', \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 255, - ['default' => null], + ['nullable' => false], 'Attribute Group Code' )->addColumn( 'tab_group_code', diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DatetimeTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DatetimeTest.php index f28fe8386eee3..6c1263ef01ece 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DatetimeTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DatetimeTest.php @@ -38,17 +38,14 @@ protected function setUp() '', false ); - $this->localeDateMock = $this->getMock('\Magento\Framework\Stdlib\DateTime\TimezoneInterface'); - $this->attributeMock = $this->getMock( '\Magento\Eav\Model\Entity\Attribute\AbstractAttribute', - [], + ['getAttributeCode', 'getFrontendLabel'], [], '', false ); - $this->attributeMock->expects($this->any())->method('getAttributeCode')->will($this->returnValue('datetime')); $this->model = new Datetime($this->booleanFactoryMock, $this->localeDateMock); $this->model->setAttribute($this->attributeMock); @@ -59,13 +56,50 @@ public function testGetValue() $attributeValue = '11-11-2011'; $date = new \DateTime($attributeValue); $object = new \Magento\Framework\DataObject(['datetime' => $attributeValue]); - $this->attributeMock->expects($this->any())->method('getData')->with('frontend_input') - ->will($this->returnValue('text')); - $this->localeDateMock->expects($this->once())->method('formatDateTime') + $this->attributeMock->expects($this->any()) + ->method('getAttributeCode') + ->willReturn('datetime'); + $this->attributeMock->expects($this->any()) + ->method('getData') + ->with('frontend_input') + ->willReturn('text'); + $this->localeDateMock->expects($this->once()) + ->method('formatDateTime') ->with($date, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::NONE, null, null, null) ->willReturn($attributeValue); $this->assertEquals($attributeValue, $this->model->getValue($object)); } + + /** + * @param mixed $labelText + * @param string $attributeCode + * @param string $expectedResult + * @dataProvider getLabelDataProvider + */ + public function testGetLocalizedLabel($labelText, $attributeCode, $expectedResult) + { + $this->attributeMock->expects($this->exactly(2)) + ->method('getFrontendLabel') + ->willReturn($labelText); + $this->attributeMock->expects($this->any()) + ->method('getAttributeCode') + ->willReturn($attributeCode); + + $this->assertInstanceOf('\Magento\Framework\Phrase', $this->model->getLocalizedLabel()); + $this->assertSame($expectedResult, (string)$this->model->getLocalizedLabel()); + } + + /** + * @return array + */ + public function getLabelDataProvider() + { + return [ + [null, 'test code', 'test code'], + ['', 'test code', 'test code'], + ['test label', 'test code', 'test label'], + ]; + } } diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php index a9a9eff9b9ec2..cde8db9b42470 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php @@ -34,11 +34,17 @@ protected function setUp() '', false ); + $translitFilter = $this->getMockBuilder(\Magento\Framework\Filter\Translit::class) + ->disableOriginalConstructor() + ->getMock(); + $translitFilter->expects($this->atLeastOnce())->method('filter')->willReturnArgument(0); + $this->eventManagerMock = $this->getMock('Magento\Framework\Event\ManagerInterface'); $contextMock = $this->getMock('Magento\Framework\Model\Context', [], [], '', false); $contextMock->expects($this->any())->method('getEventDispatcher')->willReturn($this->eventManagerMock); $constructorArguments = [ 'resource' => $this->resourceMock, + 'translitFilter' => $translitFilter, 'context' => $contextMock, ]; $objectManager = new ObjectManager($this); diff --git a/app/code/Magento/Email/Block/Adminhtml/Template/Preview.php b/app/code/Magento/Email/Block/Adminhtml/Template/Preview.php index eb34ed100fd5c..d215d37d7df3a 100644 --- a/app/code/Magento/Email/Block/Adminhtml/Template/Preview.php +++ b/app/code/Magento/Email/Block/Adminhtml/Template/Preview.php @@ -23,6 +23,11 @@ class Preview extends \Magento\Backend\Block\Widget */ protected $_emailFactory; + /** + * @var string + */ + protected $profilerName = 'email_template_proccessing'; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Framework\Filter\Input\MaliciousCode $maliciousCode @@ -48,16 +53,10 @@ public function __construct( protected function _toHtml() { $storeId = $this->getAnyStoreView()->getId(); - /** @var $template \Magento\Email\Model\Template */ - $template = $this->_emailFactory->create( - ['data' => [ - 'area' => \Magento\Framework\App\Area::AREA_FRONTEND, - 'store' => $storeId - ]] - ); - $id = (int) $this->getRequest()->getParam('id'); - if ($id) { + $template = $this->_emailFactory->create(); + + if ($id = (int)$this->getRequest()->getParam('id')) { $template->load($id); } else { $template->setTemplateType($this->getRequest()->getParam('type')); @@ -67,7 +66,7 @@ protected function _toHtml() $template->setTemplateText($this->_maliciousCode->filter($template->getTemplateText())); - \Magento\Framework\Profiler::start("email_template_proccessing"); + \Magento\Framework\Profiler::start($this->profilerName); $template->emulateDesign($storeId); $templateProcessed = $this->_appState->emulateAreaCode( @@ -80,7 +79,7 @@ protected function _toHtml() $templateProcessed = "
" . htmlspecialchars($templateProcessed) . "
"; } - \Magento\Framework\Profiler::stop("email_template_proccessing"); + \Magento\Framework\Profiler::stop($this->profilerName); return $templateProcessed; } diff --git a/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Preview.php b/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Preview.php index 9ce1e0e471297..81558a7805c86 100644 --- a/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Preview.php +++ b/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Preview.php @@ -16,7 +16,8 @@ class Preview extends \Magento\Email\Controller\Adminhtml\Email\Template public function executeInternal() { try { - $this->_view->loadLayout('systemPreview'); + $this->_view->loadLayout(); + $this->_view->getPage()->getConfig()->getTitle()->prepend(__('Email Preview')); $this->_view->renderLayout(); } catch (\Exception $e) { $this->messageManager->addError(__('An error occurred. The email template can not be opened for preview.')); diff --git a/app/code/Magento/Email/Test/Unit/Block/Adminhtml/Template/PreviewTest.php b/app/code/Magento/Email/Test/Unit/Block/Adminhtml/Template/PreviewTest.php index 599c1b81010d4..3a7d91dfefb72 100644 --- a/app/code/Magento/Email/Test/Unit/Block/Adminhtml/Template/PreviewTest.php +++ b/app/code/Magento/Email/Test/Unit/Block/Adminhtml/Template/PreviewTest.php @@ -46,42 +46,37 @@ public function testToHtml($requestParamMap) 'revertDesign' ]) ->disableOriginalConstructor() - ->getMock() - ; + ->getMock(); $template->expects($this->once()) ->method('getProcessedTemplate') ->with($this->equalTo([])) - ->will($this->returnValue(self::MALICIOUS_TEXT)); - $designConfigData = [ - 'area' => \Magento\Framework\App\Area::AREA_FRONTEND, - 'store' => $storeId - ]; + ->willReturn(self::MALICIOUS_TEXT); + $designConfigData = []; $template->expects($this->atLeastOnce()) ->method('getDesignConfig') - ->will($this->returnValue(new \Magento\Framework\DataObject( + ->willReturn(new \Magento\Framework\DataObject( $designConfigData - ))); + )); $emailFactory = $this->getMock('Magento\Email\Model\TemplateFactory', ['create'], [], '', false); - $emailFactory->expects($this->once()) + $emailFactory->expects($this->any()) ->method('create') - ->with($this->equalTo(['data' => $designConfigData])) - ->will($this->returnValue($template)); + ->willReturn($template); $request = $this->getMock('Magento\Framework\App\RequestInterface'); - $request->expects($this->any())->method('getParam')->will($this->returnValueMap($requestParamMap)); + $request->expects($this->any())->method('getParam')->willReturnMap($requestParamMap); $eventManage = $this->getMock('Magento\Framework\Event\ManagerInterface'); $scopeConfig = $this->getMock('Magento\Framework\App\Config\ScopeConfigInterface'); $design = $this->getMock('Magento\Framework\View\DesignInterface'); $store = $this->getMock('Magento\Store\Model\Store', ['getId', '__wakeup'], [], '', false); - $store->expects($this->any())->method('getId')->will($this->returnValue($storeId)); + $store->expects($this->any())->method('getId')->willReturn($storeId); $storeManager = $this->getMockBuilder('\Magento\Store\Model\StoreManagerInterface') ->disableOriginalConstructor() ->getMock(); $storeManager->expects($this->atLeastOnce()) ->method('getDefaultStoreView') - ->will($this->returnValue($store)); - $storeManager->expects($this->any())->method('getDefaultStoreView')->will($this->returnValue(null)); - $storeManager->expects($this->any())->method('getStores')->will($this->returnValue([$store])); + ->willReturn($store); + $storeManager->expects($this->any())->method('getDefaultStoreView')->willReturn(null); + $storeManager->expects($this->any())->method('getStores')->willReturn([$store]); $appState = $this->getMockBuilder('Magento\Framework\App\State') ->setConstructorArgs([ $scopeConfig @@ -94,12 +89,12 @@ public function testToHtml($requestParamMap) ['getRequest', 'getEventManager', 'getScopeConfig', 'getDesignPackage', 'getStoreManager', 'getAppState'], [], '', false ); - $context->expects($this->any())->method('getRequest')->will($this->returnValue($request)); - $context->expects($this->any())->method('getEventManager')->will($this->returnValue($eventManage)); - $context->expects($this->any())->method('getScopeConfig')->will($this->returnValue($scopeConfig)); - $context->expects($this->any())->method('getDesignPackage')->will($this->returnValue($design)); - $context->expects($this->any())->method('getStoreManager')->will($this->returnValue($storeManager)); - $context->expects($this->once())->method('getAppState')->will($this->returnValue($appState)); + $context->expects($this->any())->method('getRequest')->willReturn($request); + $context->expects($this->any())->method('getEventManager')->willReturn($eventManage); + $context->expects($this->any())->method('getScopeConfig')->willReturn($scopeConfig); + $context->expects($this->any())->method('getDesignPackage')->willReturn($design); + $context->expects($this->any())->method('getStoreManager')->willReturn($storeManager); + $context->expects($this->once())->method('getAppState')->willReturn($appState); $maliciousCode = $this->getMock( 'Magento\Framework\Filter\Input\MaliciousCode', @@ -108,9 +103,12 @@ public function testToHtml($requestParamMap) '', false ); - $maliciousCode->expects($this->once())->method('filter')->with($this->equalTo($requestParamMap[1][2])) - ->will($this->returnValue(self::MALICIOUS_TEXT)); + $maliciousCode->expects($this->once()) + ->method('filter') + ->with($this->equalTo($requestParamMap[1][2])) + ->willReturn(self::MALICIOUS_TEXT); + /** @var \Magento\Email\Block\Adminhtml\Template\Preview $preview */ $preview = $this->objectManagerHelper->getObject( 'Magento\Email\Block\Adminhtml\Template\Preview', [ diff --git a/app/code/Magento/Email/Test/Unit/Controller/Adminhtml/Email/Template/PreviewTest.php b/app/code/Magento/Email/Test/Unit/Controller/Adminhtml/Email/Template/PreviewTest.php new file mode 100644 index 0000000000000..59fccebfe3858 --- /dev/null +++ b/app/code/Magento/Email/Test/Unit/Controller/Adminhtml/Email/Template/PreviewTest.php @@ -0,0 +1,115 @@ +coreRegistryMock = $this->getMockBuilder('Magento\Framework\Registry') + ->disableOriginalConstructor() + ->getMock(); + $this->viewMock = $this->getMockBuilder('Magento\Framework\App\View') + ->disableOriginalConstructor() + ->getMock(); + $this->requestMock = $this->getMockBuilder('Magento\Framework\App\RequestInterface') + ->getMock(); + $this->pageMock = $this->getMockBuilder('Magento\Framework\View\Result\Page') + ->disableOriginalConstructor() + ->setMethods(['getConfig']) + ->getMock(); + $this->pageConfigMock = $this->getMockBuilder('Magento\Framework\View\Page\Config') + ->setMethods(['getTitle']) + ->disableOriginalConstructor() + ->getMock(); + $this->pageTitleMock = $this->getMockBuilder('Magento\Framework\View\Page\Title') + ->setMethods(['prepend']) + ->disableOriginalConstructor() + ->getMock(); + + $this->context = $objectManager->getObject('Magento\Backend\App\Action\Context', [ + 'request' => $this->requestMock, + 'view' => $this->viewMock + ]); + $this->object = $objectManager->getObject('Magento\Email\Controller\Adminhtml\Email\Template\Preview', [ + 'context' => $this->context, + 'coreRegistry' => $this->coreRegistryMock, + ]); + } + + public function testExecute() + { + $this->viewMock->expects($this->once()) + ->method('getPage') + ->willReturn($this->pageMock); + $this->pageMock->expects($this->once()) + ->method('getConfig') + ->willReturn($this->pageConfigMock); + $this->pageConfigMock->expects($this->once()) + ->method('getTitle') + ->willReturn($this->pageTitleMock); + $this->pageTitleMock->expects($this->once()) + ->method('prepend') + ->willReturnSelf(); + + $this->assertNull($this->object->execute()); + } +} diff --git a/app/code/Magento/Email/view/adminhtml/layout/adminhtml_email_template_preview.xml b/app/code/Magento/Email/view/adminhtml/layout/adminhtml_email_template_preview.xml new file mode 100644 index 0000000000000..242cda682c53f --- /dev/null +++ b/app/code/Magento/Email/view/adminhtml/layout/adminhtml_email_template_preview.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/app/code/Magento/Email/view/adminhtml/layout/systemPreview.xml b/app/code/Magento/Email/view/adminhtml/layout/systemPreview.xml deleted file mode 100644 index f58984cc6ed62..0000000000000 --- a/app/code/Magento/Email/view/adminhtml/layout/systemPreview.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/app/code/Magento/Email/view/adminhtml/templates/template/preview.phtml b/app/code/Magento/Email/view/adminhtml/templates/template/preview.phtml index d54075edcb69b..c90ad26855528 100644 --- a/app/code/Magento/Email/view/adminhtml/templates/template/preview.phtml +++ b/app/code/Magento/Email/view/adminhtml/templates/template/preview.phtml @@ -3,6 +3,8 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ + +/* @var $block \Magento\Email\Block\Adminhtml\Template\Preview */ ?> diff --git a/app/code/Magento/Marketplace/i18n/en_US.csv b/app/code/Magento/Marketplace/i18n/en_US.csv index e0a266e1291f3..8c1698ba3b1ab 100644 --- a/app/code/Magento/Marketplace/i18n/en_US.csv +++ b/app/code/Magento/Marketplace/i18n/en_US.csv @@ -1 +1,13 @@ +"Platinum Partners","Platinum Partners" +"Representing Magento's highest level of partner engagement, Magento Platinum Partners have established themselves as leaders and innovators of key products and services designed to help merchants and brands grow their business. Magento reserves the Platinum level for select trusted partners that are committed to offering integrations of commerce features, functions, and tools, as well as back-end systems and operations, to extend and enhance the power of the Magento commerce platform.","Representing Magento's highest level of partner engagement, Magento Platinum Partners have established themselves as leaders and innovators of key products and services designed to help merchants and brands grow their business. Magento reserves the Platinum level for select trusted partners that are committed to offering integrations of commerce features, functions, and tools, as well as back-end systems and operations, to extend and enhance the power of the Magento commerce platform." +"Featured Platinum Partners","Featured Platinum Partners" +"Partner search","Partner search" +"Magento has a thriving ecosystem of technology partners to help merchants and brands deliver the best possible customer experiences. They are recognized as experts in eCommerce, search, email marketing, payments, tax, fraud, optimization and analytics, fulfillment, and more. Visit the Magento Partner Directory to see all of our trusted partners.","Magento has a thriving ecosystem of technology partners to help merchants and brands deliver the best possible customer experiences. They are recognized as experts in eCommerce, search, email marketing, payments, tax, fraud, optimization and analytics, fulfillment, and more. Visit the Magento Partner Directory to see all of our trusted partners." +"More Partners","More Partners" +"Magento Marketplace","Magento Marketplace" +"Extensions and Themes are an essential component of the Magento Ecosystem. Please visit the Magento Marketplace to see the latest innovations that developers have created to enhance your Magento Store.","Extensions and Themes are an essential component of the Magento Ecosystem. Please visit the Magento Marketplace to see the latest innovations that developers have created to enhance your Magento Store." +"Visit Magento Marketplaces","Visit Magento Marketplaces" +"Read More","Read More" +"Partner Page","Partner Page" +"No partners were found","No partners were found" "Find Partners & Extensions","Find Partners & Extensions" diff --git a/app/code/Magento/Marketplace/view/adminhtml/templates/index.phtml b/app/code/Magento/Marketplace/view/adminhtml/templates/index.phtml index 53c60cf1d7a00..4a2301057c6e5 100644 --- a/app/code/Magento/Marketplace/view/adminhtml/templates/index.phtml +++ b/app/code/Magento/Marketplace/view/adminhtml/templates/index.phtml @@ -3,21 +3,20 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ + +// @codingStandardsIgnoreFile ?>
-

Platinum Partners

+

- Representing Magento's highest level of partner engagement, - Magento Platinum Partners have established themselves as - leaders and innovators of key products and services designed - to help merchants and brands grow their business. Magento - reserves the Platinum level for select trusted partners that - are committed to offering integrations of commerce features, - functions, and tools, as well as back-end systems and operations, - to extend and enhance the power of the Magento commerce platform. +

-

Featured Platinum Partners

+

@@ -30,19 +29,18 @@
-

Magento Marketplace

+

- Extensions and Themes are an essential component of the Magento - Ecosystem. Please visit the Magento Marketplace - to see the latest innovations that developers - have created to enhance your Magento Store. +

- Visit Magento Marketplaces +
diff --git a/app/code/Magento/Marketplace/view/adminhtml/templates/partners.phtml b/app/code/Magento/Marketplace/view/adminhtml/templates/partners.phtml index ae31e3005513a..7787b9d33a47d 100644 --- a/app/code/Magento/Marketplace/view/adminhtml/templates/partners.phtml +++ b/app/code/Magento/Marketplace/view/adminhtml/templates/partners.phtml @@ -23,11 +23,11 @@ $partners = $block->getPartners(); escapeHtml($partner['description']);?>
- Read More +
- Partner Page +

@@ -35,6 +35,6 @@ $partners = $block->getPartners();

- No partners were found +

diff --git a/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout.xml b/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout.xml index 097574d310747..46ec2dcccadd7 100644 --- a/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout.xml +++ b/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout.xml @@ -7,6 +7,24 @@ --> + + + + + + + + Magento_Captcha/js/view/checkout/loginCaptcha + additional-login-form-fields + user_login + authenticationPopup + + + + + + + diff --git a/app/code/Magento/Payment/view/frontend/web/transparent.js b/app/code/Magento/Payment/view/frontend/web/transparent.js index 66b3203d7bc51..bbbd6ce1b3aa2 100644 --- a/app/code/Magento/Payment/view/frontend/web/transparent.js +++ b/app/code/Magento/Payment/view/frontend/web/transparent.js @@ -46,6 +46,11 @@ define([ .off('click') .on('click', $.proxy(this._placeOrderHandler, this)); } + + this.element.validation(); + $('[data-container="' + this.options.gateway + '-cc-number"]').on('focusout', function () { + $(this).valid(); + }); }, /** diff --git a/app/code/Magento/ProductVideo/Block/Adminhtml/Product/Edit/NewVideo.php b/app/code/Magento/ProductVideo/Block/Adminhtml/Product/Edit/NewVideo.php index fbc2132db4999..473e44373b7ed 100644 --- a/app/code/Magento/ProductVideo/Block/Adminhtml/Product/Edit/NewVideo.php +++ b/app/code/Magento/ProductVideo/Block/Adminhtml/Product/Edit/NewVideo.php @@ -12,11 +12,21 @@ */ class NewVideo extends \Magento\Backend\Block\Widget\Form\Generic { + /** + * Anchor is product video + */ + const PATH_ANCHOR_PRODUCT_VIDEO = 'catalog_product_video-link'; + /** * @var \Magento\ProductVideo\Helper\Media */ protected $mediaHelper; + /** + * @var \Magento\Framework\UrlInterface + */ + protected $urlBuilder; + /** * @var \Magento\Framework\Json\EncoderInterface */ @@ -24,22 +34,23 @@ class NewVideo extends \Magento\Backend\Block\Widget\Form\Generic /** * @param \Magento\Backend\Block\Template\Context $context - * @param \Magento\ProductVideo\Helper\Media $mediaHelper - * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder * @param \Magento\Framework\Registry $registry * @param \Magento\Framework\Data\FormFactory $formFactory + * @param \Magento\ProductVideo\Helper\Media $mediaHelper + * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder * @param array $data */ public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\Framework\Registry $registry, \Magento\Framework\Data\FormFactory $formFactory, - \Magento\Framework\Json\EncoderInterface $jsonEncoder, \Magento\ProductVideo\Helper\Media $mediaHelper, + \Magento\Framework\Json\EncoderInterface $jsonEncoder, array $data = [] ) { parent::__construct($context, $registry, $formFactory, $data); $this->mediaHelper = $mediaHelper; + $this->urlBuilder = $context->getUrlBuilder(); $this->jsonEncoder = $jsonEncoder; $this->setUseContainer(true); } @@ -104,7 +115,8 @@ protected function _prepareForm() 'title' => __('Url'), 'required' => true, 'name' => 'video_url', - 'note' => 'Youtube or Vimeo supported', + 'note' => $this->getNoteVideoUrl(), + ] ); @@ -158,7 +170,7 @@ protected function _prepareForm() 'label' => '', 'title' => __('Get Video Information'), 'name' => 'new_video_get', - 'value' => 'Get Video Information', + 'value' => __('Get Video Information'), 'class' => 'action-default' ] ); @@ -248,4 +260,38 @@ protected function addMediaRoleAttributes(Fieldset $fieldset) } return $this; } + + /** + * Get note for video url + * + * @return \Magento\Framework\Phrase + */ + protected function getNoteVideoUrl() + { + $result = __('YouTube and Vimeo supported.'); + if ($this->mediaHelper->getYouTubeApiKey() === null) { + $result = __( + 'Vimeo supported.
' + . 'To add YouTube video, please enter YouTube API Key first.', + $this->getConfigApiKeyUrl() + ); + } + return $result; + } + + /** + * Get url for config params + * + * @return string + */ + protected function getConfigApiKeyUrl() + { + return $this->urlBuilder->getUrl( + 'adminhtml/system_config/edit', + [ + 'section' => 'catalog', + '_fragment' => self::PATH_ANCHOR_PRODUCT_VIDEO + ] + ); + } } diff --git a/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php b/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php index 623c82edbcb25..cf5f42da71cf4 100644 --- a/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php +++ b/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php @@ -8,6 +8,9 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\File\Uploader; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class RetrieveImage extends \Magento\Backend\App\Action { /** @@ -72,12 +75,13 @@ public function __construct( */ public function executeInternal() { + $baseTmpMediaPath = $this->mediaConfig->getBaseTmpMediaPath(); try { $remoteFileUrl = $this->getRequest()->getParam('remote_image'); - $originalFileName = $this->parseOriginalFileName($remoteFileUrl); - $localFileName = $this->localFileName($originalFileName); - $localTmpFileName = $this->generateTmpFileName($localFileName); - $localFileMediaPath = $this->appendFileSystemPath($localTmpFileName); + $originalFileName = basename($remoteFileUrl); + $localFileName = Uploader::getCorrectFileName($originalFileName); + $localTmpFileName = Uploader::getDispretionPath($localFileName) . DIRECTORY_SEPARATOR . $localFileName; + $localFileMediaPath = $baseTmpMediaPath . ($localTmpFileName); $localUniqueFileMediaPath = $this->appendNewFileName($localFileMediaPath); $this->retrieveRemoteImage($remoteFileUrl, $localUniqueFileMediaPath); $localFileFullPath = $this->appendAbsoluteFileSystemPath($localUniqueFileMediaPath); @@ -101,7 +105,7 @@ public function executeInternal() protected function appendResultSaveRemoteImage($fileName) { $fileInfo = pathinfo($fileName); - $tmpFileName = $this->generateTmpFileName($fileInfo['basename']); + $tmpFileName = Uploader::getDispretionPath($fileInfo['basename']) . DIRECTORY_SEPARATOR . $fileInfo['basename']; $result['name'] = $fileInfo['basename']; $result['type'] = $this->imageAdapter->getMimeType(); $result['error'] = 0; @@ -111,26 +115,22 @@ protected function appendResultSaveRemoteImage($fileName) return $result; } - /** - * @param string $fileName - * @return string - */ - protected function localFileName($fileName) - { - $fileName = Uploader::getCorrectFileName($fileName); - return $fileName; - } - /** * @param string $fileUrl * @param string $localFilePath - * @return bool|void + * @return void + * @throws \Magento\Framework\Exception\LocalizedException */ protected function retrieveRemoteImage($fileUrl, $localFilePath) { $this->curl->setConfig(['header' => false]); $this->curl->write('GET', $fileUrl); $image = $this->curl->read(); + if (empty($image)) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Could not get video information. Please check your connection and try again.') + ); + } $this->fileUtility->saveFile($localFilePath, $image); } @@ -146,43 +146,6 @@ protected function appendNewFileName($localFilePath) return $fileInfo['dirname'] . DIRECTORY_SEPARATOR . $fileName; } - /** - * @param string $fileUrl - * @return string - */ - protected function parseOriginalFileName($fileUrl) - { - return basename($fileUrl); - } - - /** - * @param string $fileName - * @return string - */ - protected function generateTmpFileName($fileName) - { - return Uploader::getDispretionPath($fileName) . DIRECTORY_SEPARATOR . $fileName; - } - - /** - * @param string $fileName - * @return string - */ - protected function generateFileNameWithPath($fileName) - { - return Uploader::getDispretionPath($fileName) . DIRECTORY_SEPARATOR . $fileName; - } - - /** - * @param string $localTmpFile - * @return string - */ - protected function appendFileSystemPath($localTmpFile) - { - $pathToSave = $this->mediaConfig->getBaseTmpMediaPath(); - return $pathToSave . $localTmpFile; - } - /** * @param string $localTmpFile * @return string diff --git a/app/code/Magento/ProductVideo/Model/Plugin/BaseImage.php b/app/code/Magento/ProductVideo/Model/Plugin/BaseImage.php index 809da5a9c6b3d..f9342074f99d8 100644 --- a/app/code/Magento/ProductVideo/Model/Plugin/BaseImage.php +++ b/app/code/Magento/ProductVideo/Model/Plugin/BaseImage.php @@ -6,7 +6,7 @@ namespace Magento\ProductVideo\Model\Plugin; -use Magento\Catalog\Block\Adminhtml\Product\Helper\Form\BaseImage as OriginalBloc; +use Magento\Catalog\Block\Adminhtml\Product\Helper\Form\BaseImage as OriginalBlock; use Magento\Framework\View\Element\Template; /** @@ -20,13 +20,13 @@ class BaseImage const ELEMENT_OUTPUT_TEMPLATE = 'Magento_ProductVideo::product/edit/base_image.phtml'; /** - * @param OriginalBloc $baseImage + * @param OriginalBlock $baseImage * @param Template $block * @return Template * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterAssignBlockVariables(OriginalBloc $baseImage, Template $block) + public function afterAssignBlockVariables(OriginalBlock $baseImage, Template $block) { $block->assign([ 'videoPlaceholderText' => __('Click here to add videos.'), @@ -37,13 +37,13 @@ public function afterAssignBlockVariables(OriginalBloc $baseImage, Template $blo } /** - * @param OriginalBloc $baseImage + * @param OriginalBlock $baseImage * @param Template $block * @return Template * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterCreateElementHtmlOutputBlock(OriginalBloc $baseImage, Template $block) + public function afterCreateElementHtmlOutputBlock(OriginalBlock $baseImage, Template $block) { $block->setTemplate(self::ELEMENT_OUTPUT_TEMPLATE); return $block; diff --git a/app/code/Magento/ProductVideo/Model/Plugin/ExternalVideoEntryProcessor.php b/app/code/Magento/ProductVideo/Model/Plugin/ExternalVideoEntryProcessor.php index 935e84de05c13..1ec18643158d2 100644 --- a/app/code/Magento/ProductVideo/Model/Plugin/ExternalVideoEntryProcessor.php +++ b/app/code/Magento/ProductVideo/Model/Plugin/ExternalVideoEntryProcessor.php @@ -41,13 +41,21 @@ class ExternalVideoEntryProcessor */ protected $resourceEntryMediaGallery; + /** + * @var \Magento\ProductVideo\Model\ResourceModel\Video + */ + protected $videoResourceModel; + /** * @param \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Media $resourceEntryMediaGallery + * @param \Magento\ProductVideo\Model\ResourceModel\Video $videoResourceModel */ public function __construct( - \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Media $resourceEntryMediaGallery + \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Media $resourceEntryMediaGallery, + \Magento\ProductVideo\Model\ResourceModel\Video $videoResourceModel ) { $this->resourceEntryMediaGallery = $resourceEntryMediaGallery; + $this->videoResourceModel = $videoResourceModel; } /** @@ -141,8 +149,7 @@ protected function saveAdditionalStoreData(array $videoDataCollection) */ protected function saveVideoValuesItem(array $item) { - $this->resourceEntryMediaGallery->saveDataRow( - InstallSchema::GALLERY_VALUE_VIDEO_TABLE, + $this->videoResourceModel->insertOnDuplicate( $this->prepareVideoRowDataForSave($item) ); } diff --git a/app/code/Magento/ProductVideo/Model/Plugin/ExternalVideoResourceBackend.php b/app/code/Magento/ProductVideo/Model/Plugin/ExternalVideoResourceBackend.php new file mode 100644 index 0000000000000..e29ce8831c954 --- /dev/null +++ b/app/code/Magento/ProductVideo/Model/Plugin/ExternalVideoResourceBackend.php @@ -0,0 +1,46 @@ +videoResourceModel = $videoResourceModel; + } + + /** + * @param Media $originalResourceModel + * @param array $valueIdMap + * @return array + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterDuplicate(Media $originalResourceModel, array $valueIdMap) + { + $mediaGalleryEntitiesData = $this->videoResourceModel->loadByIds(array_keys($valueIdMap)); + foreach ($mediaGalleryEntitiesData as $row) { + $row['value_id'] = $valueIdMap[$row['value_id']]; + $this->videoResourceModel->insertOnDuplicate($row); + } + + return $valueIdMap; + } +} diff --git a/app/code/Magento/ProductVideo/Model/ResourceModel/Video.php b/app/code/Magento/ProductVideo/Model/ResourceModel/Video.php new file mode 100644 index 0000000000000..3a242f71eb42a --- /dev/null +++ b/app/code/Magento/ProductVideo/Model/ResourceModel/Video.php @@ -0,0 +1,47 @@ +_init(\Magento\ProductVideo\Setup\InstallSchema::GALLERY_VALUE_VIDEO_TABLE, 'value_id'); + } + + /** + * @param array $data + * @param array $fields + * @return int + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function insertOnDuplicate(array $data, array $fields = []) + { + return $this->getConnection()->insertOnDuplicate($this->getMainTable(), $data, $fields); + } + + /** + * @param array $ids + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function loadByIds(array $ids) + { + $select = $this->getConnection()->select()->from( + $this->getMainTable() + )->where( + 'value_id IN(?)', + $ids + ); + + return $this->getConnection()->fetchAll($select); + } +} diff --git a/app/code/Magento/ProductVideo/Model/VideoExtractor.php b/app/code/Magento/ProductVideo/Model/VideoExtractor.php index be97959b6df52..aa9267e5a324e 100644 --- a/app/code/Magento/ProductVideo/Model/VideoExtractor.php +++ b/app/code/Magento/ProductVideo/Model/VideoExtractor.php @@ -12,7 +12,7 @@ class VideoExtractor implements \Magento\Framework\View\Xsd\Media\TypeDataExtrac /** * Media Entry type code */ - const MEDIA_TYPE_CODE = 'image'; + const MEDIA_TYPE_CODE = 'video'; /** * Extract configuration data of videos from the DOM structure diff --git a/app/code/Magento/ProductVideo/Setup/UpgradeData.php b/app/code/Magento/ProductVideo/Setup/UpgradeData.php new file mode 100644 index 0000000000000..b1e418c0f39df --- /dev/null +++ b/app/code/Magento/ProductVideo/Setup/UpgradeData.php @@ -0,0 +1,73 @@ +categorySetupFactory = $categorySetupFactory; + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + if (version_compare($context->getVersion(), '2.0.0.2') < 0) { + /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */ + $categorySetup = $this->categorySetupFactory->create(['setup' => $setup]); + + $entityTypeId = $categorySetup->getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY); + $attributeSetId = $categorySetup->getDefaultAttributeSetId($entityTypeId); + + $attributeGroup = $categorySetup->getAttributeGroup( + $entityTypeId, + $attributeSetId, + 'Image Management' + ); + if (isset($attributeGroup['attribute_group_name']) + && $attributeGroup['attribute_group_name'] == 'Image Management' + ) { + // update General Group + $categorySetup->updateAttributeGroup( + $entityTypeId, + $attributeSetId, + $attributeGroup['attribute_group_id'], + 'attribute_group_name', + 'Images and Videos' + ); + } + + } + + $setup->endSetup(); + } +} diff --git a/app/code/Magento/ProductVideo/Test/Unit/Block/Adminhtml/Product/Edit/NewVideoTest.php b/app/code/Magento/ProductVideo/Test/Unit/Block/Adminhtml/Product/Edit/NewVideoTest.php index e07d3144baa89..9da5b71dd4d5f 100644 --- a/app/code/Magento/ProductVideo/Test/Unit/Block/Adminhtml/Product/Edit/NewVideoTest.php +++ b/app/code/Magento/ProductVideo/Test/Unit/Block/Adminhtml/Product/Edit/NewVideoTest.php @@ -37,6 +37,11 @@ class NewVideoTest extends \PHPUnit_Framework_TestCase */ protected $jsonEncoderMock; + /** + * @var \Magento\ProductVideo\Helper\Media|\PHPUnit_Framework_MockObject_MockObject + */ + protected $mediaHelper; + /** * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager * |\Magento\ProductVideo\Block\Adminhtml\Product\Edit\NewVideo @@ -46,6 +51,7 @@ class NewVideoTest extends \PHPUnit_Framework_TestCase public function setUp() { $this->contextMock = $this->getMock('\Magento\Backend\Block\Template\Context', [], [], '', false); + $this->mediaHelper = $this->getMock('\Magento\ProductVideo\Helper\Media', [], [], '', false); $this->mathRandom = $this->getMock('\Magento\Framework\Math\Random', [], [], '', false); $this->urlBuilder = $this->getMock('\Magento\Framework\UrlInterface', [], [], '', false); $this->contextMock->expects($this->any())->method('getMathRandom')->willReturn($this->mathRandom); @@ -60,9 +66,11 @@ public function setUp() '\Magento\ProductVideo\Block\Adminhtml\Product\Edit\NewVideo', [ 'context' => $this->contextMock, + 'mediaHelper' => $this->mediaHelper, + 'urlBuilder' => $this->urlBuilder, + 'jsonEncoder' => $this->jsonEncoderMock, 'registry' => $this->registryMock, 'formFactory' => $this->formFactoryMock, - 'jsonEncoder' => $this->jsonEncoderMock ] ); } diff --git a/app/code/Magento/ProductVideo/etc/adminhtml/system.xml b/app/code/Magento/ProductVideo/etc/adminhtml/system.xml index f475ab5b70d54..d00f9a78e6b65 100644 --- a/app/code/Magento/ProductVideo/etc/adminhtml/system.xml +++ b/app/code/Magento/ProductVideo/etc/adminhtml/system.xml @@ -8,10 +8,10 @@
- + - - + +
diff --git a/app/code/Magento/ProductVideo/etc/di.xml b/app/code/Magento/ProductVideo/etc/di.xml index 99aeef0a7251e..7f27e06492a15 100644 --- a/app/code/Magento/ProductVideo/etc/di.xml +++ b/app/code/Magento/ProductVideo/etc/di.xml @@ -39,6 +39,9 @@ + + + diff --git a/app/code/Magento/ProductVideo/etc/module.xml b/app/code/Magento/ProductVideo/etc/module.xml index a6bc6bab40b5a..16107ae938e40 100644 --- a/app/code/Magento/ProductVideo/etc/module.xml +++ b/app/code/Magento/ProductVideo/etc/module.xml @@ -6,7 +6,7 @@ */ --> - + diff --git a/app/code/Magento/ProductVideo/etc/view.xml b/app/code/Magento/ProductVideo/etc/view.xml index 3a09480fc4c57..f5ce5d81d6ff7 100644 --- a/app/code/Magento/ProductVideo/etc/view.xml +++ b/app/code/Magento/ProductVideo/etc/view.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> - +