diff --git a/app/code/Magento/Backend/Model/Auth/Session.php b/app/code/Magento/Backend/Model/Auth/Session.php index d5cf7e86c4b8ae8c301a2993893a9d31d615e7e5..0e9383d4e63c6e11997e65389bc435cceb5b7af8 100644 --- a/app/code/Magento/Backend/Model/Auth/Session.php +++ b/app/code/Magento/Backend/Model/Auth/Session.php @@ -171,26 +171,13 @@ class Session extends \Magento\Framework\Session\SessionManager implements \Mage } /** - * Set session UpdatedAt to current time and update cookie expiration time + * Set session UpdatedAt to current time * * @return void */ public function prolong() { - $lifetime = $this->_config->getValue(self::XML_PATH_SESSION_LIFETIME); - $currentTime = time(); - - $this->setUpdatedAt($currentTime); - $cookieValue = $this->cookieManager->getCookie($this->getName()); - if ($cookieValue) { - $cookieMetadata = $this->cookieMetadataFactory->createPublicCookieMetadata() - ->setDuration($lifetime) - ->setPath($this->sessionConfig->getCookiePath()) - ->setDomain($this->sessionConfig->getCookieDomain()) - ->setSecure($this->sessionConfig->getCookieSecure()) - ->setHttpOnly($this->sessionConfig->getCookieHttpOnly()); - $this->cookieManager->setPublicCookie($this->getName(), $cookieValue, $cookieMetadata); - } + $this->setUpdatedAt(time()); } /** diff --git a/app/code/Magento/Backend/Model/Config/SessionLifetime/BackendModel.php b/app/code/Magento/Backend/Model/Config/SessionLifetime/BackendModel.php new file mode 100644 index 0000000000000000000000000000000000000000..ccb5c65a8c5080984ff72627760a795e2507cbde --- /dev/null +++ b/app/code/Magento/Backend/Model/Config/SessionLifetime/BackendModel.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Backend\Model\Config\SessionLifetime; + +use Magento\Framework\App\Config\Value; +use Magento\Framework\Exception\LocalizedException; + +/** + * Backend model for the admin/security/session_lifetime configuration field. Validates session lifetime. + */ +class BackendModel extends Value +{ + /** Maximum dmin session lifetime; 1 year*/ + const MAX_LIFETIME = 31536000; + + /** Minimum admin session lifetime */ + const MIN_LIFETIME = 60; + + public function beforeSave() + { + $value = (int) $this->getValue(); + if ($value > self::MAX_LIFETIME) { + throw new LocalizedException( + __('Admin session lifetime must be less than or equal to 31536000 seconds (one year)') + ); + } else if ($value < self::MIN_LIFETIME) { + throw new LocalizedException( + __('Admin session lifetime must be greater than or equal to 60 seconds') + ); + } + return parent::beforeSave(); + } +} diff --git a/app/code/Magento/Backend/Model/Session/AdminConfig.php b/app/code/Magento/Backend/Model/Session/AdminConfig.php index 9dced89caf30d572aeb36fd149850ef36acac015..8edec773402648d9be0653aab9e2496f67286d81 100644 --- a/app/code/Magento/Backend/Model/Session/AdminConfig.php +++ b/app/code/Magento/Backend/Model/Session/AdminConfig.php @@ -14,8 +14,6 @@ use Magento\Framework\Session\Config; /** * Magento Backend session configuration - * - * @method Config setSaveHandler() */ class AdminConfig extends Config { @@ -107,4 +105,14 @@ class AdminConfig extends Config $cookiePath = $baseUrl . $backendApp->getCookiePath(); return $cookiePath; } + + /** + * Set session cookie lifetime to session duration + * + * @return $this + */ + protected function configureCookieLifetime() + { + return $this->setCookieLifetime(0); + } } diff --git a/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php b/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php index 3e41177e72c0cae37f1f4324caa310d63e471e2c..b5b3fed369f326ef9724fee32f003b6ea42f359d 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php @@ -162,67 +162,7 @@ class SessionTest extends \PHPUnit_Framework_TestCase public function testProlong() { - $name = session_name(); - $cookie = 'cookie'; - $lifetime = 900; - $path = '/'; - $domain = 'magento2'; - $secure = true; - $httpOnly = true; - - $cookieMetadata = $this->getMock('Magento\Framework\Stdlib\Cookie\PublicCookieMetadata'); - $cookieMetadata->expects($this->once()) - ->method('setDuration') - ->with($lifetime) - ->will($this->returnSelf()); - $cookieMetadata->expects($this->once()) - ->method('setPath') - ->with($path) - ->will($this->returnSelf()); - $cookieMetadata->expects($this->once()) - ->method('setDomain') - ->with($domain) - ->will($this->returnSelf()); - $cookieMetadata->expects($this->once()) - ->method('setSecure') - ->with($secure) - ->will($this->returnSelf()); - $cookieMetadata->expects($this->once()) - ->method('setHttpOnly') - ->with($httpOnly) - ->will($this->returnSelf()); - - $this->cookieMetadataFactory->expects($this->once()) - ->method('createPublicCookieMetadata') - ->will($this->returnValue($cookieMetadata)); - - $this->cookieManager->expects($this->once()) - ->method('getCookie') - ->with($name) - ->will($this->returnValue($cookie)); - $this->cookieManager->expects($this->once()) - ->method('setPublicCookie') - ->with($name, $cookie, $cookieMetadata); - - $this->config->expects($this->once()) - ->method('getValue') - ->with(\Magento\Backend\Model\Auth\Session::XML_PATH_SESSION_LIFETIME) - ->will($this->returnValue($lifetime)); - $this->sessionConfig->expects($this->once()) - ->method('getCookiePath') - ->will($this->returnValue($path)); - $this->sessionConfig->expects($this->once()) - ->method('getCookieDomain') - ->will($this->returnValue($domain)); - $this->sessionConfig->expects($this->once()) - ->method('getCookieSecure') - ->will($this->returnValue($secure)); - $this->sessionConfig->expects($this->once()) - ->method('getCookieHttpOnly') - ->will($this->returnValue($httpOnly)); - $this->session->prolong(); - $this->assertLessThanOrEqual(time(), $this->session->getUpdatedAt()); } diff --git a/app/code/Magento/Backend/Test/Unit/Model/Config/SessionLifetime/BackendModelTest.php b/app/code/Magento/Backend/Test/Unit/Model/Config/SessionLifetime/BackendModelTest.php new file mode 100755 index 0000000000000000000000000000000000000000..a26910a45baade098d5322eb655469f8fd106e8e --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Model/Config/SessionLifetime/BackendModelTest.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Backend\Test\Unit\Model\Config\SessionLifetime; + +use Magento\Backend\Model\Config\SessionLifetime\BackendModel; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class BackendModelTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider adminSessionLifetimeDataProvider + */ + public function testBeforeSave($value, $errorMessage = null) + { + /** @var BackendModel $model */ + $model = (new ObjectManager($this))->getObject('Magento\Backend\Model\Config\SessionLifetime\BackendModel'); + if ($errorMessage !== null) { + $this->setExpectedException('\Magento\Framework\Exception\LocalizedException', $errorMessage); + } + $model->setValue($value); + $model->beforeSave(); + } + + public function adminSessionLifetimeDataProvider() + { + return [ + [ + BackendModel::MIN_LIFETIME - 1, + 'Admin session lifetime must be greater than or equal to 60 seconds' + ], + [ + BackendModel::MAX_LIFETIME + 1, + 'Admin session lifetime must be less than or equal to 31536000 seconds (one year)' + ], + [ + 900 + ] + ]; + } +} diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index f7b9bd917b030a232cc3584adcda2aa9653e785c..65a1872ed4999a765d002a98dd9b31ea73dbf168 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -388,7 +388,8 @@ </field> <field id="session_lifetime" translate="label comment" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Admin Session Lifetime (seconds)</label> - <comment>Values less than 60 are ignored.</comment> + <comment>Please enter at least 60 and at most 31536000 (one year).</comment> + <backend_model>Magento\Backend\Model\Config\SessionLifetime\BackendModel</backend_model> <validate>validate-digits</validate> </field> </group> diff --git a/app/code/Magento/Backend/i18n/en_US.csv b/app/code/Magento/Backend/i18n/en_US.csv index 0c5586948801fcf220e16302f0d15949d6b68f6e..a704cab6687c3ff62f0f4e239c0edc0669688b74 100644 --- a/app/code/Magento/Backend/i18n/en_US.csv +++ b/app/code/Magento/Backend/i18n/en_US.csv @@ -556,7 +556,9 @@ Security,Security "Add Secret Key to URLs","Add Secret Key to URLs" "Login is Case Sensitive","Login is Case Sensitive" "Admin Session Lifetime (seconds)","Admin Session Lifetime (seconds)" -"Values less than 60 are ignored.","Values less than 60 are ignored." +"Please enter at least 60 and at most 31536000 (one year).","Please enter at least 60 and at most 31536000 (one year)." +"Admin session lifetime must be less than or equal to 31536000 seconds (one year)","Admin session lifetime must be less than or equal to 31536000 seconds (one year)" +"Admin session lifetime must be greater than or equal to 60 seconds","Admin session lifetime must be greater than or equal to 60 seconds" Web,Web "Url Options","Url Options" "Add Store Code to Urls","Add Store Code to Urls" diff --git a/app/code/Magento/Bundle/Controller/Adminhtml/Product/Initialization/Helper/Plugin/Bundle.php b/app/code/Magento/Bundle/Controller/Adminhtml/Product/Initialization/Helper/Plugin/Bundle.php index 796af9fe13f418e5aa9df3979a583ab9f947bc4b..b6641286fd4507b2dd0990cba7ba239f4664c1fa 100644 --- a/app/code/Magento/Bundle/Controller/Adminhtml/Product/Initialization/Helper/Plugin/Bundle.php +++ b/app/code/Magento/Bundle/Controller/Adminhtml/Product/Initialization/Helper/Plugin/Bundle.php @@ -130,6 +130,7 @@ class Bundle if ((bool)$optionData['delete']) { continue; } + $option = $this->optionFactory->create(['data' => $optionData]); $option->setSku($product->getSku()); $option->setOptionId(null); diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Stock.php b/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Stock.php index dd246101c0f3517c38d042cbc02c05c0ef6e0116..0e26022d1e8a6590377c09e11601183c09ef9d0c 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Stock.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Stock.php @@ -158,20 +158,7 @@ class Stock extends \Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\ ); $this->_addAttributeToSelect($select, 'status', "e.$linkField", 'cs.store_id', $condition); - if ($this->_isManageStock()) { - $statusExpr = $connection->getCheckSql( - 'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 0', - '1', - 'cisi.is_in_stock' - ); - } else { - $statusExpr = $connection->getCheckSql( - 'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 1', - 'cisi.is_in_stock', - '1' - ); - } - + $statusExpr = $this->getStatusExpression($connection); $select->columns( [ 'status' => $connection->getLeastSql( diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php index 07a86a028ed44584e8370199b770246806e6feea..d3c27a6b3ed1a1c9bbc2b2b1279bc9dac8ebf2c5 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php @@ -20,6 +20,11 @@ class Inventory extends \Magento\Backend\Block\Widget implements \Magento\Backen */ protected $stockConfiguration; + /** + * @var array + */ + protected $disabledFields = []; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\CatalogInventory\Model\Source\Backorders $backorders @@ -112,4 +117,14 @@ class Inventory extends \Magento\Backend\Block\Widget implements \Magento\Backen { return false; } + + /** + * @param string $fieldName + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function isAvailable($fieldName) + { + return true; + } } diff --git a/app/code/Magento/Catalog/Block/Category/Plugin/PriceBoxTags.php b/app/code/Magento/Catalog/Block/Category/Plugin/PriceBoxTags.php new file mode 100644 index 0000000000000000000000000000000000000000..65c9b3ad624ee6c4ac4119c1c38064ea4943915a --- /dev/null +++ b/app/code/Magento/Catalog/Block/Category/Plugin/PriceBoxTags.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Block\Category\Plugin; + +use Magento\Catalog\Model\Product; +use Magento\Customer\Model\Session; +use Magento\Framework\App\ScopeResolverInterface; +use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Framework\Pricing\Render\PriceBox; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; + +class PriceBoxTags +{ + /** + * @var TimezoneInterface + */ + protected $dateTime; + + /** + * @var \Magento\Customer\Model\Session + */ + protected $customerSession; + + /** + * @var PriceCurrencyInterface + */ + private $priceCurrency; + + /** + * @var ScopeResolverInterface + */ + private $scopeResolver; + + /** + * PriceBoxTags constructor. + * @param PriceCurrencyInterface $priceCurrency + * @param TimezoneInterface $dateTime + * @param ScopeResolverInterface $scopeResolver + * @param Session $customerSession + */ + public function __construct( + PriceCurrencyInterface $priceCurrency, + TimezoneInterface $dateTime, + ScopeResolverInterface $scopeResolver, + Session $customerSession + ) { + $this->dateTime = $dateTime; + $this->customerSession = $customerSession; + $this->priceCurrency = $priceCurrency; + $this->scopeResolver = $scopeResolver; + } + + /** + * @param PriceBox $subject + * @param string $result + * @return string + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetCacheKey(PriceBox $subject, $result) + { + return implode( + '-', + [ + $result, + $this->priceCurrency->getCurrencySymbol(), + $this->dateTime->scopeDate($this->scopeResolver->getScope()->getId())->format('Ymd'), + $this->scopeResolver->getScope()->getId(), + $this->customerSession->getCustomerGroupId(), + ] + ); + } +} diff --git a/app/code/Magento/Catalog/Controller/Category/View.php b/app/code/Magento/Catalog/Controller/Category/View.php index b0684d90546f184c3e185c44d8d5e8e711be7f2f..e9266ee0216a5396ca9ef9eff3710fbd406d8c05 100644 --- a/app/code/Magento/Catalog/Controller/Category/View.php +++ b/app/code/Magento/Catalog/Controller/Category/View.php @@ -172,13 +172,15 @@ class View extends \Magento\Framework\App\Action\Action if ($settings->getPageLayout()) { $page->getConfig()->setPageLayout($settings->getPageLayout()); } + + $hasChildren = $category->hasChildren(); if ($category->getIsAnchor()) { - $type = $category->hasChildren() ? 'layered' : 'layered_without_children'; + $type = $hasChildren ? 'layered' : 'layered_without_children'; } else { - $type = $category->hasChildren() ? 'default' : 'default_without_children'; + $type = $hasChildren ? 'default' : 'default_without_children'; } - if (!$category->hasChildren()) { + if (!$hasChildren) { // Two levels removed from parent. Need to add default page type. $parentType = strtok($type, '_'); $page->addPageLayoutHandles(['type' => $parentType]); diff --git a/app/code/Magento/Catalog/Model/Category.php b/app/code/Magento/Catalog/Model/Category.php index dd89956d3a66d8c531dc82bdb659c3db55305697..4efa1b08969929f59ea63d1dd4595a046af0c216 100644 --- a/app/code/Magento/Catalog/Model/Category.php +++ b/app/code/Magento/Catalog/Model/Category.php @@ -1115,7 +1115,10 @@ class Category extends \Magento\Catalog\Model\AbstractModel implements $identities = [ self::CACHE_TAG . '_' . $this->getId(), ]; - if ($this->hasDataChanges() || $this->isDeleted()) { + if (!$this->getId() || $this->hasDataChanges() + || $this->isDeleted() || $this->dataHasChangedFor(self::KEY_INCLUDE_IN_MENU) + ) { + $identities[] = self::CACHE_TAG; $identities[] = Product::CACHE_PRODUCT_CATEGORY_TAG . '_' . $this->getId(); } return $identities; diff --git a/app/code/Magento/Catalog/Model/Category/DataProvider.php b/app/code/Magento/Catalog/Model/Category/DataProvider.php index 479f02796b57cbcfd15070ee3585fd0b3adf3b2c..8fa599af1ce77ed01a652551a81d89aae857245d 100644 --- a/app/code/Magento/Catalog/Model/Category/DataProvider.php +++ b/app/code/Magento/Catalog/Model/Category/DataProvider.php @@ -253,7 +253,7 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider $meta[$code]['validation'] = $rules; } - $meta[$code]['scope_label'] = $this->getScopeLabel($attribute); + $meta[$code]['scopeLabel'] = $this->getScopeLabel($attribute); $meta[$code]['componentType'] = Field::NAME; } diff --git a/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php b/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php index fdf80681159cce87ab47cb677b5cb34508a556ce..1c4c8533ef2465d4d1520dac8c47aa921c39347f 100644 --- a/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php +++ b/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php @@ -376,7 +376,7 @@ abstract class AbstractFilter extends \Magento\Framework\DataObject implements F */ protected function getAttributeIsFilterable($attribute) { - return $attribute->getIsFilterable(); + return (int)$attribute->getIsFilterable(); } /** diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php index 13b119e93f5e8f46f60c18c2e0e85cc60182c801..b33f10728a29f9f0527103b908b9abff5fca7e04 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php @@ -166,7 +166,7 @@ abstract class AbstractGroupPrice extends Price if (!empty($priceRow['delete'])) { continue; } - $compare = join( + $compare = implode( '-', array_merge( [$priceRow['website_id'], $priceRow['cust_group']], @@ -191,7 +191,7 @@ abstract class AbstractGroupPrice extends Price if ($origPrices) { foreach ($origPrices as $price) { if ($price['website_id'] == 0) { - $compare = join( + $compare = implode( '-', array_merge( [$price['website_id'], $price['cust_group']], @@ -215,7 +215,7 @@ abstract class AbstractGroupPrice extends Price continue; } - $globalCompare = join( + $globalCompare = implode( '-', array_merge([0, $priceRow['cust_group']], $this->_getAdditionalUniqueFields($priceRow)) ); @@ -243,7 +243,7 @@ abstract class AbstractGroupPrice extends Price $data = []; $price = $this->_catalogProductType->priceFactory($productTypeId); foreach ($priceData as $v) { - $key = join('-', array_merge([$v['cust_group']], $this->_getAdditionalUniqueFields($v))); + $key = implode('-', array_merge([$v['cust_group']], $this->_getAdditionalUniqueFields($v))); if ($v['website_id'] == $websiteId) { $data[$key] = $v; $data[$key]['website_price'] = $v['price']; @@ -316,7 +316,7 @@ abstract class AbstractGroupPrice extends Price $isGlobal = $this->getAttribute()->isScopeGlobal() || $websiteId == 0; $priceRows = $object->getData($this->getAttribute()->getName()); - if ($priceRows === null) { + if (null === $priceRows) { return $this; } @@ -330,7 +330,7 @@ abstract class AbstractGroupPrice extends Price } foreach ($origPrices as $data) { if ($data['website_id'] > 0 || $data['website_id'] == '0' && $isGlobal) { - $key = join( + $key = implode( '-', array_merge( [$data['website_id'], $data['cust_group']], @@ -361,7 +361,7 @@ abstract class AbstractGroupPrice extends Price continue; } - $key = join( + $key = implode( '-', array_merge([$data['website_id'], $data['cust_group']], $this->_getAdditionalUniqueFields($data)) ); diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php index 2cafca07cbfe0aeb4572dab9013268820a5eef74..5aec262a6911d28fef55ce3eeae92a1472289aa9 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php @@ -49,6 +49,11 @@ class Repository implements \Magento\Catalog\Api\ProductAttributeRepositoryInter */ protected $searchCriteriaBuilder; + /** + * @var \Magento\Catalog\Api\ProductAttributeOptionManagementInterface + */ + private $optionManagement; + /** * @param \Magento\Catalog\Model\ResourceModel\Attribute $attributeResource * @param \Magento\Catalog\Helper\Product $productHelper @@ -57,6 +62,7 @@ class Repository implements \Magento\Catalog\Api\ProductAttributeRepositoryInter * @param \Magento\Eav\Model\Config $eavConfig * @param \Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\ValidatorFactory $validatorFactory * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder + * @param \Magento\Catalog\Api\ProductAttributeOptionManagementInterface $optionManagement */ public function __construct( \Magento\Catalog\Model\ResourceModel\Attribute $attributeResource, @@ -65,7 +71,8 @@ class Repository implements \Magento\Catalog\Api\ProductAttributeRepositoryInter \Magento\Eav\Api\AttributeRepositoryInterface $eavAttributeRepository, \Magento\Eav\Model\Config $eavConfig, \Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\ValidatorFactory $validatorFactory, - \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder + \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder, + \Magento\Catalog\Api\ProductAttributeOptionManagementInterface $optionManagement ) { $this->attributeResource = $attributeResource; $this->productHelper = $productHelper; @@ -74,6 +81,7 @@ class Repository implements \Magento\Catalog\Api\ProductAttributeRepositoryInter $this->eavConfig = $eavConfig; $this->inputtypeValidatorFactory = $validatorFactory; $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->optionManagement = $optionManagement; } /** @@ -171,7 +179,10 @@ class Repository implements \Magento\Catalog\Api\ProductAttributeRepositoryInter $attribute->setIsUserDefined(1); } $this->attributeResource->save($attribute); - return $attribute; + foreach ($attribute->getOptions() as $option) { + $this->optionManagement->add($attribute->getAttributeCode(), $option); + } + return $this->get($attribute->getAttributeCode()); } /** diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php index cde304b004ac497e35502954291fda86a499533e..76729cdb7458c4869664feaf0b9e7897a932486a 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php @@ -818,4 +818,30 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute implements $this->_eavConfig->clear(); return parent::afterDelete(); } + + /** + * @inheritdoc + */ + public function __sleep() + { + return array_diff( + parent::__sleep(), + ['_indexerEavProcessor', '_productFlatIndexerProcessor', '_productFlatIndexerHelper', 'attrLockValidator'] + ); + } + + /** + * @inheritdoc + */ + public function __wakeup() + { + parent::__wakeup(); + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $this->_indexerEavProcessor = $objectManager->get(\Magento\Catalog\Model\Indexer\Product\Flat\Processor::class); + $this->_productFlatIndexerProcessor = $objectManager->get( + \Magento\Catalog\Model\Indexer\Product\Eav\Processor::class + ); + $this->_productFlatIndexerHelper = $objectManager->get(\Magento\Catalog\Helper\Product\Flat\Indexer::class); + $this->attrLockValidator = $objectManager->get(LockValidatorInterface::class); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/InventoryTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/InventoryTest.php index 9c8ea48b531eb2cd9a5b26c250ef5d9106b32ff1..48ebd822875a4d025d3b6b73c4b8a04381360f37 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/InventoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/InventoryTest.php @@ -177,4 +177,14 @@ class InventoryTest extends \PHPUnit_Framework_TestCase { $this->assertFalse($this->inventory->isHidden()); } + + /** + * Run test isEnabled method + * + * @return void + */ + public function testIsEnabled() + { + $this->assertEquals(true, $this->inventory->isAvailable('field')); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php index 1cf511043804620a61c4c80fb338e0126b160230..c4cc6f48c5607baeed7b157d0f2777c47be4194d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php @@ -63,6 +63,11 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase */ protected $searchResultMock; + /** + * @var \Magento\Eav\Api\AttributeOptionManagementInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $optionManagementMock; + protected function setUp() { $this->attributeResourceMock = @@ -99,6 +104,8 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase [], '', false); + $this->optionManagementMock = + $this->getMock('\Magento\Catalog\Api\ProductAttributeOptionManagementInterface', [], [], '', false); $this->model = new Repository( $this->attributeResourceMock, @@ -107,7 +114,8 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase $this->eavAttributeRepositoryMock, $this->eavConfigMock, $this->validatorFactoryMock, - $this->searchCriteriaBuilderMock + $this->searchCriteriaBuilderMock, + $this->optionManagementMock ); } diff --git a/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php b/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php index 8719e6f3d60994d9780a67a70b8e53c60a445c38..43dc002857c6c20f0ea6d362622681ff9e648760 100644 --- a/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php @@ -81,6 +81,9 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase ->method('getBlock') ->will($this->returnValue($this->priceBox)); + $cacheState = $this->getMockBuilder(\Magento\Framework\App\Cache\StateInterface::class) + ->getMockForAbstractClass(); + $scopeConfigMock = $this->getMockForAbstractClass('Magento\Framework\App\Config\ScopeConfigInterface'); $context = $this->getMock('Magento\Framework\View\Element\Template\Context', [], [], '', false); $context->expects($this->any()) @@ -98,6 +101,9 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase $context->expects($this->any()) ->method('getScopeConfig') ->will($this->returnValue($scopeConfigMock)); + $context->expects($this->any()) + ->method('getCacheState') + ->will($this->returnValue($cacheState)); $this->rendererPool = $this->getMockBuilder('Magento\Framework\Pricing\Render\RendererPool') ->disableOriginalConstructor() diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php index b9bf7aba3aba4e1c2b28ffb084fedde226a29323..9370390ad7bec3fbb6c5ed0c0e95e4848734acd2 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php @@ -528,7 +528,8 @@ class Eav extends AbstractModifier } // TODO: getAttributeModel() should not be used when MAGETWO-48284 is complete - if (($rules = $this->eavValidationRules->build($attribute, $meta))) { + $childData = $this->arrayManager->get($configPath, $meta, []); + if (($rules = $this->eavValidationRules->build($this->getAttributeModel($attribute), $childData))) { $meta = $this->arrayManager->merge($configPath, $meta, [ 'validation' => $rules, ]); diff --git a/app/code/Magento/Catalog/etc/frontend/di.xml b/app/code/Magento/Catalog/etc/frontend/di.xml index 07a5986dd17080ec208389ecb552ecc3b0b57b94..d9e46fb2517aede84bd5cf6eb9063619f7977cf0 100644 --- a/app/code/Magento/Catalog/etc/frontend/di.xml +++ b/app/code/Magento/Catalog/etc/frontend/di.xml @@ -60,4 +60,7 @@ </argument> </arguments> </type> + <type name="\Magento\Framework\Pricing\Render\PriceBox"> + <plugin name="catalog_price_box_key" type="Magento\Catalog\Block\Category\Plugin\PriceBoxTags" /> + </type> </config> diff --git a/app/code/Magento/CatalogInventory/Block/Stockqty/AbstractStockqty.php b/app/code/Magento/CatalogInventory/Block/Stockqty/AbstractStockqty.php index 93224e80fe3efdf07c602ec02d57bd36274b3db6..ffe5f6559c35dedcdda881b31d649a88d821c52c 100644 --- a/app/code/Magento/CatalogInventory/Block/Stockqty/AbstractStockqty.php +++ b/app/code/Magento/CatalogInventory/Block/Stockqty/AbstractStockqty.php @@ -25,11 +25,6 @@ abstract class AbstractStockqty extends \Magento\Framework\View\Element\Template */ protected $_coreRegistry; - /** - * @var \Magento\CatalogInventory\Api\StockStateInterface - */ - protected $stockState; - /** * @var \Magento\CatalogInventory\Api\StockRegistryInterface */ @@ -38,19 +33,16 @@ abstract class AbstractStockqty extends \Magento\Framework\View\Element\Template /** * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Framework\Registry $registry - * @param \Magento\CatalogInventory\Api\StockStateInterface $stockState * @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry * @param array $data */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, \Magento\Framework\Registry $registry, - \Magento\CatalogInventory\Api\StockStateInterface $stockState, \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry, array $data = [] ) { $this->_coreRegistry = $registry; - $this->stockState = $stockState; $this->stockRegistry = $stockRegistry; parent::__construct($context, $data); @@ -92,7 +84,7 @@ abstract class AbstractStockqty extends \Magento\Framework\View\Element\Template */ public function getProductStockQty($product) { - return $this->stockState->getStockQty($product->getId(), $product->getStore()->getWebsiteId()); + return $this->stockRegistry->getStockStatus($product->getId(), $product->getStore()->getWebsiteId())->getQty(); } /** diff --git a/app/code/Magento/CatalogInventory/Helper/Stock.php b/app/code/Magento/CatalogInventory/Helper/Stock.php index 8a227f53ebc1fce93a93a809ab5a0c1e887c06c4..8f7bb78776cba151fd7e3393d4144adb6c53e4cd 100644 --- a/app/code/Magento/CatalogInventory/Helper/Stock.php +++ b/app/code/Magento/CatalogInventory/Helper/Stock.php @@ -139,8 +139,16 @@ class Stock */ public function addIsInStockFilterToCollection($collection) { - $resource = $this->getStockStatusResource(); - $resource->addIsInStockFilterToCollection($collection); + $stockFlag = 'has_stock_status_filter'; + if (!$collection->hasFlag($stockFlag)) { + $isShowOutOfStock = $this->scopeConfig->getValue( + \Magento\CatalogInventory\Model\Configuration::XML_PATH_SHOW_OUT_OF_STOCK, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + $resource = $this->getStockStatusResource(); + $resource->addStockDataToCollection($collection, !$isShowOutOfStock); + $collection->setFlag($stockFlag, true); + } } /** diff --git a/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php b/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php new file mode 100644 index 0000000000000000000000000000000000000000..fdf613f5506a9aa6796881371194833c7a01318c --- /dev/null +++ b/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogInventory\Model; + +use Magento\Catalog\Model\ResourceModel\Product\Collection; + +/** + * Catalog inventory module plugin + */ +class AddStockStatusToCollection +{ + /** + * @var \Magento\CatalogInventory\Helper\Stock + */ + protected $stockHelper; + + /** + * @param \Magento\CatalogInventory\Model\Configuration $configuration + * @param \Magento\CatalogInventory\Helper\Stock $stockHelper + */ + public function __construct( + \Magento\CatalogInventory\Helper\Stock $stockHelper + ) { + $this->stockHelper = $stockHelper; + } + + /** + * @param Collection $productCollection + * @param bool $printQuery + * @param bool $logQuery + * @return array + */ + public function beforeLoad(Collection $productCollection, $printQuery = false, $logQuery = false) + { + $this->stockHelper->addIsInStockFilterToCollection($productCollection); + return [$printQuery, $logQuery]; + } +} diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php index c31e0f02c955c465e975112c36e38387904ad85a..8de183aeb391e42b893e72bd77293a0ff080d1cb 100644 --- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php @@ -7,6 +7,8 @@ namespace Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock; use Magento\Catalog\Model\ResourceModel\Product\Indexer\AbstractIndexer; +use Magento\CatalogInventory\Model\Stock; +use Magento\Framework\DB\Adapter\AdapterInterface; /** * CatalogInventory Default Stock Status Indexer Resource Model @@ -35,14 +37,20 @@ class DefaultStock extends AbstractIndexer implements StockInterface */ protected $_scopeConfig; + /** + * @var QueryProcessorComposite + */ + private $queryProcessorComposite; + /** * Class constructor * * @param \Magento\Framework\Model\ResourceModel\Db\Context $context * @param \Magento\Framework\Indexer\Table\StrategyInterface $tableStrategy * @param \Magento\Eav\Model\Config $eavConfig - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Framework\Model\Entity\MetadataPool $metadataPool + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param QueryProcessorComposite $queryProcessorComposite * @param string $connectionName */ public function __construct( @@ -51,9 +59,11 @@ class DefaultStock extends AbstractIndexer implements StockInterface \Magento\Eav\Model\Config $eavConfig, \Magento\Framework\Model\Entity\MetadataPool $metadataPool, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + QueryProcessorComposite $queryProcessorComposite, $connectionName = null ) { $this->_scopeConfig = $scopeConfig; + $this->queryProcessorComposite = $queryProcessorComposite; parent::__construct($context, $tableStrategy, $eavConfig, $metadataPool, $connectionName); } @@ -188,9 +198,10 @@ class DefaultStock extends AbstractIndexer implements StockInterface ['cisi' => $this->getTable('cataloginventory_stock_item')], 'cisi.stock_id = cis.stock_id AND cisi.product_id = e.entity_id', [] - )->columns(['qty' => $qtyExpr]) + )->columns(['qty' => new \Zend_Db_Expr('SUM(' . $qtyExpr . ')')]) ->where('cw.website_id != 0') - ->where('e.type_id = ?', $this->getTypeId()); + ->where('e.type_id = ?', $this->getTypeId()) + ->group('e.entity_id'); // add limitation of status $condition = $connection->quoteInto( @@ -205,21 +216,7 @@ class DefaultStock extends AbstractIndexer implements StockInterface $condition ); - if ($this->_isManageStock()) { - $statusExpr = $connection->getCheckSql( - 'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 0', - 1, - 'cisi.is_in_stock' - ); - } else { - $statusExpr = $connection->getCheckSql( - 'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 1', - 'cisi.is_in_stock', - 1 - ); - } - - $select->columns(['status' => $statusExpr]); + $select->columns(['status' => $this->getStatusExpression($connection, true)]); if ($entityIds !== null) { $select->where('e.entity_id IN(?)', $entityIds); @@ -238,6 +235,7 @@ class DefaultStock extends AbstractIndexer implements StockInterface { $connection = $this->getConnection(); $select = $this->_getStockStatusSelect($entityIds); + $select = $this->queryProcessorComposite->processQuery($select, $entityIds); $query = $select->insertFromSelect($this->getIdxTable()); $connection->query($query); @@ -254,6 +252,7 @@ class DefaultStock extends AbstractIndexer implements StockInterface { $connection = $this->getConnection(); $select = $this->_getStockStatusSelect($entityIds, true); + $select = $this->queryProcessorComposite->processQuery($select, $entityIds, true); $query = $connection->query($select); $i = 0; @@ -263,7 +262,7 @@ class DefaultStock extends AbstractIndexer implements StockInterface $data[] = [ 'product_id' => (int)$row['entity_id'], 'website_id' => (int)$row['website_id'], - 'stock_id' => (int)$row['stock_id'], + 'stock_id' => Stock::DEFAULT_STOCK_ID, 'qty' => (double)$row['qty'], 'stock_status' => (int)$row['status'], ]; @@ -306,4 +305,28 @@ class DefaultStock extends AbstractIndexer implements StockInterface { return $this->tableStrategy->getTableName('cataloginventory_stock_status'); } + + /** + * @param AdapterInterface $connection + * @param bool $isAggregate + * @return mixed + */ + protected function getStatusExpression(AdapterInterface $connection, $isAggregate = false) + { + $isInStockExpression = $isAggregate ? 'MAX(cisi.is_in_stock)' : 'cisi.is_in_stock'; + if ($this->_isManageStock()) { + $statusExpr = $connection->getCheckSql( + 'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 0', + 1, + $isInStockExpression + ); + } else { + $statusExpr = $connection->getCheckSql( + 'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 1', + $isInStockExpression, + 1 + ); + } + return $statusExpr; + } } diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/QueryProcessorComposite.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/QueryProcessorComposite.php new file mode 100644 index 0000000000000000000000000000000000000000..8b89df82573ae4881ae88d89eb7b84311ee5eb42 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/QueryProcessorComposite.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock; + +class QueryProcessorComposite implements QueryProcessorInterface +{ + /** + * @var array + */ + private $queryProcessors; + + /** + * QueryProcessorPool constructor. + * @param QueryProcessorInterface[] $queryProcessors + */ + public function __construct(array $queryProcessors = []) + { + $this->queryProcessors = $queryProcessors; + } + + /** + * @param \Magento\Framework\DB\Select $select + * @param null|array $entityIds + * @param bool $usePrimaryTable + * @return \Magento\Framework\DB\Select + */ + public function processQuery(\Magento\Framework\DB\Select $select, $entityIds = null, $usePrimaryTable = false) + { + foreach ($this->queryProcessors as $queryProcessor) { + $select = $queryProcessor->processQuery($select, $entityIds, $usePrimaryTable); + } + return $select; + } +} diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/QueryProcessorInterface.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/QueryProcessorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..4815e173eba03671a59c9724164c9a59a5d2187c --- /dev/null +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/QueryProcessorInterface.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock; + +use Magento\Framework\DB\Select; + +interface QueryProcessorInterface +{ + /** + * @param Select $select + * @param null|array $entityIds + * @param bool $usePrimaryTable + * @return Select + */ + public function processQuery(Select $select, $entityIds = null, $usePrimaryTable = false); +} diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php index be7e1ff2d1b64487e09ac206d40f6deecfb15f90..5fe199905837b553d16f02fb01bf4be5a07c01be 100644 --- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php @@ -198,12 +198,45 @@ class Status extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb $select->joinLeft( ['stock_status' => $this->getMainTable()], 'e.entity_id = stock_status.product_id AND stock_status.website_id=' . $websiteId, - ['salable' => 'stock_status.stock_status'] + ['is_salable' => 'stock_status.stock_status'] ); return $this; } + /** + * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $collection + * @param bool $isFilterInStock + * @return \Magento\Catalog\Model\ResourceModel\Product\Collection $collection + */ + public function addStockDataToCollection($collection, $isFilterInStock) + { + $websiteId = $this->_storeManager->getStore($collection->getStoreId())->getWebsiteId(); + $joinCondition = $this->getConnection()->quoteInto( + 'e.entity_id = stock_status_index.product_id' . ' AND stock_status_index.website_id = ?', + $websiteId + ); + + $joinCondition .= $this->getConnection()->quoteInto( + ' AND stock_status_index.stock_id = ?', + Stock::DEFAULT_STOCK_ID + ); + + $collection->getSelect()->join( + ['stock_status_index' => $this->getMainTable()], + $joinCondition, + ['is_salable' => 'stock_status'] + ); + + if ($isFilterInStock) { + $collection->getSelect()->where( + 'stock_status_index.stock_status = ?', + Stock\Status::STATUS_IN_STOCK + ); + } + return $collection; + } + /** * Add only is in stock products filter to product collection * diff --git a/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php b/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php index b6eeea162c63d9efa13d09ce264fc5be75e234a8..8af927b7b893cd2d783ba0f4bc93b517cf460645 100644 --- a/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php +++ b/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php @@ -14,6 +14,7 @@ use Magento\CatalogInventory\Api\StockItemRepositoryInterface as StockItemReposi use Magento\CatalogInventory\Model\Indexer\Stock\Processor; use Magento\CatalogInventory\Model\ResourceModel\Stock\Item as StockItemResource; use Magento\CatalogInventory\Model\Spi\StockStateProviderInterface; +use Magento\CatalogInventory\Model\StockRegistryStorage; use Magento\Framework\DB\MapperFactory; use Magento\Framework\DB\QueryBuilderFactory; use Magento\Framework\Exception\CouldNotDeleteException; @@ -83,6 +84,11 @@ class StockItemRepository implements StockItemRepositoryInterface */ protected $dateTime; + /** + * @var StockRegistryStorage + */ + protected $stockRegistryStorage; + /** * @param StockConfigurationInterface $stockConfiguration * @param StockStateProviderInterface $stockStateProvider @@ -95,6 +101,7 @@ class StockItemRepository implements StockItemRepositoryInterface * @param TimezoneInterface $localeDate * @param Processor $indexProcessor * @param DateTime $dateTime + * @param StockRegistryStorage $stockRegistryStorage * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -108,7 +115,8 @@ class StockItemRepository implements StockItemRepositoryInterface MapperFactory $mapperFactory, TimezoneInterface $localeDate, Processor $indexProcessor, - DateTime $dateTime + DateTime $dateTime, + StockRegistryStorage $stockRegistryStorage ) { $this->stockConfiguration = $stockConfiguration; $this->stockStateProvider = $stockStateProvider; @@ -118,10 +126,10 @@ class StockItemRepository implements StockItemRepositoryInterface $this->productFactory = $productFactory; $this->queryBuilderFactory = $queryBuilderFactory; $this->mapperFactory = $mapperFactory; - $this->mapperFactory = $mapperFactory; $this->localeDate = $localeDate; $this->indexProcessor = $indexProcessor; $this->dateTime = $dateTime; + $this->stockRegistryStorage = $stockRegistryStorage; } /** @@ -163,7 +171,7 @@ class StockItemRepository implements StockItemRepositoryInterface $this->indexProcessor->reindexRow($stockItem->getProductId()); } catch (\Exception $exception) { - throw new CouldNotSaveException(__($exception->getMessage())); + throw new CouldNotSaveException(__('Unable to save Stock Item'), $exception); } return $stockItem; } @@ -201,8 +209,13 @@ class StockItemRepository implements StockItemRepositoryInterface { try { $this->resource->delete($stockItem); + $this->stockRegistryStorage->removeStockItem($stockItem->getProductId()); + $this->stockRegistryStorage->removeStockStatus($stockItem->getProductId()); } catch (\Exception $exception) { - throw new CouldNotDeleteException(__($exception->getMessage())); + throw new CouldNotDeleteException( + __('Unable to remove Stock Item with id "%1"', $stockItem->getItemId()), + $exception + ); } return true; } @@ -216,7 +229,10 @@ class StockItemRepository implements StockItemRepositoryInterface $stockItem = $this->get($id); $this->delete($stockItem); } catch (\Exception $exception) { - throw new CouldNotDeleteException(__($exception->getMessage())); + throw new CouldNotDeleteException( + __('Unable to remove Stock Item with id "%1"', $id), + $exception + ); } return true; } diff --git a/app/code/Magento/CatalogInventory/Model/Stock/StockRepository.php b/app/code/Magento/CatalogInventory/Model/Stock/StockRepository.php index ceff54964d77e7211b4c9a89487e5bc61f354d6b..e76b239876ae6ba61ece6ff0d973af1b01687600 100644 --- a/app/code/Magento/CatalogInventory/Model/Stock/StockRepository.php +++ b/app/code/Magento/CatalogInventory/Model/Stock/StockRepository.php @@ -10,6 +10,7 @@ use Magento\CatalogInventory\Api\Data\StockInterface; use Magento\CatalogInventory\Api\StockRepositoryInterface; use Magento\CatalogInventory\Model\ResourceModel\Stock as StockResource; use Magento\CatalogInventory\Model\StockFactory; +use Magento\CatalogInventory\Model\StockRegistryStorage; use Magento\Framework\DB\MapperFactory; use Magento\Framework\DB\QueryBuilderFactory; use Magento\Framework\Exception\CouldNotDeleteException; @@ -47,25 +48,33 @@ class StockRepository implements StockRepositoryInterface */ protected $mapperFactory; + /** + * @var StockRegistryStorage + */ + protected $stockRegistryStorage; + /** * @param StockResource $resource * @param StockFactory $stockFactory * @param StockCollectionInterfaceFactory $collectionFactory * @param QueryBuilderFactory $queryBuilderFactory * @param MapperFactory $mapperFactory + * @param StockRegistryStorage $stockRegistryStorage */ public function __construct( StockResource $resource, StockFactory $stockFactory, StockCollectionInterfaceFactory $collectionFactory, QueryBuilderFactory $queryBuilderFactory, - MapperFactory $mapperFactory + MapperFactory $mapperFactory, + StockRegistryStorage $stockRegistryStorage ) { $this->resource = $resource; $this->stockFactory = $stockFactory; $this->stockCollectionFactory = $collectionFactory; $this->queryBuilderFactory = $queryBuilderFactory; $this->mapperFactory = $mapperFactory; + $this->stockRegistryStorage = $stockRegistryStorage; } /** @@ -78,7 +87,7 @@ class StockRepository implements StockRepositoryInterface try { $this->resource->save($stock); } catch (\Exception $exception) { - throw new CouldNotSaveException(__($exception->getMessage())); + throw new CouldNotSaveException(__('Unable to save Stock'), $exception); } return $stock; } @@ -121,8 +130,12 @@ class StockRepository implements StockRepositoryInterface { try { $this->resource->delete($stock); + $this->stockRegistryStorage->removeStock(); } catch (\Exception $exception) { - throw new CouldNotDeleteException(__($exception->getMessage())); + throw new CouldNotDeleteException( + __('Unable to remove Stock with id "%1"', $stock->getStockId()), + $exception + ); } return true; } @@ -138,7 +151,10 @@ class StockRepository implements StockRepositoryInterface $stock = $this->get($id); $this->delete($stock); } catch (\Exception $exception) { - throw new CouldNotDeleteException(__($exception->getMessage())); + throw new CouldNotDeleteException( + __('Unable to remove Stock with id "%1"', $id), + $exception + ); } return true; } diff --git a/app/code/Magento/CatalogInventory/Model/Stock/StockStatusRepository.php b/app/code/Magento/CatalogInventory/Model/Stock/StockStatusRepository.php index 04f845ed0cd2a2bcacbf1ea38309c4170639564a..3e20bbf5927eda85524dc7e3a33830d5de073da5 100644 --- a/app/code/Magento/CatalogInventory/Model/Stock/StockStatusRepository.php +++ b/app/code/Magento/CatalogInventory/Model/Stock/StockStatusRepository.php @@ -9,6 +9,7 @@ use Magento\CatalogInventory\Api\Data\StockStatusCollectionInterfaceFactory; use Magento\CatalogInventory\Api\Data\StockStatusInterface; use Magento\CatalogInventory\Api\StockStatusRepositoryInterface; use Magento\CatalogInventory\Model\ResourceModel\Stock\Status as StockStatusResource; +use Magento\CatalogInventory\Model\StockRegistryStorage; use Magento\Framework\DB\MapperFactory; use Magento\Framework\DB\QueryBuilderFactory; use Magento\Framework\Exception\CouldNotDeleteException; @@ -45,25 +46,33 @@ class StockStatusRepository implements StockStatusRepositoryInterface */ protected $mapperFactory; + /** + * @var StockRegistryStorage + */ + protected $stockRegistryStorage; + /** * @param StockStatusResource $resource * @param StatusFactory $stockStatusFactory * @param StockStatusCollectionInterfaceFactory $collectionFactory * @param QueryBuilderFactory $queryBuilderFactory * @param MapperFactory $mapperFactory + * @param StockRegistryStorage $stockRegistryStorage */ public function __construct( StockStatusResource $resource, StatusFactory $stockStatusFactory, StockStatusCollectionInterfaceFactory $collectionFactory, QueryBuilderFactory $queryBuilderFactory, - MapperFactory $mapperFactory + MapperFactory $mapperFactory, + StockRegistryStorage $stockRegistryStorage ) { $this->resource = $resource; $this->stockStatusFactory = $stockStatusFactory; $this->stockStatusCollectionFactory = $collectionFactory; $this->queryBuilderFactory = $queryBuilderFactory; $this->mapperFactory = $mapperFactory; + $this->stockRegistryStorage = $stockRegistryStorage; } /** @@ -76,7 +85,7 @@ class StockStatusRepository implements StockStatusRepositoryInterface try { $this->resource->save($stockStatus); } catch (\Exception $exception) { - throw new CouldNotSaveException(__($exception->getMessage())); + throw new CouldNotSaveException(__('Unable to save Stock Status'), $exception); } return $stockStatus; } @@ -115,8 +124,12 @@ class StockStatusRepository implements StockStatusRepositoryInterface { try { $this->resource->delete($stockStatus); + $this->stockRegistryStorage->removeStockStatus($stockStatus->getProductId()); } catch (\Exception $exception) { - throw new CouldNotDeleteException(__($exception->getMessage())); + throw new CouldNotDeleteException( + __('Unable to remove Stock Status for product %1', $stockStatus->getProductId()), + $exception + ); } return true; } @@ -132,7 +145,10 @@ class StockStatusRepository implements StockStatusRepositoryInterface $stockStatus = $this->get($id); $this->delete($stockStatus); } catch (\Exception $exception) { - throw new CouldNotDeleteException(__($exception->getMessage())); + throw new CouldNotDeleteException( + __('Unable to remove Stock Status for product %1', $id), + $exception + ); } return true; } diff --git a/app/code/Magento/CatalogInventory/Model/StockRegistryProvider.php b/app/code/Magento/CatalogInventory/Model/StockRegistryProvider.php index e8f7ae5cc723fb8219aaf9990c1d23017cb35b4e..87e91c863220b4cd4be87f16406d54cd01691497 100644 --- a/app/code/Magento/CatalogInventory/Model/StockRegistryProvider.php +++ b/app/code/Magento/CatalogInventory/Model/StockRegistryProvider.php @@ -69,19 +69,9 @@ class StockRegistryProvider implements StockRegistryProviderInterface protected $stockStatusCriteriaFactory; /** - * @var array + * @var StockRegistryStorage */ - protected $stocks = []; - - /** - * @var array - */ - protected $stockItems = []; - - /** - * @var array - */ - protected $stockStatuses = []; + protected $stockRegistryStorage; /** * @param StockRepositoryInterface $stockRepository @@ -93,6 +83,8 @@ class StockRegistryProvider implements StockRegistryProviderInterface * @param StockCriteriaInterfaceFactory $stockCriteriaFactory * @param StockItemCriteriaInterfaceFactory $stockItemCriteriaFactory * @param StockStatusCriteriaInterfaceFactory $stockStatusCriteriaFactory + * @param StockRegistryStorage $stockRegistryStorage + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( StockRepositoryInterface $stockRepository, @@ -103,7 +95,8 @@ class StockRegistryProvider implements StockRegistryProviderInterface StockStatusInterfaceFactory $stockStatusFactory, StockCriteriaInterfaceFactory $stockCriteriaFactory, StockItemCriteriaInterfaceFactory $stockItemCriteriaFactory, - StockStatusCriteriaInterfaceFactory $stockStatusCriteriaFactory + StockStatusCriteriaInterfaceFactory $stockStatusCriteriaFactory, + StockRegistryStorage $stockRegistryStorage ) { $this->stockRepository = $stockRepository; $this->stockFactory = $stockFactory; @@ -111,10 +104,10 @@ class StockRegistryProvider implements StockRegistryProviderInterface $this->stockItemFactory = $stockItemFactory; $this->stockStatusRepository = $stockStatusRepository; $this->stockStatusFactory = $stockStatusFactory; - $this->stockCriteriaFactory = $stockCriteriaFactory; $this->stockItemCriteriaFactory = $stockItemCriteriaFactory; $this->stockStatusCriteriaFactory = $stockStatusCriteriaFactory; + $this->stockRegistryStorage = $stockRegistryStorage; } /** @@ -123,18 +116,19 @@ class StockRegistryProvider implements StockRegistryProviderInterface */ public function getStock($scopeId) { - if (!isset($this->stocks[$scopeId])) { + $stock = $this->stockRegistryStorage->getStock($scopeId); + if (null === $stock) { $criteria = $this->stockCriteriaFactory->create(); $criteria->setScopeFilter($scopeId); $collection = $this->stockRepository->getList($criteria); $stock = current($collection->getItems()); if ($stock && $stock->getStockId()) { - $this->stocks[$scopeId] = $stock; + $this->stockRegistryStorage->setStock($scopeId, $stock); } else { - return $this->stockFactory->create(); + $stock = $this->stockFactory->create(); } } - return $this->stocks[$scopeId]; + return $stock; } /** @@ -144,19 +138,19 @@ class StockRegistryProvider implements StockRegistryProviderInterface */ public function getStockItem($productId, $scopeId) { - $key = $scopeId . '/' . $productId; - if (!isset($this->stockItems[$key])) { + $stockItem = $this->stockRegistryStorage->getStockItem($productId, $scopeId); + if (null === $stockItem) { $criteria = $this->stockItemCriteriaFactory->create(); $criteria->setProductsFilter($productId); $collection = $this->stockItemRepository->getList($criteria); $stockItem = current($collection->getItems()); if ($stockItem && $stockItem->getItemId()) { - $this->stockItems[$key] = $stockItem; + $this->stockRegistryStorage->setStockItem($productId, $scopeId, $stockItem); } else { - return $this->stockItemFactory->create(); + $stockItem = $this->stockItemFactory->create(); } } - return $this->stockItems[$key]; + return $stockItem; } /** @@ -166,19 +160,19 @@ class StockRegistryProvider implements StockRegistryProviderInterface */ public function getStockStatus($productId, $scopeId) { - $key = $scopeId . '/' . $productId; - if (!isset($this->stockStatuses[$key])) { + $stockStatus = $this->stockRegistryStorage->getStockStatus($productId, $scopeId); + if (null === $stockStatus) { $criteria = $this->stockStatusCriteriaFactory->create(); $criteria->setProductsFilter($productId); $criteria->setScopeFilter($scopeId); $collection = $this->stockStatusRepository->getList($criteria); $stockStatus = current($collection->getItems()); if ($stockStatus && $stockStatus->getProductId()) { - $this->stockStatuses[$key] = $stockStatus; + $this->stockRegistryStorage->setStockStatus($productId, $scopeId, $stockStatus); } else { - return $this->stockStatusFactory->create(); + $stockStatus = $this->stockStatusFactory->create(); } } - return $this->stockStatuses[$key]; + return $stockStatus; } } diff --git a/app/code/Magento/CatalogInventory/Model/StockRegistryStorage.php b/app/code/Magento/CatalogInventory/Model/StockRegistryStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..b7c29e7fc9aa163d0ea8142348d7a28d0a37b310 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Model/StockRegistryStorage.php @@ -0,0 +1,133 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogInventory\Model; + +use Magento\CatalogInventory\Api\Data\StockInterface; +use Magento\CatalogInventory\Api\Data\StockItemInterface; +use Magento\CatalogInventory\Api\Data\StockStatusInterface; + +/** + * Class StockRegistryStorage + */ +class StockRegistryStorage +{ + /** + * @var array + */ + protected $stocks = []; + + /** + * @var array + */ + private $stockItems = []; + + /** + * @var array + */ + private $stockStatuses = []; + + /** + * @param int $scopeId + * @return StockInterface + */ + public function getStock($scopeId) + { + return isset($this->stocks[$scopeId]) ? $this->stocks[$scopeId] : null; + } + + /** + * @param int $scopeId + * @param StockInterface $value + * @return void + */ + public function setStock($scopeId, StockInterface $value) + { + $this->stocks[$scopeId] = $value; + } + + /** + * @param int|null $scopeId + * @return void + */ + public function removeStock($scopeId = null) + { + if (null === $scopeId) { + $this->stocks = []; + } else { + unset($this->stocks[$scopeId]); + } + } + + /** + * @param int $productId + * @param int $scopeId + * @return StockItemInterface + */ + public function getStockItem($productId, $scopeId) + { + return isset($this->stockItems[$productId][$scopeId]) ? $this->stockItems[$productId][$scopeId] : null; + } + + /** + * @param int $productId + * @param int $scopeId + * @param StockItemInterface $value + * @return void + */ + public function setStockItem($productId, $scopeId, StockItemInterface $value) + { + $this->stockItems[$productId][$scopeId] = $value; + } + + /** + * @param int $productId + * @param int|null $scopeId + * @return void + */ + public function removeStockItem($productId, $scopeId = null) + { + if (null === $scopeId) { + unset($this->stockItems[$productId]); + } else { + unset($this->stockItems[$productId][$scopeId]); + } + } + + /** + * @param int $productId + * @param int $scopeId + * @return StockStatusInterface + */ + public function getStockStatus($productId, $scopeId) + { + return isset($this->stockStatuses[$productId][$scopeId]) ? $this->stockStatuses[$productId][$scopeId] : null; + } + + /** + * @param int $productId + * @param int $scopeId + * @param StockStatusInterface $value + * @return void + */ + public function setStockStatus($productId, $scopeId, StockStatusInterface $value) + { + $this->stockStatuses[$productId][$scopeId] = $value; + } + + /** + * @param int $productId + * @param int|null $scopeId + * @return void + */ + public function removeStockStatus($productId, $scopeId = null) + { + if (null === $scopeId) { + unset($this->stockStatuses[$productId]); + } else { + unset($this->stockStatuses[$productId][$scopeId]); + } + } +} diff --git a/app/code/Magento/CatalogInventory/Model/StockState.php b/app/code/Magento/CatalogInventory/Model/StockState.php index c3d0dc9aef0fd317d0d69e8b1b06b01b5af4f7e7..96b3162966211b4c21d0e206a12246b91b092777 100644 --- a/app/code/Magento/CatalogInventory/Model/StockState.php +++ b/app/code/Magento/CatalogInventory/Model/StockState.php @@ -146,7 +146,7 @@ class StockState implements StockStateInterface * @param float $qtyToCheck * @param float $origQty * @param int $scopeId - * @return \Magento\Framework\DataObject + * @return int */ public function checkQuoteItemQty($productId, $itemQty, $qtyToCheck, $origQty, $scopeId = null) { diff --git a/app/code/Magento/CatalogInventory/Observer/AddStockStatusToCollectionObserver.php b/app/code/Magento/CatalogInventory/Observer/AddStockStatusToCollectionObserver.php deleted file mode 100644 index e777f81ba68425cce30934f2852771aac7acfec5..0000000000000000000000000000000000000000 --- a/app/code/Magento/CatalogInventory/Observer/AddStockStatusToCollectionObserver.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\CatalogInventory\Observer; - -use Magento\Framework\Event\ObserverInterface; -use Magento\Framework\Event\Observer as EventObserver; - -/** - * Catalog inventory module observer - */ -class AddStockStatusToCollectionObserver implements ObserverInterface -{ - /** - * @var \Magento\CatalogInventory\Helper\Stock - */ - protected $stockHelper; - - /** - * @param \Magento\CatalogInventory\Helper\Stock $stockHelper - */ - public function __construct(\Magento\CatalogInventory\Helper\Stock $stockHelper) - { - $this->stockHelper = $stockHelper; - } - - /** - * Add information about product stock status to collection - * Used in for product collection after load - * - * @param EventObserver $observer - * @return void - */ - public function execute(EventObserver $observer) - { - $productCollection = $observer->getEvent()->getCollection(); - $this->stockHelper->addStockStatusToProducts($productCollection); - } -} diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Block/Stockqty/DefaultStockqtyTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Block/Stockqty/DefaultStockqtyTest.php index fd247ef871523bf14f3ee2480af22f3c648d6627..5c9396d32bfb526d7bd3da92856c00513f3c01ba 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Block/Stockqty/DefaultStockqtyTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Block/Stockqty/DefaultStockqtyTest.php @@ -20,11 +20,6 @@ class DefaultStockqtyTest extends \PHPUnit_Framework_TestCase */ protected $registryMock; - /** - * @var \Magento\CatalogInventory\Api\StockStateInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $stockState; - /** * @var \PHPUnit_Framework_MockObject_MockObject */ @@ -39,13 +34,6 @@ class DefaultStockqtyTest extends \PHPUnit_Framework_TestCase { $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->registryMock = $this->getMock('Magento\Framework\Registry', [], [], '', false); - $this->stockState = $this->getMock( - 'Magento\CatalogInventory\Api\StockStateInterface', - [], - [], - '', - false - ); $this->stockRegistryMock = $this->getMockBuilder('Magento\CatalogInventory\Api\StockRegistryInterface') ->disableOriginalConstructor() ->getMock(); @@ -56,7 +44,6 @@ class DefaultStockqtyTest extends \PHPUnit_Framework_TestCase 'Magento\CatalogInventory\Block\Stockqty\DefaultStockqty', [ 'registry' => $this->registryMock, - 'stockState' => $this->stockState, 'stockRegistry' => $this->stockRegistryMock, 'scopeConfig' => $this->scopeConfigMock ] @@ -112,10 +99,13 @@ class DefaultStockqtyTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue($product)); if ($productId) { - $this->stockState->expects($this->once()) - ->method('getStockQty') + $stockStatus = $this->getMockBuilder(\Magento\CatalogInventory\Api\Data\StockStatusInterface::class) + ->getMockForAbstractClass(); + $stockStatus->expects($this->any())->method('getQty')->willReturn($productStockQty); + $this->stockRegistryMock->expects($this->once()) + ->method('getStockStatus') ->with($this->equalTo($productId), $this->equalTo($websiteId)) - ->will($this->returnValue($productStockQty)); + ->will($this->returnValue($stockStatus)); } } $this->assertSame($expectedQty, $this->block->getStockQty()); @@ -157,10 +147,6 @@ class DefaultStockqtyTest extends \PHPUnit_Framework_TestCase ->method('getStockItem') ->with($productId) ->willReturn($stockItemMock); - $this->stockState->expects($this->once()) - ->method('getStockQty') - ->with($productId, $storeMock) - ->willReturn($stockQty); $this->assertEquals($stockQty, $this->block->getStockQtyLeft()); } diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Helper/StockTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Helper/StockTest.php index cb4209de616e798498590461c9db2e3e1891657d..01356277c5acf1f1a99f8ddfd374d85daabe6332 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Helper/StockTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Helper/StockTest.php @@ -182,10 +182,10 @@ class StockTest extends \PHPUnit_Framework_TestCase ->getMock(); $stockStatusMock = $this->getMockBuilder('Magento\CatalogInventory\Model\ResourceModel\Stock\Status') ->disableOriginalConstructor() - ->setMethods(['addIsInStockFilterToCollection']) + ->setMethods(['addStockDataToCollection']) ->getMock(); $stockStatusMock->expects($this->once()) - ->method('addIsInStockFilterToCollection') + ->method('addStockDataToCollection') ->with($collectionMock); $this->statusFactoryMock->expects($this->once()) ->method('create') diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/AddStockStatusToCollectionTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/AddStockStatusToCollectionTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9d1796cbe117e781d7a88ca76cc4dd058fcece8b --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/AddStockStatusToCollectionTest.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogInventory\Test\Unit\Model; + +use Magento\CatalogInventory\Model\AddStockStatusToCollection; + +class AddStockStatusToCollectionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var AddStockStatusToCollection + */ + protected $plugin; + + /** + * @var \Magento\CatalogInventory\Helper\Stock|\PHPUnit_Framework_MockObject_MockObject + */ + protected $stockHelper; + + protected function setUp() + { + $this->stockHelper = $this->getMock('Magento\CatalogInventory\Helper\Stock', [], [], '', false); + $this->plugin = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject( + 'Magento\CatalogInventory\Model\AddStockStatusToCollection', + [ + 'stockHelper' => $this->stockHelper, + ] + ); + } + + public function testAddStockStatusToCollection() + { + $productCollection = $this->getMockBuilder('Magento\Catalog\Model\ResourceModel\Product\Collection') + ->disableOriginalConstructor() + ->getMock(); + + $this->stockHelper->expects($this->once()) + ->method('addIsInStockFilterToCollection') + ->with($productCollection) + ->will($this->returnSelf()); + + $this->plugin->beforeLoad($productCollection); + } +} diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php index a2db32bdb642a6c50223fe5812047431f2861fb3..76c6e6cf923c563d158d3b72503333f9b35af07b 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php @@ -5,8 +5,10 @@ */ namespace Magento\CatalogInventory\Test\Unit\Model\Stock; -use \Magento\CatalogInventory\Model\Stock\StockItemRepository; -use \Magento\CatalogInventory\Api\Data as InventoryApiData; +use Magento\CatalogInventory\Model\Stock\StockItemRepository; +use Magento\CatalogInventory\Api\Data as InventoryApiData; +use Magento\CatalogInventory\Model\StockRegistryStorage; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; /** * Class StockItemRepositoryTest @@ -24,6 +26,7 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase * @var \Magento\CatalogInventory\Model\Stock\Item |\PHPUnit_Framework_MockObject_MockObject */ protected $stockItemMock; + /** * @var \Magento\CatalogInventory\Api\StockConfigurationInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -84,6 +87,14 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase */ protected $dateTime; + /** + * @var StockRegistryStorage|\PHPUnit_Framework_MockObject_MockObject + */ + protected $stockRegistryStorage; + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ protected function setUp() { $this->stockItemMock = $this->getMockBuilder('\Magento\CatalogInventory\Model\Stock\Item') @@ -168,24 +179,36 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase '', false ); + $this->stockRegistryStorage = $this->getMockBuilder(StockRegistryStorage::class) + ->disableOriginalConstructor() + ->getMock(); - $this->model = new StockItemRepository( - $this->stockConfigurationMock, - $this->stockStateProviderMock, - $this->stockItemResourceMock, - $this->stockItemFactoryMock, - $this->stockItemCollectionMock, - $this->productFactoryMock, - $this->queryBuilderFactoryMock, - $this->mapperMock, - $this->localeDateMock, - $this->indexProcessorMock, - $this->dateTime + $this->model = (new ObjectManager($this))->getObject( + StockItemRepository::class, + [ + 'stockConfiguration' => $this->stockConfigurationMock, + 'stockStateProvider' => $this->stockStateProviderMock, + 'resource' => $this->stockItemResourceMock, + 'stockItemFactory' => $this->stockItemFactoryMock, + 'stockItemCollectionFactory' => $this->stockItemCollectionMock, + 'productFactory' => $this->productFactoryMock, + 'queryBuilderFactory' => $this->queryBuilderFactoryMock, + 'mapperFactory' => $this->mapperMock, + 'localeDate' => $this->localeDateMock, + 'indexProcessor' => $this->indexProcessorMock, + 'dateTime' => $this->dateTime, + 'stockRegistryStorage' => $this->stockRegistryStorage, + ] ); } public function testDelete() { + $productId = 1; + $this->stockItemMock->expects($this->atLeastOnce())->method('getProductId')->willReturn($productId); + $this->stockRegistryStorage->expects($this->once())->method('removeStockItem')->with($productId); + $this->stockRegistryStorage->expects($this->once())->method('removeStockStatus')->with($productId); + $this->stockItemResourceMock->expects($this->once()) ->method('delete') ->with($this->stockItemMock) @@ -220,7 +243,7 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase /** * @expectedException \Magento\Framework\Exception\CouldNotDeleteException - * @expectedExceptionMessage Stock Item with id "1" does not exist. + * @expectedExceptionMessage Unable to remove Stock Item with id "1" */ public function testDeleteByIdException() { @@ -286,6 +309,8 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase $this->stockItemMock->expects($this->any())->method('getProductId')->willReturn($productId); $this->productMock->expects($this->once())->method('load')->with($productId)->willReturnSelf(); $this->productMock->expects($this->once())->method('getId')->willReturn(null); + $this->stockRegistryStorage->expects($this->never())->method('removeStockItem'); + $this->stockRegistryStorage->expects($this->never())->method('removeStockStatus'); $this->assertEquals($this->stockItemMock, $this->model->save($this->stockItemMock)); } diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockRepositoryTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockRepositoryTest.php index 3e9394a3995e53102bae88c0496080f15af113c3..779958ccf4c20fc650489926e4a907ac4d630bca 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockRepositoryTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockRepositoryTest.php @@ -5,7 +5,9 @@ */ namespace Magento\CatalogInventory\Test\Unit\Model\Stock; -use \Magento\CatalogInventory\Model\Stock\StockRepository; +use Magento\CatalogInventory\Model\Stock\StockRepository; +use Magento\CatalogInventory\Model\StockRegistryStorage; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; /** * Class StockRepositoryTest @@ -47,9 +49,13 @@ class StockRepositoryTest extends \PHPUnit_Framework_TestCase */ protected $mapperMock; + /** + * @var StockRegistryStorage|\PHPUnit_Framework_MockObject_MockObject + */ + protected $stockRegistryStorage; + protected function setUp() { - $this->stockMock = $this->getMockBuilder('\Magento\CatalogInventory\Model\Stock') ->disableOriginalConstructor() ->getMock(); @@ -77,13 +83,20 @@ class StockRepositoryTest extends \PHPUnit_Framework_TestCase $this->mapperMock = $this->getMockBuilder('Magento\Framework\DB\MapperFactory') ->disableOriginalConstructor() ->getMock(); + $this->stockRegistryStorage = $this->getMockBuilder(StockRegistryStorage::class) + ->disableOriginalConstructor() + ->getMock(); - $this->model = new StockRepository( - $this->stockResourceMock, - $this->stockFactoryMock, - $this->stockCollectionMock, - $this->queryBuilderFactoryMock, - $this->mapperMock + $this->model = (new ObjectManager($this))->getObject( + StockRepository::class, + [ + 'resource' => $this->stockResourceMock, + 'stockFactory' => $this->stockFactoryMock, + 'collectionFactory' => $this->stockCollectionMock, + 'queryBuilderFactory' => $this->queryBuilderFactoryMock, + 'mapperFactory' => $this->mapperMock, + 'stockRegistryStorage' => $this->stockRegistryStorage, + ] ); } @@ -137,6 +150,8 @@ class StockRepositoryTest extends \PHPUnit_Framework_TestCase public function testDelete() { + $this->stockRegistryStorage->expects($this->once())->method('removeStock'); + $this->stockResourceMock->expects($this->once()) ->method('delete') ->with($this->stockMock) @@ -171,7 +186,7 @@ class StockRepositoryTest extends \PHPUnit_Framework_TestCase /** * @expectedException \Magento\Framework\Exception\CouldNotDeleteException - * @expectedExceptionMessage Stock with id "1" does not exist. + * @expectedExceptionMessage Unable to remove Stock with id "1" */ public function testDeleteByIdException() { diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockStatusRepositoryTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockStatusRepositoryTest.php index 5c8d3efe356db03d40ec8ba1d4b43c0b32e336b2..5feb8e85af5421ab320fb77f9938c2458bffca80 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockStatusRepositoryTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockStatusRepositoryTest.php @@ -5,8 +5,10 @@ */ namespace Magento\CatalogInventory\Test\Unit\Model\Stock; -use \Magento\CatalogInventory\Model\Stock\StockStatusRepository; -use \Magento\CatalogInventory\Api\Data as InventoryApiData; +use Magento\CatalogInventory\Model\Stock\StockStatusRepository; +use Magento\CatalogInventory\Api\Data as InventoryApiData; +use Magento\CatalogInventory\Model\StockRegistryStorage; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; /** * Class StockStatusRepositoryTest @@ -48,6 +50,11 @@ class StockStatusRepositoryTest extends \PHPUnit_Framework_TestCase */ protected $mapperMock; + /** + * @var StockRegistryStorage|\PHPUnit_Framework_MockObject_MockObject + */ + protected $stockRegistryStorage; + protected function setUp() { $this->stockStatusMock = $this->getMockBuilder('\Magento\CatalogInventory\Model\Stock\Status') @@ -76,13 +83,20 @@ class StockStatusRepositoryTest extends \PHPUnit_Framework_TestCase $this->mapperMock = $this->getMockBuilder('Magento\Framework\DB\MapperFactory') ->disableOriginalConstructor() ->getMock(); + $this->stockRegistryStorage = $this->getMockBuilder(StockRegistryStorage::class) + ->disableOriginalConstructor() + ->getMock(); - $this->model = new StockStatusRepository( - $this->stockStatusResourceMock, - $this->stockStatusFactoryMock, - $this->stockStatusCollectionMock, - $this->queryBuilderFactoryMock, - $this->mapperMock + $this->model = (new ObjectManager($this))->getObject( + StockStatusRepository::class, + [ + 'resource' => $this->stockStatusResourceMock, + 'stockStatusFactory' => $this->stockStatusFactoryMock, + 'collectionFactory' => $this->stockStatusCollectionMock, + 'queryBuilderFactory' => $this->queryBuilderFactoryMock, + 'mapperFactory' => $this->mapperMock, + 'stockRegistryStorage' => $this->stockRegistryStorage, + ] ); } @@ -136,6 +150,10 @@ class StockStatusRepositoryTest extends \PHPUnit_Framework_TestCase public function testDelete() { + $productId = 1; + $this->stockStatusMock->expects($this->atLeastOnce())->method('getProductId')->willReturn($productId); + $this->stockRegistryStorage->expects($this->once())->method('removeStockStatus')->with($productId); + $this->stockStatusResourceMock->expects($this->once()) ->method('delete') ->with($this->stockStatusMock) diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Observer/AddStockStatusToCollectionObserverTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Observer/AddStockStatusToCollectionObserverTest.php deleted file mode 100644 index ebba8ffe469324f70f4871d73cc438690b13a911..0000000000000000000000000000000000000000 --- a/app/code/Magento/CatalogInventory/Test/Unit/Observer/AddStockStatusToCollectionObserverTest.php +++ /dev/null @@ -1,75 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\CatalogInventory\Test\Unit\Observer; - -use Magento\CatalogInventory\Observer\AddStockStatusToCollectionObserver; - -class AddStockStatusToCollectionObserverTest extends \PHPUnit_Framework_TestCase -{ - /** - * @var AddStockStatusToCollectionObserver - */ - protected $observer; - - /** - * @var \Magento\CatalogInventory\Helper\Stock|\PHPUnit_Framework_MockObject_MockObject - */ - protected $stockHelper; - - /** - * @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject - */ - protected $event; - - /** - * @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject - */ - protected $eventObserver; - - protected function setUp() - { - $this->stockHelper = $this->getMock('Magento\CatalogInventory\Helper\Stock', [], [], '', false); - - $this->event = $this->getMockBuilder('Magento\Framework\Event') - ->disableOriginalConstructor() - ->setMethods(['getCollection']) - ->getMock(); - - $this->eventObserver = $this->getMockBuilder('Magento\Framework\Event\Observer') - ->disableOriginalConstructor() - ->setMethods(['getEvent']) - ->getMock(); - - $this->eventObserver->expects($this->atLeastOnce()) - ->method('getEvent') - ->will($this->returnValue($this->event)); - - $this->observer = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject( - 'Magento\CatalogInventory\Observer\AddStockStatusToCollectionObserver', - [ - 'stockHelper' => $this->stockHelper, - ] - ); - } - - public function testAddStockStatusToCollection() - { - $productCollection = $this->getMockBuilder('Magento\Catalog\Model\ResourceModel\Product\Collection') - ->disableOriginalConstructor() - ->getMock(); - - $this->event->expects($this->once()) - ->method('getCollection') - ->will($this->returnValue($productCollection)); - - $this->stockHelper->expects($this->once()) - ->method('addStockStatusToProducts') - ->with($productCollection) - ->will($this->returnSelf()); - - $this->observer->execute($this->eventObserver); - } -} diff --git a/app/code/Magento/CatalogInventory/etc/events.xml b/app/code/Magento/CatalogInventory/etc/events.xml index 4252a093c4fe710f7a8a0cb64abb5777f0b5ef1a..96b08cf5123fbc58c53dfd42de556fd5eb1a2f21 100644 --- a/app/code/Magento/CatalogInventory/etc/events.xml +++ b/app/code/Magento/CatalogInventory/etc/events.xml @@ -12,12 +12,6 @@ <event name="catalog_product_load_after"> <observer name="inventory" instance="Magento\CatalogInventory\Observer\AddInventoryDataObserver"/> </event> - <event name="catalog_product_collection_load_after"> - <observer name="inventory" instance="Magento\CatalogInventory\Observer\AddStockStatusToCollectionObserver"/> - </event> - <event name="sales_quote_item_collection_products_after_load"> - <observer name="inventory" instance="Magento\CatalogInventory\Observer\AddStockStatusToCollectionObserver"/> - </event> <event name="sales_quote_item_qty_set_after"> <observer name="inventory" instance="Magento\CatalogInventory\Observer\QuantityValidatorObserver"/> </event> diff --git a/app/code/Magento/CatalogInventory/etc/frontend/di.xml b/app/code/Magento/CatalogInventory/etc/frontend/di.xml index baa96cc29bb52cc66798ea39b2fe4a75397eef87..5d5fa9aab000f32f7eca3253bfc176f4e9f6e64a 100644 --- a/app/code/Magento/CatalogInventory/etc/frontend/di.xml +++ b/app/code/Magento/CatalogInventory/etc/frontend/di.xml @@ -9,4 +9,7 @@ <type name="Magento\Catalog\Model\Product\Link"> <plugin name="isInStockFilter" type="Magento\CatalogInventory\Model\Plugin\ProductLinks" /> </type> + <type name="Magento\Catalog\Model\ResourceModel\Product\Collection"> + <plugin name="add_stock_information" type="Magento\CatalogInventory\Model\AddStockStatusToCollection" /> + </type> </config> diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php index 38c87413b2b410f375a4c58156e4fbe143f27b35..da07e516427d1c96b4e1aa29cda43e28f7e47b52 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php @@ -75,8 +75,7 @@ class DataProvider $derivedTable->from( ['main_table' => $this->resource->getTableName('catalog_category_product_index')], [ - 'entity_id' => 'product_id', - 'value' => 'category_id', + 'value' => 'category_id' ] )->where('main_table.store_id = ?', $currentScopeId); $derivedTable->joinInner( diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php index b326b2a59d72db1797d4e5ca36a39012bf85b616..01c123190d2e224f819fb8423084646b53e3d638 100644 --- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php +++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php @@ -92,17 +92,24 @@ class Attribute extends AbstractFilter if (empty($option['value'])) { continue; } + + $value = $option['value']; + + $count = isset($optionsFacetedData[$value]['count']) + ? (int)$optionsFacetedData[$value]['count'] + : 0; // Check filter type - if (empty($optionsFacetedData[$option['value']]['count']) - || ($this->getAttributeIsFilterable($attribute) == static::ATTRIBUTE_OPTIONS_ONLY_WITH_RESULTS - && !$this->isOptionReducesResults($optionsFacetedData[$option['value']]['count'], $productSize)) + if ( + $count === 0 + && $this->getAttributeIsFilterable($attribute) === static::ATTRIBUTE_OPTIONS_ONLY_WITH_RESULTS + && !$this->isOptionReducesResults($count, $productSize) ) { continue; } $this->itemDataBuilder->addItemData( $this->tagFilter->filter($option['label']), - $option['value'], - $optionsFacetedData[$option['value']]['count'] + $value, + $count ); } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php index 77d097019a37a585a248c65c5cee8910c9571e30..35b502da8be49b450b0587b73492084f0d7d164b 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php @@ -283,15 +283,6 @@ class AttributeTest extends \PHPUnit_Framework_TestCase $this->attribute->expects($this->exactly(2)) ->method('getAttributeCode') ->will($this->returnValue($attributeCode)); - $this->attribute->expects($this->at(3)) - ->method('getIsFilterable') - ->will($this->returnValue(1)); - $this->attribute->expects($this->at(4)) - ->method('getIsFilterable') - ->will($this->returnValue(2)); - $this->attribute->expects($this->at(5)) - ->method('getIsFilterable') - ->will($this->returnValue(1)); $this->target->setAttributeModel($this->attribute); diff --git a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php index 018fed2f513acd285b86c0bc6a13973144bd6cf5..30506d354281c5de5bdc80422f4e1d1be51dbb06 100644 --- a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php +++ b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php @@ -24,13 +24,11 @@ class PaymentInformationManagement implements \Magento\Checkout\Api\PaymentInfor */ protected $cartManagement; - /** * @var PaymentDetailsFactory */ protected $paymentDetailsFactory; - /** * @var \Magento\Quote\Api\CartTotalRepositoryInterface */ diff --git a/app/code/Magento/Config/Block/System/Config/Form/Field.php b/app/code/Magento/Config/Block/System/Config/Form/Field.php index a138be8accda554d263fb1e1f6f01d9bf6159e7f..b2bb1f1103af70af73311f0a369377d25be5c504 100644 --- a/app/code/Magento/Config/Block/System/Config/Form/Field.php +++ b/app/code/Magento/Config/Block/System/Config/Form/Field.php @@ -180,7 +180,7 @@ class Field extends \Magento\Backend\Block\Template implements \Magento\Framewor * @param string $html * @return string */ - protected function _decorateRowHtml($element, $html) + protected function _decorateRowHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element, $html) { return '<tr id="row_' . $element->getHtmlId() . '">' . $html . '</tr>'; } diff --git a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/NotificationTest.php b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/NotificationTest.php index a86323a4d9880b9071edc24f456b4ffd3268228a..ffb8c3bb285788cd8cbf419c31ee545598bbe245 100644 --- a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/NotificationTest.php +++ b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/NotificationTest.php @@ -20,6 +20,13 @@ class NotificationTest extends \PHPUnit_Framework_TestCase /** @var \Magento\Framework\Stdlib\DateTime\DateTimeFormatterInterface $dateTimeFormatter */ $dateTimeFormatter = $objectManager->getObject('Magento\Framework\Stdlib\DateTime\DateTimeFormatter'); + $localeResolver = $objectManager->getObject('Magento\Framework\Locale\Resolver'); + + $reflection = new \ReflectionClass('Magento\Framework\Stdlib\DateTime\DateTimeFormatter'); + $reflectionProperty = $reflection->getProperty('localeResolver'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($dateTimeFormatter, $localeResolver); + $formattedDate = $dateTimeFormatter->formatObject($testDatetime); $htmlId = 'test_HTML_id'; diff --git a/app/code/Magento/Config/etc/system.xsd b/app/code/Magento/Config/etc/system.xsd index 62ab3f085391d0f7c64c922655bd2aa15634775c..ecede8b085fa9973cb48a4a39ad9e0560de41074 100644 --- a/app/code/Magento/Config/etc/system.xsd +++ b/app/code/Magento/Config/etc/system.xsd @@ -95,6 +95,7 @@ <xs:element name="header_css" type="xs:string" /> <xs:element name="resource" type="typeAclResourceId" /> <xs:element ref="group" /> + <xs:element name="frontend_model" type="typeModel" /> </xs:choice> </xs:sequence> <xs:attributeGroup ref="elementsAttributeGroup"/> diff --git a/app/code/Magento/Config/etc/system_file.xsd b/app/code/Magento/Config/etc/system_file.xsd index 7d134b518b12018112cc76775e973c1da1a32ffb..514e287e6b0014853590e4ca97398aa2dfb99dd3 100644 --- a/app/code/Magento/Config/etc/system_file.xsd +++ b/app/code/Magento/Config/etc/system_file.xsd @@ -106,6 +106,7 @@ <xs:element name="resource" type="typeAclResourceId" /> <xs:element ref="group" /> <xs:element name="include" type="includeType"/> + <xs:element name="frontend_model" type="typeModel" /> </xs:choice> </xs:sequence> diff --git a/app/code/Magento/ConfigurableProduct/Helper/Product/Options/Loader.php b/app/code/Magento/ConfigurableProduct/Helper/Product/Options/Loader.php index e71e7ccf2f377261b46efc22b974db0d25d22d39..8fe421128e2e437664276eaad91d6b7dcf61dec0 100644 --- a/app/code/Magento/ConfigurableProduct/Helper/Product/Options/Loader.php +++ b/app/code/Magento/ConfigurableProduct/Helper/Product/Options/Loader.php @@ -9,6 +9,7 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\ConfigurableProduct\Api\Data\OptionInterface; use Magento\ConfigurableProduct\Model\Product\Type\Configurable; use Magento\ConfigurableProduct\Api\Data\OptionValueInterfaceFactory; +use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface; /** * Class Loader @@ -20,14 +21,23 @@ class Loader */ private $optionValueFactory; + /** + * @var JoinProcessorInterface + */ + private $extensionAttributesJoinProcessor; + /** * ReadHandler constructor * * @param OptionValueInterfaceFactory $optionValueFactory + * @param JoinProcessorInterface $extensionAttributesJoinProcessor */ - public function __construct(OptionValueInterfaceFactory $optionValueFactory) - { + public function __construct( + OptionValueInterfaceFactory $optionValueFactory, + JoinProcessorInterface $extensionAttributesJoinProcessor + ) { $this->optionValueFactory = $optionValueFactory; + $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor; } /** @@ -39,8 +49,8 @@ class Loader $options = []; /** @var Configurable $typeInstance */ $typeInstance = $product->getTypeInstance(); - $attributeCollection = $typeInstance->getConfigurableAttributes($product); - + $attributeCollection = $typeInstance->getConfigurableAttributeCollection($product); + $this->extensionAttributesJoinProcessor->process($attributeCollection); foreach ($attributeCollection as $attribute) { $values = []; $attributeOptions = $attribute->getOptions(); diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php index 727b090c73e5b1139436a539943954578ed22f35..b7c4a060bf7e4e685bec38bebe36d657662f130c 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php @@ -134,6 +134,11 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType */ protected $extensionAttributesJoinProcessor; + /** + * @var \Magento\Framework\Cache\FrontendInterface + */ + private $cache; + /** * @var MetadataPool */ @@ -159,7 +164,7 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType * @param \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable $catalogProductTypeConfigurable * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface $extensionAttributesJoinProcessor - * @param MetadataPool $metadataPool + * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -180,6 +185,7 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable $catalogProductTypeConfigurable, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface $extensionAttributesJoinProcessor, + \Magento\Framework\Cache\FrontendInterface $cache, MetadataPool $metadataPool ) { $this->typeConfigurableFactory = $typeConfigurableFactory; @@ -191,7 +197,7 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType $this->_scopeConfig = $scopeConfig; $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor; $this->metadataPool = $metadataPool; - + $this->cache = $cache; parent::__construct( $catalogProductOption, $eavConfig, @@ -203,6 +209,7 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType $logger, $productRepository ); + } /** @@ -364,9 +371,21 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType ['group' => 'CONFIGURABLE', 'method' => __METHOD__] ); if (!$product->hasData($this->_configurableAttributes)) { - $configurableAttributes = $this->getConfigurableAttributeCollection($product); - $this->extensionAttributesJoinProcessor->process($configurableAttributes); - $configurableAttributes->orderByPosition()->load(); + $cacheId = __CLASS__ . $product->getId(); + $configurableAttributes = $this->cache->load($cacheId); + if ($configurableAttributes) { + $configurableAttributes = unserialize($configurableAttributes); + $configurableAttributes->setProductFilter($product); + } else { + $configurableAttributes = $this->getConfigurableAttributeCollection($product); + $this->extensionAttributesJoinProcessor->process($configurableAttributes); + $configurableAttributes->orderByPosition()->load(); + $this->cache->save( + serialize($configurableAttributes), + $cacheId, + $product->getIdentities() + ); + } $product->setData($this->_configurableAttributes, $configurableAttributes); } \Magento\Framework\Profiler::stop('CONFIGURABLE:' . __METHOD__); @@ -461,14 +480,6 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType ['group' => 'CONFIGURABLE', 'method' => __METHOD__] ); if (!$product->hasData($this->_usedProducts)) { - if (is_null($requiredAttributeIds) && is_null($product->getData($this->_configurableAttributes))) { - // If used products load before attributes, we will load attributes. - $this->getConfigurableAttributes($product); - // After attributes loading products loaded too. - \Magento\Framework\Profiler::stop('CONFIGURABLE:' . __METHOD__); - return $product->getData($this->_usedProducts); - } - $usedProducts = []; $collection = $this->getUsedProductCollection($product) ->addAttributeToSelect('*') @@ -564,6 +575,8 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType public function save($product) { parent::save($product); + $cacheId = __CLASS__ . $product->getId(); + $this->cache->remove($cacheId); $extensionAttributes = $product->getExtensionAttributes(); diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php index 46579e63d12c104e662cf4da26afb3429906325b..2afb897268a1b4f9fb466df0f7a382050acd2669 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php @@ -267,4 +267,25 @@ class Attribute extends \Magento\Framework\Model\AbstractExtensibleModel impleme return $this->setData(self::KEY_PRODUCT_ID, $value); } //@codeCoverageIgnoreEnd + + /** + * @inheritdoc + */ + public function __sleep() + { + return array_diff( + parent::__sleep(), + ['metadataPool'] + ); + } + + /** + * @inheritdoc + */ + public function __wakeup() + { + parent::__wakeup(); + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $this->metadataPool = $objectManager->get(MetadataPool::class); + } } diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Indexer/Stock/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Indexer/Stock/Configurable.php index 4a2233b76a891ab4b9593258c7ef60bce00da763..01a6ad969f320ca8da089139dca0754a74029543 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Indexer/Stock/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Indexer/Stock/Configurable.php @@ -66,19 +66,7 @@ class Configurable extends \Magento\CatalogInventory\Model\ResourceModel\Indexer $psExpr = $this->_addAttributeToSelect($select, 'status', 'e.' . $metadata->getLinkField(), 'cs.store_id'); $psCond = $connection->quoteInto($psExpr . '=?', ProductStatus::STATUS_ENABLED); - if ($this->_isManageStock()) { - $statusExpr = $connection->getCheckSql( - 'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 0', - 1, - 'cisi.is_in_stock' - ); - } else { - $statusExpr = $connection->getCheckSql( - 'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 1', - 'cisi.is_in_stock', - 1 - ); - } + $statusExpr = $this->getStatusExpression($connection); $optExpr = $connection->getCheckSql("{$psCond} AND le.required_options = 0", 'i.stock_status', 0); $stockStatusExpr = $connection->getLeastSql(["MAX({$optExpr})", "MIN({$statusExpr})"]); diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute/Collection.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute/Collection.php index 0a11e18b822e0007eefed9205f6f04b27adb70dd..9027608058c368647b5858c5cc3c55e6d9de5e82 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute/Collection.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute/Collection.php @@ -7,7 +7,10 @@ */ namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Model\Entity\MetadataPool; use Magento\Catalog\Api\Data\ProductInterface; @@ -41,7 +44,7 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab /** * Catalog product type configurable * - * @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable + * @var Configurable */ protected $_productTypeConfigurable; @@ -63,9 +66,9 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy * @param \Magento\Framework\Event\ManagerInterface $eventManager * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\ConfigurableProduct\Model\Product\Type\Configurable $catalogProductTypeConfigurable + * @param Configurable $catalogProductTypeConfigurable * @param \Magento\Catalog\Helper\Data $catalogData - * @param \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute $resource + * @param Attribute $resource * @param MetadataPool $metadataPool * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -76,9 +79,9 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy, \Magento\Framework\Event\ManagerInterface $eventManager, \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\ConfigurableProduct\Model\Product\Type\Configurable $catalogProductTypeConfigurable, + Configurable $catalogProductTypeConfigurable, \Magento\Catalog\Helper\Data $catalogData, - \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute $resource, + Attribute $resource, MetadataPool $metadataPool, \Magento\Framework\DB\Adapter\AdapterInterface $connection = null ) { @@ -119,7 +122,7 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab /** * Get product type * - * @return \Magento\ConfigurableProduct\Model\Product\Type\Configurable + * @return Configurable */ private function getProductType() { @@ -303,8 +306,38 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab * * @return \Magento\Catalog\Model\Product */ - public function getProduct() + private function getProduct() { return $this->_product; } + + /** + * @inheritdoc + */ + public function __sleep() + { + return array_diff( + parent::__sleep(), + [ + '_product', + '_catalogData', + '_productTypeConfigurable', + '_storeManager', + 'metadataPool', + ] + ); + } + + /** + * @inheritdoc + */ + public function __wakeup() + { + parent::__wakeup(); + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $this->_storeManager = $objectManager->get(\Magento\Store\Model\StoreManagerInterface::class); + $this->_productTypeConfigurable = $objectManager->get(Configurable::class); + $this->_catalogData = $objectManager->get(\Magento\Catalog\Helper\Data::class); + $this->metadataPool = $objectManager->get(MetadataPool::class); + } } diff --git a/app/code/Magento/ConfigurableProduct/Plugin/Model/Product.php b/app/code/Magento/ConfigurableProduct/Plugin/Model/Product.php new file mode 100644 index 0000000000000000000000000000000000000000..92870ca91f42c6af17fbb94957f084ba709b8252 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Plugin/Model/Product.php @@ -0,0 +1,35 @@ +<?php +/** + * + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ConfigurableProduct\Plugin\Model; + +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; + +/** + * Plugin for Product Identity + */ +class Product +{ + /** + * Add identity of child product to identities + * + * @param \Magento\Catalog\Model\Product $product + * @param string[] $result + * @return string[] + */ + public function afterGetIdentities(\Magento\Catalog\Model\Product $product, $result) + { + /** @var Configurable $productType */ + $productType = $product->getTypeInstance(); + if ($productType instanceof Configurable) { + foreach ($productType->getUsedProductIds($product) as $productId) { + $result[] = \Magento\Catalog\Model\Product::CACHE_TAG . '_' . $productId; + } + } + return $result; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/Product/Options/LoaderTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/Product/Options/LoaderTest.php index b0f840ac224c24d600e328aa459213e847ee5365..c6081376d59e586a8d737e7f3a61dc7e9e734e53 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/Product/Options/LoaderTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/Product/Options/LoaderTest.php @@ -11,6 +11,8 @@ use Magento\ConfigurableProduct\Api\Data\OptionValueInterfaceFactory; use Magento\ConfigurableProduct\Helper\Product\Options\Loader; use Magento\ConfigurableProduct\Model\Product\Type\Configurable; use Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection; +use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** @@ -52,10 +54,13 @@ class LoaderTest extends \PHPUnit_Framework_TestCase $this->configurable = $this->getMockBuilder(Configurable::class) ->disableOriginalConstructor() - ->setMethods(['getConfigurableAttributes']) + ->setMethods(['getConfigurableAttributeCollection']) ->getMock(); - $this->loader = new Loader($this->optionValueFactory); + $extensionAttributesJoinProcessor = $this->getMockBuilder(JoinProcessorInterface::class) + ->getMockForAbstractClass(); + + $this->loader = new Loader($this->optionValueFactory, $extensionAttributesJoinProcessor); } /** @@ -75,12 +80,17 @@ class LoaderTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->setMethods(['getOptions', 'setValues']) ->getMock(); + $attributes = [$attribute]; + + $iterator = $this->getMockBuilder(Collection::class)->disableOriginalConstructor()->getMock(); + $iterator->expects($this->once())->method('getIterator') + ->willReturn(new \ArrayIterator($attributes)); $this->configurable->expects(static::once()) - ->method('getConfigurableAttributes') + ->method('getConfigurableAttributeCollection') ->with($this->product) - ->willReturn($attributes); + ->willReturn($iterator); $attribute->expects(static::once()) ->method('getOptions') diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php index ef858285e1b8035e30d9867a909ea61b5892fc18..9c2bf6575f683682be8e4d2ee5ac98c57eaf3945 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php @@ -82,6 +82,9 @@ class ConfigurableTest extends \PHPUnit_Framework_TestCase */ protected $entityMetadata; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $cache; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -144,6 +147,8 @@ class ConfigurableTest extends \PHPUnit_Framework_TestCase ->method('getMetadata') ->with(ProductInterface::class) ->willReturn($this->entityMetadata); + $this->cache = $this->getMockBuilder(\Magento\Framework\Cache\FrontendInterface::class) + ->getMockForAbstractClass(); $this->_model = $this->_objectHelper->getObject( 'Magento\ConfigurableProduct\Model\Product\Type\Configurable', @@ -159,6 +164,7 @@ class ConfigurableTest extends \PHPUnit_Framework_TestCase 'logger' => $logger, 'productRepository' => $this->productRepository, 'extensionAttributesJoinProcessor' => $this->extensionAttributesJoinProcessorMock, + 'cache' => $this->cache, 'metadataPool' => $this->metadataPool, ] ); @@ -440,13 +446,15 @@ class ConfigurableTest extends \PHPUnit_Framework_TestCase /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject $product */ $product = $this->getMockBuilder('Magento\Catalog\Model\Product') - ->setMethods(['getData', 'hasData', 'setData']) + ->setMethods(['getData', 'hasData', 'setData', 'getIdentities', 'getId']) ->disableOriginalConstructor() ->getMock(); $product->expects($this->once())->method('hasData')->with($configurableAttributes)->willReturn(false); $product->expects($this->once())->method('setData')->willReturnSelf(); $product->expects($this->once())->method('getData')->with($configurableAttributes)->willReturn($expectedData); + $product->expects($this->once())->method('getIdentities')->willReturn([1,2,3]); + $attributeCollection = $this->getMockBuilder( 'Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection' ) diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index c0622f73afe82c653383d45e50a5542a6fa9cb76..b3bfa1fb3111afb44d1a332a9323ec563ae8ad82 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -59,6 +59,9 @@ <type name="Magento\Catalog\Api\ProductRepositoryInterface"> <plugin name="configurableProductSaveOptions" type="\Magento\ConfigurableProduct\Model\Plugin\AroundProductRepositorySave"/> </type> + <type name="Magento\Catalog\Model\Product"> + <plugin name="configurable_identity" type="Magento\ConfigurableProduct\Plugin\Model\Product" /> + </type> <type name="Magento\Catalog\Model\Product\Type"> <plugin name="configurable_output" type="Magento\ConfigurableProduct\Model\Product\Type\Plugin" /> </type> @@ -143,4 +146,9 @@ <type name="\Magento\ProductVideo\Block\Product\View\Gallery"> <plugin name="product_video_gallery" type="\Magento\ConfigurableProduct\Block\Plugin\Product\Media\Gallery" /> </type> + <type name="Magento\ConfigurableProduct\Model\Product\Type\Configurable"> + <arguments> + <argument name="cache" xsi:type="object">Magento\Framework\App\Cache\Type\Collection</argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php b/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php index e582e83f3dcd4b9c2ad19da5307fc9d312ad0cbf..d08150611966309ef10ce500d7adda03489da305 100644 --- a/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php +++ b/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php @@ -642,13 +642,14 @@ class ProcessCronQueueObserverTest extends \PHPUnit_Framework_TestCase ]; // This item was scheduled 2 days ago + /** @var \Magento\Cron\Model\Schedule|\PHPUnit_Framework_MockObject_MockObject $schedule1 */ $schedule1 = $this->getMockBuilder( 'Magento\Cron\Model\Schedule' )->disableOriginalConstructor()->setMethods( ['getExecutedAt', 'getScheduledAt', 'getStatus', 'delete', '__wakeup'] )->getMock(); $schedule1->expects($this->any())->method('getExecutedAt')->will($this->returnValue(null)); - $schedule1->expects($this->any())->method('getScheduledAt')->will($this->returnValue('-2 day -1 hour')); + $schedule1->expects($this->any())->method('getScheduledAt')->will($this->returnValue('-2 day -2 hour')); $schedule1->expects($this->any())->method('getStatus')->will($this->returnValue(Schedule::STATUS_MISSED)); //we expect this job be deleted from the list $schedule1->expects($this->once())->method('delete')->will($this->returnValue(true)); diff --git a/app/code/Magento/Developer/Console/Command/XmlCatalogGenerateCommand.php b/app/code/Magento/Developer/Console/Command/XmlCatalogGenerateCommand.php index ec5419771a6fda20396abf1a5d71a92c4cdbdd81..1b51d24993d9deb5fc1f10d6a9b5eb4c9589d087 100644 --- a/app/code/Magento/Developer/Console/Command/XmlCatalogGenerateCommand.php +++ b/app/code/Magento/Developer/Console/Command/XmlCatalogGenerateCommand.php @@ -160,6 +160,7 @@ class XmlCatalogGenerateCommand extends Command */ private function getFormatters($format) { + $format = strtolower($format); if (!isset($this->formats[$format])) { return false; } diff --git a/app/code/Magento/Downloadable/Block/Sales/Order/Email/Items/Downloadable.php b/app/code/Magento/Downloadable/Block/Sales/Order/Email/Items/Downloadable.php index 5d2eaa4b8e82ee32e3ae961dbd46cf001e696d7d..32bbcc0f37519620ca52c3061754034596094d44 100644 --- a/app/code/Magento/Downloadable/Block/Sales/Order/Email/Items/Downloadable.php +++ b/app/code/Magento/Downloadable/Block/Sales/Order/Email/Items/Downloadable.php @@ -36,24 +36,27 @@ class Downloadable extends \Magento\Sales\Block\Order\Email\Items\DefaultItems protected $_itemsFactory; /** - * @var \Magento\Framework\UrlInterface + * @var \Magento\Framework\Url */ - protected $urlGenerator; + protected $frontendUrlBuilder; /** * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Downloadable\Model\Link\PurchasedFactory $purchasedFactory * @param \Magento\Downloadable\Model\ResourceModel\Link\Purchased\Item\CollectionFactory $itemsFactory + * @param \Magento\Framework\Url $frontendUrlBuilder * @param array $data */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, \Magento\Downloadable\Model\Link\PurchasedFactory $purchasedFactory, \Magento\Downloadable\Model\ResourceModel\Link\Purchased\Item\CollectionFactory $itemsFactory, + \Magento\Framework\Url $frontendUrlBuilder, array $data = [] ) { $this->_purchasedFactory = $purchasedFactory; $this->_itemsFactory = $itemsFactory; + $this->frontendUrlBuilder = $frontendUrlBuilder; parent::__construct($context, $data); } @@ -94,7 +97,7 @@ class Downloadable extends \Magento\Sales\Block\Order\Email\Items\DefaultItems */ public function getPurchasedLinkUrl($item) { - return $this->_urlBuilder->getUrl( + return $this->frontendUrlBuilder->getUrl( 'downloadable/download/link', [ 'id' => $item->getLinkHash(), diff --git a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/File/Upload.php b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/File/Upload.php index 6e056ee9e45a4c76c5701cca37e7d56563dc878c..aef2471617fa4710dd8d00b4bc69a06646d0aade 100644 --- a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/File/Upload.php +++ b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/File/Upload.php @@ -90,11 +90,7 @@ class Upload extends \Magento\Downloadable\Controller\Adminhtml\Downloadable\Fil throw new \Exception('File can not be moved from temporary folder to the destination folder.'); } - /** - * Workaround for prototype 1.7 methods "isJSON", "evalJSON" on Windows OS - */ - $result['tmp_name'] = str_replace('\\', '/', $result['tmp_name']); - $result['path'] = str_replace('\\', '/', $result['path']); + unset($result['tmp_name'], $result['path']); if (isset($result['file'])) { $relativePath = rtrim($tmpPath, '/') . '/' . ltrim($result['file'], '/'); diff --git a/app/code/Magento/Downloadable/view/frontend/templates/customer/products/list.phtml b/app/code/Magento/Downloadable/view/frontend/templates/customer/products/list.phtml index 9146b0a26a291528840897f43f7969b170dbc577..7d9d0df2b78569ff8af333f237676b074aed10b6 100644 --- a/app/code/Magento/Downloadable/view/frontend/templates/customer/products/list.phtml +++ b/app/code/Magento/Downloadable/view/frontend/templates/customer/products/list.phtml @@ -38,7 +38,9 @@ <td data-th="<?php echo $block->escapeHtml(__('Date')) ?>" class="col date"><?php /* @escapeNotVerified */ echo $block->formatDate($_item->getPurchased()->getCreatedAt()) ?></td> <td data-th="<?php echo $block->escapeHtml(__('Title')) ?>" class="col title"> <strong class="product-name"><?php echo $block->escapeHtml($_item->getPurchased()->getProductName()) ?></strong> + <?php if ($_item->getStatus() == \Magento\Downloadable\Model\Link\Purchased\Item::LINK_STATUS_AVAILABLE): ?> <a href="<?php /* @escapeNotVerified */ echo $block->getDownloadUrl($_item) ?>" title="<?php echo $block->escapeHtml(__('Start Download')) ?>" class="action download" <?php echo $block->getIsOpenInNewWindow() ? 'onclick="this.target=\'_blank\'"' : ''; ?>><?php echo $block->escapeHtml($_item->getLinkTitle()) ?></a> + <?php endif; ?> </td> <td data-th="<?php echo $block->escapeHtml(__('Status')) ?>" class="col status"><?php /* @escapeNotVerified */ echo __(ucfirst($_item->getStatus())) ?></td> <td data-th="<?php echo $block->escapeHtml(__('Remaining Downloads')) ?>" class="col remaining"><?php /* @escapeNotVerified */ echo $block->getRemainingDownloads($_item) ?></td> diff --git a/app/code/Magento/Eav/Model/Entity/Attribute.php b/app/code/Magento/Eav/Model/Entity/Attribute.php index 2c8cc458caa908df9398b9af163efa77e71e36a0..0d1a65d258d6898e59d1092d3ff7d174d36da41f 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute.php @@ -463,4 +463,28 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute\AbstractAttribute im { return [self::CACHE_TAG . '_' . $this->getId()]; } + + /** + * @inheritdoc + */ + public function __sleep() + { + return array_diff( + parent::__sleep(), + ['_localeDate', '_localeResolver', 'reservedAttributeList', 'dateTimeFormatter'] + ); + } + + /** + * @inheritdoc + */ + public function __wakeup() + { + parent::__wakeup(); + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $this->_localeDate = $objectManager->get(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class); + $this->_localeResolver = $objectManager->get(\Magento\Catalog\Model\Product\ReservedAttributeList::class); + $this->reservedAttributeList = $objectManager->get(\Magento\Framework\Locale\ResolverInterface::class); + $this->dateTimeFormatter = $objectManager->get(DateTimeFormatterInterface::class); + } } diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php index d09156a107ebb934b9165caf6a34ada6b32b39a2..81535a0af1d9f0e31ceb7af5d9862bb3b1f2bd2c 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php @@ -1237,4 +1237,45 @@ abstract class AbstractAttribute extends \Magento\Framework\Model\AbstractExtens { return $this->_setExtensionAttributes($extensionAttributes); } + + /** + * @inheritdoc + */ + public function __sleep() + { + return array_diff( + parent::__sleep(), + [ + '_eavConfig', + '_eavTypeFactory', + '_storeManager', + '_resourceHelper', + '_universalFactory', + 'optionDataFactory', + 'dataObjectProcessor', + 'dataObjectHelper', + '_entity', + '_backend', + '_source', + '_frontend', + ] + ); + } + + /** + * @inheritdoc + */ + public function __wakeup() + { + parent::__wakeup(); + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $this->_eavConfig = $objectManager->get(\Magento\Eav\Model\Config::class); + $this->_eavTypeFactory = $objectManager->get(\Magento\Eav\Model\Entity\TypeFactory::class); + $this->_storeManager = $objectManager->get(\Magento\Store\Model\StoreManagerInterface::class); + $this->_resourceHelper = $objectManager->get(\Magento\Eav\Model\ResourceModel\Helper::class); + $this->_universalFactory = $objectManager->get(\Magento\Framework\Validator\UniversalFactory ::class); + $this->optionDataFactory = $objectManager->get(\Magento\Eav\Api\Data\AttributeOptionInterfaceFactory::class); + $this->dataObjectProcessor = $objectManager->get(\Magento\Framework\Reflection\DataObjectProcessor::class); + $this->dataObjectHelper = $objectManager->get(\Magento\Framework\Api\DataObjectHelper::class); + } } diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php index de6a6600fe3af43cf0f5223ba840b36b619eb344..9c98d5299735e1cf33a16190b40de35d33aa4465 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php @@ -48,20 +48,24 @@ class OptionManagement implements \Magento\Eav\Api\AttributeOptionManagementInte if (!$attribute->usesSource()) { throw new StateException(__('Attribute %1 doesn\'t work with options', $attributeCode)); } - $key = 'new_option'; + $optionId = 'new_option'; + if ($option->getValue()) { + $this->validateOption($attribute, $option->getValue()); + $optionId = $option->getValue(); + } $options = []; - $options['value'][$key][0] = $option->getLabel(); - $options['order'][$key] = $option->getSortOrder(); + $options['value'][$optionId][0] = $option->getLabel(); + $options['order'][$optionId] = $option->getSortOrder(); if (is_array($option->getStoreLabels())) { foreach ($option->getStoreLabels() as $label) { - $options['value'][$key][$label->getStoreId()] = $label->getLabel(); + $options['value'][$optionId][$label->getStoreId()] = $label->getLabel(); } } if ($option->getIsDefault()) { - $attribute->setDefault([$key]); + $attribute->setDefault([$optionId]); } $attribute->setOption($options); @@ -87,12 +91,7 @@ class OptionManagement implements \Magento\Eav\Api\AttributeOptionManagementInte if (!$attribute->usesSource()) { throw new StateException(__('Attribute %1 doesn\'t have any option', $attributeCode)); } - - if (!$attribute->getSource()->getOptionText($optionId)) { - throw new NoSuchEntityException( - __('Attribute %1 does not contain option with Id %2', $attribute->getId(), $optionId) - ); - } + $this->validateOption($attribute, $optionId); $removalMarker = [ 'option' => [ @@ -128,4 +127,19 @@ class OptionManagement implements \Magento\Eav\Api\AttributeOptionManagementInte return $options; } + + /** + * @param \Magento\Eav\Api\Data\AttributeInterface $attribute + * @param int $optionId + * @throws NoSuchEntityException + * @return void + */ + protected function validateOption($attribute, $optionId) + { + if (!$attribute->getSource()->getOptionText($optionId)) { + throw new NoSuchEntityException( + __('Attribute %1 does not contain option with Id %2', $attribute->getAttributeCode(), $optionId) + ); + } + } } diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php index 622f920afccfa25dbd1871bab1a8b7babf82b5c1..9b9b1ad3b0d4737bebbdb4c0589bd3eaa08a1ef0 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php @@ -261,7 +261,7 @@ class OptionManagementTest extends \PHPUnit_Framework_TestCase /** * @expectedException \Magento\Framework\Exception\NoSuchEntityException - * @expectedExceptionMessage Attribute 42 does not contain option with Id option + * @expectedExceptionMessage Attribute atrCode does not contain option with Id option */ public function testDeleteWithWrongOption() { @@ -275,14 +275,15 @@ class OptionManagementTest extends \PHPUnit_Framework_TestCase false, false, true, - ['usesSource', 'getSource', 'getId', 'getOptionText', 'addData'] + ['usesSource', 'getSource', 'getAttributeCode'] ); $this->attributeRepositoryMock->expects($this->once())->method('get')->with($entityType, $attributeCode) ->willReturn($attributeMock); + $sourceMock = $this->getMockForAbstractClass('\Magento\Eav\Model\Entity\Attribute\Source\SourceInterface'); + $sourceMock->expects($this->once())->method('getOptionText')->willReturn(false); $attributeMock->expects($this->once())->method('usesSource')->willReturn(true); - $attributeMock->expects($this->once())->method('getSource')->willReturnSelf(); - $attributeMock->expects($this->once())->method('getOptionText')->willReturn(false); - $attributeMock->expects($this->once())->method('getId')->willReturn(42); + $attributeMock->expects($this->once())->method('getSource')->willReturn($sourceMock); + $attributeMock->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode); $this->resourceModelMock->expects($this->never())->method('save'); $this->model->delete($entityType, $attributeCode, $optionId); } diff --git a/app/code/Magento/GroupedProduct/Model/ResourceModel/Indexer/Stock/Grouped.php b/app/code/Magento/GroupedProduct/Model/ResourceModel/Indexer/Stock/Grouped.php index f8db07e887ca8698b47a8b33dd904cc417d7fa0d..2091c254b98c273b3add428d14514ff03c432c52 100644 --- a/app/code/Magento/GroupedProduct/Model/ResourceModel/Indexer/Stock/Grouped.php +++ b/app/code/Magento/GroupedProduct/Model/ResourceModel/Indexer/Stock/Grouped.php @@ -76,19 +76,7 @@ class Grouped extends \Magento\CatalogInventory\Model\ResourceModel\Indexer\Stoc ); $productStatusCond = $connection->quoteInto($productStatusExpr . '=?', ProductStatus::STATUS_ENABLED); - if ($this->_isManageStock()) { - $statusExpression = $connection->getCheckSql( - 'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 0', - 1, - 'cisi.is_in_stock' - ); - } else { - $statusExpression = $connection->getCheckSql( - 'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 1', - 'cisi.is_in_stock', - 1 - ); - } + $statusExpression = $this->getStatusExpression($connection); $optExpr = $connection->getCheckSql("{$productStatusCond} AND le.required_options = 0", 'i.stock_status', 0); $stockStatusExpr = $connection->getLeastSql(["MAX({$optExpr})", "MIN({$statusExpression})"]); diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Flatrate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Flatrate.php index dcd22963ff7ecf411a02cfcc5d83795143dbaead..5a222a8a506c1f8052790fc4e6e3bca9915d1935 100644 --- a/app/code/Magento/OfflineShipping/Model/Carrier/Flatrate.php +++ b/app/code/Magento/OfflineShipping/Model/Carrier/Flatrate.php @@ -5,14 +5,16 @@ */ namespace Magento\OfflineShipping\Model\Carrier; +use Magento\OfflineShipping\Model\Carrier\Flatrate\ItemPriceCalculator; use Magento\Quote\Model\Quote\Address\RateRequest; +use Magento\Shipping\Model\Carrier\AbstractCarrier; +use Magento\Shipping\Model\Carrier\CarrierInterface; use Magento\Shipping\Model\Rate\Result; /** * Flat rate shipping model */ -class Flatrate extends \Magento\Shipping\Model\Carrier\AbstractCarrier implements - \Magento\Shipping\Model\Carrier\CarrierInterface +class Flatrate extends AbstractCarrier implements CarrierInterface { /** * @var string @@ -34,12 +36,18 @@ class Flatrate extends \Magento\Shipping\Model\Carrier\AbstractCarrier implement */ protected $_rateMethodFactory; + /** + * @var ItemPriceCalculator + */ + private $itemPriceCalculator; + /** * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory * @param \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory + * @param ItemPriceCalculator $itemPriceCalculator * @param array $data */ public function __construct( @@ -48,10 +56,12 @@ class Flatrate extends \Magento\Shipping\Model\Carrier\AbstractCarrier implement \Psr\Log\LoggerInterface $logger, \Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory, \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory, + \Magento\OfflineShipping\Model\Carrier\Flatrate\ItemPriceCalculator $itemPriceCalculator, array $data = [] ) { $this->_rateResultFactory = $rateResultFactory; $this->_rateMethodFactory = $rateMethodFactory; + $this->itemPriceCalculator = $itemPriceCalculator; parent::__construct($scopeConfig, $rateErrorFactory, $logger, $data); } @@ -67,6 +77,28 @@ class Flatrate extends \Magento\Shipping\Model\Carrier\AbstractCarrier implement return false; } + $freeBoxes = $this->getFreeBoxesCount($request); + $this->setFreeBoxes($freeBoxes); + + /** @var Result $result */ + $result = $this->_rateResultFactory->create(); + + $shippingPrice = $this->getShippingPrice($request, $freeBoxes); + + if ($shippingPrice !== false) { + $method = $this->createResultMethod($shippingPrice); + $result->append($method); + } + + return $result; + } + + /** + * @param RateRequest $request + * @return int + */ + private function getFreeBoxesCount(RateRequest $request) + { $freeBoxes = 0; if ($request->getAllItems()) { foreach ($request->getAllItems() as $item) { @@ -75,64 +107,84 @@ class Flatrate extends \Magento\Shipping\Model\Carrier\AbstractCarrier implement } if ($item->getHasChildren() && $item->isShipSeparately()) { - foreach ($item->getChildren() as $child) { - if ($child->getFreeShipping() && !$child->getProduct()->isVirtual()) { - $freeBoxes += $item->getQty() * $child->getQty(); - } - } + $freeBoxes += $this->getFreeBoxesCountFromChildren($item); } elseif ($item->getFreeShipping()) { $freeBoxes += $item->getQty(); } } } - $this->setFreeBoxes($freeBoxes); + return $freeBoxes; + } - /** @var Result $result */ - $result = $this->_rateResultFactory->create(); - if ($this->getConfigData('type') == 'O') { + /** + * @return array + */ + public function getAllowedMethods() + { + return ['flatrate' => $this->getConfigData('name')]; + } + + /** + * @param RateRequest $request + * @param int $freeBoxes + * @return bool|float + */ + private function getShippingPrice(RateRequest $request, $freeBoxes) + { + $shippingPrice = false; + + $configPrice = $this->getConfigData('price'); + if ($this->getConfigData('type') === 'O') { // per order - $shippingPrice = $this->getConfigData('price'); - } elseif ($this->getConfigData('type') == 'I') { + $shippingPrice = $this->itemPriceCalculator->getShippingPricePerOrder($request, $configPrice, $freeBoxes); + } elseif ($this->getConfigData('type') === 'I') { // per item - $shippingPrice = $request->getPackageQty() * $this->getConfigData( - 'price' - ) - $this->getFreeBoxes() * $this->getConfigData( - 'price' - ); - } else { - $shippingPrice = false; + $shippingPrice = $this->itemPriceCalculator->getShippingPricePerItem($request, $configPrice, $freeBoxes); } $shippingPrice = $this->getFinalPriceWithHandlingFee($shippingPrice); - if ($shippingPrice !== false) { - /** @var \Magento\Quote\Model\Quote\Address\RateResult\Method $method */ - $method = $this->_rateMethodFactory->create(); - - $method->setCarrier('flatrate'); - $method->setCarrierTitle($this->getConfigData('title')); - - $method->setMethod('flatrate'); - $method->setMethodTitle($this->getConfigData('name')); + if ($shippingPrice !== false && ( + $request->getFreeShipping() === true || $request->getPackageQty() == $freeBoxes + ) + ) { + $shippingPrice = '0.00'; + } + return $shippingPrice; + } - if ($request->getFreeShipping() === true || $request->getPackageQty() == $this->getFreeBoxes()) { - $shippingPrice = '0.00'; - } + /** + * @param int|float $shippingPrice + * @return \Magento\Quote\Model\Quote\Address\RateResult\Method + */ + private function createResultMethod($shippingPrice) + { + /** @var \Magento\Quote\Model\Quote\Address\RateResult\Method $method */ + $method = $this->_rateMethodFactory->create(); - $method->setPrice($shippingPrice); - $method->setCost($shippingPrice); + $method->setCarrier('flatrate'); + $method->setCarrierTitle($this->getConfigData('title')); - $result->append($method); - } + $method->setMethod('flatrate'); + $method->setMethodTitle($this->getConfigData('name')); - return $result; + $method->setPrice($shippingPrice); + $method->setCost($shippingPrice); + return $method; } /** - * @return array + * @param mixed $item + * @return mixed */ - public function getAllowedMethods() + private function getFreeBoxesCountFromChildren($item) { - return ['flatrate' => $this->getConfigData('name')]; + $freeBoxes = 0; + foreach ($item->getChildren() as $child) { + if ($child->getFreeShipping() && !$child->getProduct()->isVirtual()) { + $freeBoxes += $item->getQty() * $child->getQty(); + } + } + return $freeBoxes; } } diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Flatrate/ItemPriceCalculator.php b/app/code/Magento/OfflineShipping/Model/Carrier/Flatrate/ItemPriceCalculator.php new file mode 100644 index 0000000000000000000000000000000000000000..51b63969817e097ebea8b3f524f8d8e09a168905 --- /dev/null +++ b/app/code/Magento/OfflineShipping/Model/Carrier/Flatrate/ItemPriceCalculator.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\OfflineShipping\Model\Carrier\Flatrate; + +use Magento\Quote\Model\Quote\Address\RateRequest; + +class ItemPriceCalculator +{ + /** + * @param RateRequest $request + * @param int $basePrice + * @param int $freeBoxes + * @return float + */ + public function getShippingPricePerItem( + \Magento\Quote\Model\Quote\Address\RateRequest $request, + $basePrice, + $freeBoxes + ) { + return $request->getPackageQty() * $basePrice - $freeBoxes * $basePrice; + } + + /** + * @param RateRequest $request + * @param int $basePrice + * @param int $freeBoxes + * @return float + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getShippingPricePerOrder( + \Magento\Quote\Model\Quote\Address\RateRequest $request, + $basePrice, + $freeBoxes + ) { + return $basePrice; + } +} diff --git a/app/code/Magento/OfflineShipping/Model/Config/Backend/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Config/Backend/Tablerate.php index 08d23d9245564215089f6a4fb98f0703b901a681..c65993dfe592f842d1b6b82853888516f7792dbd 100644 --- a/app/code/Magento/OfflineShipping/Model/Config/Backend/Tablerate.php +++ b/app/code/Magento/OfflineShipping/Model/Config/Backend/Tablerate.php @@ -25,8 +25,8 @@ class Tablerate extends \Magento\Framework\App\Config\Value * @param \Magento\Framework\App\Config\ScopeConfigInterface $config * @param \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList * @param \Magento\OfflineShipping\Model\ResourceModel\Carrier\TablerateFactory $tablerateFactory - * @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( diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate.php index df0228a338358448f9af9d8106af49ed87a5a6ad..82f053fdbe0a77f18ee40330102fe6c44f694426 100644 --- a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate.php +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate.php @@ -13,6 +13,9 @@ namespace Magento\OfflineShipping\Model\ResourceModel\Carrier; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\DirectoryList; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\Import; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\RateQuery; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\RateQueryFactory; /** * @SuppressWarnings(PHPMD.TooManyFields) @@ -87,50 +90,51 @@ class Tablerate extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb /** * @var \Magento\Framework\App\Config\ScopeConfigInterface */ - protected $_coreConfig; + protected $coreConfig; /** * @var \Psr\Log\LoggerInterface */ - protected $_logger; + protected $logger; /** * @var \Magento\Store\Model\StoreManagerInterface */ - protected $_storeManager; + protected $storeManager; /** - * @var \Magento\OfflineShipping\Model\Carrier\Tablerate + * @var \Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate */ - protected $_carrierTablerate; + protected $carrierTablerate; /** - * @var \Magento\Directory\Model\ResourceModel\Country\CollectionFactory + * Filesystem instance + * + * @var \Magento\Framework\Filesystem */ - protected $_countryCollectionFactory; + protected $filesystem; /** - * @var \Magento\Directory\Model\ResourceModel\Region\CollectionFactory + * @var Import */ - protected $_regionCollectionFactory; + private $import; /** - * Filesystem instance - * - * @var \Magento\Framework\Filesystem + * @var RateQueryFactory */ - protected $_filesystem; + private $rateQueryFactory; /** + * Tablerate constructor. * @param \Magento\Framework\Model\ResourceModel\Db\Context $context * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Framework\App\Config\ScopeConfigInterface $coreConfig * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\OfflineShipping\Model\Carrier\Tablerate $carrierTablerate - * @param \Magento\Directory\Model\ResourceModel\Country\CollectionFactory $countryCollectionFactory - * @param \Magento\Directory\Model\ResourceModel\Region\CollectionFactory $regionCollectionFactory - * @param \Magento\Framework\Filesystem $filesystem - * @param string $connectionName + * @param Filesystem $filesystem + * @param RateQueryFactory $rateQueryFactory + * @param Import $import + * @param null $connectionName */ public function __construct( \Magento\Framework\Model\ResourceModel\Db\Context $context, @@ -138,19 +142,19 @@ class Tablerate extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb \Magento\Framework\App\Config\ScopeConfigInterface $coreConfig, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\OfflineShipping\Model\Carrier\Tablerate $carrierTablerate, - \Magento\Directory\Model\ResourceModel\Country\CollectionFactory $countryCollectionFactory, - \Magento\Directory\Model\ResourceModel\Region\CollectionFactory $regionCollectionFactory, \Magento\Framework\Filesystem $filesystem, + Import $import, + RateQueryFactory $rateQueryFactory, $connectionName = null ) { parent::__construct($context, $connectionName); - $this->_coreConfig = $coreConfig; - $this->_logger = $logger; - $this->_storeManager = $storeManager; - $this->_carrierTablerate = $carrierTablerate; - $this->_countryCollectionFactory = $countryCollectionFactory; - $this->_regionCollectionFactory = $regionCollectionFactory; - $this->_filesystem = $filesystem; + $this->coreConfig = $coreConfig; + $this->logger = $logger; + $this->storeManager = $storeManager; + $this->carrierTablerate = $carrierTablerate; + $this->filesystem = $filesystem; + $this->import = $import; + $this->rateQueryFactory = $rateQueryFactory; } /** @@ -172,73 +176,66 @@ class Tablerate extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb public function getRate(\Magento\Quote\Model\Quote\Address\RateRequest $request) { $connection = $this->getConnection(); - $bind = [ - ':website_id' => (int)$request->getWebsiteId(), - ':country_id' => $request->getDestCountryId(), - ':region_id' => (int)$request->getDestRegionId(), - ':postcode' => $request->getDestPostcode(), - ]; - $select = $connection->select()->from( - $this->getMainTable() - )->where( - 'website_id = :website_id' - )->order( - ['dest_country_id DESC', 'dest_region_id DESC', 'dest_zip DESC'] - )->limit( - 1 - ); - - // Render destination condition - $orWhere = '(' . implode( - ') OR (', - [ - "dest_country_id = :country_id AND dest_region_id = :region_id AND dest_zip = :postcode", - "dest_country_id = :country_id AND dest_region_id = :region_id AND dest_zip = ''", - - // Handle asterix in dest_zip field - "dest_country_id = :country_id AND dest_region_id = :region_id AND dest_zip = '*'", - "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = '*'", - "dest_country_id = '0' AND dest_region_id = :region_id AND dest_zip = '*'", - "dest_country_id = '0' AND dest_region_id = 0 AND dest_zip = '*'", - "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = ''", - "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = :postcode", - "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = '*'" - ] - ) . ')'; - $select->where($orWhere); - - // Render condition by condition name - if (is_array($request->getConditionName())) { - $orWhere = []; - $i = 0; - foreach ($request->getConditionName() as $conditionName) { - $bindNameKey = sprintf(':condition_name_%d', $i); - $bindValueKey = sprintf(':condition_value_%d', $i); - $orWhere[] = "(condition_name = {$bindNameKey} AND condition_value <= {$bindValueKey})"; - $bind[$bindNameKey] = $conditionName; - $bind[$bindValueKey] = $request->getData($conditionName); - $i++; - } - if ($orWhere) { - $select->where(implode(' OR ', $orWhere)); - } - } else { - $bind[':condition_name'] = $request->getConditionName(); - $bind[':condition_value'] = $request->getData($request->getConditionName()); + $select = $connection->select()->from($this->getMainTable()); + /** @var RateQuery $rateQuery */ + $rateQuery = $this->rateQueryFactory->create(['request' => $request]); - $select->where('condition_name = :condition_name'); - $select->where('condition_value <= :condition_value'); - } + $rateQuery->prepareSelect($select); + $bindings = $rateQuery->getBindings(); - $result = $connection->fetchRow($select, $bind); + $result = $connection->fetchRow($select, $bindings); // Normalize destination zip code if ($result && $result['dest_zip'] == '*') { $result['dest_zip'] = ''; } + return $result; } + /** + * @param array $condition + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function deleteByCondition(array $condition) + { + $connection = $this->getConnection(); + $connection->beginTransaction(); + $connection->delete($this->getMainTable(), $condition); + $connection->commit(); + return $this; + } + + /** + * @param array $fields + * @param array $values + * @throws \Magento\Framework\Exception\LocalizedException + * @return void + */ + private function importData(array $fields, array $values) + { + $connection = $this->getConnection(); + $connection->beginTransaction(); + + try { + if (count($fields) && count($values)) { + $this->getConnection()->insertArray($this->getMainTable(), $fields, $values); + $this->_importedRows += count($values); + } + } catch (\Magento\Framework\Exception\LocalizedException $e) { + $connection->rollback(); + throw new \Magento\Framework\Exception\LocalizedException(__('Unable to import data'), $e); + } catch (\Exception $e) { + $connection->rollback(); + $this->logger->critical($e); + throw new \Magento\Framework\Exception\LocalizedException( + __('Something went wrong while importing table rates.') + ); + } + $connection->commit(); + } + /** * Upload table rate file and import data from it * @@ -252,142 +249,72 @@ class Tablerate extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb */ public function uploadAndImport(\Magento\Framework\DataObject $object) { + /** + * @var \Magento\Framework\App\Config\Value $object + */ if (empty($_FILES['groups']['tmp_name']['tablerate']['fields']['import']['value'])) { return $this; } + $filePath = $_FILES['groups']['tmp_name']['tablerate']['fields']['import']['value']; - $csvFile = $_FILES['groups']['tmp_name']['tablerate']['fields']['import']['value']; - $website = $this->_storeManager->getWebsite($object->getScopeId()); - - $this->_importWebsiteId = (int)$website->getId(); - $this->_importUniqueHash = []; - $this->_importErrors = []; - $this->_importedRows = 0; - - $tmpDirectory = $this->_filesystem->getDirectoryRead(DirectoryList::SYS_TMP); - $path = $tmpDirectory->getRelativePath($csvFile); - $stream = $tmpDirectory->openFile($path); - - // check and skip headers - $headers = $stream->readCsv(); - if ($headers === false || count($headers) < 5) { - $stream->close(); - throw new \Magento\Framework\Exception\LocalizedException(__('Please correct Table Rates File Format.')); - } - - if ($object->getData('groups/tablerate/fields/condition_name/inherit') == '1') { - $conditionName = (string)$this->_coreConfig->getValue('carriers/tablerate/condition_name', 'default'); - } else { - $conditionName = $object->getData('groups/tablerate/fields/condition_name/value'); - } - $this->_importConditionName = $conditionName; - - $connection = $this->getConnection(); - $connection->beginTransaction(); + $websiteId = $this->storeManager->getWebsite($object->getScopeId())->getId(); + $conditionName = $this->getConditionName($object); + $file = $this->getCsvFile($filePath); try { - $rowNumber = 1; - $importData = []; - - $this->_loadDirectoryCountries(); - $this->_loadDirectoryRegions(); - // delete old data by website and condition name $condition = [ - 'website_id = ?' => $this->_importWebsiteId, - 'condition_name = ?' => $this->_importConditionName, + 'website_id = ?' => $websiteId, + 'condition_name = ?' => $conditionName, ]; - $connection->delete($this->getMainTable(), $condition); - - while (false !== ($csvLine = $stream->readCsv())) { - $rowNumber++; - - if (empty($csvLine)) { - continue; - } - - $row = $this->_getImportRow($csvLine, $rowNumber); - if ($row !== false) { - $importData[] = $row; - } + $this->deleteByCondition($condition); - if (count($importData) == 5000) { - $this->_saveImportData($importData); - $importData = []; - } + $columns = $this->import->getColumns(); + $conditionFullName = $this->_getConditionFullName($conditionName); + foreach ($this->import->getData($file, $websiteId, $conditionName, $conditionFullName) as $bunch) { + $this->importData($columns, $bunch); } - $this->_saveImportData($importData); - $stream->close(); - } catch (\Magento\Framework\Exception\LocalizedException $e) { - $connection->rollback(); - $stream->close(); - throw new \Magento\Framework\Exception\LocalizedException(__($e->getMessage())); } catch (\Exception $e) { - $connection->rollback(); - $stream->close(); - $this->_logger->critical($e); + $this->logger->critical($e); throw new \Magento\Framework\Exception\LocalizedException( __('Something went wrong while importing table rates.') ); + } finally { + $file->close(); } - $connection->commit(); - - if ($this->_importErrors) { + if ($this->import->hasErrors()) { $error = __( 'We couldn\'t import this file because of these errors: %1', - implode(" \n", $this->_importErrors) + implode(" \n", $this->import->getErrors()) ); throw new \Magento\Framework\Exception\LocalizedException($error); } - - return $this; } /** - * Load directory countries - * - * @return \Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate + * @param \Magento\Framework\DataObject $object + * @return mixed|string */ - protected function _loadDirectoryCountries() + public function getConditionName(\Magento\Framework\DataObject $object) { - if ($this->_importIso2Countries !== null && $this->_importIso3Countries !== null) { - return $this; - } - - $this->_importIso2Countries = []; - $this->_importIso3Countries = []; - - /** @var $collection \Magento\Directory\Model\ResourceModel\Country\Collection */ - $collection = $this->_countryCollectionFactory->create(); - foreach ($collection->getData() as $row) { - $this->_importIso2Countries[$row['iso2_code']] = $row['country_id']; - $this->_importIso3Countries[$row['iso3_code']] = $row['country_id']; + if ($object->getData('groups/tablerate/fields/condition_name/inherit') == '1') { + $conditionName = (string)$this->coreConfig->getValue('carriers/tablerate/condition_name', 'default'); + } else { + $conditionName = $object->getData('groups/tablerate/fields/condition_name/value'); } - - return $this; + return $conditionName; } /** - * Load directory regions - * - * @return \Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate + * @param string $filePath + * @return \Magento\Framework\Filesystem\File\ReadInterface */ - protected function _loadDirectoryRegions() + private function getCsvFile($filePath) { - if ($this->_importRegions !== null) { - return $this; - } - - $this->_importRegions = []; - - /** @var $collection \Magento\Directory\Model\ResourceModel\Region\Collection */ - $collection = $this->_regionCollectionFactory->create(); - foreach ($collection->getData() as $row) { - $this->_importRegions[$row['country_id']][$row['code']] = (int)$row['region_id']; - } - - return $this; + $tmpDirectory = $this->filesystem->getDirectoryRead(DirectoryList::SYS_TMP); + $path = $tmpDirectory->getRelativePath($filePath); + return $tmpDirectory->openFile($path); } /** @@ -399,110 +326,13 @@ class Tablerate extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb protected function _getConditionFullName($conditionName) { if (!isset($this->_conditionFullNames[$conditionName])) { - $name = $this->_carrierTablerate->getCode('condition_name_short', $conditionName); + $name = $this->carrierTablerate->getCode('condition_name_short', $conditionName); $this->_conditionFullNames[$conditionName] = $name; } return $this->_conditionFullNames[$conditionName]; } - /** - * Validate row for import and return table rate array or false - * Error will be add to _importErrors array - * - * @param array $row - * @param int $rowNumber - * @return array|false - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - protected function _getImportRow($row, $rowNumber = 0) - { - // validate row - if (count($row) < 5) { - $this->_importErrors[] = __('Please correct Table Rates format in the Row #%1.', $rowNumber); - return false; - } - - // strip whitespace from the beginning and end of each row - foreach ($row as $k => $v) { - $row[$k] = trim($v); - } - - // validate country - if (isset($this->_importIso2Countries[$row[0]])) { - $countryId = $this->_importIso2Countries[$row[0]]; - } elseif (isset($this->_importIso3Countries[$row[0]])) { - $countryId = $this->_importIso3Countries[$row[0]]; - } elseif ($row[0] == '*' || $row[0] == '') { - $countryId = '0'; - } else { - $this->_importErrors[] = __('Please correct Country "%1" in the Row #%2.', $row[0], $rowNumber); - return false; - } - - // validate region - if ($countryId != '0' && isset($this->_importRegions[$countryId][$row[1]])) { - $regionId = $this->_importRegions[$countryId][$row[1]]; - } elseif ($row[1] == '*' || $row[1] == '') { - $regionId = 0; - } else { - $this->_importErrors[] = __('Please correct Region/State "%1" in the Row #%2.', $row[1], $rowNumber); - return false; - } - - // detect zip code - if ($row[2] == '*' || $row[2] == '') { - $zipCode = '*'; - } else { - $zipCode = $row[2]; - } - - // validate condition value - $value = $this->_parseDecimalValue($row[3]); - if ($value === false) { - $this->_importErrors[] = __( - 'Please correct %1 "%2" in the Row #%3.', - $this->_getConditionFullName($this->_importConditionName), - $row[3], - $rowNumber - ); - return false; - } - - // validate price - $price = $this->_parseDecimalValue($row[4]); - if ($price === false) { - $this->_importErrors[] = __('Please correct Shipping Price "%1" in the Row #%2.', $row[4], $rowNumber); - return false; - } - - // protect from duplicate - $hash = sprintf("%s-%d-%s-%F", $countryId, $regionId, $zipCode, $value); - if (isset($this->_importUniqueHash[$hash])) { - $this->_importErrors[] = __( - 'Duplicate Row #%1 (Country "%2", Region/State "%3", Zip "%4" and Value "%5")', - $rowNumber, - $row[0], - $row[1], - $zipCode, - $value - ); - return false; - } - $this->_importUniqueHash[$hash] = true; - - return [ - $this->_importWebsiteId, // website_id - $countryId, // dest_country_id - $regionId, // dest_region_id, - $zipCode, // dest_zip - $this->_importConditionName,// condition_name, - $value, // condition_value - $price // price - ]; - } - /** * Save import data batch * @@ -527,23 +357,4 @@ class Tablerate extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb return $this; } - - /** - * Parse and validate positive decimal value - * Return false if value is not decimal or is not positive - * - * @param string $value - * @return bool|float - */ - protected function _parseDecimalValue($value) - { - if (!is_numeric($value)) { - return false; - } - $value = (double)sprintf('%.4F', $value); - if ($value < 0.0000) { - return false; - } - return $value; - } } diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/ColumnNotFoundException.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/ColumnNotFoundException.php new file mode 100644 index 0000000000000000000000000000000000000000..8cb6cda142c0f78c8a1b975d10923b6c03f399a0 --- /dev/null +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/ColumnNotFoundException.php @@ -0,0 +1,14 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV; + +use Magento\Framework\Exception\LocalizedException; + +class ColumnNotFoundException extends LocalizedException +{ + +} diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/ColumnResolver.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/ColumnResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..c1782b6b9a9e6b153c311341f4c123c571688f5c --- /dev/null +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/ColumnResolver.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV; + +class ColumnResolver +{ + const COLUMN_COUNTRY = 'Country'; + const COLUMN_REGION = 'Region/State'; + const COLUMN_ZIP = 'Zip/Postal Code'; + const COLUMN_WEIGHT = 'Weight (and above)'; + const COLUMN_WEIGHT_DESTINATION = 'Weight (and above)'; + const COLUMN_PRICE = 'Shipping Price'; + + /** + * @var array + */ + private $nameToPositionIdMap = [ + self::COLUMN_COUNTRY => 0, + self::COLUMN_REGION => 1, + self::COLUMN_ZIP => 2, + self::COLUMN_WEIGHT => 3, + self::COLUMN_WEIGHT_DESTINATION => 3, + self::COLUMN_PRICE => 4, + ]; + + /** + * @var array + */ + private $headers; + + /** + * ColumnResolver constructor. + * @param array $headers + * @param array $columns + */ + public function __construct(array $headers, array $columns = []) + { + $this->nameToPositionIdMap = array_merge($this->nameToPositionIdMap, $columns); + $this->headers = array_map('trim', $headers); + } + + /** + * @param string $column + * @param array $values + * @return string|int|float|null + * @throws ColumnNotFoundException + */ + public function getColumnValue($column, array $values) + { + $column = (string) $column; + $columnIndex = array_search($column, $this->headers, true); + if (false === $columnIndex) { + if (array_key_exists($column, $this->nameToPositionIdMap)) { + $columnIndex = $this->nameToPositionIdMap[$column]; + } else { + throw new ColumnNotFoundException(__('Requested column "%1" cannot be resolved', $column)); + } + } + + if (!array_key_exists($columnIndex, $values)) { + throw new ColumnNotFoundException(__('Column "%1" not found', $column)); + } + + return trim($values[$columnIndex]); + } +} diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/RowException.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/RowException.php new file mode 100644 index 0000000000000000000000000000000000000000..a97bda4ec7d206e85003c9dc641a38d7bf1cb612 --- /dev/null +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/RowException.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV; + +use Magento\Framework\Exception\LocalizedException; + +class RowException extends LocalizedException +{ +} diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/RowParser.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/RowParser.php new file mode 100644 index 0000000000000000000000000000000000000000..6c1624f06005c85ad735ab533bfee20cec894c8a --- /dev/null +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/RowParser.php @@ -0,0 +1,207 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV; + +use Magento\Framework\Phrase; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\LocationDirectory; + +class RowParser +{ + /** + * @var LocationDirectory + */ + private $locationDirectory; + + /** + * RowParser constructor. + * @param LocationDirectory $locationDirectory + */ + public function __construct(LocationDirectory $locationDirectory) + { + $this->locationDirectory = $locationDirectory; + } + + /** + * @return array + */ + public function getColumns() + { + return [ + 'website_id', + 'dest_country_id', + 'dest_region_id', + 'dest_zip', + 'condition_name', + 'condition_value', + 'price', + ]; + } + + /** + * @param array $rowData + * @param int $rowNumber + * @param int $websiteId + * @param string $conditionShortName + * @param string $conditionFullName + * @param ColumnResolver $columnResolver + * @return array + * @throws ColumnNotFoundException + * @throws RowException + */ + public function parse( + array $rowData, + $rowNumber, + $websiteId, + $conditionShortName, + $conditionFullName, + ColumnResolver $columnResolver + ) { + // validate row + if (count($rowData) < 5) { + throw new RowException(__('Please correct Table Rates format in the Row #%1.', $rowNumber)); + } + + $countryId = $this->getCountryId($rowData, $rowNumber, $columnResolver); + $regionId = $this->getRegionId($rowData, $rowNumber, $columnResolver, $countryId); + $zipCode = $this->getZipCode($rowData, $columnResolver); + $conditionValue = $this->getConditionValue($rowData, $rowNumber, $conditionFullName, $columnResolver); + $price = $this->getPrice($rowData, $rowNumber, $columnResolver); + + return [ + 'website_id' => $websiteId, + 'dest_country_id' => $countryId, + 'dest_region_id' => $regionId, + 'dest_zip' => $zipCode, + 'condition_name' => $conditionShortName, + 'condition_value' => $conditionValue, + 'price' => $price, + ]; + } + + /** + * @param array $rowData + * @param int $rowNumber + * @param ColumnResolver $columnResolver + * @return null|string + * @throws ColumnNotFoundException + * @throws RowException + */ + private function getCountryId(array $rowData, $rowNumber, ColumnResolver $columnResolver) + { + $countryCode = $columnResolver->getColumnValue(ColumnResolver::COLUMN_COUNTRY, $rowData); + // validate country + if ($this->locationDirectory->hasCountryId($countryCode)) { + $countryId = $this->locationDirectory->getCountryId($countryCode); + } elseif ($countryCode === '*' || $countryCode === '') { + $countryId = '0'; + } else { + throw new RowException(__('Please correct Country "%1" in the Row #%2.', $countryCode, $rowNumber)); + } + return $countryId; + } + + /** + * @param array $rowData + * @param int $rowNumber + * @param ColumnResolver $columnResolver + * @param int $countryId + * @return int|string + * @throws ColumnNotFoundException + * @throws RowException + */ + private function getRegionId(array $rowData, $rowNumber, ColumnResolver $columnResolver, $countryId) + { + $regionCode = $columnResolver->getColumnValue(ColumnResolver::COLUMN_REGION, $rowData); + if ($countryId !== '0' && $this->locationDirectory->hasRegionId($countryId, $regionCode)) { + $regionId = $this->locationDirectory->getRegionId($countryId, $regionCode); + } elseif ($regionCode === '*' || $regionCode === '') { + $regionId = 0; + } else { + throw new RowException(__('Please correct Region/State "%1" in the Row #%2.', $regionCode, $rowNumber)); + } + return $regionId; + } + + /** + * @param array $rowData + * @param ColumnResolver $columnResolver + * @return float|int|null|string + * @throws ColumnNotFoundException + */ + private function getZipCode(array $rowData, ColumnResolver $columnResolver) + { + $zipCode = $columnResolver->getColumnValue(ColumnResolver::COLUMN_ZIP, $rowData); + if ($zipCode === '') { + $zipCode = '*'; + } + return $zipCode; + } + + /** + * @param array $rowData + * @param int $rowNumber + * @param string $conditionFullName + * @param ColumnResolver $columnResolver + * @return bool|float + * @throws ColumnNotFoundException + * @throws RowException + */ + private function getConditionValue(array $rowData, $rowNumber, $conditionFullName, ColumnResolver $columnResolver) + { + // validate condition value + $conditionValue = $columnResolver->getColumnValue($conditionFullName, $rowData); + $value = $this->_parseDecimalValue($conditionValue); + if ($value === false) { + throw new RowException( + __( + 'Please correct %1 "%2" in the Row #%3.', + $conditionFullName, + $conditionValue, + $rowNumber + ) + ); + } + return $value; + } + + /** + * @param array $rowData + * @param int $rowNumber + * @param ColumnResolver $columnResolver + * @return bool|float + * @throws ColumnNotFoundException + * @throws RowException + */ + private function getPrice(array $rowData, $rowNumber, ColumnResolver $columnResolver) + { + $priceValue = $columnResolver->getColumnValue(ColumnResolver::COLUMN_PRICE, $rowData); + $price = $this->_parseDecimalValue($priceValue); + if ($price === false) { + throw new RowException(__('Please correct Shipping Price "%1" in the Row #%2.', $priceValue, $rowNumber)); + } + return $price; + } + + /** + * Parse and validate positive decimal value + * Return false if value is not decimal or is not positive + * + * @param string $value + * @return bool|float + */ + private function _parseDecimalValue($value) + { + $result = false; + if (is_numeric($value)) { + $value = (double)sprintf('%.4F', $value); + if ($value >= 0.0000) { + $result = $value; + } + } + return $result; + } +} diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/DataHashGenerator.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/DataHashGenerator.php new file mode 100644 index 0000000000000000000000000000000000000000..2ef8d09eab7371d55e956d913f7d47b68630c666 --- /dev/null +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/DataHashGenerator.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate; + +class DataHashGenerator +{ + /** + * @param array $data + * @return string + */ + public function getHash(array $data) + { + $countryId = $data['dest_country_id']; + $regionId = $data['dest_region_id']; + $zipCode = $data['dest_zip']; + $conditionValue = $data['condition_value']; + + return sprintf("%s-%d-%s-%F", $countryId, $regionId, $zipCode, $conditionValue); + } +} diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/Import.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/Import.php new file mode 100644 index 0000000000000000000000000000000000000000..00d13fdf7737f6b361614568867643abd0c6fefc --- /dev/null +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/Import.php @@ -0,0 +1,184 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\File\ReadInterface; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\ColumnResolver; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\ColumnResolverFactory; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\RowException; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\RowParser; +use Magento\Store\Model\StoreManagerInterface; + +class Import +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @var ScopeConfigInterface + */ + private $coreConfig; + + /** + * @var array + */ + private $errors = []; + + /** + * @var CSV\RowParser + */ + private $rowParser; + + /** + * @var CSV\ColumnResolverFactory + */ + private $columnResolverFactory; + + /** + * @var DataHashGenerator + */ + private $dataHashGenerator; + + /** + * @var array + */ + private $uniqueHash = []; + + /** + * Import constructor. + * @param StoreManagerInterface $storeManager + * @param Filesystem $filesystem + * @param ScopeConfigInterface $coreConfig + * @param CSV\RowParser $rowParser + * @param CSV\ColumnResolverFactory $columnResolverFactory + * @param DataHashGenerator $dataHashGenerator + */ + public function __construct( + StoreManagerInterface $storeManager, + Filesystem $filesystem, + ScopeConfigInterface $coreConfig, + RowParser $rowParser, + ColumnResolverFactory $columnResolverFactory, + DataHashGenerator $dataHashGenerator + ) { + $this->storeManager = $storeManager; + $this->filesystem = $filesystem; + $this->coreConfig = $coreConfig; + $this->rowParser = $rowParser; + $this->columnResolverFactory = $columnResolverFactory; + $this->dataHashGenerator = $dataHashGenerator; + } + + /** + * @return bool + */ + public function hasErrors() + { + return (bool)count($this->getErrors()); + } + + /** + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + /** + * @return array + */ + public function getColumns() + { + return $this->rowParser->getColumns(); + } + + /** + * @param ReadInterface $file + * @param int $websiteId + * @param string $conditionShortName + * @param string $conditionFullName + * @param int $bunchSize + * @return \Generator + * @throws LocalizedException + */ + public function getData(ReadInterface $file, $websiteId, $conditionShortName, $conditionFullName, $bunchSize = 5000) + { + $this->errors = []; + + $headers = $this->getHeaders($file); + /** @var ColumnResolver $columnResolver */ + $columnResolver = $this->columnResolverFactory->create(['headers' => $headers]); + + $rowNumber = 1; + $items = []; + while (false !== ($csvLine = $file->readCsv())) { + try { + if (empty($csvLine)) { + continue; + } + $rowData = $this->rowParser->parse( + $csvLine, + ++$rowNumber, + $websiteId, + $conditionShortName, + $conditionFullName, + $columnResolver + ); + + // protect from duplicate + $hash = $this->dataHashGenerator->getHash($rowData); + if (array_key_exists($hash, $this->uniqueHash)) { + throw new RowException( + __( + 'Duplicate Row #%1 (duplicates row #%2)', + $rowNumber, + $this->uniqueHash[$hash] + ) + ); + } + $this->uniqueHash[$hash] = $csvLine; + + $items[] = $rowData; + if (count($items) === $bunchSize) { + yield $items; + $items = []; + } + } catch (RowException $e) { + $this->errors[] = $e->getMessage(); + } + } + if (count($items)) { + yield $items; + } + } + + /** + * @param ReadInterface $file + * @return array|bool + * @throws LocalizedException + */ + private function getHeaders(ReadInterface $file) + { + // check and skip headers + $headers = $file->readCsv(); + if ($headers === false || count($headers) < 5) { + throw new LocalizedException(__('Please correct Table Rates File Format.')); + } + return $headers; + } +} diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/LocationDirectory.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/LocationDirectory.php new file mode 100644 index 0000000000000000000000000000000000000000..a3a7d9e8c78942c6f0eac52228131e083e4b09c1 --- /dev/null +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/LocationDirectory.php @@ -0,0 +1,143 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate; + +class LocationDirectory +{ + /** + * @var array + */ + protected $regions; + + /** + * @var array + */ + protected $iso2Countries; + + /** + * @var array + */ + protected $iso3Countries; + + /** + * @var \Magento\Directory\Model\ResourceModel\Country\CollectionFactory + */ + protected $_countryCollectionFactory; + + /** + * @var \Magento\Directory\Model\ResourceModel\Region\CollectionFactory + */ + protected $_regionCollectionFactory; + + /** + * LocationDirectory constructor. + * @param \Magento\Directory\Model\ResourceModel\Country\CollectionFactory $countryCollectionFactory + * @param \Magento\Directory\Model\ResourceModel\Region\CollectionFactory $regionCollectionFactory + */ + public function __construct( + \Magento\Directory\Model\ResourceModel\Country\CollectionFactory $countryCollectionFactory, + \Magento\Directory\Model\ResourceModel\Region\CollectionFactory $regionCollectionFactory + ) { + $this->_countryCollectionFactory = $countryCollectionFactory; + $this->_regionCollectionFactory = $regionCollectionFactory; + } + + /** + * @param string $countryCode + * @return null|string + */ + public function getCountryId($countryCode) + { + $this->loadCountries(); + $countryId = null; + if (isset($this->iso2Countries[$countryCode])) { + $countryId = $this->iso2Countries[$countryCode]; + } elseif (isset($this->iso3Countries[$countryCode])) { + $countryId = $this->iso3Countries[$countryCode]; + } + + return $countryId; + } + + /** + * Load directory countries + * + * @return \Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate + */ + protected function loadCountries() + { + if ($this->iso2Countries !== null && $this->iso3Countries !== null) { + return $this; + } + + $this->iso2Countries = []; + $this->iso3Countries = []; + + /** @var $collection \Magento\Directory\Model\ResourceModel\Country\Collection */ + $collection = $this->_countryCollectionFactory->create(); + foreach ($collection->getData() as $row) { + $this->iso2Countries[$row['iso2_code']] = $row['country_id']; + $this->iso3Countries[$row['iso3_code']] = $row['country_id']; + } + + return $this; + } + + /** + * @param string $countryCode + * @return bool + */ + public function hasCountryId($countryCode) + { + $this->loadCountries(); + return isset($this->iso2Countries[$countryCode]) || isset($this->iso3Countries[$countryCode]); + } + + /** + * @param string $countryId + * @param string $regionCode + * @return bool + */ + public function hasRegionId($countryId, $regionCode) + { + $this->loadRegions(); + return isset($this->regions[$countryId][$regionCode]); + } + + /** + * Load directory regions + * + * @return \Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate + */ + protected function loadRegions() + { + if ($this->regions !== null) { + return $this; + } + + $this->regions = []; + + /** @var $collection \Magento\Directory\Model\ResourceModel\Region\Collection */ + $collection = $this->_regionCollectionFactory->create(); + foreach ($collection->getData() as $row) { + $this->regions[$row['country_id']][$row['code']] = (int)$row['region_id']; + } + + return $this; + } + + /** + * @param int $countryId + * @param string $regionCode + * @return string + */ + public function getRegionId($countryId, $regionCode) + { + $this->loadRegions(); + return $this->regions[$countryId][$regionCode]; + } +} diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/RateQuery.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/RateQuery.php new file mode 100644 index 0000000000000000000000000000000000000000..1c4d87dd11c9f9ad6051d958f62b8422df255efb --- /dev/null +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/RateQuery.php @@ -0,0 +1,115 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate; + +class RateQuery +{ + /** + * @var \Magento\Quote\Model\Quote\Address\RateRequest + */ + private $request; + + /** + * RateQuery constructor. + * @param \Magento\Quote\Model\Quote\Address\RateRequest $request + */ + public function __construct( + \Magento\Quote\Model\Quote\Address\RateRequest $request + ) { + $this->request = $request; + } + + /** + * @param \Magento\Framework\DB\Select $select + * @return \Magento\Framework\DB\Select + */ + public function prepareSelect(\Magento\Framework\DB\Select $select) + { + $select->where( + 'website_id = :website_id' + )->order( + ['dest_country_id DESC', 'dest_region_id DESC', 'dest_zip DESC'] + )->limit( + 1 + ); + + // Render destination condition + $orWhere = '(' . implode( + ') OR (', + [ + "dest_country_id = :country_id AND dest_region_id = :region_id AND dest_zip = :postcode", + "dest_country_id = :country_id AND dest_region_id = :region_id AND dest_zip = ''", + + // Handle asterisk in dest_zip field + "dest_country_id = :country_id AND dest_region_id = :region_id AND dest_zip = '*'", + "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = '*'", + "dest_country_id = '0' AND dest_region_id = :region_id AND dest_zip = '*'", + "dest_country_id = '0' AND dest_region_id = 0 AND dest_zip = '*'", + "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = ''", + "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = :postcode", + "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = '*'" + ] + ) . ')'; + $select->where($orWhere); + + // Render condition by condition name + if (is_array($this->request->getConditionName())) { + $orWhere = []; + foreach (range(0, count($this->request->getConditionName())) as $conditionNumber) { + $bindNameKey = sprintf(':condition_name_%d', $conditionNumber); + $bindValueKey = sprintf(':condition_value_%d', $conditionNumber); + $orWhere[] = "(condition_name = {$bindNameKey} AND condition_value <= {$bindValueKey})"; + } + + if ($orWhere) { + $select->where(implode(' OR ', $orWhere)); + } + } else { + $select->where('condition_name = :condition_name'); + $select->where('condition_value <= :condition_value'); + } + return $select; + } + + /** + * @return array + */ + public function getBindings() + { + $bind = [ + ':website_id' => (int)$this->request->getWebsiteId(), + ':country_id' => $this->request->getDestCountryId(), + ':region_id' => (int)$this->request->getDestRegionId(), + ':postcode' => $this->request->getDestPostcode(), + ]; + + // Render condition by condition name + if (is_array($this->request->getConditionName())) { + $i = 0; + foreach ($this->request->getConditionName() as $conditionName) { + $bindNameKey = sprintf(':condition_name_%d', $i); + $bindValueKey = sprintf(':condition_value_%d', $i); + $bind[$bindNameKey] = $conditionName; + $bind[$bindValueKey] = $this->request->getData($conditionName); + $i++; + } + } else { + $bind[':condition_name'] = $this->request->getConditionName(); + $bind[':condition_value'] = $this->request->getData($this->request->getConditionName()); + } + + return $bind; + } + + /** + * @return \Magento\Quote\Model\Quote\Address\RateRequest + */ + public function getRequest() + { + return $this->request; + } +} diff --git a/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/ColumnResolverTest.php b/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/ColumnResolverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..980204d7dab560c6414f5fb758242c221e36f973 --- /dev/null +++ b/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/ColumnResolverTest.php @@ -0,0 +1,142 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\OfflineShipping\Test\Unit\Model\ResourceModel\Carrier\Tablerate\CSV; + +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\ColumnResolver; + +/** + * Unit test for Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\ColumnResolver + */ +class ColumnResolverTest extends \PHPUnit_Framework_TestCase +{ + const CUSTOM_FIELD = 'custom_field'; + + private $values = [ + ColumnResolver::COLUMN_COUNTRY => 'country value', + ColumnResolver::COLUMN_REGION => 'region value', + ColumnResolver::COLUMN_ZIP => 'zip_value', + ColumnResolver::COLUMN_WEIGHT => 'weight_value', + ColumnResolver::COLUMN_WEIGHT_DESTINATION => 'weight_destination_value', + ColumnResolver::COLUMN_PRICE => 'price_value', + self::CUSTOM_FIELD => 'custom_value', + ]; + + /** + * @param $column + * @param $expectedValue + * @throws \Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\ColumnNotFoundException + * @dataProvider getColumnValueDataProvider + */ + public function testGetColumnValueByPosition($column, $expectedValue) + { + $headers = array_keys($this->values); + $headers = []; + $columnResolver = $this->createColumnResolver($headers); + $values = array_values($this->values); + $result = $columnResolver->getColumnValue($column, $values); + $this->assertEquals($expectedValue, $result); + } + + /** + * @param array $headers + * @param array $columns + * @return ColumnResolver + */ + private function createColumnResolver(array $headers = [], array $columns = []) + { + return new ColumnResolver($headers, $columns); + } + + /** + * @return void + * @dataProvider getColumnValueWithCustomHeaderDataProvider + */ + public function testGetColumnValueByHeader($column, $expectedValue) + { + $reversedValues = array_reverse($this->values); + $headers = array_keys($reversedValues); + $values = array_values($reversedValues); + $columnResolver = $this->createColumnResolver($headers); + $result = $columnResolver->getColumnValue($column, $values); + $this->assertEquals($expectedValue, $result); + } + + /** + * @return array + */ + public function getColumnValueDataProvider() + { + return [ + ColumnResolver::COLUMN_COUNTRY => [ + ColumnResolver::COLUMN_COUNTRY, + $this->values[ColumnResolver::COLUMN_COUNTRY], + ], + ColumnResolver::COLUMN_REGION => [ + ColumnResolver::COLUMN_REGION, + $this->values[ColumnResolver::COLUMN_REGION], + ], + ColumnResolver::COLUMN_ZIP => [ + ColumnResolver::COLUMN_ZIP, + $this->values[ColumnResolver::COLUMN_ZIP], + ], + ColumnResolver::COLUMN_WEIGHT => [ + ColumnResolver::COLUMN_WEIGHT, + $this->values[ColumnResolver::COLUMN_WEIGHT], + ], + ColumnResolver::COLUMN_WEIGHT_DESTINATION => [ + ColumnResolver::COLUMN_WEIGHT_DESTINATION, + $this->values[ColumnResolver::COLUMN_WEIGHT_DESTINATION], + ], + ColumnResolver::COLUMN_PRICE => [ + ColumnResolver::COLUMN_PRICE, + $this->values[ColumnResolver::COLUMN_PRICE], + ] + ]; + } + + /** + * @return array + */ + public function getColumnValueWithCustomHeaderDataProvider() + { + $customField = [ + self::CUSTOM_FIELD => [ + self::CUSTOM_FIELD, + $this->values[self::CUSTOM_FIELD], + ], + ]; + return array_merge($this->getColumnValueDataProvider(), $customField); + } + + /** + * @throws \Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\ColumnNotFoundException + * @expectedException \Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\ColumnNotFoundException + * @expectedExceptionMessage Requested column "custom_field" cannot be resolved + */ + public function testGetColumnValueWithUnknownColumn() + { + $columnResolver = $this->createColumnResolver(); + $values = array_values($this->values); + $columnResolver->getColumnValue(self::CUSTOM_FIELD, $values); + } + + /** + * @throws \Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\ColumnNotFoundException + * @expectedException \Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\ColumnNotFoundException + * @expectedExceptionMessage Column "new_custom_column" not found + */ + public function testGetColumnValueWithUndefinedValue() + { + $columnName = 'new_custom_column'; + + $headers = array_keys($this->values); + $headers[] = $columnName; + $columnResolver = $this->createColumnResolver($headers); + $values = array_values($this->values); + $columnResolver->getColumnValue($columnName, $values); + } +} diff --git a/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/RowParserTest.php b/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/RowParserTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fc6ec40b46e084bcd7a22807dd223e7965e54433 --- /dev/null +++ b/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/RowParserTest.php @@ -0,0 +1,211 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\OfflineShipping\Test\Unit\Model\ResourceModel\Carrier\Tablerate\CSV; + +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\ColumnResolver; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\RowException; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\RowParser; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\LocationDirectory; + +/** + * Unit test for Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\RowParser + */ +class RowParserTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ColumnResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $columnResolverMock; + + /** + * @var RowParser + */ + private $rowParser; + + /** + * @var LocationDirectory|\PHPUnit_Framework_MockObject_MockObject + */ + private $locationDirectoryMock; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + $this->locationDirectoryMock = $this->getMockBuilder(LocationDirectory::class) + ->setMethods(['hasCountryId', 'getCountryId', 'hasRegionId', 'getRegionId']) + ->disableOriginalConstructor() + ->getMock(); + $this->columnResolverMock = $this->getMockBuilder(ColumnResolver::class) + ->disableOriginalConstructor() + ->getMock(); + $this->rowParser = new RowParser( + $this->locationDirectoryMock + ); + } + + /** + * @return void + */ + public function testGetColumns() + { + $columns = $this->rowParser->getColumns(); + $this->assertTrue(is_array($columns), 'Columns should be array, ' . gettype($columns) . ' given'); + $this->assertNotEmpty($columns); + } + + /** + * @return void + */ + public function testParse() + { + $expectedResult = [ + 'website_id' => 58, + 'dest_country_id' => '0', + 'dest_region_id' => 0, + 'dest_zip' => '*', + 'condition_name' => 'condition_short_name', + 'condition_value' => 40.0, + 'price' => 350.0, + ]; + $rowData = ['a', 'b', 'c', 'd', 'e']; + $rowNumber = 120; + $websiteId = 58; + $conditionShortName = 'condition_short_name'; + $conditionFullName = 'condition_full_name'; + $columnValueMap = [ + [ColumnResolver::COLUMN_COUNTRY, $rowData, '*'], + [ColumnResolver::COLUMN_REGION, $rowData, '*'], + [ColumnResolver::COLUMN_ZIP, $rowData, ''], + [$conditionFullName, $rowData, 40], + [ColumnResolver::COLUMN_PRICE, $rowData, 350], + ]; + $result = $this->parse( + $rowData, + $conditionFullName, + $rowNumber, + $websiteId, + $conditionShortName, + $columnValueMap + ); + $this->assertEquals($expectedResult, $result); + } + + /** + * @param array $rowData + * @param $conditionFullName + * @param array $columnValueMap + * @param $expectedMessage + * @throws null|RowException + * @dataProvider parseWithExceptionDataProvider + * @expectedException \Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\RowException + */ + public function testParseWithException(array $rowData, $conditionFullName, array $columnValueMap, $expectedMessage) + { + $rowNumber = 120; + $websiteId = 58; + $conditionShortName = 'condition_short_name'; + $actualMessage = null; + $exception = null; + try { + $this->parse( + $rowData, + $conditionFullName, + $rowNumber, + $websiteId, + $conditionShortName, + $columnValueMap + ); + } catch (\Exception $e) { + $actualMessage = $e->getMessage(); + $exception = $e; + } + $this->assertEquals($expectedMessage, $actualMessage); + throw $exception; + } + + public function parseWithExceptionDataProvider() + { + $rowData = ['a', 'b', 'c', 'd', 'e']; + $conditionFullName = 'condition_full_name'; + return [ + [ + $rowData, + $conditionFullName, + [ + [ColumnResolver::COLUMN_COUNTRY, $rowData, 'XX'], + [ColumnResolver::COLUMN_REGION, $rowData, '*'], + [ColumnResolver::COLUMN_ZIP, $rowData, ''], + [$conditionFullName, $rowData, 40], + [ColumnResolver::COLUMN_PRICE, $rowData, 350], + ], + 'Please correct Country "XX" in the Row #120.', + ], + [ + $rowData, + $conditionFullName, + [ + [ColumnResolver::COLUMN_COUNTRY, $rowData, '*'], + [ColumnResolver::COLUMN_REGION, $rowData, 'AA'], + [ColumnResolver::COLUMN_ZIP, $rowData, ''], + [$conditionFullName, $rowData, 40], + [ColumnResolver::COLUMN_PRICE, $rowData, 350], + ], + 'Please correct Region/State "AA" in the Row #120.', + ], + [ + $rowData, + $conditionFullName, + [ + [ColumnResolver::COLUMN_COUNTRY, $rowData, '*'], + [ColumnResolver::COLUMN_REGION, $rowData, '*'], + [ColumnResolver::COLUMN_ZIP, $rowData, ''], + [$conditionFullName, $rowData, 'QQQ'], + [ColumnResolver::COLUMN_PRICE, $rowData, 350], + ], + 'Please correct condition_full_name "QQQ" in the Row #120.', + ], + [ + $rowData, + $conditionFullName, + [ + [ColumnResolver::COLUMN_COUNTRY, $rowData, '*'], + [ColumnResolver::COLUMN_REGION, $rowData, '*'], + [ColumnResolver::COLUMN_ZIP, $rowData, ''], + [$conditionFullName, $rowData, 40], + [ColumnResolver::COLUMN_PRICE, $rowData, 'BBB'], + ], + 'Please correct Shipping Price "BBB" in the Row #120.', + ], + ]; + } + + /** + * @param $rowData + * @param $conditionFullName + * @param $rowNumber + * @param $websiteId + * @param $conditionShortName + * @return array + * @throws \Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\RowException + */ + private function parse($rowData, $conditionFullName, $rowNumber, $websiteId, $conditionShortName, $columnValueMap) + { + $this->columnResolverMock->expects($this->any()) + ->method('getColumnValue') + ->willReturnMap($columnValueMap); + $result = $this->rowParser->parse( + $rowData, + $rowNumber, + $websiteId, + $conditionShortName, + $conditionFullName, + $this->columnResolverMock + ); + return $result; + } +} diff --git a/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/ImportTest.php b/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/ImportTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0bb192e3deacbdbc0e118795fa1e911d75781ac9 --- /dev/null +++ b/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/ImportTest.php @@ -0,0 +1,229 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\OfflineShipping\Test\Unit\Model\ResourceModel\Carrier\Tablerate; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\File\ReadInterface; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\ColumnResolverFactory; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\ColumnResolver; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\RowParser; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\DataHashGenerator; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\Import; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Unit test for Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\Import + */ +class ImportTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\Import + */ + private $import; + + /** + * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeManagerMock; + + /** + * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject + */ + private $filesystemMock; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfigMock; + + /** + * @var RowParser|\PHPUnit_Framework_MockObject_MockObject + */ + private $rowParserMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $columnResolverFactoryMock; + + /** + * @var DataHashGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + private $dataHashGeneratorMock; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) + ->getMockForAbstractClass(); + $this->filesystemMock = $this->getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->getMockForAbstractClass(); + $this->rowParserMock = $this->getMockBuilder(RowParser::class) + ->disableOriginalConstructor() + ->getMock(); + $this->columnResolverFactoryMock = $this->getMockBuilder(ColumnResolverFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->dataHashGeneratorMock = $this->getMockBuilder(DataHashGenerator::class) + ->getMock(); + $this->rowParserMock->expects($this->any()) + ->method('parse') + ->willReturnArgument(0); + $this->dataHashGeneratorMock->expects($this->any()) + ->method('getHash') + ->willReturnCallback( + function (array $data) { + return implode('_', $data); + } + ); + + $this->import = new Import( + $this->storeManagerMock, + $this->filesystemMock, + $this->scopeConfigMock, + $this->rowParserMock, + $this->columnResolverFactoryMock, + $this->dataHashGeneratorMock + ); + } + + /** + * @return void + */ + public function testGetColumns() + { + $columns = ['column_1', 'column_2']; + $this->rowParserMock->expects($this->once()) + ->method('getColumns') + ->willReturn($columns); + $result = $this->import->getColumns(); + $this->assertEquals($columns, $result); + } + + /** + * @return void + */ + public function testGetData() + { + $lines = [ + ['header_1', 'header_2', 'header_3', 'header_4', 'header_5'], + ['a1', 'b1', 'c1', 'd1', 'e1'], + ['a2', 'b2', 'c2', 'd2', 'e2'], + ['a3', 'b3', 'c3', 'd3', 'e3'], + ['a4', 'b4', 'c4', 'd4', 'e4'], + ['a5', 'b5', 'c5', 'd5', 'e5'], + ]; + $file = $this->createFileMock($lines); + $expectedResult = [ + [ + $lines[1], + $lines[2], + ], + [ + $lines[3], + $lines[4], + ], + [ + $lines[5] + ] + ]; + + $columnResolver = $this->getMockBuilder(ColumnResolver::class)->disableOriginalConstructor()->getMock(); + $this->columnResolverFactoryMock + ->expects($this->once()) + ->method('create') + ->with(['headers' => $lines[0]]) + ->willReturn($columnResolver); + + $result = []; + foreach ($this->import->getData($file, 1, 'short_name', 'full_name', 2) as $bunch) { + $result[] = $bunch; + } + $this->assertEquals($expectedResult, $result); + $this->assertFalse($this->import->hasErrors()); + $this->assertEquals([], $this->import->getErrors()); + } + + /** + * @return void + */ + public function testGetDataWithDuplicatedLine() + { + $lines = [ + ['header_1', 'header_2', 'header_3', 'header_4', 'header_5'], + ['a1', 'b1', 'c1', 'd1', 'e1'], + ['a1', 'b1', 'c1', 'd1', 'e1'], + [], + ['a2', 'b2', 'c2', 'd2', 'e2'], + ]; + $file = $this->createFileMock($lines); + $expectedResult = [ + [ + $lines[1], + $lines[4], + ], + ]; + + $columnResolver = $this->getMockBuilder(ColumnResolver::class)->disableOriginalConstructor()->getMock(); + $this->columnResolverFactoryMock + ->expects($this->once()) + ->method('create') + ->with(['headers' => $lines[0]]) + ->willReturn($columnResolver); + + $result = []; + foreach ($this->import->getData($file, 1, 'short_name', 'full_name', 2) as $bunch) { + $result[] = $bunch; + } + $this->assertEquals($expectedResult, $result); + $this->assertTrue($this->import->hasErrors()); + $this->assertEquals(['Duplicate Row #%1 (duplicates row #%2)'], $this->import->getErrors()); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Please correct Table Rates File Format. + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function testGetDataFromEmptyFile() + { + $lines = []; + $file = $this->createFileMock($lines); + foreach ($this->import->getData($file, 1, 'short_name', 'full_name', 2) as $bunch) { + $this->assertTrue(false, 'Exception about empty header is not thrown'); + } + } + + /** + * @param array $lines + * @return ReadInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function createFileMock(array $lines) + { + $file = $this->getMockBuilder(ReadInterface::class) + ->setMethods(['readCsv']) + ->getMockForAbstractClass(); + $i = 0; + foreach ($lines as $line) { + $file->expects($this->at($i)) + ->method('readCsv') + ->willReturn($line); + $i++; + } + $file->expects($this->at($i)) + ->method('readCsv') + ->willReturn(false); + return $file; + } +} diff --git a/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php b/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php index fd04f1fe4ca2fbe28854f837b0a3323d70164473..a8871f803a1be31fe12bd2fef45a1ad2a3d35f84 100644 --- a/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php +++ b/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php @@ -89,8 +89,10 @@ class BuiltinPlugin */ protected function addDebugHeaders(ResponseHttp $result) { - $cacheControl = $result->getHeader('Cache-Control')->getFieldValue(); - $this->addDebugHeader($result, 'X-Magento-Cache-Control', $cacheControl); + $cacheControlHeader = $result->getHeader('Cache-Control'); + if ($cacheControlHeader instanceof \Zend\Http\Header\HeaderInterface) { + $this->addDebugHeader($result, 'X-Magento-Cache-Control', $cacheControlHeader->getFieldValue()); + } $this->addDebugHeader($result, 'X-Magento-Cache-Debug', 'MISS', true); return $result; } diff --git a/app/code/Magento/PageCache/Model/Cache/Server.php b/app/code/Magento/PageCache/Model/Cache/Server.php index 2e2f3a87fb8c07a7eb33c9b61f4ef421992e6c49..797249c98cb09e61b2e14e955116d6c84f1cf83a 100644 --- a/app/code/Magento/PageCache/Model/Cache/Server.php +++ b/app/code/Magento/PageCache/Model/Cache/Server.php @@ -5,16 +5,17 @@ */ namespace Magento\PageCache\Model\Cache; -use Zend\Uri\Uri; +use Magento\Framework\UrlInterface; use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Config\ConfigOptionsListConstants; use Magento\Framework\App\RequestInterface; +use Zend\Uri\Uri; use Zend\Uri\UriFactory; class Server { /** - * @var \Magento\Framework\UrlInterface + * @var UrlInterface */ protected $urlBuilder; @@ -33,12 +34,12 @@ class Server /** * Constructor * - * @param \Magento\Framework\UrlInterface $urlBuilder + * @param UrlInterface $urlBuilder * @param DeploymentConfig $config * @param RequestInterface $request */ public function __construct( - \Magento\Framework\UrlInterface $urlBuilder, + UrlInterface $urlBuilder, DeploymentConfig $config, RequestInterface $request ) { @@ -56,21 +57,24 @@ class Server { $servers = []; $configuredHosts = $this->config->get(ConfigOptionsListConstants::CONFIG_PATH_CACHE_HOSTS); - if (null == $configuredHosts) { - $httpHost = $this->request->getHttpHost(); - $servers[] = $httpHost ? - UriFactory::factory('')->setHost($httpHost)->setPort(self::DEFAULT_PORT)->setScheme('http') : - UriFactory::factory($this->urlBuilder->getUrl('*', ['_nosid' => true])) // Don't use SID in building URL - ->setScheme('http') - ->setPath(null) - ->setQuery(null); - } else { + if (is_array($configuredHosts)) { foreach ($configuredHosts as $host) { - $servers[] = UriFactory::factory('')->setHost($host['host']) + $servers[] = UriFactory::factory('') + ->setHost($host['host']) ->setPort(isset($host['port']) ? $host['port'] : self::DEFAULT_PORT) - ->setScheme('http'); + ; } + } elseif ($this->request->getHttpHost()) { + $servers[] = UriFactory::factory('')->setHost($this->request->getHttpHost())->setPort(self::DEFAULT_PORT); + } else { + $servers[] = UriFactory::factory($this->urlBuilder->getUrl('*', ['_nosid' => true])); + } + + foreach (array_keys($servers) as $key) { + $servers[$key]->setScheme('http') + ->setPath('/') + ->setQuery(null); } return $servers; } diff --git a/app/code/Magento/PageCache/Model/Controller/Result/BuiltinPlugin.php b/app/code/Magento/PageCache/Model/Controller/Result/BuiltinPlugin.php index 022824faafbd41faf61b1539f2939ee0ba0277bd..f72a0e3bc46b3a2b13ee869df164d29c439e47bd 100644 --- a/app/code/Magento/PageCache/Model/Controller/Result/BuiltinPlugin.php +++ b/app/code/Magento/PageCache/Model/Controller/Result/BuiltinPlugin.php @@ -73,8 +73,10 @@ class BuiltinPlugin } if ($this->state->getMode() == \Magento\Framework\App\State::MODE_DEVELOPER) { - $cacheControl = $response->getHeader('Cache-Control')->getFieldValue(); - $response->setHeader('X-Magento-Cache-Control', $cacheControl); + $cacheControlHeader = $response->getHeader('Cache-Control'); + if ($cacheControlHeader instanceof \Zend\Http\Header\HeaderInterface) { + $response->setHeader('X-Magento-Cache-Control', $cacheControlHeader->getFieldValue()); + } $response->setHeader('X-Magento-Cache-Debug', 'MISS', true); } diff --git a/app/code/Magento/PageCache/Observer/FlushAllCache.php b/app/code/Magento/PageCache/Observer/FlushAllCache.php index a704c5e116bcb9df49849baf615f8671f61f0f3b..712cc51005569a953646ba69810f05e42a63e3d7 100644 --- a/app/code/Magento/PageCache/Observer/FlushAllCache.php +++ b/app/code/Magento/PageCache/Observer/FlushAllCache.php @@ -6,12 +6,15 @@ */ namespace Magento\PageCache\Observer; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Event\ObserverInterface; class FlushAllCache implements ObserverInterface { /** * @var \Magento\Framework\App\PageCache\Cache + * + * @deprecated */ protected $_cache; @@ -22,6 +25,11 @@ class FlushAllCache implements ObserverInterface */ protected $_config; + /** + * @var \Magento\PageCache\Model\Cache\Type + */ + private $fullPageCache; + /** * @param \Magento\PageCache\Model\Config $config * @param \Magento\Framework\App\PageCache\Cache $cache @@ -41,7 +49,20 @@ class FlushAllCache implements ObserverInterface public function execute(\Magento\Framework\Event\Observer $observer) { if ($this->_config->getType() == \Magento\PageCache\Model\Config::BUILT_IN) { - $this->_cache->clean(); + $this->getCache()->clean(); + } + } + + /** + * TODO: Workaround to support backwards compatibility, will rework to use Dependency Injection in MAGETWO-49547 + * + * @return \Magento\PageCache\Model\Cache\Type + */ + private function getCache() + { + if (!$this->fullPageCache) { + $this->fullPageCache = ObjectManager::getInstance()->get('\Magento\PageCache\Model\Cache\Type'); } + return $this->fullPageCache; } } diff --git a/app/code/Magento/PageCache/Observer/FlushCacheByTags.php b/app/code/Magento/PageCache/Observer/FlushCacheByTags.php index c77cacb77cce35648dab065025d9d147a93d9721..116162c5047b5491a424b55e9125b2ed99e21434 100644 --- a/app/code/Magento/PageCache/Observer/FlushCacheByTags.php +++ b/app/code/Magento/PageCache/Observer/FlushCacheByTags.php @@ -6,12 +6,15 @@ */ namespace Magento\PageCache\Observer; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Event\ObserverInterface; class FlushCacheByTags implements ObserverInterface { /** * @var \Magento\Framework\App\PageCache\Cache + * + * @deprecated */ protected $_cache; @@ -22,6 +25,11 @@ class FlushCacheByTags implements ObserverInterface */ protected $_config; + /** + * @var \Magento\PageCache\Model\Cache\Type + */ + private $fullPageCache; + /** * @param \Magento\PageCache\Model\Config $config * @param \Magento\Framework\App\PageCache\Cache $cache @@ -49,9 +57,22 @@ class FlushCacheByTags implements ObserverInterface $tags[] = preg_replace("~_\\d+$~", '', $tag); } if (!empty($tags)) { - $this->_cache->clean(array_unique($tags)); + $this->getCache()->clean(\Zend_Cache::CLEANING_MODE_ALL, array_unique($tags)); } } } } + + /** + * TODO: Workaround to support backwards compatibility, will rework to use Dependency Injection in MAGETWO-49547 + * + * @return \Magento\PageCache\Model\Cache\Type + */ + private function getCache() + { + if (!$this->fullPageCache) { + $this->fullPageCache = ObjectManager::getInstance()->get('\Magento\PageCache\Model\Cache\Type'); + } + return $this->fullPageCache; + } } diff --git a/app/code/Magento/PageCache/Test/Unit/Model/Cache/ServerTest.php b/app/code/Magento/PageCache/Test/Unit/Model/Cache/ServerTest.php index f18c5e04468da59169ae6b3b5642d428b7113667..82d6a10598fa7f193138fa0b352f100fbf6f75ac 100644 --- a/app/code/Magento/PageCache/Test/Unit/Model/Cache/ServerTest.php +++ b/app/code/Magento/PageCache/Test/Unit/Model/Cache/ServerTest.php @@ -5,11 +5,13 @@ */ namespace Magento\PageCache\Test\Unit\Model\Cache; +use \Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use \Magento\PageCache\Model\Cache\Server; use \Zend\Uri\UriFactory; class ServerTest extends \PHPUnit_Framework_TestCase { - /** @var \Magento\PageCache\Model\Cache\Server */ + /** @var Server */ protected $model; /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\App\DeploymentConfig */ @@ -30,7 +32,7 @@ class ServerTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $objectManager = new ObjectManager($this); $this->model = $objectManager->getObject( 'Magento\PageCache\Model\Cache\Server', [ @@ -56,12 +58,9 @@ class ServerTest extends \PHPUnit_Framework_TestCase $url, $hostConfig = null ) { - $this->configMock->expects($this->once()) - ->method('get') - ->willReturn($hostConfig); - $this->requestMock->expects($this->exactly($getHttpHostCallCtr)) - ->method('getHttpHost') - ->willReturn($httpHost); + $this->configMock->expects($this->once())->method('get')->willReturn($hostConfig); + $this->requestMock->expects($this->exactly($getHttpHostCallCtr))->method('getHttpHost')->willReturn($httpHost); + $this->urlBuilderMock->expects($this->exactly($getUrlCallCtr)) ->method('getUrl') ->with('*', ['_nosid' => true]) @@ -70,30 +69,32 @@ class ServerTest extends \PHPUnit_Framework_TestCase $uris = []; if (null === $hostConfig) { if (!empty($httpHost)) { - $uris[] = UriFactory::factory('')->setHost($httpHost) - ->setPort(\Magento\PageCache\Model\Cache\Server::DEFAULT_PORT) - ->setScheme('http'); + $uris[] = UriFactory::factory('')->setHost($httpHost)->setPort(Server::DEFAULT_PORT); } if (!empty($url)) { $uris[] = UriFactory::factory($url); } } else { foreach ($hostConfig as $host) { - $port = isset($host['port']) ? $host['port'] : \Magento\PageCache\Model\Cache\Server::DEFAULT_PORT; - $uris[] = UriFactory::factory('')->setHost($host['host']) - ->setPort($port) - ->setScheme('http'); + $port = isset($host['port']) ? $host['port'] : Server::DEFAULT_PORT; + $uris[] = UriFactory::factory('')->setHost($host['host'])->setPort($port); } } + foreach (array_keys($uris) as $key) { + $uris[$key]->setScheme('http') + ->setPath('/') + ->setQuery(null); + } + $this->assertEquals($uris, $this->model->getUris()); } public function getUrisDataProvider() { return [ - 'http host' => [1, '127.0.0.1', 0, '',], - 'url' => [1, '', 1, 'http://host',], + 'http host' => [2, '127.0.0.1', 0, ''], + 'url' => [1, '', 1, 'http://host'], 'config' => [ 0, '', diff --git a/app/code/Magento/PageCache/Test/Unit/Observer/FlushAllCacheTest.php b/app/code/Magento/PageCache/Test/Unit/Observer/FlushAllCacheTest.php index 1ea42d6e592be247a64e22456962f030762a627f..20706ef5b790b42db45fcae76b5bcb5e728d01fe 100644 --- a/app/code/Magento/PageCache/Test/Unit/Observer/FlushAllCacheTest.php +++ b/app/code/Magento/PageCache/Test/Unit/Observer/FlushAllCacheTest.php @@ -12,18 +12,19 @@ namespace Magento\PageCache\Test\Unit\Observer; class FlushAllCacheTest extends \PHPUnit_Framework_TestCase { /** @var \Magento\PageCache\Observer\FlushAllCache */ - protected $_model; + private $_model; /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\PageCache\Model\Config */ - protected $_configMock; + private $_configMock; /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\PageCache\Cache */ - protected $_cacheMock; + private $_cacheMock; - /** - * @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject| - */ - protected $observerMock; + /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Event\Observer */ + private $observerMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\PageCache\Model\Cache\Type */ + private $fullPageCacheMock; /** * Set up all mocks and data for test @@ -38,13 +39,18 @@ class FlushAllCacheTest extends \PHPUnit_Framework_TestCase false ); $this->_cacheMock = $this->getMock('Magento\Framework\App\PageCache\Cache', ['clean'], [], '', false); - + $this->fullPageCacheMock = $this->getMock('\Magento\PageCache\Model\Cache\Type', ['clean'], [], '', false); $this->observerMock = $this->getMock('Magento\Framework\Event\Observer'); $this->_model = new \Magento\PageCache\Observer\FlushAllCache( $this->_configMock, $this->_cacheMock ); + + $reflection = new \ReflectionClass('\Magento\PageCache\Observer\FlushAllCache'); + $reflectionProperty = $reflection->getProperty('fullPageCache'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->_model, $this->fullPageCacheMock); } /** @@ -60,7 +66,7 @@ class FlushAllCacheTest extends \PHPUnit_Framework_TestCase $this->returnValue(\Magento\PageCache\Model\Config::BUILT_IN) ); - $this->_cacheMock->expects($this->once())->method('clean'); + $this->fullPageCacheMock->expects($this->once())->method('clean'); $this->_model->execute($this->observerMock); } } diff --git a/app/code/Magento/PageCache/Test/Unit/Observer/FlushCacheByTagsTest.php b/app/code/Magento/PageCache/Test/Unit/Observer/FlushCacheByTagsTest.php index 4020e4c9e3f9ff5e424acfad4d437ee8ec8d7176..a34dc5543c1593cacb08d2f1dc7dc738c8c938a3 100644 --- a/app/code/Magento/PageCache/Test/Unit/Observer/FlushCacheByTagsTest.php +++ b/app/code/Magento/PageCache/Test/Unit/Observer/FlushCacheByTagsTest.php @@ -20,6 +20,9 @@ class FlushCacheByTagsTest extends \PHPUnit_Framework_TestCase /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\PageCache\Cache */ protected $_cacheMock; + /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\PageCache\Model\Cache\Type */ + private $fullPageCacheMock; + /** * Set up all mocks and data for test */ @@ -33,11 +36,16 @@ class FlushCacheByTagsTest extends \PHPUnit_Framework_TestCase false ); $this->_cacheMock = $this->getMock('Magento\Framework\App\PageCache\Cache', ['clean'], [], '', false); + $this->fullPageCacheMock = $this->getMock('\Magento\PageCache\Model\Cache\Type', ['clean'], [], '', false); $this->_model = new \Magento\PageCache\Observer\FlushCacheByTags( $this->_configMock, $this->_cacheMock ); + $reflection = new \ReflectionClass('\Magento\PageCache\Observer\FlushCacheByTags'); + $reflectionProperty = $reflection->getProperty('fullPageCache'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->_model, $this->fullPageCacheMock); } /** @@ -59,16 +67,14 @@ class FlushCacheByTagsTest extends \PHPUnit_Framework_TestCase $eventMock = $this->getMock('Magento\Framework\Event', ['getObject'], [], '', false); $eventMock->expects($this->once())->method('getObject')->will($this->returnValue($observedObject)); $observerObject->expects($this->once())->method('getEvent')->will($this->returnValue($eventMock)); - $this->_configMock->expects( - $this->once() - )->method( - 'getType' - )->will( - $this->returnValue(\Magento\PageCache\Model\Config::BUILT_IN) - ); + $this->_configMock->expects($this->once()) + ->method('getType') + ->willReturn(\Magento\PageCache\Model\Config::BUILT_IN); $observedObject->expects($this->once())->method('getIdentities')->will($this->returnValue($tags)); - $this->_cacheMock->expects($this->once())->method('clean')->with($this->equalTo($expectedTags)); + $this->fullPageCacheMock->expects($this->once()) + ->method('clean') + ->with(\Zend_Cache::CLEANING_MODE_ALL, $this->equalTo($expectedTags)); } $this->_model->execute($observerObject); @@ -102,7 +108,7 @@ class FlushCacheByTagsTest extends \PHPUnit_Framework_TestCase ); $observedObject->expects($this->once())->method('getIdentities')->will($this->returnValue($tags)); - $this->_cacheMock->expects($this->never())->method('clean'); + $this->fullPageCacheMock->expects($this->never())->method('clean'); $this->_model->execute($observerObject); } diff --git a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Field/Hidden.php b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Field/Hidden.php index 1a5e6618d3f36a3138208d1faca614d57e43ec92..d8a23f08db80e15b9cf7770e47d1391972aec1e4 100644 --- a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Field/Hidden.php +++ b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Field/Hidden.php @@ -18,7 +18,7 @@ class Hidden extends \Magento\Config\Block\System\Config\Form\Field * @param string $html * @return string */ - protected function _decorateRowHtml($element, $html) + protected function _decorateRowHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element, $html) { return '<tr id="row_' . $element->getHtmlId() . '" style="display: none;">' . $html . '</tr>'; } diff --git a/app/code/Magento/Reports/Block/Adminhtml/Sales/Grid/Column/Renderer/Date.php b/app/code/Magento/Reports/Block/Adminhtml/Sales/Grid/Column/Renderer/Date.php index 7ffe3a73a943da66043c958b92be6f53c1cb1b3a..e763d77f3b4803d153911af7aacf372eed6d0965 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Sales/Grid/Column/Renderer/Date.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Sales/Grid/Column/Renderer/Date.php @@ -13,6 +13,12 @@ use Magento\Framework\Stdlib\DateTime\DateTimeFormatterInterface; */ class Date extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\Date { + + /** + * @var \Magento\Framework\Locale\ResolverInterface + */ + private $localeResolver; + /** * Constructor * @@ -28,7 +34,7 @@ class Date extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\Date array $data = [] ) { parent::__construct($context, $dateTimeFormatter, $data); - $this->_localeResolver = $localeResolver; + $this->localeResolver = $localeResolver; } /** @@ -41,7 +47,7 @@ class Date extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\Date $format = $this->getColumn()->getFormat(); if (!$format) { $dataBundle = new DataBundle(); - $resourceBundle = $dataBundle->get($this->_localeResolver->getLocale()); + $resourceBundle = $dataBundle->get($this->localeResolver->getLocale()); $formats = $resourceBundle['calendar']['gregorian']['availableFormats']; switch ($this->getColumn()->getPeriodType()) { case 'month': @@ -81,7 +87,7 @@ class Date extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\Date } else { $date = $this->_localeDate->date(new \DateTime($data), null, false); } - return $this->dateTimeFormatter->formatObject($date, $format, $this->_localeResolver->getLocale()); + return $this->dateTimeFormatter->formatObject($date, $format, $this->localeResolver->getLocale()); } return $this->getColumn()->getDefault(); } diff --git a/app/code/Magento/Review/view/frontend/web/js/process-reviews.js b/app/code/Magento/Review/view/frontend/web/js/process-reviews.js index 19e25f16a442f352dbb3df185ee39511d8f5d08e..b55d20968db6923bab8e796e60d3b17b486420b8 100644 --- a/app/code/Magento/Review/view/frontend/web/js/process-reviews.js +++ b/app/code/Magento/Review/view/frontend/web/js/process-reviews.js @@ -10,6 +10,7 @@ define([ function processReviews(url, fromPages) { $.ajax({ url: url, + cache: true, dataType: 'html' }).done(function (data) { $('#product-review-container').html(data); diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/View/Items.php b/app/code/Magento/Sales/Block/Adminhtml/Order/View/Items.php index 326fb104bbb2c242679fccb91bcbc16fe0ef515e..0f84c6a4f0205bd9c41b58e86f81561e41b2183e 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/View/Items.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/View/Items.php @@ -12,6 +12,15 @@ use Magento\Sales\Model\ResourceModel\Order\Item\Collection; */ class Items extends \Magento\Sales\Block\Adminhtml\Items\AbstractItems { + /** + * @return array + */ + public function getColumns() + { + $columns = array_key_exists('columns', $this->_data) ? $this->_data['columns'] : []; + return $columns; + } + /** * Retrieve required options from parent * diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/View/Items/Renderer/DefaultRenderer.php b/app/code/Magento/Sales/Block/Adminhtml/Order/View/Items/Renderer/DefaultRenderer.php index 30f059d0dbfe3c4e390469e9a7a4499e65feaa00..c22d0e7b55fbb691a5852203be5e78af703cb676 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/View/Items/Renderer/DefaultRenderer.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/View/Items/Renderer/DefaultRenderer.php @@ -254,4 +254,57 @@ class DefaultRenderer extends \Magento\Sales\Block\Adminhtml\Items\Renderer\Defa $this->_checkoutHelper->getPriceInclTax($item) ); } + + /** + * @param \Magento\Framework\DataObject|Item $item + * @param string $column + * @param null $field + * @return string + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function getColumnHtml(\Magento\Framework\DataObject $item, $column, $field = null) + { + $html = ''; + switch ($column) { + case 'product': + if ($this->canDisplayContainer()) { + $html .= '<div id="' . $this->getHtmlId() . '">'; + } + $html .= $this->getColumnHtml($item, 'name'); + if ($this->canDisplayContainer()) { + $html .= '</div>'; + } + break; + case 'status': + $html = $item->getStatus(); + break; + case 'price-original': + $html = $this->displayPriceAttribute('original_price'); + break; + case 'price': + $html = $this->displayPriceAttribute('price'); + break; + case 'tax-amount': + $html = $this->displayPriceAttribute('tax_amount'); + break; + case 'tax-percent': + $html = $this->displayTaxPercent($item); + break; + case 'discont': + $html = $this->displayPriceAttribute('discount_amount'); + break; + default: + $html = parent::getColumnHtml($item, $column, $field); + } + return $html; + } + + /** + * @return array + */ + public function getColumns() + { + $columns = array_key_exists('columns', $this->_data) ? $this->_data['columns'] : []; + return $columns; + } } diff --git a/app/code/Magento/Sales/Model/Order/Email/Sender/OrderSender.php b/app/code/Magento/Sales/Model/Order/Email/Sender/OrderSender.php index d32aed76cb78b0efb7f17bfd5898317dec6cddda..8c0a31d790c966af5a8af7db35d699b8eb3d054e 100644 --- a/app/code/Magento/Sales/Model/Order/Email/Sender/OrderSender.php +++ b/app/code/Magento/Sales/Model/Order/Email/Sender/OrderSender.php @@ -127,13 +127,14 @@ class OrderSender extends Sender 'formattedShippingAddress' => $this->getFormattedShippingAddress($order), 'formattedBillingAddress' => $this->getFormattedBillingAddress($order), ]; + $transport = new \Magento\Framework\DataObject($transport); $this->eventManager->dispatch( 'email_order_set_template_vars_before', ['sender' => $this, 'transport' => $transport] ); - $this->templateContainer->setTemplateVars($transport); + $this->templateContainer->setTemplateVars($transport->getData()); parent::prepareTemplate($order); } diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_view.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_view.xml index 10915f10d9355c4da3401dcab6fc330fe71b76dd..4e98f3b893d90505e4531409744c89e4d914e46b 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_view.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_view.xml @@ -24,7 +24,36 @@ <block class="Magento\Sales\Block\Adminhtml\Order\View\Messages" name="order_messages"/> <block class="Magento\Sales\Block\Adminhtml\Order\View\Info" name="order_info" template="order/view/info.phtml"/> <block class="Magento\Sales\Block\Adminhtml\Order\View\Items" name="order_items" template="order/view/items.phtml"> - <block class="Magento\Sales\Block\Adminhtml\Order\View\Items\Renderer\DefaultRenderer" as="default" template="order/view/items/renderer/default.phtml"/> + <arguments> + <argument name="columns" xsi:type="array"> + <item name="product" xsi:type="string" translate="true">Product</item> + <item name="status" xsi:type="string" translate="true">Item Status</item> + <item name="price-original" xsi:type="string" translate="true">Original Price</item> + <item name="price" xsi:type="string" translate="true">Price</item> + <item name="ordered-qty" xsi:type="string" translate="true">Qty</item> + <item name="subtotal" xsi:type="string" translate="true">Subtotal</item> + <item name="tax-amount" xsi:type="string" translate="true">Tax Amount</item> + <item name="tax-percent" xsi:type="string" translate="true">Tax Percent</item> + <item name="discont" xsi:type="string" translate="true">Discount Amount</item> + <item name="total" xsi:type="string" translate="true">Row Total</item> + </argument> + </arguments> + <block class="Magento\Sales\Block\Adminhtml\Order\View\Items\Renderer\DefaultRenderer" as="default" template="order/view/items/renderer/default.phtml"> + <arguments> + <argument name="columns" xsi:type="array"> + <item name="product" xsi:type="string" translate="false">col-product</item> + <item name="status" xsi:type="string" translate="false">col-status</item> + <item name="price-original" xsi:type="string" translate="false">col-price-original</item> + <item name="price" xsi:type="string" translate="false">col-price</item> + <item name="qty" xsi:type="string" translate="false">col-ordered-qty</item> + <item name="subtotal" xsi:type="string" translate="false">col-subtotal</item> + <item name="tax-amount" xsi:type="string" translate="false">col-tax-amount</item> + <item name="tax-percent" xsi:type="string" translate="false">col-tax-percent</item> + <item name="discont" xsi:type="string" translate="false">col-discont</item> + <item name="total" xsi:type="string" translate="false">col-total</item> + </argument> + </arguments> + </block> <block class="Magento\Sales\Block\Adminhtml\Items\Column\Qty" name="column_qty" template="items/column/qty.phtml" group="column"/> <block class="Magento\Sales\Block\Adminhtml\Items\Column\Name" name="column_name" template="items/column/name.phtml" group="column"/> <block class="Magento\Framework\View\Element\Text\ListText" name="order_item_extra_info"/> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/view/items.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/view/items.phtml index 879872ee9271a7bcb883f5ff2b84c5687fe8a5e5..41cb768997bfba9ff1532c50bdf32cefa7f5d864 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/view/items.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/view/items.phtml @@ -5,23 +5,23 @@ */ // @codingStandardsIgnoreFile - ?> -<?php $_order = $block->getOrder() ?> +<?php +/** + * @var \Magento\Sales\Block\Adminhtml\Order\View\Items $block + */ +$_order = $block->getOrder() ?> <div class="admin__table-wrapper"> <table class="data-table admin__table-primary edit-order-table"> <thead> <tr class="headings"> - <th class="col-product"><span><?php /* @escapeNotVerified */ echo __('Product') ?></span></th> - <th class="col-status"><span><?php /* @escapeNotVerified */ echo __('Item Status') ?></span></th> - <th class="col-price-original"><span><?php /* @escapeNotVerified */ echo __('Original Price') ?></span></th> - <th class="col-price"><span><?php /* @escapeNotVerified */ echo __('Price') ?></span></th> - <th class="col-ordered-qty"><span><?php /* @escapeNotVerified */ echo __('Qty') ?></span></th> - <th class="col-subtotal"><span><?php /* @escapeNotVerified */ echo __('Subtotal') ?></span></th> - <th class="col-tax-amount"><span><?php /* @escapeNotVerified */ echo __('Tax Amount') ?></span></th> - <th class="col-tax-percent"><span><?php /* @escapeNotVerified */ echo __('Tax Percent') ?></span></th> - <th class="col-discont"><span><?php /* @escapeNotVerified */ echo __('Discount Amount') ?></span></th> - <th class="col-total last"><span><?php /* @escapeNotVerified */ echo __('Row Total') ?></span></th> + <?php $i = 0; + $columns = $block->getColumns(); + $lastItemNumber = count($columns) ?> + <?php foreach ($columns as $columnName => $columnTitle):?> + <?php $i++; ?> + <th class="col-<?php /* @noEscape */ echo $columnName ?><?php /* @noEscape */ echo ($i === $lastItemNumber ? ' last' : '')?>"><span><?php /* @noEscape */ echo $columnTitle ?></span></th> + <?php endforeach; ?> </tr> </thead> <?php $_items = $block->getItemsCollection();?> @@ -31,7 +31,7 @@ } else { $i++; }?> - <tbody class="<?php /* @escapeNotVerified */ echo $i%2 ? 'even' : 'odd' ?>"> + <tbody class="<?php /* @noEscape */ echo $i%2 ? 'even' : 'odd' ?>"> <?php echo $block->getItemHtml($_item) ?> <?php echo $block->getItemExtraInfoHtml($_item) ?> </tbody> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/view/items/renderer/default.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/view/items/renderer/default.phtml index da3367344c439d6deabd167a801bcc80e5937fa2..aec3fc17b54125b97f86e6ee7fc78498535b4db8 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/view/items/renderer/default.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/view/items/renderer/default.phtml @@ -7,33 +7,15 @@ // @codingStandardsIgnoreFile ?> -<?php /** @var $block \Magento\Sales\Block\Adminhtml\Order\View\Items\Renderer\DefaultRenderer */ ?> +<?php /** @var \Magento\Sales\Block\Adminhtml\Order\View\Items\Renderer\DefaultRenderer $block */ ?> <?php $_item = $block->getItem() ?> <?php $block->setPriceDataObject($_item) ?> <tr> - <td class="col-product"> - <?php if ($block->canDisplayContainer()): ?> - <div id="<?php echo $block->getHtmlId() ?>"> - <?php endif; ?> - <?php echo $block->getColumnHtml($_item, 'name') ?> - <?php if ($block->canDisplayContainer()): ?> - </div> - <?php endif ?> - </td> - <td class="col-status"><?php /* @escapeNotVerified */ echo $_item->getStatus() ?></td> - <td class="col-price-original"><?php /* @escapeNotVerified */ echo $block->displayPriceAttribute('original_price') ?></td> - <td class="col-price"> - <?php echo $block->getColumnHtml($_item, 'price'); ?> - </td> - <td class="col-ordered-qty"><?php echo $block->getColumnHtml($_item, 'qty') ?></td> - - <td class="col-subtotal"> - <?php echo $block->getColumnHtml($_item, 'subtotal'); ?> - </td> - <td class="col-tax-amount"><?php /* @escapeNotVerified */ echo $block->displayPriceAttribute('tax_amount') ?></td> - <td class="col-tax-percent"><?php /* @escapeNotVerified */ echo $block->displayTaxPercent($_item) ?></td> - <td class="col-discont"><?php /* @escapeNotVerified */ echo $block->displayPriceAttribute('discount_amount') ?></td> - <td class="col-total last"> - <?php echo $block->getColumnHtml($_item, 'total'); ?> - </td> + <?php $i = 0; + $columns = $block->getColumns(); + $lastItemNumber = count($columns) ?> + <?php foreach ($columns as $columnName => $columnClass):?> + <?php $i++; ?> + <td class="<?php echo /* @noEscape */ $columnClass?><?php /* @noEscape */ echo ($i === $lastItemNumber ? ' last' : '')?>"><?php /* @escapeNotVerified */ echo $block->getColumnHtml($_item, $columnName) ?></td> + <?php endforeach; ?> </tr> diff --git a/app/code/Magento/Sales/view/frontend/email/order_new.html b/app/code/Magento/Sales/view/frontend/email/order_new.html index 8c7bbf3302938959d1c1cb62649914e9a77b1202..5963b574830c5718c0d1adbf7632aaac53a8ff1a 100644 --- a/app/code/Magento/Sales/view/frontend/email/order_new.html +++ b/app/code/Magento/Sales/view/frontend/email/order_new.html @@ -13,6 +13,7 @@ "var payment_html|raw":"Payment Details", "var formattedShippingAddress|raw":"Shipping Address", "var order.getShippingDescription()":"Shipping Description" +"var shipping_msg":"Shipping message" } @--> {{template config_path="design/email/header_template"}} @@ -73,6 +74,9 @@ <td class="method-info"> <h3>{{trans "Shipping Method"}}</h3> <p>{{var order.getShippingDescription()}}</p> + {{if shipping_msg}} + <p>{{var shipping_msg}}</p> + {{/if}} </td> {{/depend}} </tr> diff --git a/app/code/Magento/Sales/view/frontend/email/order_new_guest.html b/app/code/Magento/Sales/view/frontend/email/order_new_guest.html index 42bd401c7ae6651b9214959ee53c04011f422c13..4669fc43ff077cf241512343db8cdb2a4afc8f66 100644 --- a/app/code/Magento/Sales/view/frontend/email/order_new_guest.html +++ b/app/code/Magento/Sales/view/frontend/email/order_new_guest.html @@ -15,6 +15,7 @@ "var payment_html|raw":"Payment Details", "var formattedShippingAddress|raw":"Shipping Address", "var order.getShippingDescription()":"Shipping Description" +"var shipping_msg":"Shipping message" } @--> {{template config_path="design/email/header_template"}} @@ -71,6 +72,9 @@ <td class="method-info"> <h3>{{trans "Shipping Method"}}</h3> <p>{{var order.getShippingDescription()}}</p> + {{if shipping_msg}} + <p>{{var shipping_msg}}</p> + {{/if}} </td> {{/depend}} </tr> diff --git a/app/code/Magento/Security/Controller/Adminhtml/Session/Check.php b/app/code/Magento/Security/Controller/Adminhtml/Session/Check.php index e14a2b8f7ffb51684eb4f42b8e6beec81f0029d2..bdf990f05f619119ee7e474ea5d99c2003ea818e 100644 --- a/app/code/Magento/Security/Controller/Adminhtml/Session/Check.php +++ b/app/code/Magento/Security/Controller/Adminhtml/Session/Check.php @@ -7,6 +7,7 @@ namespace Magento\Security\Controller\Adminhtml\Session; use Magento\Backend\App\Action\Context; use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Security\Helper\SecurityCookie; use Magento\Security\Model\AdminSessionsManager; /** @@ -17,15 +18,14 @@ class Check extends \Magento\Backend\App\Action /** * @var JsonFactory */ - protected $jsonFactory; + private $jsonFactory; /** * @var AdminSessionsManager */ - protected $sessionsManager; + private $sessionsManager; /** - * Check constructor. * @param Context $context * @param JsonFactory $jsonFactory * @param AdminSessionsManager $sessionsManager @@ -48,7 +48,7 @@ class Check extends \Magento\Backend\App\Action /** @var \Magento\Framework\Controller\Result\Json $resultJson */ return $this->jsonFactory->create()->setData( [ - 'isActive' => $this->sessionsManager->getCurrentSession()->isActive() + 'isActive' => $this->sessionsManager->getCurrentSession()->isLoggedInStatus() ] ); } diff --git a/app/code/Magento/Security/Model/AdminSessionInfo.php b/app/code/Magento/Security/Model/AdminSessionInfo.php index d47ae28dc4224b38d477f2fa80517699307b70b2..87b9e3ca0f17a855eb49ffcc94a75cd363404bab 100644 --- a/app/code/Magento/Security/Model/AdminSessionInfo.php +++ b/app/code/Magento/Security/Model/AdminSessionInfo.php @@ -89,17 +89,20 @@ class AdminSessionInfo extends \Magento\Framework\Model\AbstractModel */ public function isLoggedInStatus() { + $this->checkActivity(); return $this->getData('status') == self::LOGGED_IN; } /** - * Check if a user is active + * Check if session is timed out and set status accordingly * - * @return bool + * @return void */ - public function isActive() + private function checkActivity() { - return $this->isLoggedInStatus() && !$this->isSessionExpired(); + if ($this->isSessionExpired()) { + $this->setData('status', self::LOGGED_OUT); + } } /** diff --git a/app/code/Magento/Security/Model/AdminSessionsManager.php b/app/code/Magento/Security/Model/AdminSessionsManager.php index 77c057f7a63f8d23e7440d7126e318f8b598f690..29249ee9fb350aff6d8da260c191a732457b31e6 100644 --- a/app/code/Magento/Security/Model/AdminSessionsManager.php +++ b/app/code/Magento/Security/Model/AdminSessionsManager.php @@ -147,7 +147,7 @@ class AdminSessionsManager { switch ((int)$statusCode) { case AdminSessionInfo::LOGGED_IN: - $reasonMessage = ''; + $reasonMessage = null; break; case AdminSessionInfo::LOGGED_OUT_BY_LOGIN: $reasonMessage = __( diff --git a/app/code/Magento/Security/Model/Plugin/AuthSession.php b/app/code/Magento/Security/Model/Plugin/AuthSession.php index 1ee9aa5cc9ba0d673009918308984454e71eb91b..ea15828afc733e135bfc16f7325dad2fdac21ff2 100644 --- a/app/code/Magento/Security/Model/Plugin/AuthSession.php +++ b/app/code/Magento/Security/Model/Plugin/AuthSession.php @@ -7,7 +7,6 @@ namespace Magento\Security\Model\Plugin; use Magento\Backend\Model\Auth\Session; use Magento\Security\Model\AdminSessionsManager; -use Magento\Framework\Stdlib\Cookie\CookieReaderInterface; /** * Magento\Backend\Model\Auth\Session decorator @@ -62,7 +61,7 @@ class AuthSession public function aroundProlong(Session $session, \Closure $proceed) { if (!$this->isSessionCheckRequest()) { - if (!$this->sessionsManager->getCurrentSession()->isActive()) { + if (!$this->sessionsManager->getCurrentSession()->isLoggedInStatus()) { $session->destroy(); $this->addUserLogoutNotification(); return null; @@ -84,10 +83,8 @@ class AuthSession $this->securityCookieHelper->setLogoutReasonCookie( $this->sessionsManager->getCurrentSession()->getStatus() ); - } else { - $this->messageManager->addError( - $this->sessionsManager->getLogoutReasonMessage() - ); + } else if ($message = $this->sessionsManager->getLogoutReasonMessage()) { + $this->messageManager->addError($message); } return $this; diff --git a/app/code/Magento/Security/Test/Unit/Controller/Adminhtml/Session/CheckTest.php b/app/code/Magento/Security/Test/Unit/Controller/Adminhtml/Session/CheckTest.php index 959c7e62a63c1a99f3d60472278f432429c38dcc..03bc6029a9669a7d398db75207ffcb3473853cc9 100644 --- a/app/code/Magento/Security/Test/Unit/Controller/Adminhtml/Session/CheckTest.php +++ b/app/code/Magento/Security/Test/Unit/Controller/Adminhtml/Session/CheckTest.php @@ -64,7 +64,7 @@ class CheckTest extends \PHPUnit_Framework_TestCase $this->currentSession = $this->getMock( '\Magento\Security\Model\AdminSessionInfo', - ['isActive'], + ['isLoggedInStatus'], [], '', false @@ -92,7 +92,7 @@ class CheckTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); $this->sessionsManager->expects($this->any())->method('getCurrentSession')->willReturn($this->currentSession); - $this->currentSession->expects($this->any())->method('isActive') + $this->currentSession->expects($this->any())->method('isLoggedInStatus') ->will($this->returnValue($result)); $this->jsonFactory->expects($this->any())->method('create')->willReturn($jsonMock); $jsonMock->expects($this->once()) diff --git a/app/code/Magento/Security/Test/Unit/Model/AdminSessionInfoTest.php b/app/code/Magento/Security/Test/Unit/Model/AdminSessionInfoTest.php index 8d1e5569cb28b46ec16cb33095e5c748f3116516..efbae38d58d2a0edf6a9504b1fdbf7e3edc7a31c 100644 --- a/app/code/Magento/Security/Test/Unit/Model/AdminSessionInfoTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/AdminSessionInfoTest.php @@ -19,7 +19,7 @@ class AdminSessionInfoTest extends \PHPUnit_Framework_TestCase protected $model; /** - * @var \Magento\Security\Helper\SecurityConfig + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Security\Helper\SecurityConfig */ protected $securityConfigMock; @@ -57,9 +57,25 @@ class AdminSessionInfoTest extends \PHPUnit_Framework_TestCase public function testIsLoggedInStatus() { $this->model->setData('status', \Magento\Security\Model\AdminSessionInfo::LOGGED_IN); + $this->model->setUpdatedAt(901); + $this->securityConfigMock->expects($this->once())->method('getAdminSessionLifetime')->willReturn(100); + $this->securityConfigMock->expects($this->once())->method('getCurrentTimestamp')->willReturn(1000); $this->assertEquals(true, $this->model->isLoggedInStatus()); } + /** + * @return void + */ + public function testIsLoggedInStatusExpired() + { + $this->model->setData('status', \Magento\Security\Model\AdminSessionInfo::LOGGED_IN); + $this->model->setUpdatedAt(899); + $this->securityConfigMock->expects($this->once())->method('getAdminSessionLifetime')->willReturn(100); + $this->securityConfigMock->expects($this->once())->method('getCurrentTimestamp')->willReturn(1000); + $this->assertEquals(false, $this->model->isLoggedInStatus()); + $this->assertEquals(\Magento\Security\Model\AdminSessionInfo::LOGGED_OUT, $this->model->getStatus()); + } + /** * @param bool $expectedResult * @param string $sessionLifetime @@ -96,25 +112,6 @@ class AdminSessionInfoTest extends \PHPUnit_Framework_TestCase ]; } - /** - * @param bool $expectedResult - * @param bool $sessionLifetime - * @dataProvider dataProviderIsActive - */ - public function testIsActive($expectedResult, $sessionLifetime) - { - $this->model->setData('status', \Magento\Security\Model\AdminSessionInfo::LOGGED_IN); - $this->securityConfigMock->expects($this->any()) - ->method('getAdminSessionLifetime') - ->will($this->returnValue($sessionLifetime)); - $this->securityConfigMock->expects($this->any()) - ->method('getCurrentTimestamp') - ->will($this->returnValue(10)); - $this->model->setUpdatedAt(9); - - $this->assertEquals($expectedResult, $this->model->isActive()); - } - /** * @return array */ diff --git a/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php b/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php index 14ea2b4e2c2ca69d07b826b6b0142713cfddb4a3..23534ff62deddc7f918ac4418e9b747905f2d433 100644 --- a/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php @@ -86,7 +86,7 @@ class AuthSessionTest extends \PHPUnit_Framework_TestCase $this->currentSessionMock = $this->getMock( '\Magento\Security\Model\AdminSessionInfo', - ['isActive', 'getStatus'], + ['isLoggedInStatus', 'getStatus'], [], '', false @@ -128,7 +128,7 @@ class AuthSessionTest extends \PHPUnit_Framework_TestCase }; $this->currentSessionMock->expects($this->once()) - ->method('isActive') + ->method('isLoggedInStatus') ->willReturn(false); $this->authSessionMock->expects($this->once()) @@ -197,7 +197,7 @@ class AuthSessionTest extends \PHPUnit_Framework_TestCase }; $this->currentSessionMock->expects($this->any()) - ->method('isActive') + ->method('isLoggedInStatus') ->willReturn(true); $this->adminSessionsManagerMock->expects($this->any()) diff --git a/app/code/Magento/Shipping/Model/Rate/Result.php b/app/code/Magento/Shipping/Model/Rate/Result.php index 697b91b4d5941629c46b1caf9d4bffdbbecf0fb9..a988e149e9e838aa45efb875594280e062b98836 100644 --- a/app/code/Magento/Shipping/Model/Rate/Result.php +++ b/app/code/Magento/Shipping/Model/Rate/Result.php @@ -91,7 +91,7 @@ class Result /** * Return all quotes in the result * - * @return array + * @return \Magento\Quote\Model\Quote\Address\RateResult\Method[] */ public function getAllRates() { diff --git a/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php b/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php index 69caff420de64d68793217b6fbf210ed6e41ce56..f93d353445b4814c911f5891d70dee5fde410241 100644 --- a/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php +++ b/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php @@ -24,7 +24,8 @@ use Magento\Swatches\Model\Swatch; * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Configurable extends \Magento\ConfigurableProduct\Block\Product\View\Type\Configurable +class Configurable extends \Magento\ConfigurableProduct\Block\Product\View\Type\Configurable implements + \Magento\Framework\DataObject\IdentityInterface { /** * Path to template file with Swatch renderer. @@ -106,6 +107,26 @@ class Configurable extends \Magento\ConfigurableProduct\Block\Product\View\Type\ ); } + /** + * Get Key for caching block content + * + * @return string + */ + public function getCacheKey() + { + return parent::getCacheKey() . '-' . $this->getProduct()->getId(); + } + + /** + * Get block cache life time + * + * @return int + */ + protected function getCacheLifetime() + { + return parent::hasCacheLifetime() ? parent::getCacheLifetime() : 3600; + } + /** * Get Swatch config data * @@ -385,4 +406,18 @@ class Configurable extends \Magento\ConfigurableProduct\Block\Product\View\Type\ { return $this->getBaseUrl() . self::MEDIA_CALLBACK_ACTION; } + + /** + * Return unique ID(s) for each object in system + * + * @return string[] + */ + public function getIdentities() + { + if ($this->product instanceof \Magento\Framework\DataObject\IdentityInterface) { + return $this->product->getIdentities(); + } else { + return []; + } + } } diff --git a/app/code/Magento/Ui/DataProvider/EavValidationRules.php b/app/code/Magento/Ui/DataProvider/EavValidationRules.php index c8ae4b77340c4fefc7291c27bf5236ff92909796..18d249dc41da216d7605f306c4c4447b5e4b8242 100644 --- a/app/code/Magento/Ui/DataProvider/EavValidationRules.php +++ b/app/code/Magento/Ui/DataProvider/EavValidationRules.php @@ -15,11 +15,9 @@ class EavValidationRules /** * @var array */ - protected $validationRul = [ - 'input_validation' => [ - 'email' => ['validate-email' => true], - 'date' => ['validate-date' => true], - ], + protected $validationRules = [ + 'email' => ['validate-email' => true], + 'date' => ['validate-date' => true], ]; /** @@ -31,30 +29,23 @@ class EavValidationRules */ public function build(AbstractAttribute $attribute, array $data) { - $rules = []; - if (!empty($data['arguments']['data']['config']['required'])) { - $rules['required-entry'] = true; + $validation = []; + if (isset($data['required']) && $data['required'] == 1) { + $validation = array_merge($validation, ['required-entry' => true]); } if ($attribute->getFrontendInput() === 'price') { - $rules['validate-zero-or-greater'] = true; + $validation = array_merge($validation, ['validate-zero-or-greater' => true]); } - - $validation = $attribute->getValidateRules(); - if (!empty($validation)) { - foreach ($validation as $type => $ruleName) { - switch ($type) { - case 'input_validation': - if (isset($this->validationRul[$type][$ruleName])) { - $rules = array_merge($rules, $this->validationRul[$type][$ruleName]); - } - break; - case 'min_text_length': - case 'max_text_length': - $rules = array_merge($rules, [$type => $ruleName]); - break; - } - + if ($attribute->getValidateRules()) { + $validation = array_merge($validation, $attribute->getValidateRules()); + } + $rules = []; + foreach ($validation as $type => $ruleName) { + $rule = [$type => $ruleName]; + if ($type === 'input_validation') { + $rule = isset($this->validationRules[$ruleName]) ? $this->validationRules[$ruleName] : []; } + $rules = array_merge($rules, $rule); } return $rules; diff --git a/app/code/Magento/Ui/Test/Unit/DataProvider/EavValidationRulesTest.php b/app/code/Magento/Ui/Test/Unit/DataProvider/EavValidationRulesTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e3248fb9b661685917a1cc55c799b94582bab874 --- /dev/null +++ b/app/code/Magento/Ui/Test/Unit/DataProvider/EavValidationRulesTest.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Ui\Test\Unit\DataProvider; + +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Ui\DataProvider\EavValidationRules; + +/** + * Class EavValidationRulesTest + */ +class EavValidationRulesTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ObjectManager + */ + protected $objectManager; + + /** + * @var \Magento\Ui\DataProvider\EavValidationRules + */ + protected $subject; + + /** + * @var \Magento\Eav\Model\Entity\Attribute\AbstractAttribute|\PHPUnit_Framework_MockObject_MockObject + */ + protected $attributeMock; + + protected function setUp() + { + $this->objectManager = new ObjectManager($this); + $this->attributeMock = + $this->getMockBuilder(AbstractAttribute::class) + ->setMethods(['getFrontendInput', 'getValidateRules']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->subject = new EavValidationRules(); + } + + /** + * @param array $data + * @param array $expected + * @dataProvider buildDataProvider + */ + public function testBuild($attributeInputType, $validateRules, $data, $expected) + { + $this->attributeMock->expects($this->once())->method('getFrontendInput')->willReturn($attributeInputType); + $this->attributeMock->expects($this->any())->method('getValidateRules')->willReturn($validateRules); + $validationRules = $this->subject->build($this->attributeMock, $data); + $this->assertEquals($expected, $validationRules); + } + + /** + * @return array + */ + public function buildDataProvider() + { + return [ + ['', '', [], []], + ['', null, [], []], + ['', false, [], []], + ['', [], [], []], + ['', '', ['required' => 1], ['required-entry' => true]], + ['price', '', [], ['validate-zero-or-greater' => true]], + ['price', '', ['required' => 1], ['validate-zero-or-greater' => true, 'required-entry' => true]], + ['', ['input_validation' => 'email'], [], ['validate-email' => true]], + ['', ['input_validation' => 'date'], [], ['validate-date' => true]], + ['', ['input_validation' => 'other'], [], []], + ['', ['max_text_length' => '254'], ['required' => 1], ['max_text_length' => 254, 'required-entry' => true]], + ['', ['max_text_length' => '254', 'min_text_length' => 1], [], + ['max_text_length' => 254, 'min_text_length' => 1]], + ['', ['max_text_length' => '254', 'input_validation' => 'date'], [], + ['max_text_length' => 254, 'validate-date' => true]], + ]; + } +} diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/button.js b/app/code/Magento/Ui/view/base/web/js/form/components/button.js index 6acce9e43ad6cc5ef976ca6446aaf05717b55018..9fbd07790a30e813a2b30bead14377f619ced490 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/button.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/button.js @@ -16,9 +16,9 @@ define([ additionalClasses: {}, displayArea: 'outsideGroup', displayAsLink: false, + visible: true, elementTmpl: 'ui/form/element/button', template: 'ui/form/components/button/simple', - visible: true, disabled: false }, diff --git a/app/code/Magento/Widget/Model/ResourceModel/Layout/Update.php b/app/code/Magento/Widget/Model/ResourceModel/Layout/Update.php index ec04453079d143e696535f418ddcff3dcdf1e06b..a6cbd245bbaf09bbe586974d8573112cccb421aa 100644 --- a/app/code/Magento/Widget/Model/ResourceModel/Layout/Update.php +++ b/app/code/Magento/Widget/Model/ResourceModel/Layout/Update.php @@ -63,6 +63,7 @@ class Update extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb $bind = ['theme_id' => $theme->getId(), 'store_id' => $store->getId()]; $cacheKey = implode('-', $bind); if (!isset($this->layoutUpdateCache[$cacheKey])) { + $this->layoutUpdateCache[$cacheKey] = []; foreach ($this->getConnection()->fetchAll($this->_getFetchUpdatesByHandleSelect(), $bind) as $layout) { if (!isset($this->layoutUpdateCache[$cacheKey][$layout['handle']])) { $this->layoutUpdateCache[$cacheKey][$layout['handle']] = ''; diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/order_new.html b/app/design/frontend/Magento/luma/Magento_Sales/email/order_new.html index 66cf888a6112d27fad29a9e2bb6d653a807e84ce..97c09dd74c91fa6839a78eeb4d0e524a08afdfa2 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/email/order_new.html +++ b/app/design/frontend/Magento/luma/Magento_Sales/email/order_new.html @@ -13,6 +13,7 @@ "var payment_html|raw":"Payment Details", "var formattedShippingAddress|raw":"Shipping Address", "var order.getShippingDescription()":"Shipping Description" +"var shipping_msg":"Shipping message" } @--> {{template config_path="design/email/header_template"}} @@ -70,6 +71,9 @@ <td class="method-info"> <h3>{{trans "Shipping Method"}}</h3> <p>{{var order.getShippingDescription()}}</p> + {{if shipping_msg}} + <p>{{var shipping_msg}}</p> + {{/if}} </td> {{/depend}} </tr> diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/order_new_guest.html b/app/design/frontend/Magento/luma/Magento_Sales/email/order_new_guest.html index d2e42978760d04aac34abf1ce0657ca043d41be6..28c89ba3e264572a156535dd631cb68b677ccbbe 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/email/order_new_guest.html +++ b/app/design/frontend/Magento/luma/Magento_Sales/email/order_new_guest.html @@ -15,6 +15,7 @@ "var payment_html|raw":"Payment Details", "var formattedShippingAddress|raw":"Shipping Address", "var order.getShippingDescription()":"Shipping Description" +"var shipping_msg":"Shipping message" } @--> {{template config_path="design/email/header_template"}} @@ -70,6 +71,9 @@ <td class="method-info"> <h3>{{trans "Shipping Method"}}</h3> <p>{{var order.getShippingDescription()}}</p> + {{if shipping_msg}} + <p>{{var shipping_msg}}</p> + {{/if}} </td> {{/depend}} </tr> diff --git a/app/etc/di.xml b/app/etc/di.xml index bc0812e5d423c84983516c9235d1a3c4f0c5885c..ee07e6b9fb21b4a4e17c0627ca4c1af8e35a3df9 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1075,6 +1075,11 @@ </argument> </arguments> </type> + <type name="Magento\Framework\Validator\Factory"> + <arguments> + <argument name="cache" xsi:type="object">Magento\Framework\App\Cache\Type\Config</argument> + </arguments> + </type> <type name="Magento\Server\Reflection" shared="false" /> <type name="Magento\Framework\Reflection\DataObjectProcessor"> <arguments> diff --git a/composer.json b/composer.json index db3b1b781892ec5e2aa952d01748ca36425f4c3a..b5222ae5f4fa824f5a0bde29995e1b5b0458d7fb 100644 --- a/composer.json +++ b/composer.json @@ -60,14 +60,14 @@ "ext-xsl": "*", "ext-mbstring": "*", "ext-openssl": "*", - "ext-zip": "*" + "ext-zip": "*", + "sjparkinson/static-review": "~4.1" }, "require-dev": { "phpunit/phpunit": "4.1.0", "squizlabs/php_codesniffer": "1.5.3", "phpmd/phpmd": "@stable", "pdepend/pdepend": "2.2.2", - "sjparkinson/static-review": "~4.1", "fabpot/php-cs-fixer": "~1.2", "lusitanian/oauth": "~0.3 <=0.7.0" }, diff --git a/composer.lock b/composer.lock index d7525677fdd8f1118add45701b401ebbc9dcc361..bdda4dadaf3f72c4319faa4e7be3f5c5a24f298d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "ac56596efe7d8aa9a070776885b20938", - "content-hash": "cedec14d2f9b4094b7aca6bdbbfc17c9", + "hash": "a784bbb7f1a3fc568160aa4129b1920f", + "content-hash": "d171af939bed91b4acc9b011f456528a", "packages": [ { "name": "braintree/braintree_php", @@ -265,6 +265,55 @@ ], "time": "2016-01-25 15:43:01" }, + { + "name": "league/climate", + "version": "2.6.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/climate.git", + "reference": "28851c909017424f61cc6a62089316313c645d1c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/climate/zipball/28851c909017424f61cc6a62089316313c645d1c", + "reference": "28851c909017424f61cc6a62089316313c645d1c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "dev-master", + "phpunit/phpunit": "4.1.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\CLImate\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joe Tannenbaum", + "email": "hey@joe.codes", + "homepage": "http://joe.codes/", + "role": "Developer" + } + ], + "description": "PHP's best friend for the terminal. CLImate allows you to easily output colored text, special formats, and more.", + "keywords": [ + "cli", + "colors", + "command", + "php", + "terminal" + ], + "time": "2015-01-18 14:31:58" + }, { "name": "magento/composer", "version": "1.0.2", @@ -301,16 +350,16 @@ }, { "name": "magento/magento-composer-installer", - "version": "0.1.5", + "version": "0.1.6", "source": { "type": "git", "url": "https://github.com/magento/magento-composer-installer.git", - "reference": "1b33917bfc3f4a0856276dcbe46ce35362f50fc3" + "reference": "5b5d29ebe060bc6754c2999206923489db8ca641" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/1b33917bfc3f4a0856276dcbe46ce35362f50fc3", - "reference": "1b33917bfc3f4a0856276dcbe46ce35362f50fc3", + "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/5b5d29ebe060bc6754c2999206923489db8ca641", + "reference": "5b5d29ebe060bc6754c2999206923489db8ca641", "shasum": "" }, "require": { @@ -373,7 +422,7 @@ "composer-installer", "magento" ], - "time": "2015-09-14 19:59:55" + "time": "2016-01-25 22:04:43" }, { "name": "magento/zendframework1", @@ -795,9 +844,62 @@ ], "time": "2015-11-21 02:21:41" }, + { + "name": "sjparkinson/static-review", + "version": "4.1.1", + "source": { + "type": "git", + "url": "https://github.com/sjparkinson/static-review.git", + "reference": "493c3410cf146a12fca84209bad126c494e125f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sjparkinson/static-review/zipball/493c3410cf146a12fca84209bad126c494e125f0", + "reference": "493c3410cf146a12fca84209bad126c494e125f0", + "shasum": "" + }, + "require": { + "league/climate": "~2.0", + "php": ">=5.4.0", + "symfony/console": "~2.0", + "symfony/process": "~2.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.0", + "sensiolabs/security-checker": "~2.0", + "squizlabs/php_codesniffer": "~1.0" + }, + "suggest": { + "sensiolabs/security-checker": "Required for ComposerSecurityReview.", + "squizlabs/php_codesniffer": "Required for PhpCodeSnifferReview." + }, + "bin": [ + "bin/static-review.php" + ], + "type": "library", + "autoload": { + "psr-4": { + "StaticReview\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Samuel Parkinson", + "email": "sam.james.parkinson@gmail.com", + "homepage": "http://samp.im" + } + ], + "description": "An extendable framework for version control hooks.", + "time": "2014-09-22 08:40:36" + }, { "name": "symfony/console", - "version": "v2.6.12", + "version": "v2.6.13", "target-dir": "Symfony/Component/Console", "source": { "type": "git", @@ -855,16 +957,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v2.8.1", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc" + "reference": "ee278f7c851533e58ca307f66305ccb9188aceda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc", - "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ee278f7c851533e58ca307f66305ccb9188aceda", + "reference": "ee278f7c851533e58ca307f66305ccb9188aceda", "shasum": "" }, "require": { @@ -911,7 +1013,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2015-10-30 20:15:42" + "time": "2016-01-13 10:28:07" }, { "name": "symfony/finder", @@ -2710,55 +2812,6 @@ "description": "A tool to automatically fix PHP code style", "time": "2016-01-20 19:00:28" }, - { - "name": "league/climate", - "version": "2.6.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/climate.git", - "reference": "28851c909017424f61cc6a62089316313c645d1c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/climate/zipball/28851c909017424f61cc6a62089316313c645d1c", - "reference": "28851c909017424f61cc6a62089316313c645d1c", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "mockery/mockery": "dev-master", - "phpunit/phpunit": "4.1.*" - }, - "type": "library", - "autoload": { - "psr-4": { - "League\\CLImate\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Joe Tannenbaum", - "email": "hey@joe.codes", - "homepage": "http://joe.codes/", - "role": "Developer" - } - ], - "description": "PHP's best friend for the terminal. CLImate allows you to easily output colored text, special formats, and more.", - "keywords": [ - "cli", - "colors", - "command", - "php", - "terminal" - ], - "time": "2015-01-18 14:31:58" - }, { "name": "lusitanian/oauth", "version": "v0.7.0", @@ -2906,7 +2959,7 @@ "name": "Manuel Pichler", "email": "github@manuel-pichler.de", "homepage": "https://github.com/manuelpichler", - "role": "Project Founder" + "role": "Project founder" }, { "name": "Other contributors", @@ -3619,59 +3672,6 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2015-06-21 13:59:46" }, - { - "name": "sjparkinson/static-review", - "version": "4.1.1", - "source": { - "type": "git", - "url": "https://github.com/sjparkinson/static-review.git", - "reference": "493c3410cf146a12fca84209bad126c494e125f0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sjparkinson/static-review/zipball/493c3410cf146a12fca84209bad126c494e125f0", - "reference": "493c3410cf146a12fca84209bad126c494e125f0", - "shasum": "" - }, - "require": { - "league/climate": "~2.0", - "php": ">=5.4.0", - "symfony/console": "~2.0", - "symfony/process": "~2.0" - }, - "require-dev": { - "mockery/mockery": "~0.9", - "phpunit/phpunit": "~4.0", - "sensiolabs/security-checker": "~2.0", - "squizlabs/php_codesniffer": "~1.0" - }, - "suggest": { - "sensiolabs/security-checker": "Required for ComposerSecurityReview.", - "squizlabs/php_codesniffer": "Required for PhpCodeSnifferReview." - }, - "bin": [ - "bin/static-review.php" - ], - "type": "library", - "autoload": { - "psr-4": { - "StaticReview\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Samuel Parkinson", - "email": "sam.james.parkinson@gmail.com", - "homepage": "http://samp.im" - } - ], - "description": "An extendable framework for version control hooks.", - "time": "2014-09-22 08:40:36" - }, { "name": "squizlabs/php_codesniffer", "version": "1.5.3", @@ -3910,16 +3910,16 @@ }, { "name": "symfony/stopwatch", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "6aeac8907e3e1340a0033b0a9ec075f8e6524800" + "reference": "4a204804952ff267ace88cf499e0b4bb302a475e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/6aeac8907e3e1340a0033b0a9ec075f8e6524800", - "reference": "6aeac8907e3e1340a0033b0a9ec075f8e6524800", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/4a204804952ff267ace88cf499e0b4bb302a475e", + "reference": "4a204804952ff267ace88cf499e0b4bb302a475e", "shasum": "" }, "require": { @@ -3955,7 +3955,7 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2015-10-30 23:35:59" + "time": "2016-01-03 15:35:16" }, { "name": "symfony/yaml", diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php index 2e31bec6182ee8d142cab94e7bc18eb3e49d9354..6f55dff0ba9f7d4b83a6799e16b7f44d8775b7f8 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php @@ -69,7 +69,6 @@ class ProductAttributeOptionManagementInterfaceTest extends WebapiAbstract $optionData = [ AttributeOptionInterface::LABEL => 'new color', - AttributeOptionInterface::VALUE => 'grey', AttributeOptionInterface::SORT_ORDER => 100, AttributeOptionInterface::IS_DEFAULT => true, AttributeOptionInterface::STORE_LABELS => [ diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php index 41032701784ce7c9788a9da60abb28a2516ce807..7b8e51fb2a7a4d93e46a16746b8b29f0bc4c2149 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php @@ -84,56 +84,26 @@ class ProductAttributeRepositoryTest extends \Magento\TestFramework\TestCase\Web $expectedData = [ 'attribute_code' => $attributeCode, - 'frontend_labels' => [ - [ - 'store_id' => 0, - 'label' => 'front_lbl' - ], - ], 'is_required' => true, - "default_value" => "", "frontend_input" => "select", "is_visible_on_front" => true, "is_searchable" => true, "is_visible_in_advanced_search" => true, "is_filterable" => true, "is_filterable_in_search" => true, - "options" => [ - [ - "label" => "string", - "value" => "string", - "sort_order" => 0, - "is_default" => true, - "store_labels" => [ - [ - "store_id" => 0, - "label" => "Red" - ], - [ - "store_id" => 1, - "label" => "Blue" - ], - [ - "store_id" => 2, - "label" => "Yellow" - ] - ] - ] - ] ]; - $this->assertEquals($attribute['options'], $expectedData['options']); - $this->assertEquals($attribute['attribute_code'], $expectedData['attribute_code']); - $this->assertEquals($attribute['frontend_labels'], $expectedData['frontend_labels']); - $this->assertEquals($attribute['is_required'], $expectedData['is_required']); - $this->assertEquals($attribute['default_value'], $expectedData['default_value']); - $this->assertEquals($attribute['frontend_input'], $expectedData['frontend_input']); - $this->assertEquals($attribute['is_filterable'], $expectedData['is_filterable']); - $this->assertEquals($attribute['is_filterable_in_search'], $expectedData['is_filterable_in_search']); - $this->assertEquals( - $attribute['is_visible_in_advanced_search'], - $expectedData['is_visible_in_advanced_search'] - ); + $this->assertEquals('front_lbl', $attribute['default_frontend_label']); + foreach ($expectedData as $key => $value) { + $this->assertEquals($value, $attribute[$key]); + } + //Validate options + //'Blue' should be first as it has sort_order = 0 + $this->assertEquals('Default Blue', $attribute['options'][1]['label']); + $this->assertArrayHasKey('default_value', $attribute); + //'Blue' should be set as default + $this->assertEquals($attribute['default_value'], $attribute['options'][1]['value']); + $this->assertEquals('Default Red', $attribute['options'][2]['label']); } /** @@ -153,46 +123,111 @@ class ProductAttributeRepositoryTest extends \Magento\TestFramework\TestCase\Web } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_attribute.php + * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/create_attribute_service.php */ public function testUpdate() { - $attributeCode = 'test_attribute_code_333'; - $attribute = $this->getAttribute($attributeCode); + $attributeCode = 'label_attr_code3df4tr3'; + $attribute = $this->createAttribute($attributeCode); + //Make sure that 'Blue' is set as default + $this->assertEquals($attribute['default_value'], $attribute['options'][1]['value']); $attributeData = [ 'attribute' => [ 'attribute_id' => $attribute['attribute_id'], 'frontend_labels' => [ ['store_id' => 0, 'label' => 'front_lbl_new'], ], - 'default_value' => 'default value new', + "options" => [ + //Update existing + [ + "value" => $attribute['options'][1]['value'], + "label" => "New Label", + "store_labels" => [ + [ + "store_id" => 1, + "label" => "Default Blue Updated" + ] + ] + ], + //Add new option + [ + "label" => "Green", + "value" => "", + "sort_order" => 200, + "is_default" => true, + "store_labels" => [ + [ + "store_id" => 0, + "label" => "Admin Green" + ], + [ + "store_id" => 1, + "label" => "Default Green" + ] + ] + ] + ], 'is_required' => false, - 'frontend_input' => 'text', - ], - ]; - - $serviceInfo = [ - 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . '/' . $attributeCode, - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, - ], - 'soap' => [ - 'service' => self::SERVICE_NAME, - 'serviceVersion' => self::SERVICE_VERSION, - 'operation' => self::SERVICE_NAME . 'Save', + 'frontend_input' => 'select', ], ]; - - if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) { - $attributeData['attribute']['attributeCode'] = $attributeCode; - } - $result = $this->_webApiCall($serviceInfo, $attributeData); + $result = $this->updateAttribute($attributeCode, $attributeData); $this->assertEquals($attribute['attribute_id'], $result['attribute_id']); $this->assertEquals($attributeCode, $result['attribute_code']); - $this->assertEquals('default value new', $result['default_value']); $this->assertEquals('front_lbl_new', $result['default_frontend_label']); + //New option set as default + $this->assertEquals($result['options'][3]['value'], $result['default_value']); + $this->assertEquals("Default Blue Updated", $result['options'][1]['label']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/create_attribute_service.php + */ + public function testUpdateNotExistingOption() + { + $attributeCode = 'label_attr_code3df4tr3'; + $attribute = $this->createAttribute($attributeCode); + $expectedMessage = 'Attribute %1 does not contain option with Id %2'; + + $attributeData = [ + 'attribute' => [ + 'attribute_id' => $attribute['attribute_id'], + 'is_required' => true, + 'frontend_labels' => [ + ['store_id' => 0, 'label' => 'front_lbl_new'], + ], + "options" => [ + [ + "value" => 1, + "label" => "New Label", + "store_labels" => [ + [ + "store_id" => 1, + "label" => "new label" + ] + ] + ], + ], + 'frontend_input' => 'select', + ] + ]; + + try { + $this->updateAttribute($attributeCode, $attributeData); + $this->fail("Expected exception"); + } catch (\SoapFault $e) { + $this->assertContains( + $expectedMessage, + $e->getMessage(), + "SoapFault does not contain expected message." + ); + } catch (\Exception $e) { + $errorObj = $this->processRestExceptionResult($e); + $this->assertEquals($expectedMessage, $errorObj['message']); + $this->assertEquals([$attributeCode, 1], $errorObj['parameters']); + } } /** @@ -204,9 +239,6 @@ class ProductAttributeRepositoryTest extends \Magento\TestFramework\TestCase\Web $this->assertTrue($this->deleteAttribute($attributeCode)); } - /** - * @magentoApiDataFixture Magento/Catalog/_files/product_attribute.php - */ public function testDeleteNoSuchEntityException() { $attributeCode = 'some_test_code'; @@ -266,22 +298,34 @@ class ProductAttributeRepositoryTest extends \Magento\TestFramework\TestCase\Web "is_filterable_in_search" => true, "options" => [ [ - "label" => "string", - "value" => "string", - "sort_order" => 0, - "is_default" => true, + "label" => "Red", + "value" => "", + "sort_order" => 100, + "is_default" => false, "store_labels" => [ [ "store_id" => 0, - "label" => "Red" + "label" => "Admin Red" ], [ "store_id" => 1, - "label" => "Blue" + "label" => "Default Red" + ] + ] + ], + [ + "label" => "Blue", + "value" => "", + "sort_order" => 0, + "is_default" => true, + "store_labels" => [ + [ + "store_id" => 0, + "label" => "Admin Blue" ], [ - "store_id" => 2, - "label" => "Yellow" + "store_id" => 1, + "label" => "Default Blue" ] ] ] @@ -345,4 +389,29 @@ class ProductAttributeRepositoryTest extends \Magento\TestFramework\TestCase\Web ]; return $this->_webApiCall($serviceInfo, ['attributeCode' => $attributeCode]); } + + /** + * Update attribute by code + * + * @param $attributeCode + * @return array|bool|float|int|string + */ + protected function updateAttribute($attributeCode, $attributeData) + { + if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) { + $attributeData['attribute']['attributeCode'] = $attributeCode; + } + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . '/' . $attributeCode, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + return $this->_webApiCall($serviceInfo, $attributeData); + } } diff --git a/dev/tests/functional/lib/Magento/Mtf/Client/Element/DropdownmultiselectElement.php b/dev/tests/functional/lib/Magento/Mtf/Client/Element/DropdownmultiselectElement.php index f698d6014e87b70169158ada5398a9b772c07176..302dc54c8fa68bac68af84dd60af56e90a5a44c5 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Client/Element/DropdownmultiselectElement.php +++ b/dev/tests/functional/lib/Magento/Mtf/Client/Element/DropdownmultiselectElement.php @@ -32,7 +32,7 @@ class DropdownmultiselectElement extends MultiselectElement * * @var string */ - protected $optionByValue = './/li[label[contains(normalize-space(.), %s)]]'; + protected $optionByValue = './/li//label[contains(normalize-space(.), %s)]'; /** * Set values. diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/ExpireAdminSessionTest.php b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/ExpireAdminSessionTest.php index 5e29ed43311ce3d3538c6e12c4532a126564d0bb..dcadc4165c7552d72277a9652350af9fd5e37941 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/ExpireAdminSessionTest.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/ExpireAdminSessionTest.php @@ -7,6 +7,7 @@ namespace Magento\Backend\Test\TestCase; use Magento\Backend\Test\Page\Adminhtml\SystemConfigEdit; +use Magento\Backend\Test\Page\AdminAuthLogin; use Magento\Config\Test\Fixture\ConfigData; use Magento\Mtf\TestCase\Injectable; @@ -46,10 +47,14 @@ class ExpireAdminSessionTest extends Injectable * * @param SystemConfigEdit $systemConfigEdit * @param ConfigData $sessionLifetimeConfig + * @param AdminAuthLogin $adminAuthLogin * @return void */ - public function test(SystemConfigEdit $systemConfigEdit, ConfigData $sessionLifetimeConfig) - { + public function test( + SystemConfigEdit $systemConfigEdit, + ConfigData $sessionLifetimeConfig, + AdminAuthLogin $adminAuthLogin + ) { $this->systemConfigEdit = $systemConfigEdit; $this->sessionLifetimeConfig = $sessionLifetimeConfig; $this->systemConfigEdit->open(); @@ -71,7 +76,7 @@ class ExpireAdminSessionTest extends Injectable */ sleep($section[$keys[0]]['label']); - $this->systemConfigEdit->open(); + $adminAuthLogin->open(); } /** diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/NavigateMenuTest.xml index 847a1f2c242e6cbc840de37609d98898dd308a48..02535cf1bf01d5fc35f8c3bedcaaf64e634d64e3 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/NavigateMenuTest.xml @@ -14,7 +14,7 @@ </variation> <variation name="NavigateMenuTest10"> <data name="menuItem" xsi:type="string">Products > Categories</data> - <data name="pageTitle" xsi:type="string">Categories</data> + <data name="pageTitle" xsi:type="string">Default Category</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest11"> diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Block/Adminhtml/Block/Edit/CmsForm.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/Block/Adminhtml/Block/Edit/CmsForm.xml index 51cd1e54461e37fe1a48002f66635beec9a0d103..ae909b6ef26245cfa8487459ec318fb12d223623 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/Block/Adminhtml/Block/Edit/CmsForm.xml +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Block/Adminhtml/Block/Edit/CmsForm.xml @@ -12,7 +12,8 @@ <input>multiselectgrouplist</input> </stores> <is_active> - <input>checkbox</input> + <selector>.admin__actions-switch-label</selector> + <input>switcher</input> </is_active> </fields> </mapping> diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Block/Adminhtml/Page/Edit/PageForm.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/Block/Adminhtml/Page/Edit/PageForm.xml index 61805c1c1ed3bdfd9aee91114c3ef411be527f23..c6bea89c36dca31d7962ea4ae612d600c25d391d 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/Block/Adminhtml/Page/Edit/PageForm.xml +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Block/Adminhtml/Page/Edit/PageForm.xml @@ -13,7 +13,8 @@ <fields> <title /> <is_active> - <input>checkbox</input> + <selector>.admin__actions-switch-label</selector> + <input>switcher</input> </is_active> </fields> </page_information> diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Block/Adminhtml/Page/Edit/Tab/Content.php b/dev/tests/functional/tests/app/Magento/Cms/Test/Block/Adminhtml/Page/Edit/Tab/Content.php index 9402c76580fb4ecf768668ca62e6ab3470aea0a8..4293a6a7faf0509964ef0720b3e023ae53290070 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/Block/Adminhtml/Page/Edit/Tab/Content.php +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Block/Adminhtml/Page/Edit/Tab/Content.php @@ -59,6 +59,13 @@ class Content extends Tab */ protected $contentHeading = '[name="content_heading"]'; + /** + * Header locator. + * + * @var string + */ + protected $header = 'header.page-header'; + /** * Clicking in content tab 'Insert Variable' button. * @@ -85,7 +92,12 @@ class Content extends Tab $context = $element === null ? $this->_rootElement : $element; $addWidgetButton = $context->find($this->addWidgetButton); if ($addWidgetButton->isVisible()) { - $addWidgetButton->click(); + try { + $addWidgetButton->click(); + } catch (\PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e) { + $this->browser->find($this->header)->hover(); + $addWidgetButton->click(); + } } } diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Constraint/AssertCmsBlockNotInGrid.php b/dev/tests/functional/tests/app/Magento/Cms/Test/Constraint/AssertCmsBlockNotInGrid.php index ad3ce5daef521523e6e439da296394955d995c81..2742eaee67c0f101baba23c35ac5ce17e0217ad8 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/Constraint/AssertCmsBlockNotInGrid.php +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Constraint/AssertCmsBlockNotInGrid.php @@ -29,6 +29,7 @@ class AssertCmsBlockNotInGrid extends AbstractConstraint { $cmsBlockIndex->open(); $data = $cmsBlock->getData(); + $data['is_active'] = $data['is_active'] == 'Yes' ? 'Enabled' : 'Disabled'; if (isset($data['stores'])) { $storeId = is_array($data['stores']) ? reset($data['stores']) : $data['stores']; $parts = explode("/", $storeId); diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Form/Register.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Form/Register.php index d5de5868dba76339fe1fc00741d348f9bf65c3d1..80c66c31d9db3b448abd34d82b5abcda39333343 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Form/Register.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Form/Register.php @@ -30,6 +30,20 @@ class Register extends Form */ protected $customerAttribute = "[name='%s']"; + /** + * Locator for password error + * + * @var string + */ + protected $passwordError = "#password-error"; + + /** + * Locator for password confirmation error + * + * @var string + */ + protected $passwordConfirmationError = "#password-confirmation-error"; + /** * Create new customer account and fill billing address if it exists * @@ -44,4 +58,26 @@ class Register extends Form } $this->_rootElement->find($this->submit, Locator::SELECTOR_CSS)->click(); } + + /** + * Get password error on new customer registration form. + * + * @return string + * + */ + public function getPasswordError() + { + return $this->_rootElement->find($this->passwordError, Locator::SELECTOR_CSS)->getText(); + } + + /** + * Get password confirmation error on new customer registration form. + * + * @return string + * + */ + public function getPasswordConfirmationError() + { + return $this->_rootElement->find($this->passwordConfirmationError, Locator::SELECTOR_CSS)->getText(); + } } diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateExistingCustomerFrontendEntity.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateExistingCustomerFrontendEntity.xml index 08af0a568a63a7eefed7903ab59ead405e7d65dc..a57ce7ec5a2343a09efb44066ee6f9e0b8242bc7 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateExistingCustomerFrontendEntity.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateExistingCustomerFrontendEntity.xml @@ -11,8 +11,8 @@ <data name="customer/data/firstname" xsi:type="string">john</data> <data name="customer/data/lastname" xsi:type="string">doe</data> <data name="customer/data/email" xsi:type="string">johndoe%isolation%@example.com</data> - <data name="customer/data/password" xsi:type="string">123123q</data> - <data name="customer/data/password_confirmation" xsi:type="string">123123q</data> + <data name="customer/data/password" xsi:type="string">123123q#</data> + <data name="customer/data/password_confirmation" xsi:type="string">123123q#</data> <constraint name="Magento\Customer\Test\Constraint\AssertCustomerFailRegisterMessage" /> </variation> </testCase> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/RegisterCustomerFrontendEntityTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/RegisterCustomerFrontendEntityTest.xml index 061ebbfd7518ad9dbab4055045ee86d56f6aa17d..a61daa8fd10e628805828390b6eb6222071fdba4 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/RegisterCustomerFrontendEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/RegisterCustomerFrontendEntityTest.xml @@ -41,23 +41,5 @@ <constraint name="Magento\Customer\Test\Constraint\AssertCustomerSuccessRegisterMessage" /> <constraint name="Magento\Customer\Test\Constraint\AssertCustomerRedirectToDashboard" /> </variation> - <variation name="RegisterCustomerFrontendEntityTestVariation4" summary="Customer password length is below 8 characters"> - <data name="customer/data/firstname" xsi:type="string">john</data> - <data name="customer/data/lastname" xsi:type="string">doe</data> - <data name="customer/data/email" xsi:type="string">johndoe%isolation%@example.com</data> - <data name="customer/data/is_subscribed" xsi:type="string">No</data> - <data name="customer/data/password" xsi:type="string">123123q</data> - <data name="customer/data/password_confirmation" xsi:type="string">123123q</data> - <constraint name="Magento\Customer\Test\Constraint\AssertPasswordLengthErrorMessage" /> - </variation> - <variation name="RegisterCustomerFrontendEntityTestVariation5" summary="Customer password is not secure enough"> - <data name="customer/data/firstname" xsi:type="string">john</data> - <data name="customer/data/lastname" xsi:type="string">doe</data> - <data name="customer/data/email" xsi:type="string">johndoe%isolation%@example.com</data> - <data name="customer/data/is_subscribed" xsi:type="string">No</data> - <data name="customer/data/password" xsi:type="string">123123qw</data> - <data name="customer/data/password_confirmation" xsi:type="string">123123qw</data> - <constraint name="Magento\Customer\Test\Constraint\AssertPasswordIsNotSecureEnoughMessage" /> - </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/LockAdminUserWhenCreatingNewIntegrationTest.php b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/LockAdminUserWhenCreatingNewIntegrationTest.php index 5d3336f9068174c01f2da08ab2210088ff77b27a..52b071596e145faf52497b8443e8a5942ed2042f 100644 --- a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/LockAdminUserWhenCreatingNewIntegrationTest.php +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/LockAdminUserWhenCreatingNewIntegrationTest.php @@ -107,6 +107,7 @@ class LockAdminUserWhenCreatingNewIntegrationTest extends Injectable $this->adminAuthLogin->open(); $this->adminAuthLogin->getLoginBlock()->fill($customAdmin); $this->adminAuthLogin->getLoginBlock()->submit(); + // Steps $this->integrationIndexPage->open(); $this->integrationIndexPage->getGridPageActions()->addNew(); @@ -114,6 +115,11 @@ class LockAdminUserWhenCreatingNewIntegrationTest extends Injectable $this->integrationNewPage->getIntegrationForm()->fill($integration); $this->integrationNewPage->getFormPageActions()->saveNew(); } + + // Reload page + $this->adminAuthLogin->open(); + $this->adminAuthLogin->getLoginBlock()->fill($customAdmin); + $this->adminAuthLogin->getLoginBlock()->submit(); } /** diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOrderBackendTest.php b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOrderBackendTest.php index 13b674ade8077da81bc1dff92597c55abd9618ab..eefedf3069bdcc3d9bb0b81ddb00bceaa170140c 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOrderBackendTest.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOrderBackendTest.php @@ -45,7 +45,6 @@ class CreateOrderBackendTest extends Scenario */ public function test() { - $this->markTestIncomplete('MAGETWO-48742'); $this->executeScenario(); } } diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertPasswordIsNotSecureEnoughMessage.php b/dev/tests/functional/tests/app/Magento/Security/Test/Constraint/AssertPasswordIsNotSecureEnoughMessage.php similarity index 83% rename from dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertPasswordIsNotSecureEnoughMessage.php rename to dev/tests/functional/tests/app/Magento/Security/Test/Constraint/AssertPasswordIsNotSecureEnoughMessage.php index 7eff752f870a5cc1e99e8bf2089e65c8915f57b5..ce0eaf98e091b84c586d44c3e6c298a9f8109ce3 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertPasswordIsNotSecureEnoughMessage.php +++ b/dev/tests/functional/tests/app/Magento/Security/Test/Constraint/AssertPasswordIsNotSecureEnoughMessage.php @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -namespace Magento\Customer\Test\Constraint; +namespace Magento\Security\Test\Constraint; use Magento\Customer\Test\Page\CustomerAccountCreate; use Magento\Mtf\Constraint\AbstractConstraint; @@ -23,9 +23,9 @@ class AssertPasswordIsNotSecureEnoughMessage extends AbstractConstraint */ public function processAssert(CustomerAccountCreate $registerPage) { - $expectedErrorMessage = 'Minimum different classes of characters in password are 3.' . + $expectedErrorMessage = 'Minimum of different classes of characters in password is 3.' . ' Classes of characters: Lower Case, Upper Case, Digits, Special Characters.'; - $errorMessage = $registerPage->getMessagesBlock()->getErrorMessage(); + $errorMessage = $registerPage->getRegisterForm()->getPasswordError(); \PHPUnit_Framework_Assert::assertEquals( $expectedErrorMessage, $errorMessage, diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertPasswordLengthErrorMessage.php b/dev/tests/functional/tests/app/Magento/Security/Test/Constraint/AssertPasswordLengthErrorMessage.php similarity index 74% rename from dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertPasswordLengthErrorMessage.php rename to dev/tests/functional/tests/app/Magento/Security/Test/Constraint/AssertPasswordLengthErrorMessage.php index 3a985a74b0281abc0243978f105129fc55535f6a..12a3bf248913ae3f0f973e528b98ed3287ebe57f 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertPasswordLengthErrorMessage.php +++ b/dev/tests/functional/tests/app/Magento/Security/Test/Constraint/AssertPasswordLengthErrorMessage.php @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -namespace Magento\Customer\Test\Constraint; +namespace Magento\Security\Test\Constraint; use Magento\Customer\Test\Page\CustomerAccountCreate; use Magento\Mtf\Constraint\AbstractConstraint; @@ -14,7 +14,7 @@ use Magento\Mtf\Constraint\AbstractConstraint; */ class AssertPasswordLengthErrorMessage extends AbstractConstraint { - const PASSWORD_LENGTH_ERROR_MESSAGE = 'Please enter a password with at least 8 characters.'; + const PASSWORD_LENGTH_ERROR_MESSAGE = 'Minimum length of this field must be equal or greater than 8 symbols'; /** * Assert that appropriate message is displayed on "Create New Customer Account" page(frontend) if password length @@ -25,11 +25,11 @@ class AssertPasswordLengthErrorMessage extends AbstractConstraint */ public function processAssert(CustomerAccountCreate $registerPage) { - $errorMessage = $registerPage->getMessagesBlock()->getErrorMessage(); - \PHPUnit_Framework_Assert::assertEquals( + $errorMessage = $registerPage->getRegisterForm()->getPasswordError(); + \PHPUnit_Framework_Assert::assertContains( self::PASSWORD_LENGTH_ERROR_MESSAGE, $errorMessage, - 'The messages are not equal.' + 'Incorrect password error message.' ); } diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/Page/UserAccountForgotPassword.php b/dev/tests/functional/tests/app/Magento/Security/Test/Page/UserAccountForgotPassword.php new file mode 100644 index 0000000000000000000000000000000000000000..20e35317df6d788788c950d67e4110d3e34e08f0 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/Page/UserAccountForgotPassword.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Security\Test\Page; + +use Magento\Mtf\Page\Page; + +/** + * Class UserAccountForgotPassword + */ +class UserAccountForgotPassword extends Page +{ + const MCA = 'admin/auth/forgotpassword'; + + /** + * Blocks' config + * + * @var array + */ + protected $blocks = [ + 'messagesBlock' => [ + 'class' => 'Magento\Backend\Test\Block\Messages', + 'locator' => '.messages', + 'strategy' => 'css selector', + ], + 'forgotPasswordForm' => [ + 'class' => 'Magento\Security\Test\Block\Form\ForgotPassword', + 'locator' => '#login-form', + 'strategy' => 'css selector', + ], + ]; + + /** + * Constructor. + */ + protected function initUrl() + { + $this->url = $_ENV['app_backend_url'] . self::MCA; + } + + /** + * @return \Magento\Backend\Test\Block\Messages + */ + public function getMessagesBlock() + { + return $this->getBlockInstance('messagesBlock'); + } + + /** + * @return \Magento\Security\Test\Block\Form\ForgotPassword + */ + public function getForgotPasswordForm() + { + return $this->getBlockInstance('forgotPasswordForm'); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/Page/UserAccountForgotPassword.xml b/dev/tests/functional/tests/app/Magento/Security/Test/Page/UserAccountForgotPassword.xml deleted file mode 100644 index 7d467309126831fafbe47ebdd186c5ea58879be9..0000000000000000000000000000000000000000 --- a/dev/tests/functional/tests/app/Magento/Security/Test/Page/UserAccountForgotPassword.xml +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" ?> -<!-- -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd"> - <page name="UserAccountForgotPassword" mca="admin/admin/auth/forgotpassword" module="Magento_Security"> - <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator=".messages" strategy="css selector"/> - <block name="forgotPasswordForm" class="Magento\Security\Test\Block\Form\ForgotPassword" locator="#login-form" strategy="css selector"/> - </page> -</config> \ No newline at end of file diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/NewFrontendCustomerPasswordTest.php b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/NewFrontendCustomerPasswordTest.php new file mode 100644 index 0000000000000000000000000000000000000000..77a37dc4369e51a296a786f13ed3e32fb9763f65 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/NewFrontendCustomerPasswordTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Security\Test\TestCase; + +use Magento\Customer\Test\Fixture\Customer; +use Magento\Customer\Test\Page\CustomerAccountCreate; +use Magento\Cms\Test\Page\CmsIndex; +use Magento\Mtf\TestCase\Injectable; + +/** + * Test Flow: + * 1. Go to frontend. + * 2. Click Register link. + * 3. Fill registry form. + * 4. Click 'Create account' button. + * 5. Perform assertions. + * + * @ZephyrId MAGETWO-49044 + */ +class NewFrontendCustomerPasswordTest extends Injectable +{ + /* tags */ + const MVP = 'yes'; + const DOMAIN = 'PS'; + /* end tags */ + + /** + * Customer registry page. + * + * @var CustomerAccountCreate + */ + protected $customerAccountCreate; + + /** + * Cms page. + * + * @var CmsIndex $cmsIndex + */ + protected $cmsIndex; + + /** + * Inject data. + * + * @param CustomerAccountCreate $customerAccountCreate + * @param CmsIndex $cmsIndex + * @return void + */ + public function __inject( + CustomerAccountCreate $customerAccountCreate, + CmsIndex $cmsIndex + ) { + $this->customerAccountCreate = $customerAccountCreate; + $this->cmsIndex = $cmsIndex; + } + + /** + * Create Customer account on Storefront. + * + * @param Customer $customer + * @return void + */ + public function test(Customer $customer) + { + // Steps + $this->cmsIndex->open(); + $this->cmsIndex->getLinksBlock()->openLink('Create an Account'); + $this->customerAccountCreate->getRegisterForm()->registerCustomer($customer); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/NewFrontendCustomerPasswordTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/NewFrontendCustomerPasswordTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..5426cc7679bc8131f1dee2c6e2ab702d02b6deb6 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/NewFrontendCustomerPasswordTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Security\Test\TestCase\NewFrontendCustomerPasswordTest" summary="New frontend customer password check" ticketId="MAGETWO-49044"> + <variation name="PasswordLengthTest" summary="Customer password length is below 8 characters"> + <data name="customer/data/firstname" xsi:type="string">john</data> + <data name="customer/data/lastname" xsi:type="string">doe</data> + <data name="customer/data/email" xsi:type="string">johndoe%isolation%@example.com</data> + <data name="customer/data/is_subscribed" xsi:type="string">No</data> + <data name="customer/data/password" xsi:type="string">123123</data> + <data name="customer/data/password_confirmation" xsi:type="string">123123</data> + <constraint name="Magento\Security\Test\Constraint\AssertPasswordLengthErrorMessage" /> + </variation> + <variation name="PasswordComplexityTest" summary="Customer password is not secure enough"> + <data name="customer/data/firstname" xsi:type="string">john</data> + <data name="customer/data/lastname" xsi:type="string">doe</data> + <data name="customer/data/email" xsi:type="string">johndoe%isolation%@example.com</data> + <data name="customer/data/is_subscribed" xsi:type="string">No</data> + <data name="customer/data/password" xsi:type="string">123123qa</data> + <data name="customer/data/password_confirmation" xsi:type="string">123123qa</data> + <constraint name="Magento\Security\Test\Constraint\AssertPasswordIsNotSecureEnoughMessage" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserIsLocked.php b/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserIsLocked.php index c2aef263fce46fbba4b88437e29111bb1d47dbb5..1a3631c74e5379bfe6afe18aa46c161d4ae1e774 100644 --- a/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserIsLocked.php +++ b/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserIsLocked.php @@ -14,7 +14,7 @@ use Magento\Mtf\Constraint\AbstractConstraint; */ class AssertUserIsLocked extends AbstractConstraint { - const USER_ACCOUNT_DISABLED_MESSAGE = 'Your account is temporarily disabled.'; + const USER_ACCOUNT_DISABLED_MESSAGE = 'account is temporarily disabled'; /** * Verify that user account has been locked. @@ -25,10 +25,12 @@ class AssertUserIsLocked extends AbstractConstraint public function processAssert( AdminAuthLogin $adminAuth ) { - \PHPUnit_Framework_Assert::assertEquals( + $ignoreCase = true; + \PHPUnit_Framework_Assert::assertContains( self::USER_ACCOUNT_DISABLED_MESSAGE, $adminAuth->getMessagesBlock()->getErrorMessage(), - 'Message "' . self::USER_ACCOUNT_DISABLED_MESSAGE . '" is not visible.' + 'Message "' . self::USER_ACCOUNT_DISABLED_MESSAGE . '" is not visible.', + $ignoreCase ); } diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserWhenCreatingNewRoleTest.php b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserWhenCreatingNewRoleTest.php index 41d0ec81d7a950d05c6055493ea35378b7e50c7a..87e8a9c971e87dac46f57df66003f4e7d1d99be4 100644 --- a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserWhenCreatingNewRoleTest.php +++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserWhenCreatingNewRoleTest.php @@ -103,7 +103,7 @@ class LockAdminUserWhenCreatingNewRoleTest extends Injectable )->run(); $customAdmin->persist(); - //Steps + // Steps $this->adminAuthLogin->open(); $this->adminAuthLogin->getLoginBlock()->fill($customAdmin); $this->adminAuthLogin->getLoginBlock()->submit(); @@ -114,6 +114,11 @@ class LockAdminUserWhenCreatingNewRoleTest extends Injectable $this->userRoleEditRole->getRoleFormTabs()->fill($role); $this->userRoleEditRole->getPageActions()->save(); } + + // Reload + $this->adminAuthLogin->open(); + $this->adminAuthLogin->getLoginBlock()->fill($customAdmin); + $this->adminAuthLogin->getLoginBlock()->submit(); } /** diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Category/DataProviderTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Category/DataProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2d47c0b49915b93045298d03059a8f9ce22e7e3c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Category/DataProviderTest.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Model\Category; + +use Magento\Catalog\Model\Category\DataProvider; +use Magento\Eav\Model\Config as EavConfig; +use Magento\TestFramework\Helper\Bootstrap; + +class DataProviderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var DataProvider + */ + private $dataProvider; + + /** + * @var \Magento\Eav\Model\Entity\Type + */ + private $entityType; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + parent::setUp(); + $objectManager = Bootstrap::getObjectManager(); + $this->dataProvider = $objectManager->create( + DataProvider::class, + [ + 'name' => 'category_form_data_source', + 'primaryFieldName' => 'entity_id', + 'requestFieldName' => 'id' + ] + ); + + $this->entityType = $objectManager->create(EavConfig::class)->getEntityType('catalog_category'); + } + + /** + * @return void + */ + public function testGetMetaRequiredAttributes() + { + $requiredAttributes = [ + 'general' => ['name'], + 'display_settings' => ['available_sort_by', 'default_sort_by'], + ]; + $meta = $this->dataProvider->getMeta(); + $this->assertArrayHasKey('url_key', $meta['search_engine_optimization']['children']); + foreach ($requiredAttributes as $scope => $attributes) { + foreach ($attributes as $attribute) { + $this->assertArrayHasKey($attribute, $meta[$scope]['children']); + $data = $meta[$scope]['children'][$attribute]; + $this->assertTrue($data['arguments']['data']['config']['validation']['required-entry']); + } + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTreeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTreeTest.php index 46a36dbf0a0230214c77a0daadb096387390a985..3540b9ad076ac280d830352f9a1077bf2088512d 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTreeTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTreeTest.php @@ -125,7 +125,7 @@ class CategoryTreeTest extends \PHPUnit_Framework_TestCase public function testGetChildren() { $this->_model->load(3); - $this->assertEquals('4', $this->_model->getChildren()); + $this->assertEquals('4,13', $this->_model->getChildren()); } public function testGetPathInStore() @@ -186,7 +186,7 @@ class CategoryTreeTest extends \PHPUnit_Framework_TestCase { $this->_model->load(3); $children = $this->_model->getChildrenCategories(); - $this->assertEquals(1, count($children)); + $this->assertEquals(2, count($children)); } public function testGetChildrenCategoriesEmpty() diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/CategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/CategoryTest.php index 2a2f4c6c1da25765fc81ed1a93e511589b575da9..26729a485c31e0b4ca9c0330da00248595af6850 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/CategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/CategoryTest.php @@ -126,11 +126,10 @@ class CategoryTest extends \PHPUnit_Framework_TestCase $this->assertInstanceOf('Magento\Catalog\Model\Category', $category); $this->assertEquals(3, $category->getId()); - $items = $model->getItems(); $this->assertInternalType('array', $items); - $this->assertEquals(1, count($items)); + $this->assertEquals(2, count($items)); /** @var $item \Magento\Catalog\Model\Layer\Filter\Item */ $item = $items[0]; @@ -139,6 +138,12 @@ class CategoryTest extends \PHPUnit_Framework_TestCase $this->assertSame($model, $item->getFilter()); $this->assertEquals('Category 1.1', $item->getLabel()); $this->assertEquals(4, $item->getValue()); - $this->assertEquals(1, $item->getCount()); + $this->assertEquals(2, $item->getCount()); + + $item = $items[1]; + $this->assertInstanceOf('Magento\Catalog\Model\Layer\Filter\Item', $item); + $this->assertEquals('Category 1.2', $item->getLabel()); + $this->assertEquals(13, $item->getValue()); + $this->assertEquals(2, $item->getCount()); } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductExternalTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductExternalTest.php index c87b553e18f8b4772ef7a5d562161cafa2508d21..3b39cfe48a314e0797b6938742b7354d54329d57 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductExternalTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductExternalTest.php @@ -112,7 +112,7 @@ class ProductExternalTest extends \PHPUnit_Framework_TestCase $this->_model->setId( $this->productRepository->get('simple')->getId() ); - $this->assertEquals([2, 3, 4], $this->_model->getCategoryIds()); + $this->assertEquals([2, 3, 4, 13], $this->_model->getCategoryIds()); } public function testGetCategoryCollection() @@ -132,7 +132,7 @@ class ProductExternalTest extends \PHPUnit_Framework_TestCase foreach ($fixtureCollection as $category) { $ids[] = $category->getId(); } - $this->assertEquals([2, 3, 4], $ids); + $this->assertEquals([2, 3, 4, 13], $ids); } public function testGetWebsiteIds() @@ -342,7 +342,7 @@ class ProductExternalTest extends \PHPUnit_Framework_TestCase $actualCategoryIds = $this->_model->getAvailableInCategories(); sort($actualCategoryIds); // not depend on the order of items - $this->assertEquals([10, 11, 12], $actualCategoryIds); + $this->assertEquals([10, 11, 12, 13], $actualCategoryIds); //Check not visible product $this->_model->load( $this->productRepository->get('simple-3')->getId() diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php index df15c30672c5471668bd850d446f4d00e497a42c..066e64a2ac81052b2208dd8b79519d5c2f77f7f7 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php @@ -165,6 +165,20 @@ $category->setId(12) ->setPosition(8) ->save(); +$category = $objectManager->create('Magento\Catalog\Model\Category'); +$category->isObjectNew(true); +$category->setId(13) + ->setName('Category 1.2') + ->setParentId(3) + ->setPath('1/2/3/13') + ->setLevel(3) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setIsActive(true) + ->setIsAnchor(true) + ->setPosition(2) + ->save(); + /** @var $product \Magento\Catalog\Model\Product */ $product = $objectManager->create('Magento\Catalog\Model\Product'); $product->isObjectNew(true); @@ -183,7 +197,7 @@ $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) $categoryLinkManagement->assignProductToCategories( $product->getSku(), - [2, 3, 4] + [2, 3, 4, 13] ); $product = $objectManager->create('Magento\Catalog\Model\Product'); @@ -203,7 +217,7 @@ $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) $categoryLinkManagement->assignProductToCategories( $product->getSku(), - [5] + [5, 4] ); $product = $objectManager->create('Magento\Catalog\Model\Product'); @@ -244,5 +258,5 @@ $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) $categoryLinkManagement->assignProductToCategories( $product->getSku(), - [10, 11, 12] + [10, 11, 12, 13] ); diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/CategoryTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/CategoryTest.php index 52931492226fd0accba927552a97474c3d1adfe1..6f3ef297d0a57aa856327519d875addf5e339305 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/CategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/CategoryTest.php @@ -106,26 +106,31 @@ class CategoryTest extends \PHPUnit_Framework_TestCase { $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $request = $objectManager->get('Magento\TestFramework\Request'); - $request->setParam('cat', 4); + $request->setParam('cat', 3); $this->_model->apply($request); /** @var $category \Magento\Catalog\Model\Category */ $category = $objectManager->get('Magento\Framework\Registry')->registry(self::CURRENT_CATEGORY_FILTER); $this->assertInstanceOf('Magento\Catalog\Model\Category', $category); - $this->assertEquals(4, $category->getId()); + $this->assertEquals(3, $category->getId()); $items = $this->_model->getItems(); $this->assertInternalType('array', $items); - $this->assertEquals(1, count($items)); + $this->assertEquals(2, count($items)); /** @var $item \Magento\Catalog\Model\Layer\Filter\Item */ $item = $items[0]; - $this->assertInstanceOf('Magento\Catalog\Model\Layer\Filter\Item', $item); $this->assertSame($this->_model, $item->getFilter()); - $this->assertEquals('Category 1.1.1', $item->getLabel()); - $this->assertEquals(5, $item->getValue()); - $this->assertEquals(1, $item->getCount()); + $this->assertEquals('Category 1.1', $item->getLabel()); + $this->assertEquals(4, $item->getValue()); + $this->assertEquals(2, $item->getCount()); + + $item = $items[1]; + $this->assertInstanceOf('Magento\Catalog\Model\Layer\Filter\Item', $item); + $this->assertEquals('Category 1.2', $item->getLabel()); + $this->assertEquals(13, $item->getValue()); + $this->assertEquals(2, $item->getCount()); } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Plugin/Catalog/Block/Adminhtml/Category/Tab/AttributesTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Plugin/Catalog/Block/Adminhtml/Category/Tab/AttributesTest.php index 86754a471fd4b1504fbb7c1b43389a12a2da32c4..27d196bb59e7b40058ea30f28bc05aa4eff59fcb 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Plugin/Catalog/Block/Adminhtml/Category/Tab/AttributesTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Plugin/Catalog/Block/Adminhtml/Category/Tab/AttributesTest.php @@ -46,12 +46,13 @@ class AttributesTest extends \PHPUnit_Framework_TestCase */ public function testGetAttributesMeta() { - $meta = $this->dataProvider->getAttributesMeta($this->entityType); - $this->assertArrayHasKey('url_key', $meta); - $this->assertEquals('text', $meta['url_key']['dataType']); - $this->assertEquals('input', $meta['url_key']['formElement']); - $this->assertEquals('1', $meta['url_key']['visible']); - $this->assertEquals('0', $meta['url_key']['required']); - $this->assertEquals('[STORE VIEW]', $meta['url_key']['scope_label']); + $meta = $this->dataProvider->getMeta(); + $this->assertArrayHasKey('url_key', $meta['search_engine_optimization']['children']); + $urlKeyData = $meta['search_engine_optimization']['children']['url_key']['arguments']['data']['config']; + $this->assertEquals('text', $urlKeyData['dataType']); + $this->assertEquals('input', $urlKeyData['formElement']); + $this->assertEquals('1', $urlKeyData['visible']); + $this->assertEquals('0', $urlKeyData['required']); + $this->assertEquals('[STORE VIEW]', $urlKeyData['scopeLabel']); } } diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Import/Product/Type/ConfigurableTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Import/Product/Type/ConfigurableTest.php index 3bf0ad712e9d87190e8d7d2ecc33cddef518a34d..5f510ff8f58d67dcf7068845e0f21726e605f59b 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Import/Product/Type/ConfigurableTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Import/Product/Type/ConfigurableTest.php @@ -5,6 +5,7 @@ */ namespace Magento\ConfigurableImportExport\Model\Import\Product\Type; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Framework\App\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\ImportExport\Model\Import; @@ -92,8 +93,7 @@ class ConfigurableTest extends \PHPUnit_Framework_TestCase $productId = $resource->getIdBySku(self::TEST_PRODUCT_NAME); $this->assertTrue(is_numeric($productId)); /** @var \Magento\Catalog\Model\Product $product */ - $product = $this->objectManager->create(\Magento\Catalog\Model\Product::class); - $product->load($productId); + $product = $this->objectManager->get(ProductRepositoryInterface::class)->getById($productId); $this->assertFalse($product->isObjectNew()); $this->assertEquals(self::TEST_PRODUCT_NAME, $product->getName()); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/ConfigurableTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/ConfigurableTest.php index a20c3e7b9ab0e4d38fa9482a01b39f7ed23fe29c..6dfe109ee431fbba639abac20418575395e029ad 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/ConfigurableTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/ConfigurableTest.php @@ -128,7 +128,7 @@ class ConfigurableTest extends \PHPUnit_Framework_TestCase $attributeId = (int)$testConfigurable->getId(); $attributes = $this->model->getUsedProductAttributes($this->product); $this->assertArrayHasKey($attributeId, $attributes); - $this->assertSame($testConfigurable, $attributes[$attributeId]); + $this->assertEquals($testConfigurable->getData(), $attributes[$attributeId]->getData()); } public function testGetConfigurableAttributes() diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample b/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample index 9074f2a8e0926e210c42a3853daff74537e5d237..af5b2e39078ab5f2fb77f2269e3db905cd8ce3e3 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample @@ -56,7 +56,7 @@ class Proxy extends \Magento\Framework\Code\GeneratorTest\SourceClassWithNamespa */ public function __sleep() { - return array('_subject', '_isShared'); + return ['_subject', '_isShared', '_instanceName']; } /** diff --git a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payment_express.php b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payment_express.php index be4608b73fd656b953ceb49221950e808ee336fa..19d117c855f42c849afb643d90fc5d96df236c00 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payment_express.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payment_express.php @@ -26,12 +26,6 @@ $product->setTypeId('simple') ->setName('Simple Product') ->setSku('simple') ->setPrice(10) - ->setStockData([ - 'use_config_manage_stock' => 1, - 'qty' => 100, - 'is_qty_decimal' => 0, - 'is_in_stock' => 100, -]) ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/quote.php b/dev/tests/integration/testsuite/Magento/Sales/_files/quote.php index c89043d6345db606a24d0f9f0878b495ba5b36dc..f265f82244f19787e9f483e4cef95822aec1aebf 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/quote.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/quote.php @@ -12,7 +12,6 @@ $product->setTypeId('simple') ->setSku('simple') ->setPrice(10) ->setTaxClassId(0) - ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setMetaTitle('meta title') ->setMetaKeyword('meta keyword') ->setMetaDescription('meta description') diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php index 1bf0dfb280ed80d164b7638c30f7d6e0697a8bd7..9863052b4f26b980196969b2a621c67a9da4ce26 100755 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php @@ -4129,6 +4129,7 @@ return [ 'Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Media', 'Magento\Catalog\Model\ResourceModel\Product\Gallery' ], + ['Magento\CatalogInventory\Observer\AddStockStatusToCollectionObserver'], ['Magento\CatalogRule\Block\Adminhtml\Promo\Catalog\Edit\Tab\Actions'], ['Magento\CatalogRule\Block\Adminhtml\Promo\Catalog\Edit\Tab\Main'], ['Magento\CatalogRule\Block\Adminhtml\Promo\Catalog\Edit\Form'], diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_namespaces.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_namespaces.php index 1cca1b9c94fce2c29f81a297d039dfa97c374f29..38c51080c2086b77c352fdf522deea9b9ad4e47f 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_namespaces.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_namespaces.php @@ -75,7 +75,6 @@ return [ ['Magento\Core\Model\Resource\Config', 'Magento\Config\Model\ResourceModel\Config'], ['Magento\Backend\Block\System\Config', 'Magento\Config\Block\System\Config'], ['Magento\Backend\Controller\Adminhtml\System\Config', 'Magento\Config\Controller\Adminhtml\System\Config'], - ['Magento\Backend\Model\Config', 'Magento\Config\Model\Config'], ['Magento\Core\Model\Variable', 'Magento\Variable\Model\Variable'], ['Magento\Catalog\Service'], ['Magento\CheckoutAgreements\Service'], diff --git a/lib/internal/Magento/Framework/AclFactory.php b/lib/internal/Magento/Framework/AclFactory.php index 3e9839830049d1ec5d384e673e67a64a1de0e866..a48000fbdd33c80c5de69a42c2329f7675c2f984 100644 --- a/lib/internal/Magento/Framework/AclFactory.php +++ b/lib/internal/Magento/Framework/AclFactory.php @@ -31,6 +31,6 @@ class AclFactory */ public function create() { - return $this->_objectManager->create('Magento\Framework\Acl'); + return $this->_objectManager->create(Acl::class); } } diff --git a/lib/internal/Magento/Framework/App/Config/Value.php b/lib/internal/Magento/Framework/App/Config/Value.php index 8489ffd5c5f28f50cf68aab8196445f9fc9971af..dc09a05efc9d776514ae82e143a60b6fb3b41851 100644 --- a/lib/internal/Magento/Framework/App/Config/Value.php +++ b/lib/internal/Magento/Framework/App/Config/Value.php @@ -122,4 +122,18 @@ class Value extends \Magento\Framework\Model\AbstractModel implements \Magento\F return parent::afterSave(); } + + /** + * {@inheritdoc} + * + * {@inheritdoc}. In addition, it sets status 'invalidate' for config caches + * + * @return $this + */ + public function afterDelete() + { + $this->cacheTypeList->invalidate(\Magento\Framework\App\Cache\Type\Config::TYPE_IDENTIFIER); + + return parent::afterDelete(); + } } diff --git a/lib/internal/Magento/Framework/App/PageCache/Cache.php b/lib/internal/Magento/Framework/App/PageCache/Cache.php index 7cb801730b1fe34b1f2a1e395a6371151ef95b44..9e58ed64d2c0adc75e345d80fc0e77f5eb0ec5d5 100644 --- a/lib/internal/Magento/Framework/App/PageCache/Cache.php +++ b/lib/internal/Magento/Framework/App/PageCache/Cache.php @@ -7,11 +7,15 @@ namespace Magento\Framework\App\PageCache; /** * Cache model for builtin cache + * + * @deprecated */ class Cache extends \Magento\Framework\App\Cache { /** * @var string + * + * @deprecated */ protected $_frontendIdentifier = 'page_cache'; } diff --git a/lib/internal/Magento/Framework/App/PageCache/Kernel.php b/lib/internal/Magento/Framework/App/PageCache/Kernel.php index b065336108b9a985dd090650500254d94f16a03c..587e8100b5f97f35a20d1c933f0b92da945fe5d2 100644 --- a/lib/internal/Magento/Framework/App/PageCache/Kernel.php +++ b/lib/internal/Magento/Framework/App/PageCache/Kernel.php @@ -5,13 +5,17 @@ */ namespace Magento\Framework\App\PageCache; +use Magento\Framework\App\ObjectManager; + /** * Builtin cache processor */ class Kernel { /** - * @var Cache + * @var \Magento\PageCache\Model\Cache\Type + * + * @deprecated */ protected $cache; @@ -25,6 +29,11 @@ class Kernel */ protected $request; + /** + * @var \Magento\PageCache\Model\Cache\Type + */ + private $fullPageCache; + /** * @param Cache $cache * @param Identifier $identifier @@ -48,7 +57,7 @@ class Kernel public function load() { if ($this->request->isGet() || $this->request->isHead()) { - return unserialize($this->cache->load($this->identifier->getValue())); + return unserialize($this->getCache()->load($this->identifier->getValue())); } return false; } @@ -75,8 +84,21 @@ class Kernel if (!headers_sent()) { header_remove('Set-Cookie'); } - $this->cache->save(serialize($response), $this->identifier->getValue(), $tags, $maxAge); + $this->getCache()->save(serialize($response), $this->identifier->getValue(), $tags, $maxAge); } } } + + /** + * TODO: Workaround to support backwards compatibility, will rework to use Dependency Injection in MAGETWO-49547 + * + * @return \Magento\PageCache\Model\Cache\Type + */ + private function getCache() + { + if (!$this->fullPageCache) { + $this->fullPageCache = ObjectManager::getInstance()->get('\Magento\PageCache\Model\Cache\Type'); + } + return $this->fullPageCache; + } } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/Config/ValueTest.php b/lib/internal/Magento/Framework/App/Test/Unit/Config/ValueTest.php index 175509af622a025d5b05b879e227f763cd3a3f56..be5dccf9920f83bfed4403f482bcda53579b8006 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/Config/ValueTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/Config/ValueTest.php @@ -191,4 +191,13 @@ class ValueTest extends \PHPUnit_Framework_TestCase [1, 'other_value'], ]; } + + /** + * @return void; + */ + public function testAfterDelete() + { + $this->cacheTypeListMock->expects($this->once())->method('invalidate'); + $this->assertInstanceOf(get_class($this->model), $this->model->afterDelete()); + } } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/PageCache/KernelTest.php b/lib/internal/Magento/Framework/App/Test/Unit/PageCache/KernelTest.php index 1028112e36aefb5e92a9d62af8bf126caa6d5121..070223bf8ff3b6d2578db75b9baad8871a138d4d 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/PageCache/KernelTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/PageCache/KernelTest.php @@ -9,41 +9,41 @@ use \Magento\Framework\App\PageCache\Kernel; class KernelTest extends \PHPUnit_Framework_TestCase { - /** - * @var Kernel - */ + /** @var Kernel */ protected $kernel; - /** - * @var \Magento\Framework\App\PageCache\Cache|\PHPUnit_Framework_MockObject_MockObject - */ + /** @var \Magento\Framework\App\PageCache\Cache|\PHPUnit_Framework_MockObject_MockObject */ protected $cacheMock; - /** - * @var \Magento\Framework\App\PageCache\Identifier|\PHPUnit_Framework_MockObject_MockObject - */ + /** @var \Magento\Framework\App\PageCache\Identifier|\PHPUnit_Framework_MockObject_MockObject */ protected $identifierMock; - /** - * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject - */ + /** @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject */ protected $requestMock; - /** - * @var \Magento\Framework\App\Response\Http|\PHPUnit_Framework_MockObject_MockObject - */ + /** @var \Magento\Framework\App\Response\Http|\PHPUnit_Framework_MockObject_MockObject */ protected $responseMock; + /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\PageCache\Model\Cache\Type */ + private $fullPageCacheMock; + /** * Setup */ public function setUp() { $this->cacheMock = $this->getMock('Magento\Framework\App\PageCache\Cache', [], [], '', false); + $this->fullPageCacheMock = $this->getMock('\Magento\PageCache\Model\Cache\Type', [], [], '', false); $this->identifierMock = $this->getMock('Magento\Framework\App\PageCache\Identifier', [], [], '', false); $this->requestMock = $this->getMock('Magento\Framework\App\Request\Http', [], [], '', false); $this->kernel = new Kernel($this->cacheMock, $this->identifierMock, $this->requestMock); + + $reflection = new \ReflectionClass('\Magento\Framework\App\PageCache\Kernel'); + $reflectionProperty = $reflection->getProperty('fullPageCache'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->kernel, $this->fullPageCacheMock); + $this->responseMock = $this->getMockBuilder( 'Magento\Framework\App\Response\Http' )->setMethods( @@ -63,7 +63,7 @@ class KernelTest extends \PHPUnit_Framework_TestCase { $this->requestMock->expects($this->once())->method('isGet')->will($this->returnValue($isGet)); $this->requestMock->expects($this->any())->method('isHead')->will($this->returnValue($isHead)); - $this->cacheMock->expects( + $this->fullPageCacheMock->expects( $this->any() )->method( 'load' @@ -136,7 +136,7 @@ class KernelTest extends \PHPUnit_Framework_TestCase $this->responseMock->expects($this->at($at[2])) ->method('clearHeader') ->with($this->equalTo('X-Magento-Tags')); - $this->cacheMock->expects($this->once()) + $this->fullPageCacheMock->expects($this->once()) ->method('save'); $this->kernel->process($this->responseMock); } @@ -173,7 +173,7 @@ class KernelTest extends \PHPUnit_Framework_TestCase if ($overrideHeaders) { $this->responseMock->expects($this->once())->method('setNoCacheHeaders'); } - $this->cacheMock->expects($this->never())->method('save'); + $this->fullPageCacheMock->expects($this->never())->method('save'); $this->kernel->process($this->responseMock); } diff --git a/lib/internal/Magento/Framework/Cache/Backend/Database.php b/lib/internal/Magento/Framework/Cache/Backend/Database.php index f02c9975ab4b87fc73b6fde8180a3a082a08e8c2..4ed3a7be309e6ec5fb1c67ea14aa0a3eb6d16ef8 100644 --- a/lib/internal/Magento/Framework/Cache/Backend/Database.php +++ b/lib/internal/Magento/Framework/Cache/Backend/Database.php @@ -48,6 +48,7 @@ class Database extends \Zend_Cache_Backend implements \Zend_Cache_Backend_Extend 'tags_table' => '', 'tags_table_callback' => '', 'store_data' => true, + 'infinite_loop_flag' => false, ]; /** @@ -145,13 +146,16 @@ class Database extends \Zend_Cache_Backend implements \Zend_Cache_Backend_Extend */ public function load($id, $doNotTestCacheValidity = false) { - if ($this->_options['store_data']) { + if ($this->_options['store_data'] && !$this->_options['infinite_loop_flag']) { + $this->_options['infinite_loop_flag'] = true; $select = $this->_getConnection()->select()->from($this->_getDataTable(), 'data')->where('id=:cache_id'); if (!$doNotTestCacheValidity) { $select->where('expire_time=0 OR expire_time>?', time()); } - return $this->_getConnection()->fetchOne($select, ['cache_id' => $id]); + $result = $this->_getConnection()->fetchOne($select, ['cache_id' => $id]); + $this->_options['infinite_loop_flag'] = false; + return $result; } else { return false; } @@ -165,7 +169,8 @@ class Database extends \Zend_Cache_Backend implements \Zend_Cache_Backend_Extend */ public function test($id) { - if ($this->_options['store_data']) { + if ($this->_options['store_data'] && !$this->_options['infinite_loop_flag']) { + $this->_options['infinite_loop_flag'] = true; $select = $this->_getConnection()->select()->from( $this->_getDataTable(), 'update_time' @@ -175,7 +180,9 @@ class Database extends \Zend_Cache_Backend implements \Zend_Cache_Backend_Extend 'expire_time=0 OR expire_time>?', time() ); - return $this->_getConnection()->fetchOne($select, ['cache_id' => $id]); + $result = $this->_getConnection()->fetchOne($select, ['cache_id' => $id]); + $this->_options['infinite_loop_flag'] = false; + return $result; } else { return false; } @@ -195,17 +202,21 @@ class Database extends \Zend_Cache_Backend implements \Zend_Cache_Backend_Extend */ public function save($data, $id, $tags = [], $specificLifetime = false) { - if ($this->_options['store_data']) { - $connection = $this->_getConnection(); - $dataTable = $this->_getDataTable(); + $result = false; + if (!$this->_options['infinite_loop_flag']) { + $this->_options['infinite_loop_flag'] = true; + $result = true; + if ($this->_options['store_data']) { + $connection = $this->_getConnection(); + $dataTable = $this->_getDataTable(); - $lifetime = $this->getLifetime($specificLifetime); - $time = time(); - $expire = $lifetime === 0 || $lifetime === null ? 0 : $time + $lifetime; + $lifetime = $this->getLifetime($specificLifetime); + $time = time(); + $expire = $lifetime === 0 || $lifetime === null ? 0 : $time + $lifetime; - $dataCol = $connection->quoteIdentifier('data'); - $expireCol = $connection->quoteIdentifier('expire_time'); - $query = "INSERT INTO {$dataTable} (\n {$connection->quoteIdentifier( + $dataCol = $connection->quoteIdentifier('data'); + $expireCol = $connection->quoteIdentifier('expire_time'); + $query = "INSERT INTO {$dataTable} (\n {$connection->quoteIdentifier( 'id' )},\n {$dataCol},\n {$connection->quoteIdentifier( 'create_time' @@ -213,13 +224,14 @@ class Database extends \Zend_Cache_Backend implements \Zend_Cache_Backend_Extend 'update_time' )},\n {$expireCol})\n VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE\n {$dataCol}=VALUES({$dataCol}),\n {$expireCol}=VALUES({$expireCol})"; - $result = $connection->query($query, [$id, $data, $time, $time, $expire])->rowCount(); - if (!$result) { - return false; + $result = $connection->query($query, [$id, $data, $time, $time, $expire])->rowCount(); } + if ($result) { + $result = $this->_saveTags($id, $tags); + } + $this->_options['infinite_loop_flag'] = false; } - $tagRes = $this->_saveTags($id, $tags); - return $tagRes; + return $result; } /** @@ -230,8 +242,11 @@ class Database extends \Zend_Cache_Backend implements \Zend_Cache_Backend_Extend */ public function remove($id) { - if ($this->_options['store_data']) { - return $this->_getConnection()->delete($this->_getDataTable(), ['id=?' => $id]); + if ($this->_options['store_data'] && !$this->_options['infinite_loop_flag']) { + $this->_options['infinite_loop_flag'] = true; + $result = $this->_getConnection()->delete($this->_getDataTable(), ['id=?' => $id]); + $this->_options['infinite_loop_flag'] = false; + return $result; } return false; } @@ -255,34 +270,26 @@ class Database extends \Zend_Cache_Backend implements \Zend_Cache_Backend_Extend */ public function clean($mode = \Zend_Cache::CLEANING_MODE_ALL, $tags = []) { - $connection = $this->_getConnection(); - switch ($mode) { - case \Zend_Cache::CLEANING_MODE_ALL: - if ($this->_options['store_data']) { - $result = $connection->query('TRUNCATE TABLE ' . $this->_getDataTable()); - } else { - $result = true; - } - $result = $result && $connection->query('TRUNCATE TABLE ' . $this->_getTagsTable()); - break; - case \Zend_Cache::CLEANING_MODE_OLD: - if ($this->_options['store_data']) { - $result = $connection->delete( - $this->_getDataTable(), - ['expire_time> ?' => 0, 'expire_time<= ?' => time()] - ); - } else { - $result = true; - } - break; - case \Zend_Cache::CLEANING_MODE_MATCHING_TAG: - case \Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: - case \Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: - $result = $this->_cleanByTags($mode, $tags); - break; - default: - \Zend_Cache::throwException('Invalid mode for clean() method'); - break; + if (!$this->_options['infinite_loop_flag']) { + $this->_options['infinite_loop_flag'] = true; + $connection = $this->_getConnection(); + switch ($mode) { + case \Zend_Cache::CLEANING_MODE_ALL: + $result = $this->cleanAll($connection); + break; + case \Zend_Cache::CLEANING_MODE_OLD: + $result = $this->cleanOld($connection); + break; + case \Zend_Cache::CLEANING_MODE_MATCHING_TAG: + case \Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + case \Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $result = $this->_cleanByTags($mode, $tags); + break; + default: + \Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + $this->_options['infinite_loop_flag'] = false; } return $result; @@ -543,4 +550,41 @@ class Database extends \Zend_Cache_Backend implements \Zend_Cache_Backend_Extend return true; } } + + /** + * Clean all cache entries + * + * @param $connection + * @return bool + */ + private function cleanAll($connection) + { + if ($this->_options['store_data']) { + $result = $connection->query('TRUNCATE TABLE ' . $this->_getDataTable()); + } else { + $result = true; + } + $result = $result && $connection->query('TRUNCATE TABLE ' . $this->_getTagsTable()); + return $result; + } + + /** + * Clean old cache entries + * + * @param $connection + * @return bool + */ + private function cleanOld($connection) + { + if ($this->_options['store_data']) { + $result = $connection->delete( + $this->_getDataTable(), + ['expire_time> ?' => 0, 'expire_time<= ?' => time()] + ); + return $result; + } else { + $result = true; + return $result; + } + } } diff --git a/lib/internal/Magento/Framework/Component/ComponentRegistrar.php b/lib/internal/Magento/Framework/Component/ComponentRegistrar.php index 44258c7f135ce0e122f3a63d6f0fe1fd26737fe4..fd122da9985ee9fee355113bf59c5a00ac373ae7 100644 --- a/lib/internal/Magento/Framework/Component/ComponentRegistrar.php +++ b/lib/internal/Magento/Framework/Component/ComponentRegistrar.php @@ -46,7 +46,10 @@ class ComponentRegistrar implements ComponentRegistrarInterface { self::validateType($type); if (isset(self::$paths[$type][$componentName])) { - throw new \LogicException('\'' . $componentName . '\' component already exists'); + throw new \LogicException( + ucfirst($type) . ' \'' . $componentName . '\' from \'' . $path . '\' ' + . 'has been already defined in \'' . self::$paths[$type][$componentName] . '\'.' + ); } else { self::$paths[$type][$componentName] = str_replace('\\', '/', $path); } diff --git a/lib/internal/Magento/Framework/Component/Test/Unit/ComponentRegistrarTest.php b/lib/internal/Magento/Framework/Component/Test/Unit/ComponentRegistrarTest.php index db7fa8f35e8c5eafdf6f7a797654a8947483c832..93f6983ce237aaaae81d22ce5a044ca356c55057 100644 --- a/lib/internal/Magento/Framework/Component/Test/Unit/ComponentRegistrarTest.php +++ b/lib/internal/Magento/Framework/Component/Test/Unit/ComponentRegistrarTest.php @@ -45,11 +45,11 @@ class ComponentRegistrarTest extends \PHPUnit_Framework_TestCase /** * @expectedException \LogicException - * @expectedExceptionMessage 'test_module_one' component already exists + * @expectedExceptionMessageRegExp /Module 'test_module_one' from '\w+' has been already defined in '\w+'./ */ public function testRegistrarWithExceptionForModules() { - ComponentRegistrar::register(ComponentRegistrar::MODULE, "test_module_one", "some/path/name/one"); + ComponentRegistrar::register(ComponentRegistrar::MODULE, "test_module_one", "some/path/name/onemore"); } public function testGetPath() diff --git a/lib/internal/Magento/Framework/Controller/ResultFactory.php b/lib/internal/Magento/Framework/Controller/ResultFactory.php index db78cf29c63014d7102ef03cabd1378fd6ee828d..1543368a9dd82985428f002efac6a01ae5f255f6 100644 --- a/lib/internal/Magento/Framework/Controller/ResultFactory.php +++ b/lib/internal/Magento/Framework/Controller/ResultFactory.php @@ -30,12 +30,12 @@ class ResultFactory * @var array */ protected $typeMap = [ - self::TYPE_JSON => 'Magento\Framework\Controller\Result\Json', - self::TYPE_RAW => 'Magento\Framework\Controller\Result\Raw', - self::TYPE_REDIRECT => 'Magento\Framework\Controller\Result\Redirect', - self::TYPE_FORWARD => 'Magento\Framework\Controller\Result\Forward', - self::TYPE_LAYOUT => 'Magento\Framework\View\Result\Layout', - self::TYPE_PAGE => 'Magento\Framework\View\Result\Page', + self::TYPE_JSON => Result\Json::class, + self::TYPE_RAW => Result\Raw::class, + self::TYPE_REDIRECT => Result\Redirect::class, + self::TYPE_FORWARD => Result\Forward::class, + self::TYPE_LAYOUT => \Magento\Framework\View\Result\Layout::class, + self::TYPE_PAGE => \Magento\Framework\View\Result\Page::class, ]; /** diff --git a/lib/internal/Magento/Framework/CurrencyFactory.php b/lib/internal/Magento/Framework/CurrencyFactory.php index a1a126aecd1f99f568e00437842cd9e652184fb6..1f87858182d0e3b2fb2e94cddb4c50f24c7d9691 100644 --- a/lib/internal/Magento/Framework/CurrencyFactory.php +++ b/lib/internal/Magento/Framework/CurrencyFactory.php @@ -11,7 +11,7 @@ namespace Magento\Framework; class CurrencyFactory { /** - * @var \Magento\Framework\ObjectManagerInterface + * @var ObjectManagerInterface */ protected $_objectManager = null; @@ -21,10 +21,10 @@ class CurrencyFactory protected $_instanceName = null; /** - * @param \Magento\Framework\ObjectManagerInterface $objectManager + * @param ObjectManagerInterface $objectManager * @param string $instanceName */ - public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager, $instanceName = 'Magento\Framework\CurrencyInterface') + public function __construct(ObjectManagerInterface $objectManager, $instanceName = CurrencyInterface::class) { $this->_objectManager = $objectManager; $this->_instanceName = $instanceName; @@ -34,7 +34,7 @@ class CurrencyFactory * Create class instance with specified parameters * * @param array $data - * @return \Magento\Framework\CurrencyInterface + * @return CurrencyInterface */ public function create(array $data = []) { diff --git a/lib/internal/Magento/Framework/DB/Select.php b/lib/internal/Magento/Framework/DB/Select.php index 75abe8a90ed85df29a5ff6edcff0246e08424921..913dc4a4a92e7d7dace42fed2ff89447ffeabfe9 100644 --- a/lib/internal/Magento/Framework/DB/Select.php +++ b/lib/internal/Magento/Framework/DB/Select.php @@ -5,6 +5,7 @@ */ namespace Magento\Framework\DB; +use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Adapter\AdapterInterface; /** @@ -501,4 +502,32 @@ class Select extends \Zend_Db_Select { return $this->selectRenderer->render($this); } + + /** + * @return string[] + */ + public function __sleep() + { + $properties = array_keys(get_object_vars($this)); + $properties = array_diff( + $properties, + [ + '_adapter', + 'selectRenderer' + ] + ); + return $properties; + } + + /** + * Init not serializable fields + * + * @return void + */ + public function __wakeup() + { + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $this->_adapter = $objectManager->get(ResourceConnection::class)->getConnection(); + $this->selectRenderer = $objectManager->get(\Magento\Framework\DB\Select\SelectRenderer::class); + } } diff --git a/lib/internal/Magento/Framework/Data/Collection.php b/lib/internal/Magento/Framework/Data/Collection.php index a4cf35da2a1de5063bbe73dbb5aecbac4a491431..ef0416419d18ca8a93b01a2a20be6235b4a35c0e 100644 --- a/lib/internal/Magento/Framework/Data/Collection.php +++ b/lib/internal/Magento/Framework/Data/Collection.php @@ -866,4 +866,30 @@ class Collection implements \IteratorAggregate, \Countable, ArrayInterface, Coll { return array_key_exists($flag, $this->_flags); } + + /** + * @return string[] + */ + public function __sleep() + { + $properties = array_keys(get_object_vars($this)); + $properties = array_diff( + $properties, + [ + '_entityFactory', + ] + ); + return $properties; + } + + /** + * Init not serializable fields + * + * @return void + */ + public function __wakeup() + { + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $this->_entityFactory = $objectManager->get(EntityFactoryInterface::class); + } } diff --git a/lib/internal/Magento/Framework/Data/Collection/AbstractDb.php b/lib/internal/Magento/Framework/Data/Collection/AbstractDb.php index b35881d0132767be0e28c9d9fd62fc99c99637f8..dc2b8de38ab12642791ed106d81d91da9f63438a 100644 --- a/lib/internal/Magento/Framework/Data/Collection/AbstractDb.php +++ b/lib/internal/Magento/Framework/Data/Collection/AbstractDb.php @@ -5,6 +5,7 @@ */ namespace Magento\Framework\Data\Collection; +use Magento\Framework\App\ResourceConnection; use Magento\Framework\Data\Collection\Db\FetchStrategyInterface; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Select; @@ -880,4 +881,27 @@ abstract class AbstractDb extends \Magento\Framework\Data\Collection } throw new \LogicException("Main table cannot be identified."); } + + /** + * @inheritdoc + */ + public function __sleep() + { + return array_diff( + parent::__sleep(), + ['_fetchStrategy', '_logger', '_conn', 'extensionAttributesJoinProcessor'] + ); + } + + /** + * @inheritdoc + */ + public function __wakeup() + { + parent::__wakeup(); + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $this->_fetchStrategy = $objectManager->get(Logger::class); + $this->_logger = $objectManager->get(FetchStrategyInterface::class); + $this->_conn = $objectManager->get(ResourceConnection::class)->getConnection(); + } } diff --git a/lib/internal/Magento/Framework/DataObject/IdentityInterface.php b/lib/internal/Magento/Framework/DataObject/IdentityInterface.php index 9d8ab0169388e5a4a8ba3ce2e7043c01cb2973a1..614ca594e3c9d331b04bf299d2130470c3e8ddfd 100644 --- a/lib/internal/Magento/Framework/DataObject/IdentityInterface.php +++ b/lib/internal/Magento/Framework/DataObject/IdentityInterface.php @@ -15,7 +15,7 @@ interface IdentityInterface /** * Return unique ID(s) for each object in system * - * @return array + * @return string[] */ public function getIdentities(); } diff --git a/lib/internal/Magento/Framework/EventFactory.php b/lib/internal/Magento/Framework/EventFactory.php index 47433049bac1b5f03c13407f7bbb468d9cec5dde..e73cb8b96a0941156975523d44924318cb124016 100644 --- a/lib/internal/Magento/Framework/EventFactory.php +++ b/lib/internal/Magento/Framework/EventFactory.php @@ -26,6 +26,6 @@ class EventFactory */ public function create($arguments = []) { - return $this->_objectManager->create('Magento\Framework\Event', $arguments); + return $this->_objectManager->create(Event::class, $arguments); } } diff --git a/lib/internal/Magento/Framework/FlagFactory.php b/lib/internal/Magento/Framework/FlagFactory.php index 99aa8d0047d19c9e85030a15b173c724bb53d77b..fcd066591a061b50fb865ec97c6435395008da1f 100644 --- a/lib/internal/Magento/Framework/FlagFactory.php +++ b/lib/internal/Magento/Framework/FlagFactory.php @@ -32,7 +32,7 @@ class FlagFactory */ public function __construct( \Magento\Framework\ObjectManagerInterface $objectManager, - $instanceName = 'Magento\Framework\Flag' + $instanceName = Flag::class ) { $this->_objectManager = $objectManager; $this->_instanceName = $instanceName; diff --git a/lib/internal/Magento/Framework/Interception/Interceptor.php b/lib/internal/Magento/Framework/Interception/Interceptor.php index 1fcf8e4134812d81e65fbc3844cb3b63b4331e31..1c13c0c3b0bc83104e5bc353b32f1679ccfc37ff 100644 --- a/lib/internal/Magento/Framework/Interception/Interceptor.php +++ b/lib/internal/Magento/Framework/Interception/Interceptor.php @@ -82,10 +82,12 @@ trait Interceptor public function __sleep() { if (method_exists(get_parent_class($this), '__sleep')) { - return array_diff(parent::__sleep(), ['pluginLocator', 'pluginList', 'chain', 'subjectType']); + $properties = parent::__sleep(); } else { - return array_keys(get_class_vars(get_parent_class($this))); + $properties = array_keys(get_object_vars($this)); } + $properties = array_diff($properties, ['pluginLocator', 'pluginList', 'chain', 'subjectType', 'pluginLocator']); + return $properties; } /** diff --git a/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php b/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php index a1a7a29ba3313b4cd45128281cc7bae8d5320590..3bbf7a2f9743aa0ebe250c4be8e2f64581f81bad 100644 --- a/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php +++ b/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php @@ -17,7 +17,6 @@ use Magento\Framework\Interception\ObjectManager\ConfigInterface; use Magento\Framework\ObjectManager\RelationsInterface; use Magento\Framework\ObjectManager\DefinitionInterface as ClassDefinitions; use Magento\Framework\ObjectManagerInterface; -use Zend\Soap\Exception\InvalidArgumentException; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -115,7 +114,7 @@ class PluginList extends Scoped implements InterceptionPluginList * * @param string $type * @return array - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -162,7 +161,7 @@ class PluginList extends Scoped implements InterceptionPluginList } $pluginType = $this->_omConfig->getOriginalInstanceType($plugin['instance']); if (!class_exists($pluginType)) { - throw new InvalidArgumentException('Plugin class ' . $pluginType . ' doesn\'t exist'); + throw new \InvalidArgumentException('Plugin class ' . $pluginType . ' doesn\'t exist'); } foreach ($this->_definitions->getMethodList($pluginType) as $pluginMethod => $methodTypes) { $current = isset($lastPerMethod[$pluginMethod]) ? $lastPerMethod[$pluginMethod] : '__self'; diff --git a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php index 24802ef1ac110d72e91c1af0e40c527843dce481..7be929ccc5a7e6362a97fcf8daedef27d6b3a36e 100644 --- a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php +++ b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php @@ -7,6 +7,7 @@ namespace Magento\Framework\Model; use Magento\Framework\Api\AttributeValueFactory; +use Magento\Framework\Api\ExtensionAttributesFactory; /** * Abstract model with custom attributes support. @@ -19,7 +20,7 @@ abstract class AbstractExtensibleModel extends AbstractModel implements \Magento\Framework\Api\CustomAttributesDataInterface { /** - * @var \Magento\Framework\Api\ExtensionAttributesFactory + * @var ExtensionAttributesFactory */ protected $extensionAttributesFactory; @@ -46,7 +47,7 @@ abstract class AbstractExtensibleModel extends AbstractModel implements /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry - * @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory + * @param ExtensionAttributesFactory $extensionFactory * @param AttributeValueFactory $customAttributeFactory * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection @@ -55,7 +56,7 @@ abstract class AbstractExtensibleModel extends AbstractModel implements public function __construct( \Magento\Framework\Model\Context $context, \Magento\Framework\Registry $registry, - \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory, + ExtensionAttributesFactory $extensionFactory, AttributeValueFactory $customAttributeFactory, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, @@ -328,4 +329,23 @@ abstract class AbstractExtensibleModel extends AbstractModel implements { return $this->getData(self::EXTENSION_ATTRIBUTES_KEY); } + + /** + * @inheritdoc + */ + public function __sleep() + { + return array_diff(parent::__sleep(), ['extensionAttributesFactory', 'customAttributeFactory']); + } + + /** + * @inheritdoc + */ + public function __wakeup() + { + parent::__wakeup(); + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $this->extensionAttributesFactory = $objectManager->get(ExtensionAttributesFactory::class); + $this->customAttributeFactory = $objectManager->get(AttributeValueFactory::class); + } } diff --git a/lib/internal/Magento/Framework/Model/AbstractModel.php b/lib/internal/Magento/Framework/Model/AbstractModel.php index f72c455daf860caec4e77dbf71c901832015dec7..d53820eff1855582e5ad01ddcf0e936f05cb8cf1 100644 --- a/lib/internal/Magento/Framework/Model/AbstractModel.php +++ b/lib/internal/Magento/Framework/Model/AbstractModel.php @@ -220,7 +220,19 @@ abstract class AbstractModel extends \Magento\Framework\DataObject public function __sleep() { $properties = array_keys(get_object_vars($this)); - $properties = array_diff($properties, ['_eventManager', '_cacheManager', '_registry', '_appState']); + $properties = array_diff( + $properties, + [ + '_eventManager', + '_cacheManager', + '_registry', + '_appState', + '_actionValidator', + '_logger', + '_resourceCollection', + '_resource', + ] + ); return $properties; } @@ -232,12 +244,15 @@ abstract class AbstractModel extends \Magento\Framework\DataObject public function __wakeup() { $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); - $this->_eventManager = $objectManager->get('Magento\Framework\Event\ManagerInterface'); - $this->_cacheManager = $objectManager->get('Magento\Framework\App\CacheInterface'); $this->_registry = $objectManager->get('Magento\Framework\Registry'); + $context = $objectManager->get('Magento\Framework\Model\Context'); if ($context instanceof \Magento\Framework\Model\Context) { $this->_appState = $context->getAppState(); + $this->_eventManager = $context->getEventDispatcher(); + $this->_cacheManager = $context->getCacheManager(); + $this->_logger = $context->getLogger(); + $this->_actionValidator = $context->getActionValidator(); } } @@ -263,7 +278,6 @@ abstract class AbstractModel extends \Magento\Framework\DataObject return $this->_idFieldName; } - /** * Identifier getter * diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php index de66653672dd6be913988ea1acd68391c47ec531..578b2c151164182c1d8242c2862fed5003b89ef6 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php @@ -592,4 +592,25 @@ abstract class AbstractCollection extends \Magento\Framework\Data\Collection\Abs } return $this; } + + /** + * @inheritdoc + */ + public function __sleep() + { + return array_diff( + parent::__sleep(), + ['_resource', '_eventManager'] + ); + } + + /** + * @inheritdoc + */ + public function __wakeup() + { + parent::__wakeup(); + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $this->_eventManager = $objectManager->get(\Magento\Framework\Event\ManagerInterface::class); + } } diff --git a/lib/internal/Magento/Framework/Mview/Config/Data/Proxy.php b/lib/internal/Magento/Framework/Mview/Config/Data/Proxy.php index a026f2a60c82e51d076d99b2b7655000c84db58f..15ebfc6a8539aeac634ae57805f7bd356facc2c8 100644 --- a/lib/internal/Magento/Framework/Mview/Config/Data/Proxy.php +++ b/lib/internal/Magento/Framework/Mview/Config/Data/Proxy.php @@ -59,7 +59,7 @@ class Proxy extends \Magento\Framework\Mview\Config\Data implements */ public function __sleep() { - return ['_subject', '_isShared']; + return ['subject', 'isShared']; } /** diff --git a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php index c0f86fa70b2bef63efa7896f6b30dc2c228acaa7..442ad8261a403fbe34fb659e2a1f4df8b1a901ca 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php +++ b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php @@ -81,7 +81,7 @@ class Proxy extends \Magento\Framework\Code\Generator\EntityAbstract $methods = [$construct]; $methods[] = [ 'name' => '__sleep', - 'body' => 'return array(\'_subject\', \'_isShared\');', + 'body' => 'return [\'_subject\', \'_isShared\', \'_instanceName\'];', 'docblock' => ['tags' => [['name' => 'return', 'description' => 'array']]], ]; $methods[] = [ diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/_files/SampleProxy.txt b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/_files/SampleProxy.txt index f3fadd9d31b50927ab0d573ead7521c89f2adc1e..43c99aa5e6843e3676a6b47c618022c78e5a9bc4 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/_files/SampleProxy.txt +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/_files/SampleProxy.txt @@ -52,7 +52,7 @@ class Sample_Proxy extends \Magento\Framework\ObjectManager\Code\Generator\Sampl */ public function __sleep() { - return array('_subject', '_isShared'); + return ['_subject', '_isShared', '_instanceName']; } /** diff --git a/lib/internal/Magento/Framework/Pricing/Render/PriceBox.php b/lib/internal/Magento/Framework/Pricing/Render/PriceBox.php index 45181680b33ec8ec1767df9c8b720713e5f576e1..a8664e3f10a341809c78fddde828e0b3178b2631 100644 --- a/lib/internal/Magento/Framework/Pricing/Render/PriceBox.php +++ b/lib/internal/Magento/Framework/Pricing/Render/PriceBox.php @@ -6,6 +6,7 @@ namespace Magento\Framework\Pricing\Render; +use Magento\Framework\DataObject\IdentityInterface; use Magento\Framework\Pricing\Amount\AmountInterface; use Magento\Framework\Pricing\SaleableInterface; use Magento\Framework\Pricing\Price\PriceInterface; @@ -17,8 +18,11 @@ use Magento\Framework\View\Element\Template; * @method bool hasListClass() * @method string getListClass() */ -class PriceBox extends Template implements PriceBoxRenderInterface +class PriceBox extends Template implements PriceBoxRenderInterface, IdentityInterface { + /** Default block lifetime */ + const DEFAULT_LIFETIME = 3600; + /** * @var SaleableInterface */ @@ -65,6 +69,26 @@ class PriceBox extends Template implements PriceBoxRenderInterface return parent::_toHtml(); } + /** + * Get Key for caching block content + * + * @return string + */ + public function getCacheKey() + { + return parent::getCacheKey() . '-' . $this->getPriceId() . '-' . $this->getPrice()->getPriceCode(); + } + + /** + * Get block cache life time + * + * @return int + */ + protected function getCacheLifetime() + { + return parent::hasCacheLifetime() ? parent::getCacheLifetime() : self::DEFAULT_LIFETIME; + } + /** * @return SaleableInterface */ @@ -146,4 +170,19 @@ class PriceBox extends Template implements PriceBoxRenderInterface { return $this->rendererPool; } + + /** + * Return unique ID(s) for each object in system + * + * @return array + */ + public function getIdentities() + { + $item = $this->getSaleableItem(); + if ($item instanceof IdentityInterface) { + return $item->getIdentities(); + } else { + return []; + } + } } diff --git a/lib/internal/Magento/Framework/Pricing/Test/Unit/Render/PriceBoxTest.php b/lib/internal/Magento/Framework/Pricing/Test/Unit/Render/PriceBoxTest.php index 8ffa2d1749f62a234c97f6da15f573d4e3a00995..9363c9fc0196c2870e60803907d4374c57c08434 100644 --- a/lib/internal/Magento/Framework/Pricing/Test/Unit/Render/PriceBoxTest.php +++ b/lib/internal/Magento/Framework/Pricing/Test/Unit/Render/PriceBoxTest.php @@ -54,6 +54,8 @@ class PriceBoxTest extends \PHPUnit_Framework_TestCase $layout = $this->getMock('Magento\Framework\View\LayoutInterface'); $eventManager = $this->getMock('Magento\Framework\Event\ManagerInterface'); $scopeConfigMock = $this->getMockForAbstractClass('Magento\Framework\App\Config\ScopeConfigInterface'); + $cacheState = $this->getMockBuilder(\Magento\Framework\App\Cache\StateInterface::class) + ->getMockForAbstractClass(); $storeConfig = $this->getMockBuilder('Magento\Store\Model\Store\Config') ->disableOriginalConstructor() ->getMock(); @@ -72,6 +74,9 @@ class PriceBoxTest extends \PHPUnit_Framework_TestCase $this->context->expects($this->any()) ->method('getScopeConfig') ->will($this->returnValue($scopeConfigMock)); + $this->context->expects($this->any()) + ->method('getCacheState') + ->will($this->returnValue($cacheState)); $this->saleable = $this->getMock('Magento\Framework\Pricing\SaleableInterface'); diff --git a/lib/internal/Magento/Framework/Session/Config.php b/lib/internal/Magento/Framework/Session/Config.php index 793794b61826e09f088fef985593e40aae9aada5..99f3eb96d773c8dfa34c6f0a6380027c6288e174 100644 --- a/lib/internal/Magento/Framework/Session/Config.php +++ b/lib/internal/Magento/Framework/Session/Config.php @@ -18,44 +18,28 @@ use Magento\Framework\Session\SaveHandlerInterface; */ class Config implements ConfigInterface { - /** - * Configuration path for session save method - */ + /** Configuration path for session save method */ const PARAM_SESSION_SAVE_METHOD = 'session/save'; - /** - * Configuration path for session save path - */ + /** Configuration path for session save path */ const PARAM_SESSION_SAVE_PATH = 'session/save_path'; - /** - * Configuration path for session cache limiter - */ + /** Configuration path for session cache limiter */ const PARAM_SESSION_CACHE_LIMITER = 'session/cache_limiter'; - /** - * Configuration path for cookie domain - */ + /** Configuration path for cookie domain */ const XML_PATH_COOKIE_DOMAIN = 'web/cookie/cookie_domain'; - /** - * Configuration path for cookie lifetime - */ + /** Configuration path for cookie lifetime */ const XML_PATH_COOKIE_LIFETIME = 'web/cookie/cookie_lifetime'; - /** - * Configuration path for cookie http only param - */ + /** Configuration path for cookie http only param */ const XML_PATH_COOKIE_HTTPONLY = 'web/cookie/cookie_httponly'; - /** - * Configuration path for cookie path - */ + /** Configuration path for cookie path */ const XML_PATH_COOKIE_PATH = 'web/cookie/cookie_path'; - /** - * Cookie default lifetime - */ + /** Cookie default lifetime */ const COOKIE_LIFETIME_DEFAULT = 3600; /** @@ -65,19 +49,13 @@ class Config implements ConfigInterface */ protected $options = []; - /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface - */ + /** @var \Magento\Framework\App\Config\ScopeConfigInterface */ protected $_scopeConfig; - /** - * @var \Magento\Framework\Stdlib\StringUtils - */ + /** @var \Magento\Framework\Stdlib\StringUtils */ protected $_stringHelper; - /** - * @var \Magento\Framework\App\RequestInterface - */ + /** @var \Magento\Framework\App\RequestInterface */ protected $_httpRequest; /** @@ -92,17 +70,16 @@ class Config implements ConfigInterface 'session.cookie_httponly', ]; - /** - * @var string - */ + /** @var string */ protected $_scopeType; - /** - * @var string - */ + /** @var string */ + protected $lifetimePath; + + /** @var string */ private $saveHandlerName; - /** @var \Magento\Framework\ValidatorFactory */ + /** @var \Magento\Framework\ValidatorFactory */ protected $_validatorFactory; /** @@ -131,6 +108,7 @@ class Config implements ConfigInterface $this->_stringHelper = $stringHelper; $this->_httpRequest = $request; $this->_scopeType = $scopeType; + $this->lifetimePath = $lifetimePath; /** * Session handler @@ -170,8 +148,7 @@ class Config implements ConfigInterface /** * Cookie settings: lifetime, path, domain, httpOnly. These govern settings for the session cookie. */ - $lifetime = $this->_scopeConfig->getValue($lifetimePath, $this->_scopeType); - $this->setCookieLifetime($lifetime, self::COOKIE_LIFETIME_DEFAULT); + $this->configureCookieLifetime(); $path = $this->_scopeConfig->getValue(self::XML_PATH_COOKIE_PATH, $this->_scopeType); $path = empty($path) ? $this->_httpRequest->getBasePath() : $path; @@ -580,4 +557,15 @@ class Config implements ConfigInterface throw new \BadMethodCallException(sprintf('Method "%s" does not exist in %s', $method, get_class($this))); } } + + /** + * Set session cookie lifetime according to configuration + * + * @return $this + */ + protected function configureCookieLifetime() + { + $lifetime = $this->_scopeConfig->getValue($this->lifetimePath, $this->_scopeType); + return $this->setCookieLifetime($lifetime, self::COOKIE_LIFETIME_DEFAULT); + } } diff --git a/lib/internal/Magento/Framework/Session/Test/Unit/ConfigTest.php b/lib/internal/Magento/Framework/Session/Test/Unit/ConfigTest.php index 3c50426ce22347baf077bcf3f50739b5fa73bb6d..60d980f77df1ee84ddb89cbb22f116baa25a9322 100644 --- a/lib/internal/Magento/Framework/Session/Test/Unit/ConfigTest.php +++ b/lib/internal/Magento/Framework/Session/Test/Unit/ConfigTest.php @@ -376,7 +376,7 @@ class ConfigTest extends \PHPUnit_Framework_TestCase [ 'session.save_handler' => 'files', 'session.cache_limiter' => 'files', - 'session.cookie_lifetime' => 7200, + 'session.cookie_lifetime' => 0, 'session.cookie_path' => '/', 'session.cookie_domain' => 'init.host', 'session.cookie_httponly' => false, @@ -443,7 +443,6 @@ class ConfigTest extends \PHPUnit_Framework_TestCase $this->configMock = $this->getMock('Magento\Framework\App\Config\ScopeConfigInterface'); $getValueReturnMap = [ - ['test_web/test_cookie/test_cookie_lifetime', 'store', null, 7200], ['web/cookie/cookie_path', 'store', null, ''], ]; $this->configMock->method('getValue') diff --git a/lib/internal/Magento/Framework/Stdlib/DateTime/DateTimeFormatter.php b/lib/internal/Magento/Framework/Stdlib/DateTime/DateTimeFormatter.php index d4d136e0c9c14912e843b783d626c24487419e51..e0958f5635ad0c3cbfb6a63a19c106d143366a7e 100644 --- a/lib/internal/Magento/Framework/Stdlib/DateTime/DateTimeFormatter.php +++ b/lib/internal/Magento/Framework/Stdlib/DateTime/DateTimeFormatter.php @@ -18,21 +18,43 @@ class DateTimeFormatter implements DateTimeFormatterInterface */ protected $useIntlFormatObject; + /** + * @var \Magento\Framework\Locale\ResolverInterface + */ + private $localeResolver; + /** * @param bool|null $useIntlFormatObject */ - public function __construct($useIntlFormatObject = null) - { + public function __construct( + $useIntlFormatObject = null + ) { $this->useIntlFormatObject = (null === $useIntlFormatObject) ? !defined('HHVM_VERSION') : $useIntlFormatObject; } + /** + * Get locale resolver + * + * @return \Magento\Framework\Locale\ResolverInterface|mixed + */ + private function getLocaleResolver() + { + if ($this->localeResolver === null) { + $this->localeResolver = \Magento\Framework\App\ObjectManager::getInstance()->get( + 'Magento\Framework\Locale\ResolverInterface' + ); + } + return $this->localeResolver; + } + /** * {@inheritdoc} */ public function formatObject($object, $format = null, $locale = null) { + $locale = (null === $locale) ? $this->getLocaleResolver()->getLocale() : $locale; if ($this->useIntlFormatObject) { return \IntlDateFormatter::formatObject($object, $format, $locale); } diff --git a/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/DateTimeFormatterTest.php b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/DateTimeFormatterTest.php index 2c6358bc6ddc042a70a321ac017e271220861dd0..7a08498555ecace563771b3c432118e008486779 100644 --- a/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/DateTimeFormatterTest.php +++ b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/DateTimeFormatterTest.php @@ -10,33 +10,58 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; class DateTimeFormatterTest extends \PHPUnit_Framework_TestCase { /** - * @var \Magento\Framework\Stdlib\DateTime\DateTimeFormatter + * @var ObjectManager */ - protected $dateTimeFormatter; + protected $objectManager; + + /** + * @var \Magento\Framework\Locale\ResolverInterface | \PHPUnit_Framework_MockObject_MockObject + */ + protected $localeResolverMock; protected function setUp() { if (defined('HHVM_VERSION')) { $this->markTestSkipped('Skip this test for hhvm due to problem with \IntlDateFormatter::formatObject'); } + $this->objectManager = new ObjectManager($this); + $this->localeResolverMock = $this->getMockBuilder('Magento\Framework\Locale\ResolverInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->localeResolverMock->expects($this->any()) + ->method('getLocale') + ->willReturn('fr-FR'); - $this->dateTimeFormatter = (new ObjectManager($this)) - ->getObject('Magento\Framework\Stdlib\DateTime\DateTimeFormatter', [ - 'useIntlFormatObject' => false, - ]); } /** * @param \IntlCalendar|\DateTime $object * @param string|int|array|null $format * @param string|null $locale + * @param boolean $useIntlFormatObject * @dataProvider dataProviderFormatObject */ - public function testFormatObject($object, $format = null, $locale = null) + public function testFormatObject($object, $format = null, $locale = null, $useIntlFormatObject = false) { + $dateTimeFormatter = $this->objectManager->getObject( + 'Magento\Framework\Stdlib\DateTime\DateTimeFormatter', + [ + 'useIntlFormatObject' => $useIntlFormatObject, + ] + ); + + $reflection = new \ReflectionClass(get_class($dateTimeFormatter)); + $reflectionProperty = $reflection->getProperty('localeResolver'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($dateTimeFormatter, $this->localeResolverMock); + $this->assertEquals( - \IntlDateFormatter::formatObject($object, $format, $locale), - $this->dateTimeFormatter->formatObject($object, $format, $locale) + \IntlDateFormatter::formatObject( + $object, + $format, + (null === $locale) ? 'fr-FR' : $locale + ), + $dateTimeFormatter->formatObject($object, $format, $locale) ); } @@ -59,7 +84,6 @@ class DateTimeFormatterTest extends \PHPUnit_Framework_TestCase [new \DateTime('2013-09-09 09:09:09 Europe/Madrid'), \IntlDateFormatter::FULL, 'es_ES'], [new \DateTime('2013-09-09 09:09:09 -01:00'), null, null], [new \DateTime('2013-09-09 09:09:09 +01:00'), null, null], - [$calendar, null, null], [$calendar, \IntlDateFormatter::FULL, null], [$calendar, null, 'en-US'], @@ -70,6 +94,26 @@ class DateTimeFormatterTest extends \PHPUnit_Framework_TestCase [\IntlCalendar::fromDateTime('2013-09-09 09:09:09 Europe/Madrid'), \IntlDateFormatter::FULL, 'es_ES'], [\IntlCalendar::fromDateTime('2013-09-09 09:09:09 -01:00'), null, null], [\IntlCalendar::fromDateTime('2013-09-09 09:09:09 +01:00'), null, null], + [$date, null, null, true], + [$date, \IntlDateFormatter::FULL, null, true], + [$date, null, 'en-US', true], + [$date, [\IntlDateFormatter::SHORT, \IntlDateFormatter::FULL], 'en-US', true], + [$date, 'E y-MM-d HH,mm,ss.SSS v', 'en-US', true], + [$date, [\IntlDateFormatter::NONE, \IntlDateFormatter::FULL], null, true], + [$date, "d 'of' MMMM y", 'en_US', true], + [new \DateTime('2013-09-09 09:09:09 Europe/Madrid'), \IntlDateFormatter::FULL, 'es_ES', true], + [new \DateTime('2013-09-09 09:09:09 -01:00'), null, null, true], + [new \DateTime('2013-09-09 09:09:09 +01:00'), null, null, true], + [$calendar, null, null, true], + [$calendar, \IntlDateFormatter::FULL, null, true], + [$calendar, null, 'en-US', true], + [$calendar, [\IntlDateFormatter::SHORT, \IntlDateFormatter::FULL], 'en-US', true], + [$calendar, 'E y-MM-d HH,mm,ss.SSS v', 'en-US', true], + [$calendar, [\IntlDateFormatter::NONE, \IntlDateFormatter::FULL], null, true], + [$calendar, "d 'of' MMMM y", 'en_US', true], + [\IntlCalendar::fromDateTime('2013-09-09 09:09:09 Europe/Madrid'), \IntlDateFormatter::FULL, 'es_ES', true], + [\IntlCalendar::fromDateTime('2013-09-09 09:09:09 -01:00'), null, null, true], + [\IntlCalendar::fromDateTime('2013-09-09 09:09:09 +01:00'), null, null, true], ]; } @@ -79,6 +123,17 @@ class DateTimeFormatterTest extends \PHPUnit_Framework_TestCase */ public function testFormatObjectIfPassedWrongFormat() { - $this->dateTimeFormatter->formatObject(new \DateTime('2013-06-06 17:05:06 Europe/Dublin'), new \StdClass()); + $dateTimeFormatter = $this->objectManager->getObject( + 'Magento\Framework\Stdlib\DateTime\DateTimeFormatter', + [ + 'useIntlFormatObject' => false, + ] + ); + + $reflection = new \ReflectionClass(get_class($dateTimeFormatter)); + $reflectionProperty = $reflection->getProperty('localeResolver'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($dateTimeFormatter, $this->localeResolverMock); + $dateTimeFormatter->formatObject(new \DateTime('2013-06-06 17:05:06 Europe/Dublin'), new \StdClass()); } } diff --git a/lib/internal/Magento/Framework/Translate/Inline/Proxy.php b/lib/internal/Magento/Framework/Translate/Inline/Proxy.php index 99440a39863f34a7951de2a2f845f241bd6064ea..ffa9368c5454d3937c1c23ca85f987e63d2d78f8 100644 --- a/lib/internal/Magento/Framework/Translate/Inline/Proxy.php +++ b/lib/internal/Magento/Framework/Translate/Inline/Proxy.php @@ -59,7 +59,7 @@ class Proxy extends \Magento\Framework\Translate\Inline implements */ public function __sleep() { - return ['_subject', '_isShared']; + return ['subject', 'isShared']; } /** diff --git a/lib/internal/Magento/Framework/UrlFactory.php b/lib/internal/Magento/Framework/UrlFactory.php index f3e65d808ca58ceb6aba8bbc05f24daa62b878b5..65c7d55f1aac584d59863eb19a7f55a992c532fc 100644 --- a/lib/internal/Magento/Framework/UrlFactory.php +++ b/lib/internal/Magento/Framework/UrlFactory.php @@ -11,7 +11,7 @@ namespace Magento\Framework; class UrlFactory { /** - * @var \Magento\Framework\ObjectManagerInterface + * @var ObjectManagerInterface */ protected $_objectManager = null; @@ -21,10 +21,10 @@ class UrlFactory protected $_instanceName = null; /** - * @param \Magento\Framework\ObjectManagerInterface $objectManager + * @param ObjectManagerInterface $objectManager * @param string $instanceName */ - public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager, $instanceName = 'Magento\Framework\UrlInterface') + public function __construct(ObjectManagerInterface $objectManager, $instanceName = UrlInterface::class) { $this->_objectManager = $objectManager; $this->_instanceName = $instanceName; @@ -34,7 +34,7 @@ class UrlFactory * Create Url instance with specified parameters * * @param array $data - * @return \Magento\Framework\UrlInterface + * @return UrlInterface */ public function create(array $data = []) { diff --git a/lib/internal/Magento/Framework/Validator/Factory.php b/lib/internal/Magento/Framework/Validator/Factory.php index ef5679b220c2460dbc1e94d7d09760af0436fa31..d35bae829050054446b009c249b65d72baa849d8 100644 --- a/lib/internal/Magento/Framework/Validator/Factory.php +++ b/lib/internal/Magento/Framework/Validator/Factory.php @@ -10,8 +10,13 @@ namespace Magento\Framework\Validator; +use Magento\Framework\Cache\FrontendInterface; + class Factory { + /** cache key */ + const CACHE_KEY = __CLASS__; + /** * @var \Magento\Framework\ObjectManagerInterface */ @@ -29,18 +34,47 @@ class Factory */ private $isDefaultTranslatorInitialized = false; + /** + * @var \Magento\Framework\Module\Dir\Reader + */ + private $moduleReader; + + /** + * @var FrontendInterface + */ + private $cache; + /** * Initialize dependencies * * @param \Magento\Framework\ObjectManagerInterface $objectManager * @param \Magento\Framework\Module\Dir\Reader $moduleReader + * @param FrontendInterface $cache */ public function __construct( \Magento\Framework\ObjectManagerInterface $objectManager, - \Magento\Framework\Module\Dir\Reader $moduleReader + \Magento\Framework\Module\Dir\Reader $moduleReader, + FrontendInterface $cache ) { $this->_objectManager = $objectManager; - $this->_configFiles = $moduleReader->getConfigurationFiles('validation.xml'); + $this->moduleReader = $moduleReader; + $this->cache = $cache; + } + + /** + * Init cached list of validation files + */ + protected function _initializeConfigList() + { + if (!$this->_configFiles) { + $this->_configFiles = $this->cache->load(self::CACHE_KEY); + if (!$this->_configFiles) { + $this->_configFiles = $this->moduleReader->getConfigurationFiles('validation.xml'); + $this->cache->save(serialize($this->_configFiles), self::CACHE_KEY); + } else { + $this->_configFiles = unserialize($this->_configFiles); + } + } } /** @@ -73,6 +107,7 @@ class Factory */ public function getValidatorConfig() { + $this->_initializeConfigList(); $this->_initializeDefaultTranslator(); return $this->_objectManager->create('Magento\Framework\Validator\Config', ['configFiles' => $this->_configFiles]); } diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/FactoryTest.php b/lib/internal/Magento/Framework/Validator/Test/Unit/FactoryTest.php index 222510a23bfc3839b10bb225ea59aeb3a0ff950c..d685e332a56118490139cc668d0026583c572e5b 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/FactoryTest.php +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/FactoryTest.php @@ -29,6 +29,8 @@ class FactoryTest extends \PHPUnit_Framework_TestCase */ protected $_validatorConfig; + private $cache; + /** * @var \Magento\Framework\Translate\AdapterInterface|null */ @@ -88,6 +90,9 @@ class FactoryTest extends \PHPUnit_Framework_TestCase $this->_translateAdapter = $this->getMockBuilder( 'Magento\Framework\TranslateInterface' )->disableOriginalConstructor()->getMock(); + + $this->cache = $this->getMockBuilder(\Magento\Framework\Cache\FrontendInterface::class) + ->getMockForAbstractClass(); } /** @@ -107,7 +112,7 @@ class FactoryTest extends \PHPUnit_Framework_TestCase $factory = new \Magento\Framework\Validator\Factory( $this->_objectManager, $this->_config, - $this->_translateAdapter + $this->cache ); $actualConfig = $factory->getValidatorConfig(); $this->assertInstanceOf( @@ -147,7 +152,7 @@ class FactoryTest extends \PHPUnit_Framework_TestCase $factory = new \Magento\Framework\Validator\Factory( $this->_objectManager, $this->_config, - $this->_translateAdapter + $this->cache ); $this->assertInstanceOf( 'Magento\Framework\Validator\Builder', @@ -174,7 +179,7 @@ class FactoryTest extends \PHPUnit_Framework_TestCase $factory = new \Magento\Framework\Validator\Factory( $this->_objectManager, $this->_config, - $this->_translateAdapter + $this->cache ); $this->assertInstanceOf('Magento\Framework\Validator', $factory->createValidator('test', 'class', [])); } diff --git a/lib/internal/Magento/Framework/ValidatorFactory.php b/lib/internal/Magento/Framework/ValidatorFactory.php index 6ec20b848735893f948d14562b2a66591b8dda30..6202a8b52cf3cbc201da18befa7f1e3fd0eccac8 100644 --- a/lib/internal/Magento/Framework/ValidatorFactory.php +++ b/lib/internal/Magento/Framework/ValidatorFactory.php @@ -10,7 +10,7 @@ namespace Magento\Framework; */ class ValidatorFactory { - const DEFAULT_INSTANCE_NAME = 'Magento\Framework\Validator'; + const DEFAULT_INSTANCE_NAME = Validator::class; /** * Object Manager instance diff --git a/lib/internal/Magento/Framework/View/Element/AbstractBlock.php b/lib/internal/Magento/Framework/View/Element/AbstractBlock.php index bfed571826a805cbeb263704644171dae4297cb6..2a48f91117cc15858fd8743b706438ae3ab174b2 100644 --- a/lib/internal/Magento/Framework/View/Element/AbstractBlock.php +++ b/lib/internal/Magento/Framework/View/Element/AbstractBlock.php @@ -5,6 +5,8 @@ */ namespace Magento\Framework\View\Element; +use Magento\Framework\DataObject\IdentityInterface; + /** * Base Content Block class * @@ -965,16 +967,17 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl if ($this->hasData('cache_key')) { return static::CACHE_KEY_PREFIX . $this->getData('cache_key'); } + /** * don't prevent recalculation by saving generated cache key * because of ability to render single block instance with different data */ $key = $this->getCacheKeyInfo(); - //ksort($key); // ignore order - $key = array_values($key); - // ignore array keys + + $key = array_values($key); // ignore array keys + $key = implode('|', $key); - $key = sha1($key); + $key = sha1($key); // use hashing to hide potentially private data return static::CACHE_KEY_PREFIX . $key; } @@ -991,6 +994,10 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl $tags = $this->getData('cache_tags'); } $tags[] = self::CACHE_GROUP; + + if ($this instanceof IdentityInterface) { + $tags += $this->getIdentities(); + } return $tags; } @@ -1047,7 +1054,7 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl $data ); - $this->_cache->save($data, $cacheKey, $this->getCacheTags(), $this->getCacheLifetime()); + $this->_cache->save($data, $cacheKey, array_unique($this->getCacheTags()), $this->getCacheLifetime()); return $this; } diff --git a/nginx.conf.sample b/nginx.conf.sample index 87b28aa9f691d71666b2a91b433b77b1b4d672c2..04e6dda144b3cc6ea00f36a4004d234d6df20f7f 100644 --- a/nginx.conf.sample +++ b/nginx.conf.sample @@ -80,6 +80,12 @@ location /static/ { if ($MAGE_MODE = "production") { expires max; } + + # Remove signature of the static files that is used to overcome the browser cache + location ~ ^/static/version { + rewrite ^/static/(version\d*/)?(.*)$ /static/$2 last; + } + location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; @@ -145,9 +151,10 @@ location ~ cron\.php { location ~ (index|get|static|report|404|503)\.php$ { try_files $uri =404; fastcgi_pass fastcgi_backend; + fastcgi_buffers 1024 4k; fastcgi_param PHP_FLAG "session.auto_start=off \n suhosin.session.cryptua=off"; - fastcgi_param PHP_VALUE "memory_limit=256M \n max_execution_time=600"; + fastcgi_param PHP_VALUE "memory_limit=768M \n max_execution_time=600"; fastcgi_read_timeout 600s; fastcgi_connect_timeout 600s; fastcgi_param MAGE_MODE $MAGE_MODE; diff --git a/setup/src/Magento/Setup/Fixtures/StoresFixture.php b/setup/src/Magento/Setup/Fixtures/StoresFixture.php index fd694bc35de2ce86667d2b487d631714ef170aa9..21b0da0d01a9acaa3e425ccb218d9b66fe54d5a2 100644 --- a/setup/src/Magento/Setup/Fixtures/StoresFixture.php +++ b/setup/src/Magento/Setup/Fixtures/StoresFixture.php @@ -33,7 +33,6 @@ class StoresFixture extends Fixture /** @var \Magento\Store\Model\StoreManager $storeManager */ $storeManager = $this->fixtureModel->getObjectManager()->create('Magento\Store\Model\StoreManager'); /** @var $category \Magento\Catalog\Model\Category */ - $category = $this->fixtureModel->getObjectManager()->create('Magento\Catalog\Model\Category'); /** @var $defaultWebsite \Magento\Store\Model\Website */ $defaultWebsite = $storeManager->getWebsite(); @@ -76,19 +75,18 @@ class StoresFixture extends Fixture //Create $storeGroupsCount websites $websiteNumber = 0; for ($i = 0; $i < $storeGroupsCount; $i++) { + $category = $this->fixtureModel->getObjectManager()->create('Magento\Catalog\Model\Category'); $websiteId = $websitesId[$websiteNumber]; $groupId = null; - $parentCategoryId = null; $categoryPath = '1'; $storeGroupName = sprintf('Store Group %d - website_id_%d', $i + 1, $websiteId); if ($i == 0 && $websiteId == $defaultWebsiteId) { $groupId = $defaultStoreGroupId; - $parentCategoryId = $defaultParentCategoryId; $categoryPath = '1/' . $defaultParentCategoryId; + $category->load($defaultParentCategoryId); } - $category->load($parentCategoryId); $category->setName("Category $storeGroupName") ->setPath($categoryPath)