Skip to content

Commit 8564170

Browse files
committed
Merge remote-tracking branch 'mainline/2.2-develop' into MAGETWO-86780
2 parents c50f2df + 5e4bb2b commit 8564170

File tree

186 files changed

+5584
-4236
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

186 files changed

+5584
-4236
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
Welcome to Magento 2 installation! We're glad you chose to install Magento 2, a cutting edge, feature-rich eCommerce solution that gets results.
66

77
## Magento system requirements
8-
[Magento system requirements](http://devdocs.magento.com/magento-system-requirements.html)
8+
[Magento system requirements](http://devdocs.magento.com/guides/v2.2/install-gde/system-requirements2.html)
99

1010
## Install Magento
1111
To install Magento, see either:
1212

1313
* [Magento DevBox](https://magento.com/tech-resources/download), the easiest way to get started with Magento.
14-
* [Installation guide](http://devdocs.magento.com/guides/v2.0/install-gde/bk-install-guide.html)
14+
* [Installation guide](http://devdocs.magento.com/guides/v2.2/install-gde/bk-install-guide.html)
1515

1616
<h2>Contributing to the Magento 2 code base</h2>
1717
Contributions can take the form of new components or features, changes to existing features, tests, documentation (such as developer guides, user guides, examples, or specifications), bug fixes, optimizations, or just good suggestions.
@@ -22,8 +22,8 @@ To learn about issues, click [here][2]. To open an issue, click [here][3].
2222

2323
To suggest documentation improvements, click [here][4].
2424

25-
[1]: <http://devdocs.magento.com/guides/v2.0/contributor-guide/contributing.html>
26-
[2]: <http://devdocs.magento.com/guides/v2.0/contributor-guide/contributing.html#report>
25+
[1]: <http://devdocs.magento.com/guides/v2.2/contributor-guide/contributing.html>
26+
[2]: <http://devdocs.magento.com/guides/v2.2/contributor-guide/contributing.html#report>
2727
[3]: <https://github.com/magento/magento2/issues>
2828
[4]: <http://devdocs.magento.com>
2929

app/code/Magento/Backup/Model/Db.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ public function createBackup(\Magento\Framework\Backup\Db\BackupInterface $backu
154154

155155
if ($tableStatus->getDataLength() > self::BUFFER_LENGTH) {
156156
if ($tableStatus->getAvgRowLength() < self::BUFFER_LENGTH) {
157-
$limit = floor(self::BUFFER_LENGTH / $tableStatus->getAvgRowLength());
157+
$limit = floor(self::BUFFER_LENGTH / max($tableStatus->getAvgRowLength(), 1));
158158
$multiRowsLength = ceil($tableStatus->getRows() / $limit);
159159
} else {
160160
$limit = 1;

app/code/Magento/Bundle/Model/Product/CopyConstructor/Bundle.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,17 @@ public function build(Product $product, Product $duplicate)
2727
$bundleOptions = $product->getExtensionAttributes()->getBundleProductOptions() ?: [];
2828
$duplicatedBundleOptions = [];
2929
foreach ($bundleOptions as $key => $bundleOption) {
30-
$duplicatedBundleOptions[$key] = clone $bundleOption;
30+
$duplicatedBundleOption = clone $bundleOption;
31+
/**
32+
* Set option and selection ids to 'null' in order to create new option(selection) for duplicated product,
33+
* but not modifying existing one, which led to lost of option(selection) in original product.
34+
*/
35+
$productLinks = $duplicatedBundleOption->getProductLinks() ?: [];
36+
foreach ($productLinks as $productLink) {
37+
$productLink->setSelectionId(null);
38+
}
39+
$duplicatedBundleOption->setOptionId(null);
40+
$duplicatedBundleOptions[$key] = $duplicatedBundleOption;
3141
}
3242
$duplicate->getExtensionAttributes()->setBundleProductOptions($duplicatedBundleOptions);
3343
}

app/code/Magento/Bundle/Test/Unit/Model/Product/CopyConstructor/BundleTest.php

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
namespace Magento\Bundle\Test\Unit\Model\Product\CopyConstructor;
77

88
use Magento\Bundle\Api\Data\BundleOptionInterface;
9+
use Magento\Bundle\Model\Link;
910
use Magento\Bundle\Model\Product\CopyConstructor\Bundle;
1011
use Magento\Catalog\Api\Data\ProductExtensionInterface;
1112
use Magento\Catalog\Model\Product;
@@ -45,6 +46,7 @@ public function testBuildNegative()
4546
*/
4647
public function testBuildPositive()
4748
{
49+
/** @var Product|\PHPUnit_Framework_MockObject_MockObject $product */
4850
$product = $this->getMockBuilder(Product::class)
4951
->disableOriginalConstructor()
5052
->getMock();
@@ -60,18 +62,42 @@ public function testBuildPositive()
6062
->method('getExtensionAttributes')
6163
->willReturn($extensionAttributesProduct);
6264

65+
$productLink = $this->getMockBuilder(Link::class)
66+
->setMethods(['setSelectionId'])
67+
->disableOriginalConstructor()
68+
->getMock();
69+
$productLink->expects($this->exactly(2))
70+
->method('setSelectionId')
71+
->with($this->identicalTo(null));
72+
$firstOption = $this->getMockBuilder(BundleOptionInterface::class)
73+
->setMethods(['getProductLinks'])
74+
->disableOriginalConstructor()
75+
->getMockForAbstractClass();
76+
$firstOption->expects($this->once())
77+
->method('getProductLinks')
78+
->willReturn([$productLink]);
79+
$firstOption->expects($this->once())
80+
->method('setOptionId')
81+
->with($this->identicalTo(null));
82+
$secondOption = $this->getMockBuilder(BundleOptionInterface::class)
83+
->setMethods(['getProductLinks'])
84+
->disableOriginalConstructor()
85+
->getMockForAbstractClass();
86+
$secondOption->expects($this->once())
87+
->method('getProductLinks')
88+
->willReturn([$productLink]);
89+
$secondOption->expects($this->once())
90+
->method('setOptionId')
91+
->with($this->identicalTo(null));
6392
$bundleOptions = [
64-
$this->getMockBuilder(BundleOptionInterface::class)
65-
->disableOriginalConstructor()
66-
->getMockForAbstractClass(),
67-
$this->getMockBuilder(BundleOptionInterface::class)
68-
->disableOriginalConstructor()
69-
->getMockForAbstractClass()
93+
$firstOption,
94+
$secondOption
7095
];
7196
$extensionAttributesProduct->expects($this->once())
7297
->method('getBundleProductOptions')
7398
->willReturn($bundleOptions);
7499

100+
/** @var Product|\PHPUnit_Framework_MockObject_MockObject $duplicate */
75101
$duplicate = $this->getMockBuilder(Product::class)
76102
->disableOriginalConstructor()
77103
->getMock();

app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php

Lines changed: 128 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
use Magento\CatalogImportExport\Model\Export\RowCustomizerInterface;
1010
use Magento\CatalogImportExport\Model\Import\Product as ImportProductModel;
1111
use Magento\Bundle\Model\ResourceModel\Selection\Collection as SelectionCollection;
12-
use Magento\ImportExport\Controller\Adminhtml\Import;
1312
use Magento\ImportExport\Model\Import as ImportModel;
1413
use \Magento\Catalog\Model\Product\Type\AbstractType;
14+
use \Magento\Framework\App\ObjectManager;
15+
use \Magento\Store\Model\StoreManagerInterface;
1516

1617
/**
1718
* Class RowCustomizer
@@ -105,6 +106,35 @@ class RowCustomizer implements RowCustomizerInterface
105106
AbstractType::SHIPMENT_SEPARATELY => 'separately',
106107
];
107108

109+
/**
110+
* @var \Magento\Bundle\Model\ResourceModel\Option\Collection[]
111+
*/
112+
private $optionCollections = [];
113+
114+
/**
115+
* @var array
116+
*/
117+
private $storeIdToCode = [];
118+
119+
/**
120+
* @var string
121+
*/
122+
private $optionCollectionCacheKey = '_cache_instance_options_collection';
123+
124+
/**
125+
* @var StoreManagerInterface
126+
*/
127+
private $storeManager;
128+
129+
/**
130+
* @param StoreManagerInterface $storeManager
131+
* @throws \RuntimeException
132+
*/
133+
public function __construct(StoreManagerInterface $storeManager)
134+
{
135+
$this->storeManager = $storeManager;
136+
}
137+
108138
/**
109139
* Retrieve list of bundle specific columns
110140
* @return array
@@ -207,15 +237,13 @@ protected function populateBundleData($collection)
207237
*/
208238
protected function getFormattedBundleOptionValues($product)
209239
{
210-
/** @var \Magento\Bundle\Model\ResourceModel\Option\Collection $optionsCollection */
211-
$optionsCollection = $product->getTypeInstance()
212-
->getOptionsCollection($product)
213-
->setOrder('position', Collection::SORT_ORDER_ASC);
214-
240+
$optionCollections = $this->getProductOptionCollections($product);
215241
$bundleData = '';
216-
foreach ($optionsCollection as $option) {
242+
$optionTitles = $this->getBundleOptionTitles($product);
243+
foreach ($optionCollections->getItems() as $option) {
244+
$optionValues = $this->getFormattedOptionValues($option, $optionTitles);
217245
$bundleData .= $this->getFormattedBundleSelections(
218-
$this->getFormattedOptionValues($option),
246+
$optionValues,
219247
$product->getTypeInstance()
220248
->getSelectionsCollection([$option->getId()], $product)
221249
->setOrder('position', Collection::SORT_ORDER_ASC)
@@ -266,16 +294,23 @@ function ($value, $key) {
266294
* Retrieve option value of bundle product
267295
*
268296
* @param \Magento\Bundle\Model\Option $option
297+
* @param string[] $optionTitles
269298
* @return string
270299
*/
271-
protected function getFormattedOptionValues($option)
300+
protected function getFormattedOptionValues($option, $optionTitles = [])
272301
{
273-
return 'name' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR
274-
. $option->getTitle() . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
275-
. 'type' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR
276-
. $option->getType() . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
277-
. 'required' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR
278-
. $option->getRequired();
302+
$names = implode(ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, array_map(
303+
function ($title, $storeName) {
304+
return $storeName . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR . $title;
305+
},
306+
$optionTitles[$option->getOptionId()],
307+
array_keys($optionTitles[$option->getOptionId()])
308+
));
309+
return $names . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
310+
. 'type' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR
311+
. $option->getType() . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
312+
. 'required' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR
313+
. $option->getRequired();
279314
}
280315

281316
/**
@@ -380,4 +415,82 @@ private function parseAdditionalAttributes($additionalAttributes)
380415
}
381416
return $preparedAttributes;
382417
}
418+
419+
/**
420+
* Get product options titles.
421+
*
422+
* Values for all store views (default) should be specified with 'name' key.
423+
* If user want to specify value or change existing for non default store views it should be specified with
424+
* 'name_' prefix and needed store view suffix.
425+
*
426+
* For example:
427+
* - 'name=All store views name' for all store views
428+
* - 'name_specific_store=Specific store name' for store view with 'specific_store' store code
429+
*
430+
* @param \Magento\Catalog\Model\Product $product
431+
* @return array
432+
*/
433+
private function getBundleOptionTitles(\Magento\Catalog\Model\Product $product): array
434+
{
435+
$optionCollections = $this->getProductOptionCollections($product);
436+
$optionsTitles = [];
437+
/** @var \Magento\Bundle\Model\Option $option */
438+
foreach ($optionCollections->getItems() as $option) {
439+
$optionsTitles[$option->getId()]['name'] = $option->getTitle();
440+
}
441+
$storeIds = $product->getStoreIds();
442+
if (count($storeIds) > 1) {
443+
foreach ($storeIds as $storeId) {
444+
$optionCollections = $this->getProductOptionCollections($product, $storeId);
445+
/** @var \Magento\Bundle\Model\Option $option */
446+
foreach ($optionCollections->getItems() as $option) {
447+
$optionTitle = $option->getTitle();
448+
if ($optionsTitles[$option->getId()]['name'] != $optionTitle) {
449+
$optionsTitles[$option->getId()]['name_' . $this->getStoreCodeById($storeId)] = $optionTitle;
450+
}
451+
}
452+
}
453+
}
454+
return $optionsTitles;
455+
}
456+
457+
/**
458+
* Get product options collection by provided product model.
459+
*
460+
* Set given store id to the product if it was defined (default store id will be set if was not).
461+
*
462+
* @param \Magento\Catalog\Model\Product $product $product
463+
* @param int $storeId
464+
* @return \Magento\Bundle\Model\ResourceModel\Option\Collection
465+
*/
466+
private function getProductOptionCollections(
467+
\Magento\Catalog\Model\Product $product,
468+
$storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID
469+
): \Magento\Bundle\Model\ResourceModel\Option\Collection {
470+
$productSku = $product->getSku();
471+
if (!isset($this->optionCollections[$productSku][$storeId])) {
472+
$product->unsetData($this->optionCollectionCacheKey);
473+
$product->setStoreId($storeId);
474+
$this->optionCollections[$productSku][$storeId] = $product->getTypeInstance()
475+
->getOptionsCollection($product)
476+
->setOrder('position', Collection::SORT_ORDER_ASC);
477+
}
478+
return $this->optionCollections[$productSku][$storeId];
479+
}
480+
481+
/**
482+
* Retrieve store code by it's ID.
483+
*
484+
* Collect store id in $storeIdToCode[] private variable if it was not initialized earlier.
485+
*
486+
* @param int $storeId
487+
* @return string
488+
*/
489+
private function getStoreCodeById($storeId): string
490+
{
491+
if (!isset($this->storeIdToCode[$storeId])) {
492+
$this->storeIdToCode[$storeId] = $this->storeManager->getStore($storeId)->getCode();
493+
}
494+
return $this->storeIdToCode[$storeId];
495+
}
383496
}

0 commit comments

Comments
 (0)