Skip to content

Commit 2ac70ca

Browse files
committed
MC-42194: Product videos "disappear" on scheduled updates
- Fix missing product videos information in new scheduled update
1 parent ed2ffad commit 2ac70ca

File tree

7 files changed

+441
-2
lines changed

7 files changed

+441
-2
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model\Product\Gallery;
9+
10+
use Magento\Catalog\Api\Data\ProductAttributeInterface;
11+
use Magento\Catalog\Api\Data\ProductInterface;
12+
use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
13+
use Magento\Catalog\Model\Product;
14+
use Magento\Catalog\Model\ResourceModel\Product\Gallery;
15+
use Magento\Eav\Model\ResourceModel\AttributeValue;
16+
use Magento\Framework\EntityManager\EntityMetadata;
17+
use Magento\Framework\EntityManager\MetadataPool;
18+
use Magento\Framework\EntityManager\Operation\ExtensionInterface;
19+
use Magento\Framework\Serialize\Serializer\Json;
20+
21+
/**
22+
* Copy gallery data from one product to another
23+
*/
24+
class CopyHandler implements ExtensionInterface
25+
{
26+
/**
27+
* @var EntityMetadata
28+
*/
29+
private $metadata;
30+
31+
/**
32+
* @var Gallery
33+
*/
34+
private $galleryResourceModel;
35+
36+
/**
37+
* @var ProductAttributeRepositoryInterface
38+
*/
39+
private $attributeRepository;
40+
41+
/**
42+
* @var AttributeValue
43+
*/
44+
private $attributeValue;
45+
46+
/**
47+
* @var Json
48+
*/
49+
private $json;
50+
51+
/**
52+
* @var ProductAttributeInterface
53+
*/
54+
private $attribute;
55+
56+
/**
57+
* @param MetadataPool $metadataPool
58+
* @param Gallery $galleryResourceModel
59+
* @param ProductAttributeRepositoryInterface $attributeRepository
60+
* @param AttributeValue $attributeValue
61+
* @param Json $json
62+
*/
63+
public function __construct(
64+
MetadataPool $metadataPool,
65+
Gallery $galleryResourceModel,
66+
ProductAttributeRepositoryInterface $attributeRepository,
67+
AttributeValue $attributeValue,
68+
Json $json
69+
) {
70+
$this->metadata = $metadataPool->getMetadata(ProductInterface::class);
71+
$this->galleryResourceModel = $galleryResourceModel;
72+
$this->attributeRepository = $attributeRepository;
73+
$this->attributeValue = $attributeValue;
74+
$this->json = $json;
75+
}
76+
77+
/**
78+
* Copy gallery data from one product to another
79+
*
80+
* @param Product $product
81+
* @param array $arguments
82+
* @return void
83+
*/
84+
public function execute($product, $arguments = []): void
85+
{
86+
$fromId = (int) $arguments['original_link_id'];
87+
$toId = $product->getData($this->metadata->getLinkField());
88+
$attributeId = $this->getAttribute()->getAttributeId();
89+
$valueIdMap = $this->galleryResourceModel->duplicate($attributeId, [], $fromId, $toId);
90+
$gallery = $this->getMediaGalleryCollection($product);
91+
92+
if (!empty($gallery['images'])) {
93+
$images = [];
94+
foreach ($gallery['images'] as $key => $image) {
95+
$valueId = $image['value_id'] ?? null;
96+
$newKey = $key;
97+
if ($valueId !== null) {
98+
$newValueId = $valueId;
99+
if (isset($valueIdMap[$valueId])) {
100+
$newValueId = $valueIdMap[$valueId];
101+
}
102+
if (((int) $valueId) === $key) {
103+
$newKey = $newValueId;
104+
}
105+
$image['value_id'] = $newValueId;
106+
}
107+
$images[$newKey] = $image;
108+
}
109+
$gallery['images'] = $images;
110+
$attrCode = $this->getAttribute()->getAttributeCode();
111+
$product->setData($attrCode, $gallery);
112+
}
113+
114+
//Copy media attribute values from one product to another
115+
if (isset($arguments['media_attribute_codes'])) {
116+
$values = $this->attributeValue->getValues(
117+
ProductInterface::class,
118+
$fromId,
119+
$arguments['media_attribute_codes']
120+
);
121+
if ($values) {
122+
foreach (array_keys($values) as $key) {
123+
$values[$key][$this->metadata->getLinkField()] = $product->getData($this->metadata->getLinkField());
124+
unset($values[$key]['value_id']);
125+
}
126+
$this->attributeValue->insertValues(
127+
ProductInterface::class,
128+
$values
129+
);
130+
}
131+
}
132+
}
133+
134+
/**
135+
* Get product media gallery collection
136+
*
137+
* @param Product $product
138+
* @return array
139+
*/
140+
private function getMediaGalleryCollection(Product $product): array
141+
{
142+
$attrCode = $this->getAttribute()->getAttributeCode();
143+
$value = $product->getData($attrCode);
144+
145+
if (is_array($value) && isset($value['images'])) {
146+
if (!is_array($value['images']) && strlen($value['images']) > 0) {
147+
$value['images'] = $this->json->unserialize($value['images']);
148+
}
149+
150+
if (!is_array($value['images'])) {
151+
$value['images'] = [];
152+
}
153+
}
154+
155+
return $value;
156+
}
157+
158+
/**
159+
* Returns media gallery attribute instance
160+
*
161+
* @return ProductAttributeInterface
162+
*/
163+
private function getAttribute(): ProductAttributeInterface
164+
{
165+
if (!$this->attribute) {
166+
$this->attribute = $this->attributeRepository->get(
167+
ProductInterface::MEDIA_GALLERY
168+
);
169+
}
170+
171+
return $this->attribute;
172+
}
173+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model\Product\Gallery;
9+
10+
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\Catalog\Model\Product;
12+
use Magento\Catalog\Model\ResourceModel\Product\Gallery;
13+
use Magento\Eav\Model\ResourceModel\AttributeValue;
14+
use Magento\Framework\EntityManager\EntityMetadata;
15+
use Magento\Framework\EntityManager\MetadataPool;
16+
use Magento\Framework\EntityManager\Operation\ExtensionInterface;
17+
18+
/**
19+
* Delete all media gallery records for provided product
20+
*/
21+
class DeleteHandler implements ExtensionInterface
22+
{
23+
/**
24+
* @var EntityMetadata
25+
*/
26+
private $metadata;
27+
28+
/**
29+
* @var Gallery
30+
*/
31+
private $galleryResourceModel;
32+
33+
/**
34+
* @var AttributeValue
35+
*/
36+
private $attributeValue;
37+
38+
/**
39+
* @param MetadataPool $metadataPool
40+
* @param Gallery $galleryResourceModel
41+
* @param AttributeValue $attributeValue
42+
*/
43+
public function __construct(
44+
MetadataPool $metadataPool,
45+
Gallery $galleryResourceModel,
46+
AttributeValue $attributeValue
47+
) {
48+
$this->metadata = $metadataPool->getMetadata(ProductInterface::class);
49+
$this->galleryResourceModel = $galleryResourceModel;
50+
$this->attributeValue = $attributeValue;
51+
}
52+
53+
/**
54+
* Delete all media gallery records for provided product
55+
*
56+
* @param Product $product
57+
* @param array $arguments
58+
* @return void
59+
*/
60+
public function execute($product, $arguments = []): void
61+
{
62+
$valuesId = $this->getMediaGalleryValuesId($product);
63+
if ($valuesId) {
64+
$this->galleryResourceModel->deleteGallery($valuesId);
65+
}
66+
if (isset($arguments['media_attribute_codes'])) {
67+
$values = $this->attributeValue->getValues(
68+
ProductInterface::class,
69+
(int) $product->getData($this->metadata->getLinkField()),
70+
$arguments['media_attribute_codes']
71+
);
72+
if ($values) {
73+
$this->attributeValue->deleteValues(
74+
ProductInterface::class,
75+
$values
76+
);
77+
}
78+
}
79+
}
80+
81+
/**
82+
* Get product media gallery values IDs
83+
*
84+
* @param Product $product
85+
* @return array
86+
*/
87+
private function getMediaGalleryValuesId(Product $product): array
88+
{
89+
$connection = $this->galleryResourceModel->getConnection();
90+
$select = $connection->select()
91+
->from($this->galleryResourceModel->getTable(Gallery::GALLERY_VALUE_TO_ENTITY_TABLE))
92+
->where(
93+
$this->metadata->getLinkField() . '=?',
94+
$product->getData($this->metadata->getLinkField()),
95+
\Zend_Db::INT_TYPE
96+
);
97+
return $connection->fetchCol($select);
98+
}
99+
}

app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,9 @@ public function duplicate($attributeId, $newFiles, $originalProductId, $newProdu
452452
// Duplicate per store gallery values
453453
$select = $this->getConnection()->select()->from(
454454
$this->getTable(self::GALLERY_VALUE_TABLE)
455+
)->where(
456+
$linkField . ' = ?',
457+
$originalProductId
455458
)->where(
456459
'value_id IN(?)',
457460
array_keys($valueIdMap)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
11+
<actionGroup name="AdminProductAssertImageAltTextActionGroup">
12+
<annotations>
13+
<description>Assert product image alt text.</description>
14+
</annotations>
15+
<arguments>
16+
<argument name="image" defaultValue="ProductImage"/>
17+
<argument name="altText" defaultValue="{{ProductImage.title}}" type="string"/>
18+
</arguments>
19+
<conditionalClick selector="{{AdminProductImagesSection.productImagesToggle}}" dependentSelector="{{AdminProductImagesSection.imageFile(image.fileName)}}" visible="false" stepKey="expandImages"/>
20+
<waitForElementVisible selector="{{AdminProductImagesSection.imageFile(image.fileName)}}" stepKey="seeProductImageName"/>
21+
<click selector="{{AdminProductImagesSection.imageFile(image.fileName)}}" stepKey="clickProductImage"/>
22+
<waitForElementVisible selector="{{AdminProductImagesSection.altText}}" stepKey="seeAltTextSection"/>
23+
<grabValueFrom selector="{{AdminProductImagesSection.altText}}" stepKey="actualAltText"/>
24+
<assertEquals stepKey="assertAltText">
25+
<expectedResult type="string">{{altText}}</expectedResult>
26+
<actualResult type="variable">actualAltText</actualResult>
27+
</assertEquals>
28+
<click selector="{{AdminSlideOutDialogSection.closeButton}}" stepKey="clickCloseButton"/>
29+
</actionGroup>
30+
</actionGroups>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
11+
<actionGroup name="AdminProductChangeImageAltTextActionGroup">
12+
<annotations>
13+
<description>Change product image alt text.</description>
14+
</annotations>
15+
<arguments>
16+
<argument name="image" defaultValue="ProductImage"/>
17+
<argument name="altText" defaultValue="{{ProductImage.title}}" type="string"/>
18+
</arguments>
19+
<conditionalClick selector="{{AdminProductImagesSection.productImagesToggle}}" dependentSelector="{{AdminProductImagesSection.imageFile(image.fileName)}}" visible="false" stepKey="expandImages"/>
20+
<waitForElementVisible selector="{{AdminProductImagesSection.imageFile(image.fileName)}}" stepKey="seeProductImageName"/>
21+
<click selector="{{AdminProductImagesSection.imageFile(image.fileName)}}" stepKey="clickProductImage"/>
22+
<waitForElementVisible selector="{{AdminProductImagesSection.altText}}" stepKey="seeAltTextSection"/>
23+
<fillField selector="{{AdminProductImagesSection.altText}}" userInput="{{altText}}" stepKey="fillAltTextSection"/>
24+
<click selector="{{AdminSlideOutDialogSection.closeButton}}" stepKey="clickCloseButton"/>
25+
</actionGroup>
26+
</actionGroups>

0 commit comments

Comments
 (0)