diff --git a/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml b/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml index 4db99b3ed5de7..843328fbf17d7 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml @@ -9,7 +9,7 @@ ?>
@@ -21,8 +21,13 @@
  • escapeHtml(__('Contact a system administrator or store owner to gain permissions.')) ?>
  • escapeHtml(__('Return to ')) ?> - - escapeHtml(__('previous page')) ?>escapeHtml(__('.')) ?> + + + escapeHtml(__('previous page')) ?>escapeHtml(__('.')) ?> + + + escapeHtml(__('previous page')) ?>escapeHtml(__('.')) ?> +
  • diff --git a/app/code/Magento/Catalog/Model/Category/Link/SaveHandler.php b/app/code/Magento/Catalog/Model/Category/Link/SaveHandler.php index 29a1ef60a43de..f22c6903a230c 100644 --- a/app/code/Magento/Catalog/Model/Category/Link/SaveHandler.php +++ b/app/code/Magento/Catalog/Model/Category/Link/SaveHandler.php @@ -119,7 +119,9 @@ private function mergeCategoryLinks($newCategoryPositions, $oldCategoryPositions if ($key === false) { $result[] = $newCategoryPosition; - } elseif ($oldCategoryPositions[$key]['position'] != $newCategoryPosition['position']) { + } elseif (isset($oldCategoryPositions[$key]) + && $oldCategoryPositions[$key]['position'] != $newCategoryPosition['position'] + ) { $result[] = $newCategoryPositions[$key]; unset($oldCategoryPositions[$key]); } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Config.php b/app/code/Magento/Catalog/Model/ResourceModel/Config.php index 7fb13265cd130..7b5d4e09a3599 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Config.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Config.php @@ -149,8 +149,7 @@ public function getAttributesUsedForSortBy() ['main_table' => $this->getTable('eav_attribute')] )->join( ['additional_table' => $this->getTable('catalog_eav_attribute')], - 'main_table.attribute_id = additional_table.attribute_id', - [] + 'main_table.attribute_id = additional_table.attribute_id' )->joinLeft( ['al' => $this->getTable('eav_attribute_label')], 'al.attribute_id = main_table.attribute_id AND al.store_id = ' . $this->getStoreId(), diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/Link/SaveHandlerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/Link/SaveHandlerTest.php index 78db12be56b42..0b85ef38387fa 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Category/Link/SaveHandlerTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/Link/SaveHandlerTest.php @@ -197,6 +197,21 @@ public function getCategoryDataProvider() ], [], //affected category_ids ], + [ + [3], //model category_ids + [ + ['category_id' => 3, 'position' => 20], + ['category_id' => 4, 'position' => 30], + ], // dto category links + [ + ['category_id' => 3, 'position' => 10], + ], + [ + ['category_id' => 3, 'position' => 20], + ['category_id' => 4, 'position' => 30], + ], + [3, 4], //affected category_ids + ], ]; } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/ConfigTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/ConfigTest.php new file mode 100644 index 0000000000000..abbcef942373e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/ConfigTest.php @@ -0,0 +1,107 @@ +resource = $this->createMock(\Magento\Framework\App\ResourceConnection::class); + $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); + $this->eavConfig = $this->createMock(\Magento\Eav\Model\Config::class); + + $this->model = $objectManager->getObject( + \Magento\Catalog\Model\ResourceModel\Config::class, + [ + 'resource' => $this->resource, + 'storeManager' => $this->storeManager, + 'eavConfig' => $this->eavConfig, + ] + ); + + parent::setUp(); + } + + public function testGetAttributesUsedForSortBy() + { + $expression = 'someExpression'; + $storeId = 1; + $entityTypeId = 4; + + $connectionMock = $this->createMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); + $selectMock = $this->createMock(\Magento\Framework\DB\Select::class); + $storeMock = $this->createMock(\Magento\Store\Api\Data\StoreInterface::class); + $entityTypeMock = $this->createMock(\Magento\Eav\Model\Entity\Type::class); + + $this->resource->expects($this->atLeastOnce())->method('getConnection')->willReturn($connectionMock); + + $connectionMock->expects($this->once())->method('getCheckSql') + ->with('al.value IS NULL', 'main_table.frontend_label', 'al.value') + ->willReturn($expression); + $connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($selectMock); + + $this->resource->expects($this->exactly(3))->method('getTableName')->withConsecutive( + ['eav_attribute'], + ['catalog_eav_attribute'], + ['eav_attribute_label'] + )->willReturnOnConsecutiveCalls('eav_attribute', 'catalog_eav_attribute', 'eav_attribute_label'); + + $this->storeManager->expects($this->once())->method('getStore')->willReturn($storeMock); + $storeMock->expects($this->once())->method('getId')->willReturn($storeId); + + $this->eavConfig->expects($this->once())->method('getEntityType')->willReturn($entityTypeMock); + $entityTypeMock->expects($this->once())->method('getId')->willReturn($entityTypeId); + + $selectMock->expects($this->once())->method('from') + ->with(['main_table' => 'eav_attribute'])->willReturn($selectMock); + $selectMock->expects($this->once())->method('join')->with( + ['additional_table' => 'catalog_eav_attribute'], + 'main_table.attribute_id = additional_table.attribute_id' + )->willReturn($selectMock); + $selectMock->expects($this->once())->method('joinLeft') + ->with( + ['al' => 'eav_attribute_label'], + 'al.attribute_id = main_table.attribute_id AND al.store_id = ' . $storeId, + ['store_label' => $expression] + )->willReturn($selectMock); + $selectMock->expects($this->exactly(2))->method('where')->withConsecutive( + ['main_table.entity_type_id = ?', $entityTypeId], + ['additional_table.used_for_sort_by = ?', 1] + )->willReturn($selectMock); + + $connectionMock->expects($this->once())->method('fetchAll')->with($selectMock); + + $this->model->getAttributesUsedForSortBy(); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Storage/DbStorage.php b/app/code/Magento/CatalogUrlRewrite/Model/Storage/DbStorage.php index 748589924d916..f0351467e5f0e 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Storage/DbStorage.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Storage/DbStorage.php @@ -12,25 +12,37 @@ class DbStorage extends BaseDbStorage { /** - * @param array $data - * @return \Magento\Framework\DB\Select + * {@inheritDoc} */ protected function prepareSelect(array $data) { + $metadata = []; + if (array_key_exists(UrlRewrite::METADATA, $data)) { + $metadata = $data[UrlRewrite::METADATA]; + unset($data[UrlRewrite::METADATA]); + } + $select = $this->connection->select(); - $select->from(['url_rewrite' => $this->resource->getTableName('url_rewrite')]) - ->joinLeft( - ['relation' => $this->resource->getTableName(Product::TABLE_NAME)], - 'url_rewrite.url_rewrite_id = relation.url_rewrite_id' - ) - ->where('url_rewrite.entity_id IN (?)', $data['entity_id']) - ->where('url_rewrite.entity_type = ?', $data['entity_type']) - ->where('url_rewrite.store_id IN (?)', $data['store_id']); - if (empty($data[UrlRewrite::METADATA]['category_id'])) { + $select->from([ + 'url_rewrite' => $this->resource->getTableName(self::TABLE_NAME) + ]); + $select->joinLeft( + ['relation' => $this->resource->getTableName(Product::TABLE_NAME)], + 'url_rewrite.url_rewrite_id = relation.url_rewrite_id' + ); + + foreach ($data as $column => $value) { + $select->where('url_rewrite.' . $column . ' IN (?)', $value); + } + if (empty($metadata['category_id'])) { $select->where('relation.category_id IS NULL'); } else { - $select->where('relation.category_id = ?', $data[UrlRewrite::METADATA]['category_id']); + $select->where( + 'relation.category_id = ?', + $metadata['category_id'] + ); } + return $select; } } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php new file mode 100644 index 0000000000000..d00b0c87fa5ad --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php @@ -0,0 +1,142 @@ +urlRewriteFactory = $this + ->getMockBuilder(UrlRewriteFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->dataObjectHelper = $this->createMock(DataObjectHelper::class); + $this->connectionMock = $this->createMock(AdapterInterface::class); + $this->select = $this->createPartialMock( + Select::class, + ['from', 'where', 'deleteFromSelect', 'joinLeft'] + ); + $this->resource = $this->createMock(ResourceConnection::class); + + $this->resource->expects($this->any()) + ->method('getConnection') + ->will($this->returnValue($this->connectionMock)); + $this->connectionMock->expects($this->any()) + ->method('select') + ->will($this->returnValue($this->select)); + + $this->storage = (new ObjectManager($this))->getObject( + DbStorage::class, + [ + 'urlRewriteFactory' => $this->urlRewriteFactory, + 'dataObjectHelper' => $this->dataObjectHelper, + 'resource' => $this->resource, + ] + ); + } + + public function testPrepareSelect() + { + //Passing expected parameters, checking select built. + $entityType = 'custom'; + $entityId= 42; + $storeId = 0; + $categoryId = 2; + $redirectType = 301; + //Expecting this methods to be called on select + $this->select + ->expects($this->at(2)) + ->method('where') + ->with('url_rewrite.entity_id IN (?)', $entityId) + ->willReturn($this->select); + $this->select + ->expects($this->at(3)) + ->method('where') + ->with('url_rewrite.entity_type IN (?)', $entityType) + ->willReturn($this->select); + $this->select + ->expects($this->at(4)) + ->method('where') + ->with('url_rewrite.store_id IN (?)', $storeId) + ->willReturn($this->select); + $this->select + ->expects($this->at(5)) + ->method('where') + ->with('url_rewrite.redirect_type IN (?)', $redirectType) + ->willReturn($this->select); + $this->select + ->expects($this->at(6)) + ->method('where') + ->with('relation.category_id = ?', $categoryId) + ->willReturn($this->select); + //Preparing mocks to be used + $this->select + ->expects($this->any()) + ->method('from') + ->willReturn($this->select); + $this->select + ->expects($this->any()) + ->method('joinLeft') + ->willReturn($this->select); + //Indirectly calling prepareSelect + $this->storage->findOneByData([ + UrlRewrite::ENTITY_ID => $entityId, + UrlRewrite::ENTITY_TYPE => $entityType, + UrlRewrite::STORE_ID => $storeId, + UrlRewrite::REDIRECT_TYPE => $redirectType, + UrlRewrite::METADATA => ['category_id' => $categoryId] + ]); + } +} diff --git a/app/code/Magento/Customer/Model/Address/AbstractAddress.php b/app/code/Magento/Customer/Model/Address/AbstractAddress.php index 3b141d4cb7f68..a6ba510932d3d 100644 --- a/app/code/Magento/Customer/Model/Address/AbstractAddress.php +++ b/app/code/Magento/Customer/Model/Address/AbstractAddress.php @@ -269,7 +269,7 @@ public function setData($key, $value = null) { if (is_array($key)) { $key = $this->_implodeArrayField($key); - } elseif (is_array($value) && !empty($value) && $this->isAddressMultilineAttribute($key)) { + } elseif (is_array($value) && $this->isAddressMultilineAttribute($key)) { $value = $this->_implodeArrayValues($value); } return parent::setData($key, $value); @@ -309,7 +309,11 @@ protected function _implodeArrayField(array $data) */ protected function _implodeArrayValues($value) { - if (is_array($value) && count($value)) { + if (is_array($value)) { + if (!count($value)) { + return ''; + } + $isScalar = false; foreach ($value as $val) { if (is_scalar($val)) { diff --git a/app/code/Magento/Customer/Test/Unit/Model/Address/AbstractAddressTest.php b/app/code/Magento/Customer/Test/Unit/Model/Address/AbstractAddressTest.php index 23b8b38c962b9..2eef9a44cab74 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Address/AbstractAddressTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Address/AbstractAddressTest.php @@ -366,6 +366,15 @@ public function testGetStreetFullAlwaysReturnsString($expectedResult, $street) $this->assertEquals($expectedResult, $this->model->getStreetFull()); } + /** + * @dataProvider getStreetFullDataProvider + */ + public function testSetDataStreetAlwaysConvertedToString($expectedResult, $street) + { + $this->model->setData('street', $street); + $this->assertEquals($expectedResult, $this->model->getData('street')); + } + /** * @return array */ diff --git a/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber.php b/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber.php index c72ae42031001..c7ce4b2f2f11b 100644 --- a/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber.php @@ -118,17 +118,37 @@ public function loadByEmail($subscriberEmail) */ public function loadByCustomerData(\Magento\Customer\Api\Data\CustomerInterface $customer) { - $select = $this->connection->select()->from($this->getMainTable())->where('customer_id=:customer_id'); - - $result = $this->connection->fetchRow($select, ['customer_id' => $customer->getId()]); + $select = $this->connection + ->select() + ->from($this->getMainTable()) + ->where('customer_id=:customer_id and store_id=:store_id'); + + $result = $this->connection + ->fetchRow( + $select, + [ + 'customer_id' => $customer->getId(), + 'store_id' => $customer->getStoreId() + ] + ); if ($result) { return $result; } - $select = $this->connection->select()->from($this->getMainTable())->where('subscriber_email=:subscriber_email'); - - $result = $this->connection->fetchRow($select, ['subscriber_email' => $customer->getEmail()]); + $select = $this->connection + ->select() + ->from($this->getMainTable()) + ->where('subscriber_email=:subscriber_email and store_id=:store_id'); + + $result = $this->connection + ->fetchRow( + $select, + [ + 'subscriber_email' => $customer->getEmail(), + 'store_id' => $customer->getStoreId() + ] + ); if ($result) { return $result; diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index ea2a42241d8fc..cc143fdc52e3b 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -360,6 +360,7 @@ public function loadByCustomerId($customerId) { try { $customerData = $this->customerRepository->getById($customerId); + $customerData->setStoreId($this->_storeManager->getStore()->getId()); $data = $this->getResource()->loadByCustomerData($customerData); $this->addData($data); if (!empty($data) && $customerData->getId() && !$this->getCustomerId()) { diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php index 7716f4744a922..5a4032dc4dffd 100644 --- a/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php +++ b/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php @@ -187,6 +187,12 @@ public function testUpdateSubscription() $customerDataMock->expects($this->once())->method('getStoreId')->willReturn('store_id'); $customerDataMock->expects($this->once())->method('getEmail')->willReturn('email'); + $storeModel = $this->getMockBuilder(\Magento\Store\Model\Store::class) + ->disableOriginalConstructor() + ->setMethods(['getId']) + ->getMock(); + $this->storeManager->expects($this->any())->method('getStore')->willReturn($storeModel); + $this->assertEquals($this->subscriber, $this->subscriber->updateSubscription($customerId)); } diff --git a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js index 03ce42bf25c4a..1dfcc95a552c6 100644 --- a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js +++ b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js @@ -134,7 +134,6 @@ define([ */ _create: function () { $(this.element).on('gallery:loaded', $.proxy(function () { - this.fotoramaItem = $(this.element).find('.fotorama-item'); this._initialize(); }, this)); }, @@ -154,6 +153,7 @@ define([ this.defaultVideoData = this.options.videoData = this.videoDataPlaceholder; } + this.fotoramaItem = $(this.element).find('.fotorama-item'); this.clearEvents(); if (this._checkForVideoExist()) { @@ -164,6 +164,8 @@ define([ this._initFotoramaVideo(); this._attachFotoramaEvents(); } + + this.element.trigger('AddFotoramaVideoEvents:loaded'); }, /** diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php index d01ae7304bdc6..e25b770b7a81e 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php @@ -34,6 +34,7 @@ * Test class for sales quote address model * * @see \Magento\Quote\Model\Quote\Address + * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AddressTest extends \PHPUnit\Framework\TestCase @@ -48,6 +49,11 @@ class AddressTest extends \PHPUnit\Framework\TestCase */ private $quote; + /** + * @var \Magento\Quote\Model\Quote\Address\CustomAttributeListInterface | \PHPUnit_Framework_MockObject_MockObject + */ + private $attributeList; + /** * @var \Magento\Framework\App\Config | \PHPUnit_Framework_MockObject_MockObject */ @@ -166,9 +172,13 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); + $this->attributeList = $this->createMock(\Magento\Quote\Model\Quote\Address\CustomAttributeListInterface::class); + $this->attributeList->method('getAttributes')->willReturn([]); + $this->address = $objectManager->getObject( \Magento\Quote\Model\Quote\Address::class, [ + 'attributeList' => $this->attributeList, 'scopeConfig' => $this->scopeConfig, 'serializer' => $this->serializer, 'storeManager' => $this->storeManager, diff --git a/app/code/Magento/ReleaseNotification/i18n/en_US.csv b/app/code/Magento/ReleaseNotification/i18n/en_US.csv index 3fd0be63b4ab1..0f4a1f87b8b4e 100644 --- a/app/code/Magento/ReleaseNotification/i18n/en_US.csv +++ b/app/code/Magento/ReleaseNotification/i18n/en_US.csv @@ -1,42 +1,42 @@ "Next >","Next >" "< Back","< Back" "Done","Done" -"What's new for Magento 2.2.2","What's new for Magento 2.2.2" -"

    Magento 2.2.2 offers an exciting set of features and enhancements, including:

    +"What's new with Magento 2.2.2","What's new with Magento 2.2.2" +"

    Magento 2.2.2 offers advanced new features, including:


    Advanced Reporting

    Gain valuable insights through a dynamic suite of product, order, and customer reports, powered by Magento Business Intelligence.

    -
    -

    Developer Experience

    -

    We've improved the entire development lifecycle - installation, development, and maintenance - - while ensuring Magento's commitment to quality.

    +
    +

    Instant Purchase

    +

    Simplify ordering and boost conversion rates by allowing your customers to use stored + payment and shipping information to skip tedious checkout steps.

    -
    -

    Business-to-Business (B2B) Magento Commerce only

    -

    Features to manage your complex company accounts, rapid reordering, new buyers' roles and - permissions, and more.

    +
    +

    Email Marketing Automation

    +

    Send smarter, faster email campaigns with marketing automation from dotmailer, powered by + your Magento store's live data.

    Release notes and additional details can be found at Magento DevDocs. -

    ","

    Magento 2.2.2 offers an exciting set of features and enhancements, including:

    +

    ","

    Magento 2.2.2 offers advanced new features, including:


    Advanced Reporting

    Gain valuable insights through a dynamic suite of product, order, and customer reports, powered by Magento Business Intelligence.

    -
    -

    Developer Experience

    -

    We've improved the entire development lifecycle - installation, development, and maintenance - - while ensuring Magento's commitment to quality.

    +
    +

    Instant Purchase

    +

    Simplify ordering and boost conversion rates by allowing your customers to use stored + payment and shipping information to skip tedious checkout steps.

    -
    -

    Business-to-Business (B2B) Magento Commerce only

    -

    Features to manage your complex company accounts, rapid reordering, new buyers' roles and - permissions, and more.

    +
    +

    Email Marketing Automation

    +

    Send smarter, faster email campaigns with marketing automation from dotmailer, powered by + your Magento store's live data.

    Release notes and additional details can be found at Magento DevDocs. @@ -46,7 +46,7 @@ provides you with a dynamic suite of reports with rich insights about the health of your business.


    As part of the Advanced Reporting service, we may also use your customer data for such purposes as benchmarking, improving our products and services, and providing you - with new and improved analytics.


    By using Magento 2.2, you agree to the Advanced + with new and improved analytics.


    By using Magento 2.2.2, you agree to the Advanced Reporting Privacy Policy and Terms of Service. You may opt out at any time from the Stores Configuration page.

    @@ -54,54 +54,59 @@ provides you with a dynamic suite of reports with rich insights about the health of your business.


    As part of the Advanced Reporting service, we may also use your customer data for such purposes as benchmarking, improving our products and services, and providing you - with new and improved analytics.


    By using Magento 2.2, you agree to the Advanced + with new and improved analytics.


    By using Magento 2.2.2, you agree to the Advanced Reporting Privacy Policy and Terms - of Service. You may opt out at any time from the Stores Configuration page.

    - " -"Developer Experience","Developer Experience" -"

    Magento's 2.2.2 release offers a set of improvements that were developed using increased - quality standards. The release includes these features, among others: + of Service. You may opt out at any time from the Stores Configuration page.

    " +"Instant Purchase","Instant Purchase" +"

    Now you can deliver an Amazon-like experience with a new, streamlined checkout option. + Logged-in customers can use previously-stored payment credentials and shipping information + to skip steps, making the process faster and easier, especially for mobile shoppers. Key + features include:

      -
    • GitHub Community Moderator Team
    • -
    • GitHub Community Videos
    • -
    • DevDocs Enhancements
    • -
    -

    Find the 2.2.2 details and future plans in the - Magento DevBlog. -

    ","

    Magento's 2.2.2 release offers a set of improvements that were developed using increased - quality standards. The release includes these features, among others: +

  • Configurable “Instant Purchase” button to place orders
  • +
  • Support for all payment solutions using Braintree Vault, including Braintree Credit + Card, Braintree PayPal, and PayPal Payflow Pro.
  • +
  • Shipping to the customer’s default address using the lowest cost, available shipping + method
  • +
  • Ability for developers to customize the Instant Purchase business logic to meet + merchant needs
  • + ","

    Now you can deliver an Amazon-like experience with a new, streamlined checkout option. + Logged-in customers can use previously-stored payment credentials and shipping information + to skip steps, making the process faster and easier, especially for mobile shoppers. Key + features include:

      -
    • GitHub Community Moderator Team
    • -
    • GitHub Community Videos
    • -
    • DevDocs Enhancements
    • -
    -

    Find the 2.2.2 details and future plans in the - Magento DevBlog. -

    " -"Business-to-Business (B2B) Magento Commerce only","Business-to-Business (B2B) Magento Commerce only" -"

    Magento Commerce 2.2.2 offers rich new functionality that empowers B2B merchants to transform - their online purchasing experience to achieve greater operational efficiency, improved customer - service, and sales growth. New capabilities include: +

  • Configurable “Instant Purchase” button to place orders
  • +
  • Support for all payment solutions using Braintree Vault, including Braintree Credit + Card, Braintree PayPal, and PayPal Payflow Pro.
  • +
  • Shipping to the customer’s default address using the lowest cost, available shipping + method
  • +
  • Ability for developers to customize the Instant Purchase business logic to meet + merchant needs
  • + " +"Email Marketing Automation","Email Marketing Automation" +"

    Unlock an unparalleled level of insight and control of your eCommerce marketing with + dotmailer Email Marketing Automation. Included with Magento 2.2.2 for easy set-up, dotmailer + ensures every customer’s journey is captured, segmented, and personalized, enabling you to + deliver customer-centric campaigns that beat your results over and over again. Benefits include:

      -
    • Company accounts with multiple tiers of buyers
    • -
    • Buyer roles and permissions
    • -
    • Custom catalogs and pricing
    • -
    • Quoting support
    • -
    • Rapid reorder experience
    • -
    • Payments on credit
    • -
    ","

    Magento Commerce 2.2.2 offers rich new functionality that empowers B2B merchants to transform - their online purchasing experience to achieve greater operational efficiency, improved customer - service, and sales growth. New capabilities include: +

  • No obligation 14-day trial.
  • +
  • Automation campaigns using your live Magento store data that drive revenue, + including abandoned cart, abandoned browse, product replenishment, and many more
  • +
  • Built-in solution for transactional emails.
  • +
  • Telephone support and advice from marketing experts included.
  • + ","

    Unlock an unparalleled level of insight and control of your eCommerce marketing with + dotmailer Email Marketing Automation. Included with Magento 2.2.2 for easy set-up, dotmailer + ensures every customer’s journey is captured, segmented, and personalized, enabling you to + deliver customer-centric campaigns that beat your results over and over again. Benefits include:

      -
    • Company accounts with multiple tiers of buyers
    • -
    • Buyer roles and permissions
    • -
    • Custom catalogs and pricing
    • -
    • Quoting support
    • -
    • Rapid reorder experience
    • -
    • Payments on credit
    • +
    • No obligation 14-day trial.
    • +
    • Automation campaigns using your live Magento store data that drive revenue, + including abandoned cart, abandoned browse, product replenishment, and many more
    • +
    • Built-in solution for transactional emails.
    • +
    • Telephone support and advice from marketing experts included.
    " diff --git a/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml b/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml index 708d366f455bf..1ddfde5cafb9c 100644 --- a/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml +++ b/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml @@ -43,7 +43,7 @@ true - + @@ -60,22 +60,22 @@ release-notification-text Magento 2.2.2 offers an exciting set of features and enhancements, including:

    +

    Magento 2.2.2 offers advanced new features, including:


    Advanced Reporting

    Gain valuable insights through a dynamic suite of product, order, and customer reports, powered by Magento Business Intelligence.

    -
    -

    Developer Experience

    -

    We've improved the entire development lifecycle - installation, development, and maintenance - - while ensuring Magento's commitment to quality.

    +
    +

    Instant Purchase

    +

    Simplify ordering and boost conversion rates by allowing your customers to use stored + payment and shipping information to skip tedious checkout steps.

    -
    -

    Business-to-Business (B2B) Magento Commerce only

    -

    Features to manage your complex company accounts, rapid reordering, new buyers' roles and - permissions, and more.

    +

    Release notes and additional details can be found at Magento DevDocs. @@ -139,7 +139,7 @@ provides you with a dynamic suite of reports with rich insights about the health of your business.


    As part of the Advanced Reporting service, we may also use your customer data for such purposes as benchmarking, improving our products and services, and providing you - with new and improved analytics.


    By using Magento 2.2, you agree to the Advanced + with new and improved analytics.


    By using Magento 2.2.2, you agree to the Advanced Reporting Privacy Policy and Terms of Service. You may opt out at any time from the Stores Configuration page.

    ]]> @@ -184,7 +184,7 @@ closeModal - ns = ${ $.ns }, index = developer_experience_modal + ns = ${ $.ns }, index = instant_purchase_modal openModal @@ -198,12 +198,12 @@ - + actionCancel - - + + @@ -220,17 +220,20 @@ release-notification-text Magento's 2.2.2 release offers a set of improvements that were developed using increased - quality standards. The release includes these features, among others: +

    Now you can deliver an Amazon-like experience with a new, streamlined checkout option. + Logged-in customers can use previously-stored payment credentials and shipping information + to skip steps, making the process faster and easier, especially for mobile shoppers. Key + features include:

      -
    • GitHub Community Moderator Team
    • -
    • GitHub Community Videos
    • -
    • DevDocs Enhancements
    • -
    -

    Find the 2.2.2 details and future plans in the - Magento DevBlog. -

    ]]> +
  • Configurable “Instant Purchase” button to place orders
  • +
  • Support for all payment solutions using Braintree Vault, including Braintree Credit + Card, Braintree PayPal, and PayPal Payflow Pro.
  • +
  • Shipping to the customer’s default address using the lowest cost, available shipping + method
  • +
  • Ability for developers to customize the Instant Purchase business logic to meet + merchant needs
  • + ]]>
    @@ -247,7 +250,7 @@ release-notification-button-back - ns = ${ $.ns }, index = developer_experience_modal + ns = ${ $.ns }, index = instant_purchase_modal closeModal @@ -268,11 +271,11 @@ release-notification-button-next - ns = ${ $.ns }, index = developer_experience_modal + ns = ${ $.ns }, index = instant_purchase_modal closeModal - ns = ${ $.ns }, index = b2b_modal + ns = ${ $.ns }, index = email_marketing_modal openModal @@ -286,13 +289,12 @@
    - + actionCancel - - + + @@ -309,17 +311,17 @@ release-notification-text Magento Commerce 2.2.2 offers rich new functionality that empowers B2B merchants to transform - their online purchasing experience to achieve greater operational efficiency, improved customer - service, and sales growth. New capabilities include: +

    Unlock an unparalleled level of insight and control of your eCommerce marketing with + dotmailer Email Marketing Automation. Included with Magento 2.2.2 for easy set-up, dotmailer + ensures every customer’s journey is captured, segmented, and personalized, enabling you to + deliver customer-centric campaigns that beat your results over and over again. Benefits include:

      -
    • Company accounts with multiple tiers of buyers
    • -
    • Buyer roles and permissions
    • -
    • Custom catalogs and pricing
    • -
    • Quoting support
    • -
    • Rapid reorder experience
    • -
    • Payments on credit
    • +
    • No obligation 14-day trial.
    • +
    • Automation campaigns using your live Magento store data that drive revenue, + including abandoned cart, abandoned browse, product replenishment, and many more.
    • +
    • Built-in solution for transactional emails.
    • +
    • Telephone support and advice from marketing experts included.
    ]]>
    @@ -337,11 +339,11 @@ release-notification-button-back - ns = ${ $.ns }, index = b2b_modal + ns = ${ $.ns }, index = email_marketing_modal closeModal - ns = ${ $.ns }, index = developer_experience_modal + ns = ${ $.ns }, index = instant_purchase_modal openModal @@ -358,7 +360,7 @@ release-notification-button-next - ns = ${ $.ns }, index = b2b_modal + ns = ${ $.ns }, index = email_marketing_modal closeReleaseNotes diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/AbstractCreate.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/AbstractCreate.php index 323c2df975a8a..b684fe8c62df2 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/AbstractCreate.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/AbstractCreate.php @@ -160,4 +160,22 @@ public function convertPrice($value, $format = true) ) : $this->priceCurrency->convert($value, $this->getStore()); } + + /** + * If item is quote or wishlist we need to get product from it. + * + * @param $item + * + * @return Product + */ + public function getProduct($item) + { + if ($item instanceof Product) { + $product = $item; + } else { + $product = $item->getProduct(); + } + + return $product; + } } diff --git a/app/code/Magento/Sales/Model/Order/Shipment.php b/app/code/Magento/Sales/Model/Order/Shipment.php index cb26fb611f219..64e20d5a69041 100644 --- a/app/code/Magento/Sales/Model/Order/Shipment.php +++ b/app/code/Magento/Sales/Model/Order/Shipment.php @@ -93,6 +93,11 @@ class Shipment extends AbstractModel implements EntityInterface, ShipmentInterfa */ protected $orderRepository; + /** + * @var \Magento\Sales\Model\ResourceModel\Order\Shipment\Track\Collection|null + */ + private $tracksCollection = null; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -331,16 +336,11 @@ public function addItem(\Magento\Sales\Model\Order\Shipment\Item $item) */ public function getTracksCollection() { - if (!$this->hasData(ShipmentInterface::TRACKS)) { - $this->setTracks($this->_trackCollectionFactory->create()->setShipmentFilter($this->getId())); - - if ($this->getId()) { - foreach ($this->getTracks() as $track) { - $track->setShipment($this); - } - } + if ($this->tracksCollection === null) { + $this->tracksCollection = $this->_trackCollectionFactory->create()->setShipmentFilter($this->getId()); } - return $this->getTracks(); + + return $this->tracksCollection; } /** diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Shipment/Relation.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Shipment/Relation.php index 5851b2d936139..9c8671d02c578 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Order/Shipment/Relation.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Shipment/Relation.php @@ -62,8 +62,8 @@ public function processRelation(\Magento\Framework\Model\AbstractModel $object) $this->shipmentItemResource->save($item); } } - if (null !== $object->getTracks()) { - foreach ($object->getTracks() as $track) { + if (null !== $object->getTracksCollection()) { + foreach ($object->getTracksCollection() as $track) { $track->setParentId($object->getId()); $this->shipmentTrackResource->save($track); } diff --git a/app/code/Magento/Sales/Test/Unit/Block/Adminhtml/Order/Create/AbstractCreateTest.php b/app/code/Magento/Sales/Test/Unit/Block/Adminhtml/Order/Create/AbstractCreateTest.php index 447fd7791ecbd..e010674ca354e 100644 --- a/app/code/Magento/Sales/Test/Unit/Block/Adminhtml/Order/Create/AbstractCreateTest.php +++ b/app/code/Magento/Sales/Test/Unit/Block/Adminhtml/Order/Create/AbstractCreateTest.php @@ -3,8 +3,10 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Sales\Test\Unit\Block\Adminhtml\Order\Create; +use Magento\Catalog\Model\Product; use Magento\Catalog\Pricing\Price\FinalPrice; class AbstractCreateTest extends \PHPUnit\Framework\TestCase @@ -67,4 +69,34 @@ public function testGetItemPrice() ->willReturn($resultPrice); $this->assertEquals($resultPrice, $this->model->getItemPrice($this->productMock)); } + + /** + * @param $item + * + * @dataProvider getProductDataProvider + */ + public function testGetProduct($item) + { + $product = $this->model->getProduct($item); + + self::assertInstanceOf(Product::class, $product); + } + + /** + * DataProvider for testGetProduct. + * + * @return array + */ + public function getProductDataProvider() + { + $productMock = $this->createMock(Product::class); + + $itemMock = $this->createMock(\Magento\Wishlist\Model\Item::class); + $itemMock->expects($this->once())->method('getProduct')->willReturn($productMock); + + return [ + [$productMock], + [$itemMock], + ]; + } } diff --git a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Shipment/RelationTest.php b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Shipment/RelationTest.php index 787e6f8e065d2..a7a615fb0f508 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Shipment/RelationTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Shipment/RelationTest.php @@ -86,7 +86,8 @@ protected function setUp() 'getId', 'getItems', 'getTracks', - 'getComments' + 'getComments', + 'getTracksCollection', ] ) ->getMock(); @@ -123,7 +124,7 @@ public function testProcessRelations() ->method('getComments') ->willReturn([$this->commentMock]); $this->shipmentMock->expects($this->exactly(2)) - ->method('getTracks') + ->method('getTracksCollection') ->willReturn([$this->trackMock]); $this->itemMock->expects($this->once()) ->method('setParentId') diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/create/sidebar/items.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/sidebar/items.phtml index 2ca2420934519..2dbf717f73439 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/sidebar/items.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/sidebar/items.phtml @@ -67,11 +67,7 @@ canDisplayPrice()): ?> - getDataId() == 'cart'): ?> - getItemPrice($_item->getProduct()) ?> - - getItemPrice($_item) ?> - + getItemPrice($block->getProduct($_item)) ?> diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Subselect.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Subselect.php index 108cc341253ae..1e8fbf43ec3bc 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Subselect.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Subselect.php @@ -136,6 +136,7 @@ public function asHtml() * * @param \Magento\Framework\Model\AbstractModel $model * @return bool + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function validate(\Magento\Framework\Model\AbstractModel $model) { @@ -145,8 +146,22 @@ public function validate(\Magento\Framework\Model\AbstractModel $model) $attr = $this->getAttribute(); $total = 0; foreach ($model->getQuote()->getAllVisibleItems() as $item) { - if (parent::validate($item)) { - $total += $item->getData($attr); + $hasValidChild = false; + $useChildrenTotal = ($item->getProductType() == \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE); + $childrenAttrTotal = 0; + $children = $item->getChildren(); + if (!empty($children)) { + foreach ($children as $child) { + if (parent::validate($child)) { + $hasValidChild = true; + if ($useChildrenTotal) { + $childrenAttrTotal += $child->getData($attr); + } + } + } + } + if ($hasValidChild || parent::validate($item)) { + $total += (($hasValidChild && $useChildrenTotal) ? $childrenAttrTotal : $item->getData($attr)); } } return $this->validateAttribute($total); diff --git a/app/code/Magento/Store/Block/Switcher.php b/app/code/Magento/Store/Block/Switcher.php index 074dcb0262040..3d8d46983b5aa 100644 --- a/app/code/Magento/Store/Block/Switcher.php +++ b/app/code/Magento/Store/Block/Switcher.php @@ -223,9 +223,12 @@ public function getStoreName() */ public function getTargetStorePostData(\Magento\Store\Model\Store $store, $data = []) { - $data[\Magento\Store\Api\StoreResolverInterface::PARAM_NAME] = $store->getCode(); + $data[\Magento\Store\Api\StoreResolverInterface::PARAM_NAME] + = $store->getCode(); + //We need to fromStore as true because it will enable proper URL + //rewriting during store switching. return $this->_postDataHelper->getPostData( - $store->getCurrentUrl(false), + $store->getCurrentUrl(true), $data ); } diff --git a/app/code/Magento/Store/Model/Store.php b/app/code/Magento/Store/Model/Store.php index aca8c4a0ba5ec..56751f2188411 100644 --- a/app/code/Magento/Store/Model/Store.php +++ b/app/code/Magento/Store/Model/Store.php @@ -1136,7 +1136,14 @@ public function isDefault() public function getCurrentUrl($fromStore = true) { $sidQueryParam = $this->_sidResolver->getSessionIdQueryParam($this->_getSession()); - $requestString = $this->_url->escape(ltrim($this->_request->getRequestString(), '/')); + /** @var string $requestString Request path without query parameters */ + $requestString = $this->_url->escape( + preg_replace( + '/\?.*?$/', + '', + ltrim($this->_request->getRequestString(), '/') + ) + ); $storeUrl = $this->getUrl('', ['_secure' => $this->_storeManager->getStore()->isCurrentlySecure()]); diff --git a/app/code/Magento/Store/Test/Unit/Block/SwitcherTest.php b/app/code/Magento/Store/Test/Unit/Block/SwitcherTest.php index 5f0ba6c0b42d3..8b4799d2b3437 100644 --- a/app/code/Magento/Store/Test/Unit/Block/SwitcherTest.php +++ b/app/code/Magento/Store/Test/Unit/Block/SwitcherTest.php @@ -53,7 +53,7 @@ public function testGetTargetStorePostData() $storeSwitchUrl = 'http://domain.com/stores/store/switch'; $store->expects($this->atLeastOnce()) ->method('getCurrentUrl') - ->with(false) + ->with(true) ->willReturn($storeSwitchUrl); $this->corePostDataHelper->expects($this->any()) ->method('getPostData') diff --git a/app/code/Magento/Store/Test/Unit/Model/StoreTest.php b/app/code/Magento/Store/Test/Unit/Model/StoreTest.php index aef54a47971ff..c05584c2d8bcb 100644 --- a/app/code/Magento/Store/Test/Unit/Model/StoreTest.php +++ b/app/code/Magento/Store/Test/Unit/Model/StoreTest.php @@ -370,10 +370,11 @@ public function testGetBaseUrlWrongType() * @param boolean $secure * @param string $url * @param string $expected + * @param bool|string $fromStore */ - public function testGetCurrentUrl($secure, $url, $expected) + public function testGetCurrentUrl($secure, $url, $expected, $fromStore) { - $defaultStore = $this->createPartialMock(\Magento\Store\Model\Store::class, [ + $defaultStore = $this->createPartialMock(Store::class, [ 'getId', 'isCurrentlySecure', '__wakeup' @@ -386,15 +387,31 @@ public function testGetCurrentUrl($secure, $url, $expected) $config = $this->getMockForAbstractClass(\Magento\Framework\App\Config\ReinitableConfigInterface::class); - $this->requestMock->expects($this->atLeastOnce())->method('getRequestString')->will($this->returnValue('')); + $requestString = preg_replace( + '/http(s?)\:\/\/[a-z0-9\-]+\//i', + '', + $url + ); + $this->requestMock + ->expects($this->atLeastOnce()) + ->method('getRequestString') + ->willReturn($requestString); $this->requestMock->expects($this->atLeastOnce())->method('getQueryValue')->will($this->returnValue([ 'SID' => 'sid' ])); $urlMock = $this->getMockForAbstractClass(\Magento\Framework\UrlInterface::class); - $urlMock->expects($this->atLeastOnce())->method('setScope')->will($this->returnSelf()); - $urlMock->expects($this->any())->method('getUrl') - ->will($this->returnValue($url)); + $urlMock + ->expects($this->atLeastOnce()) + ->method('setScope') + ->will($this->returnSelf()); + $urlMock->expects($this->any()) + ->method('getUrl') + ->will($this->returnValue(str_replace($requestString, '', $url))); + $urlMock + ->expects($this->atLeastOnce()) + ->method('escape') + ->willReturnArgument(0); $storeManager = $this->getMockForAbstractClass(\Magento\Store\Model\StoreManagerInterface::class); $storeManager->expects($this->any()) @@ -409,7 +426,7 @@ public function testGetCurrentUrl($secure, $url, $expected) $model->setStoreId(2); $model->setCode('scope_code'); - $this->assertEquals($expected, $model->getCurrentUrl(false)); + $this->assertEquals($expected, $model->getCurrentUrl($fromStore)); } /** @@ -418,9 +435,31 @@ public function testGetCurrentUrl($secure, $url, $expected) public function getCurrentUrlDataProvider() { return [ - [true, 'http://test/url', 'http://test/url?SID=sid&___store=scope_code'], - [true, 'http://test/url?SID=sid1&___store=scope', 'http://test/url?SID=sid&___store=scope_code'], - [false, 'https://test/url', 'https://test/url?SID=sid&___store=scope_code'] + [ + true, + 'http://test/url', + 'http://test/url?SID=sid&___store=scope_code', + false + ], + [ + true, + 'http://test/url?SID=sid1&___store=scope', + 'http://test/url?SID=sid&___store=scope_code', + false + ], + [ + false, + 'https://test/url', + 'https://test/url?SID=sid&___store=scope_code', + false + ], + [ + true, + 'http://test/u/u.2?__store=scope_code', + 'http://test/u/u.2?' + . 'SID=sid&___store=scope_code&___from_store=old-store', + 'old-store' + ] ]; } diff --git a/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php b/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php index aef8a25da2834..dfd3d6ce15f71 100644 --- a/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php +++ b/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php @@ -119,6 +119,12 @@ public function __construct( $configurableAttributeData, $data ); + + $this->addData( + [ + 'cache_lifetime' => isset($data['cache_lifetime']) ? $data['cache_lifetime'] : 3600 + ] + ); } /** diff --git a/app/code/Magento/Swatches/view/frontend/layout/checkout_cart_configure_type_configurable.xml b/app/code/Magento/Swatches/view/frontend/layout/checkout_cart_configure_type_configurable.xml index 593ae32374417..62d6e57221f82 100644 --- a/app/code/Magento/Swatches/view/frontend/layout/checkout_cart_configure_type_configurable.xml +++ b/app/code/Magento/Swatches/view/frontend/layout/checkout_cart_configure_type_configurable.xml @@ -6,7 +6,13 @@ */ --> - - - + + + + + false + + + + diff --git a/app/code/Magento/Swatches/view/frontend/layout/wishlist_index_configure_type_configurable.xml b/app/code/Magento/Swatches/view/frontend/layout/wishlist_index_configure_type_configurable.xml index 28bf7baac0a36..9f47b4386c742 100644 --- a/app/code/Magento/Swatches/view/frontend/layout/wishlist_index_configure_type_configurable.xml +++ b/app/code/Magento/Swatches/view/frontend/layout/wishlist_index_configure_type_configurable.xml @@ -12,7 +12,11 @@ - + + + false + + diff --git a/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml b/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml index 7ecd6558ef6ea..75a39a0e4e270 100644 --- a/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml +++ b/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ ?> - +