1212use Magento \Catalog \Model \Product ;
1313use Magento \Catalog \Model \Product \Media \Config ;
1414use Magento \Catalog \Model \ResourceModel \Product \Gallery ;
15+ use Magento \Catalog \Model \ResourceModel \Product \MediaGalleryValue ;
16+ use Magento \Eav \Model \ResourceModel \AttributeValue ;
1517use Magento \Framework \App \Filesystem \DirectoryList ;
1618use Magento \Framework \App \ObjectManager ;
1719use Magento \Framework \EntityManager \MetadataPool ;
3032 * @api
3133 *
3234 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
35+ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
36+ * @SuppressWarnings(PHPMD.TooManyFields)
3337 * @since 101.0.0
3438 */
3539class CreateHandler implements ExtensionInterface
@@ -90,7 +94,7 @@ class CreateHandler implements ExtensionInterface
9094 /**
9195 * @var array
9296 */
93- private $ imagesGallery ;
97+ private $ mediaEavCache ;
9498
9599 /**
96100 * @var \Magento\Store\Model\StoreManagerInterface
@@ -102,6 +106,21 @@ class CreateHandler implements ExtensionInterface
102106 */
103107 private $ deleteValidator ;
104108
109+ /**
110+ * @var MediaGalleryValue
111+ */
112+ private $ mediaGalleryValue ;
113+
114+ /**
115+ * @var AttributeValue
116+ */
117+ private AttributeValue $ attributeValue ;
118+
119+ /**
120+ * @var \Magento\Eav\Model\Config
121+ */
122+ private $ eavConfig ;
123+
105124 /**
106125 * @var string[]
107126 */
@@ -121,7 +140,11 @@ class CreateHandler implements ExtensionInterface
121140 * @param Database $fileStorageDb
122141 * @param StoreManagerInterface|null $storeManager
123142 * @param DeleteValidator|null $deleteValidator
143+ * @param MediaGalleryValue|null $mediaGalleryValue
144+ * @param AttributeValue|null $attributeValue
145+ * @param \Magento\Eav\Model\Config|null $config
124146 * @throws FileSystemException
147+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
125148 */
126149 public function __construct (
127150 MetadataPool $ metadataPool ,
@@ -132,7 +155,10 @@ public function __construct(
132155 Filesystem $ filesystem ,
133156 Database $ fileStorageDb ,
134157 StoreManagerInterface $ storeManager = null ,
135- ?DeleteValidator $ deleteValidator = null
158+ ?DeleteValidator $ deleteValidator = null ,
159+ ?MediaGalleryValue $ mediaGalleryValue = null ,
160+ ?AttributeValue $ attributeValue = null ,
161+ ?\Magento \Eav \Model \Config $ config = null
136162 ) {
137163 $ this ->metadata = $ metadataPool ->getMetadata (\Magento \Catalog \Api \Data \ProductInterface::class);
138164 $ this ->attributeRepository = $ attributeRepository ;
@@ -143,6 +169,9 @@ public function __construct(
143169 $ this ->fileStorageDb = $ fileStorageDb ;
144170 $ this ->storeManager = $ storeManager ?: ObjectManager::getInstance ()->get (StoreManagerInterface::class);
145171 $ this ->deleteValidator = $ deleteValidator ?: ObjectManager::getInstance ()->get (DeleteValidator::class);
172+ $ this ->mediaGalleryValue = $ mediaGalleryValue ?? ObjectManager::getInstance ()->get (MediaGalleryValue::class);
173+ $ this ->attributeValue = $ attributeValue ?? ObjectManager::getInstance ()->get (AttributeValue::class);
174+ $ this ->eavConfig = $ config ?? ObjectManager::getInstance ()->get (\Magento \Eav \Model \Config::class);
146175 }
147176
148177 /**
@@ -159,6 +188,7 @@ public function __construct(
159188 */
160189 public function execute ($ product , $ arguments = [])
161190 {
191+ $ this ->mediaEavCache = null ;
162192 $ attrCode = $ this ->getAttribute ()->getAttributeCode ();
163193
164194 $ value = $ product ->getData ($ attrCode );
@@ -279,14 +309,15 @@ protected function processDeletedImages($product, array &$images)
279309 */
280310 protected function processNewAndExistingImages ($ product , array &$ images )
281311 {
312+ $ existingGalleryStoreValues = $ this ->getExistingGalleryStoreValues ($ product );
282313 foreach ($ images as &$ image ) {
283314 if (empty ($ image ['removed ' ])) {
284315 $ isNew = empty ($ image ['value_id ' ]);
285316 $ data = $ this ->processNewImage ($ product , $ image );
286317
287318 // Add per store labels, position, disabled
288- $ data ['value_id ' ] = $ image ['value_id ' ];
289- $ data ['label ' ] = isset ($ image ['label ' ]) ? $ image ['label ' ] : '' ;
319+ $ data ['value_id ' ] = ( int ) $ image ['value_id ' ];
320+ $ data ['label ' ] = ! empty ($ image ['label ' ]) ? $ image ['label ' ] : null ;
290321 $ data ['position ' ] = isset ($ image ['position ' ]) && $ image ['position ' ] !== ''
291322 ? (int )$ image ['position ' ]
292323 : null ;
@@ -295,34 +326,90 @@ protected function processNewAndExistingImages($product, array &$images)
295326
296327 $ data [$ this ->metadata ->getLinkField ()] = (int )$ product ->getData ($ this ->metadata ->getLinkField ());
297328
298- $ this ->saveGalleryStoreValue ($ product , $ data );
299- if ($ isNew && $ data ['store_id ' ] !== Store::DEFAULT_STORE_ID ) {
300- $ dataForDefaultScope = $ data ;
301- $ dataForDefaultScope ['store_id ' ] = Store::DEFAULT_STORE_ID ;
302- $ dataForDefaultScope ['disabled ' ] = 0 ;
303- $ dataForDefaultScope ['label ' ] = null ;
304- $ this ->saveGalleryStoreValue ($ product , $ dataForDefaultScope );
329+ if ($ isNew || $ this ->hasGalleryStoreValueChanged ($ data , $ existingGalleryStoreValues )) {
330+ $ this ->saveGalleryStoreValue ($ product , $ data , $ isNew );
305331 }
306332 }
307333 }
308334 }
309335
336+ /**
337+ * Get existing gallery store values
338+ *
339+ * @param Product $product
340+ * @return array
341+ * @throws \Exception
342+ */
343+ private function getExistingGalleryStoreValues (Product $ product ): array
344+ {
345+ $ existingMediaGalleryValues = [];
346+ if (!$ product ->isObjectNew ()) {
347+ $ productId = (int )$ product ->getData ($ this ->metadata ->getLinkField ());
348+ foreach ($ this ->mediaGalleryValue ->getAllByEntityId ($ productId ) as $ data ) {
349+ $ existingMediaGalleryValues [] = [
350+ 'value_id ' => (int ) $ data ['value_id ' ],
351+ 'store_id ' => (int ) $ data ['store_id ' ],
352+ 'label ' => $ data ['label ' ] ?: null ,
353+ 'position ' => $ data ['position ' ] !== null ? (int )$ data ['position ' ] : null ,
354+ 'disabled ' => (int ) $ data ['disabled ' ],
355+ ];
356+ }
357+ }
358+ return $ existingMediaGalleryValues ;
359+ }
360+
361+ /**
362+ * Check if gallery store value has changed
363+ *
364+ * @param array $data
365+ * @param array $existingGalleryStoreValues
366+ * @return bool
367+ */
368+ private function hasGalleryStoreValueChanged (array $ data , array $ existingGalleryStoreValues ): bool
369+ {
370+ foreach ($ existingGalleryStoreValues as $ existingGalleryStoreValue ) {
371+ if ($ existingGalleryStoreValue ['value_id ' ] === $ data ['value_id ' ]
372+ && $ existingGalleryStoreValue ['store_id ' ] === $ data ['store_id ' ]
373+ && $ existingGalleryStoreValue ['label ' ] === $ data ['label ' ]
374+ && $ existingGalleryStoreValue ['position ' ] === $ data ['position ' ]
375+ && $ existingGalleryStoreValue ['disabled ' ] === $ data ['disabled ' ]
376+ ) {
377+ return false ;
378+ }
379+ }
380+
381+ return true ;
382+ }
383+
310384 /**
311385 * Save media gallery store value
312386 *
313387 * @param Product $product
314388 * @param array $data
389+ * @param bool $isNewImage
315390 */
316- private function saveGalleryStoreValue (Product $ product , array $ data ): void
391+ private function saveGalleryStoreValue (Product $ product , array $ data, bool $ isNewImage ): void
317392 {
318- if (!$ product ->isObjectNew ()) {
319- $ this ->resourceModel ->deleteGalleryValueInStore (
320- $ data ['value_id ' ],
321- $ data [$ this ->metadata ->getLinkField ()],
322- $ data ['store_id ' ]
323- );
393+ $ items = [];
394+ $ items [] = $ data ;
395+ if ($ isNewImage && $ data ['store_id ' ] !== Store::DEFAULT_STORE_ID ) {
396+ $ dataForDefaultScope = $ data ;
397+ $ dataForDefaultScope ['store_id ' ] = Store::DEFAULT_STORE_ID ;
398+ $ dataForDefaultScope ['disabled ' ] = 0 ;
399+ $ dataForDefaultScope ['label ' ] = null ;
400+ $ items [] = $ dataForDefaultScope ;
401+ }
402+
403+ foreach ($ items as $ item ) {
404+ if (!$ product ->isObjectNew ()) {
405+ $ this ->resourceModel ->deleteGalleryValueInStore (
406+ $ item ['value_id ' ],
407+ $ item [$ this ->metadata ->getLinkField ()],
408+ $ item ['store_id ' ]
409+ );
410+ }
411+ $ this ->resourceModel ->insertGalleryValueInStore ($ item );
324412 }
325- $ this ->resourceModel ->insertGalleryValueInStore ($ data );
326413 }
327414
328415 /**
@@ -530,29 +617,26 @@ private function processMediaAttribute(
530617 array $ clearImages ,
531618 array $ newImages
532619 ): void {
533- $ storeId = $ product ->isObjectNew () ? Store::DEFAULT_STORE_ID : (int ) $ product ->getStoreId ();
534- /***
535- * Attributes values are saved as default value in single store mode
536- * @see \Magento\Catalog\Model\ResourceModel\AbstractResource::_saveAttributeValue
537- */
538- if ($ storeId === Store::DEFAULT_STORE_ID
539- || $ this ->storeManager ->hasSingleStore ()
540- || $ this ->getMediaAttributeStoreValue ($ product , $ mediaAttrCode , $ storeId ) !== null
541- ) {
542- $ value = $ product ->getData ($ mediaAttrCode );
620+ $ storeId = $ this ->getStoreIdForUpdate ($ product );
621+ $ oldValue = $ this ->getMediaAttributeStoreValue ($ product , $ mediaAttrCode , $ storeId );
622+ // Prevent from breaking store inheritance
623+ if ($ oldValue !== false || $ storeId === Store::DEFAULT_STORE_ID ) {
624+ $ value = $ product ->hasData ($ mediaAttrCode ) ? $ product ->getData ($ mediaAttrCode ) : $ oldValue ;
543625 $ newValue = $ value ;
544626 if (in_array ($ value , $ clearImages )) {
545627 $ newValue = 'no_selection ' ;
546628 }
547629 if (in_array ($ value , array_keys ($ newImages ))) {
548630 $ newValue = $ newImages [$ value ]['new_file ' ];
549631 }
550- $ product ->setData ($ mediaAttrCode , $ newValue );
551- $ product ->addAttributeUpdate (
552- $ mediaAttrCode ,
553- $ newValue ,
554- $ storeId
555- );
632+ if ($ oldValue !== $ newValue ) {
633+ $ product ->setData ($ mediaAttrCode , $ newValue );
634+ $ product ->addAttributeUpdate (
635+ $ mediaAttrCode ,
636+ $ newValue ,
637+ $ storeId
638+ );
639+ }
556640 }
557641 }
558642
@@ -565,6 +649,7 @@ private function processMediaAttribute(
565649 * @param array $newImages
566650 * @param array $existImages
567651 * @SuppressWarnings(PHPMD.CyclomaticComplexity)
652+ * @SuppressWarnings(PHPMD.NPathComplexity)
568653 */
569654 private function processMediaAttributeLabel (
570655 Product $ product ,
@@ -573,6 +658,9 @@ private function processMediaAttributeLabel(
573658 array $ newImages ,
574659 array $ existImages
575660 ): void {
661+ $ storeId = $ this ->getStoreIdForUpdate ($ product );
662+ $ oldAttrLabelValue = $ this ->getMediaAttributeStoreValue ($ product , $ mediaAttrCode . '_label ' , $ storeId );
663+
576664 $ resetLabel = false ;
577665 $ attrData = $ product ->getData ($ mediaAttrCode );
578666 if (in_array ($ attrData , $ clearImages )) {
@@ -595,33 +683,58 @@ private function processMediaAttributeLabel(
595683 $ product ->setData ($ mediaAttrCode . '_label ' , null );
596684 $ resetLabel = true ;
597685 }
598- if (!empty ($ product ->getData ($ mediaAttrCode . '_label ' ))
599- || $ resetLabel === true
600- ) {
686+
687+ $ newAttrLabelValue = $ product ->getData ($ mediaAttrCode . '_label ' );
688+
689+ if ($ newAttrLabelValue !== $ oldAttrLabelValue && ($ resetLabel || !empty ($ newAttrLabelValue ))) {
601690 $ product ->addAttributeUpdate (
602691 $ mediaAttrCode . '_label ' ,
603- $ product -> getData ( $ mediaAttrCode . ' _label ' ) ,
604- $ product -> getStoreId ()
692+ $ newAttrLabelValue ,
693+ $ storeId
605694 );
606695 }
607696 }
608697
609698 /**
610- * Get product images for all stores
699+ * Get store id to update media attribute
611700 *
612- * @param ProductInterface $product
613- * @return array
701+ * Attributes values are saved in "all store views" in single store mode
702+ *
703+ * @param Product $product
704+ * @return int
705+ * @see \Magento\Catalog\Model\ResourceModel\AbstractResource::_saveAttributeValue
614706 */
615- private function getImagesForAllStores ( ProductInterface $ product )
707+ private function getStoreIdForUpdate ( Product $ product ): int
616708 {
617- if ($ this ->imagesGallery === null ) {
618- $ storeIds = array_keys ($ this ->storeManager ->getStores ());
619- $ storeIds [] = 0 ;
709+ return $ product ->isObjectNew () || $ this ->storeManager ->hasSingleStore ()
710+ ? Store::DEFAULT_STORE_ID
711+ : (int ) $ product ->getStoreId ();
712+ }
620713
621- $ this ->imagesGallery = $ this ->resourceModel ->getProductImages ($ product , $ storeIds );
714+ /**
715+ * Get all media attributes values
716+ *
717+ * @param Product $product
718+ * @return array
719+ */
720+ private function getMediaAttributesValues (Product $ product ): array
721+ {
722+ if ($ this ->mediaEavCache === null ) {
723+ $ attributeCodes = [];
724+ foreach ($ this ->mediaConfig ->getMediaAttributeCodes () as $ attributeCode ) {
725+ $ attributeCodes [] = $ attributeCode ;
726+ if (in_array ($ attributeCode , $ this ->mediaAttributesWithLabels )) {
727+ $ attributeCodes [] = $ attributeCode . '_label ' ;
728+ }
729+ }
730+ $ this ->mediaEavCache = $ this ->attributeValue ->getValues (
731+ ProductInterface::class,
732+ (int ) $ product ->getData ($ this ->metadata ->getLinkField ()),
733+ $ attributeCodes
734+ );
622735 }
623736
624- return $ this ->imagesGallery ;
737+ return $ this ->mediaEavCache ;
625738 }
626739
627740 /**
@@ -630,18 +743,22 @@ private function getImagesForAllStores(ProductInterface $product)
630743 * @param Product $product
631744 * @param string $attributeCode
632745 * @param int|null $storeId
633- * @return string|null
746+ * @return mixed|false
634747 */
635- private function getMediaAttributeStoreValue (Product $ product , string $ attributeCode , int $ storeId = null ): ?string
636- {
637- $ gallery = $ this ->getImagesForAllStores ($ product );
748+ private function getMediaAttributeStoreValue (
749+ Product $ product ,
750+ string $ attributeCode ,
751+ int $ storeId = null
752+ ): mixed {
753+ $ attributes = $ this ->eavConfig ->getEntityAttributes (Product::ENTITY );
754+ $ attributeId = $ attributes [$ attributeCode ]->getAttributeId ();
638755 $ storeId = $ storeId === null ? (int ) $ product ->getStoreId () : $ storeId ;
639- foreach ($ gallery as $ image ) {
640- if ($ image [ ' attribute_code ' ] === $ attributeCode && ((int )$ image ['store_id ' ]) === $ storeId ) {
641- return $ image [ ' filepath ' ];
756+ foreach ($ this -> getMediaAttributesValues ( $ product ) as $ value ) {
757+ if ($ value [ ' attribute_id ' ] === $ attributeId && ((int )$ value ['store_id ' ]) === $ storeId ) {
758+ return $ value [ ' value ' ];
642759 }
643760 }
644- return null ;
761+ return false ;
645762 }
646763
647764 /**
0 commit comments