Skip to content

Commit 5ff7a60

Browse files
authored
Merge branch '2.4-develop' into Hammer_Platform_Health_Scope_25082023
2 parents 94f6302 + 7f35b87 commit 5ff7a60

File tree

36 files changed

+1862
-138
lines changed

36 files changed

+1862
-138
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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\CatalogGraphQl\Model\Resolver\Cache\Product\MediaGallery;
9+
10+
use Magento\Catalog\Model\Product;
11+
use Magento\Framework\EntityManager\HydratorPool;
12+
use Magento\Framework\EntityManager\TypeResolver;
13+
use Magento\GraphQlResolverCache\Model\Resolver\Result\DehydratorInterface;
14+
15+
/**
16+
* MediaGallery resolver data dehydrator to create snapshot data necessary to restore model.
17+
*/
18+
class ProductModelDehydrator implements DehydratorInterface
19+
{
20+
/**
21+
* @var TypeResolver
22+
*/
23+
private TypeResolver $typeResolver;
24+
25+
/**
26+
* @var HydratorPool
27+
*/
28+
private HydratorPool $hydratorPool;
29+
30+
/**
31+
* @param HydratorPool $hydratorPool
32+
* @param TypeResolver $typeResolver
33+
*/
34+
public function __construct(
35+
HydratorPool $hydratorPool,
36+
TypeResolver $typeResolver
37+
) {
38+
$this->typeResolver = $typeResolver;
39+
$this->hydratorPool = $hydratorPool;
40+
}
41+
42+
/**
43+
* @inheritdoc
44+
*/
45+
public function dehydrate(array &$resolvedValue): void
46+
{
47+
if (count($resolvedValue) > 0) {
48+
$firstKey = array_key_first($resolvedValue);
49+
$this->dehydrateMediaGalleryEntity($resolvedValue[$firstKey]);
50+
foreach ($resolvedValue as $key => &$value) {
51+
if ($key !== $firstKey) {
52+
unset($value['model']);
53+
}
54+
}
55+
}
56+
}
57+
58+
/**
59+
* Dehydrate the resolved value of a media gallery entity.
60+
*
61+
* @param array $mediaGalleryEntityResolvedValue
62+
* @return void
63+
* @throws \Exception
64+
*/
65+
private function dehydrateMediaGalleryEntity(array &$mediaGalleryEntityResolvedValue): void
66+
{
67+
if (array_key_exists('model', $mediaGalleryEntityResolvedValue)
68+
&& $mediaGalleryEntityResolvedValue['model'] instanceof Product) {
69+
/** @var Product $model */
70+
$model = $mediaGalleryEntityResolvedValue['model'];
71+
$entityType = $this->typeResolver->resolve($model);
72+
$mediaGalleryEntityResolvedValue['model_info']['model_data'] = $this->hydratorPool->getHydrator($entityType)
73+
->extract($model);
74+
$mediaGalleryEntityResolvedValue['model_info']['model_entity_type'] = $entityType;
75+
$mediaGalleryEntityResolvedValue['model_info']['model_id'] = $model->getId();
76+
unset($mediaGalleryEntityResolvedValue['model']);
77+
}
78+
}
79+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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\CatalogGraphQl\Model\Resolver\Cache\Product\MediaGallery;
9+
10+
use Magento\Catalog\Model\Product;
11+
use Magento\Catalog\Model\ProductFactory;
12+
use Magento\Framework\EntityManager\HydratorPool;
13+
use Magento\GraphQlResolverCache\Model\Resolver\Result\HydratorInterface;
14+
use Magento\GraphQlResolverCache\Model\Resolver\Result\PrehydratorInterface;
15+
16+
/**
17+
* Product resolver data hydrator to rehydrate propagated model.
18+
*/
19+
class ProductModelHydrator implements HydratorInterface, PrehydratorInterface
20+
{
21+
/**
22+
* @var ProductFactory
23+
*/
24+
private ProductFactory $productFactory;
25+
26+
/**
27+
* @var Product[]
28+
*/
29+
private array $products = [];
30+
31+
/**
32+
* @var HydratorPool
33+
*/
34+
private HydratorPool $hydratorPool;
35+
36+
/**
37+
* @param ProductFactory $productFactory
38+
* @param HydratorPool $hydratorPool
39+
*/
40+
public function __construct(
41+
ProductFactory $productFactory,
42+
HydratorPool $hydratorPool
43+
) {
44+
$this->hydratorPool = $hydratorPool;
45+
$this->productFactory = $productFactory;
46+
}
47+
48+
/**
49+
* @inheritdoc
50+
*/
51+
public function hydrate(array &$resolverData): void
52+
{
53+
if (array_key_exists('model_info', $resolverData)) {
54+
if (isset($this->products[$resolverData['model_info']['model_id']])) {
55+
$resolverData['model'] = $this->products[$resolverData['model_info']['model_id']];
56+
} else {
57+
$hydrator = $this->hydratorPool->getHydrator($resolverData['model_info']['model_entity_type']);
58+
$model = $this->productFactory->create();
59+
$hydrator->hydrate($model, $resolverData['model_info']['model_data']);
60+
$this->products[$resolverData['model_info']['model_id']] = $model;
61+
$resolverData['model'] = $this->products[$resolverData['model_info']['model_id']];
62+
}
63+
unset($resolverData['model_info']);
64+
}
65+
}
66+
67+
/**
68+
* @inheritDoc
69+
*/
70+
public function prehydrate(array &$resolverData): void
71+
{
72+
$firstKey = array_key_first($resolverData);
73+
foreach ($resolverData as &$value) {
74+
$value['model_info'] = &$resolverData[$firstKey]['model_info'];
75+
}
76+
}
77+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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\CatalogGraphQl\Model\Resolver\Cache\Product\MediaGallery;
9+
10+
use Magento\Catalog\Model\Product;
11+
use Magento\GraphQlResolverCache\Model\Resolver\Result\Cache\IdentityInterface;
12+
13+
/**
14+
* Identity for resolved media gallery for resolver cache type
15+
*/
16+
class ResolverCacheIdentity implements IdentityInterface
17+
{
18+
/**
19+
* @var string
20+
*/
21+
public const CACHE_TAG = 'gql_media_gallery';
22+
23+
/**
24+
* @inheritDoc
25+
*/
26+
public function getIdentities($resolvedData, ?array $parentResolvedData = null): array
27+
{
28+
if (empty($resolvedData)) {
29+
return [];
30+
}
31+
/** @var Product $mediaGalleryEntryProduct */
32+
$mediaGalleryEntryProduct = array_pop($resolvedData)['model'];
33+
return [
34+
sprintf('%s_%s', self::CACHE_TAG, $mediaGalleryEntryProduct->getId())
35+
];
36+
}
37+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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\CatalogGraphQl\Model\Resolver\Cache\Product\MediaGallery;
9+
10+
use Magento\Catalog\Model\Product;
11+
use Magento\CatalogGraphQl\Model\Resolver\Product\MediaGallery\ChangeDetector;
12+
use Magento\Framework\App\Cache\Tag\StrategyInterface;
13+
14+
class TagsStrategy implements StrategyInterface
15+
{
16+
/**
17+
* @var ChangeDetector
18+
*/
19+
private $mediaGalleryChangeDetector;
20+
21+
/**
22+
* @param ChangeDetector $mediaGalleryChangeDetector
23+
*/
24+
public function __construct(ChangeDetector $mediaGalleryChangeDetector)
25+
{
26+
$this->mediaGalleryChangeDetector = $mediaGalleryChangeDetector;
27+
}
28+
29+
/**
30+
* @inheritDoc
31+
*/
32+
public function getTags($object)
33+
{
34+
if ($object instanceof Product &&
35+
!$object->isObjectNew() &&
36+
$this->mediaGalleryChangeDetector->isChanged($object)
37+
) {
38+
return [
39+
sprintf('%s_%s', ResolverCacheIdentity::CACHE_TAG, $object->getId())
40+
];
41+
}
42+
43+
return [];
44+
}
45+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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\CatalogGraphQl\Model\Resolver\CacheKey\FactorProvider;
9+
10+
use Magento\Framework\Model\AbstractModel;
11+
use Magento\GraphQl\Model\Query\ContextInterface;
12+
use Magento\GraphQlResolverCache\Model\Resolver\Result\CacheKey\ParentValueFactorProviderInterface;
13+
14+
/**
15+
* Provides product id from the model object in the parent resolved value
16+
* as a factor to use in the cache key for resolver cache
17+
*/
18+
class ParentProductEntityId implements ParentValueFactorProviderInterface
19+
{
20+
/**
21+
* Factor name.
22+
*/
23+
private const NAME = "PARENT_ENTITY_PRODUCT_ID";
24+
25+
/**
26+
* @inheritdoc
27+
*/
28+
public function getFactorName(): string
29+
{
30+
return static::NAME;
31+
}
32+
33+
/**
34+
* @inheritDoc
35+
*/
36+
public function getFactorValue(ContextInterface $context, array $parentValue): string
37+
{
38+
if (array_key_exists('model_info', $parentValue)
39+
&& array_key_exists('model_id', $parentValue['model_info'])) {
40+
return (string)$parentValue['model_info']['model_id'];
41+
} elseif (array_key_exists('model', $parentValue) && $parentValue['model'] instanceof AbstractModel) {
42+
return (string)$parentValue['model']->getId();
43+
}
44+
throw new \InvalidArgumentException(__CLASS__ . " factor provider requires parent value " .
45+
"to contain product model id or product model.");
46+
}
47+
48+
/**
49+
* @inheritDoc
50+
*/
51+
public function isRequiredOrigData(): bool
52+
{
53+
return false;
54+
}
55+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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\CatalogGraphQl\Model\Resolver\Product\MediaGallery;
9+
10+
use Magento\Catalog\Model\Product;
11+
use Magento\Framework\Serialize\SerializerInterface;
12+
13+
class ChangeDetector
14+
{
15+
/**
16+
* @var SerializerInterface
17+
*/
18+
private $serializer;
19+
20+
/**
21+
* @param SerializerInterface $serializer
22+
*/
23+
public function __construct(
24+
SerializerInterface $serializer
25+
) {
26+
$this->serializer = $serializer;
27+
}
28+
29+
/**
30+
* Check if the media gallery of the given product is changed
31+
*
32+
* @param Product $product
33+
* @return bool
34+
*/
35+
public function isChanged(Product $product): bool
36+
{
37+
if ($product->isDeleted()) {
38+
return true;
39+
}
40+
41+
if (!$product->hasDataChanges()) {
42+
return false;
43+
}
44+
45+
$mediaGalleryImages = $product->getMediaGallery('images') ?? [];
46+
47+
$origMediaGalleryImages = $product->getOrigData('media_gallery')['images'] ?? [];
48+
49+
$origMediaGalleryImageKeys = array_keys($origMediaGalleryImages);
50+
$mediaGalleryImageKeys = array_keys($mediaGalleryImages);
51+
52+
if ($origMediaGalleryImageKeys !== $mediaGalleryImageKeys) {
53+
return true;
54+
}
55+
56+
// remove keys from original array that are not in new array; some keys are omitted from the new array on save
57+
foreach ($mediaGalleryImages as $imageKey => $mediaGalleryImage) {
58+
$origMediaGalleryImages[$imageKey] = array_intersect_key(
59+
$origMediaGalleryImages[$imageKey],
60+
$mediaGalleryImage
61+
);
62+
63+
// client UI converts null values to empty string due to behavior of HTML encoding;
64+
// match this behavior before performing comparison
65+
foreach ($origMediaGalleryImages[$imageKey] as $key => &$value) {
66+
if ($value === null) {
67+
$value = '';
68+
}
69+
70+
if ($mediaGalleryImages[$imageKey][$key] === null) {
71+
$mediaGalleryImages[$imageKey][$key] = '';
72+
}
73+
}
74+
}
75+
76+
$mediaGalleryImagesSerializedString = $this->serializer->serialize($mediaGalleryImages);
77+
$origMediaGalleryImagesSerializedString = $this->serializer->serialize($origMediaGalleryImages);
78+
79+
return $origMediaGalleryImagesSerializedString != $mediaGalleryImagesSerializedString;
80+
}
81+
}

0 commit comments

Comments
 (0)