diff --git a/vendor/magento/module-catalog/Model/Product/Gallery/CopyHandler.php b/vendor/magento/module-catalog/Model/Product/Gallery/CopyHandler.php
new file mode 100644
index 00000000000..5e4d3e39161
--- /dev/null
+++ b/vendor/magento/module-catalog/Model/Product/Gallery/CopyHandler.php
@@ -0,0 +1,173 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+declare(strict_types=1);
+
+namespace Magento\Catalog\Model\Product\Gallery;
+
+use Magento\Catalog\Api\Data\ProductAttributeInterface;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
+use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\ResourceModel\Product\Gallery;
+use Magento\Eav\Model\ResourceModel\AttributeValue;
+use Magento\Framework\EntityManager\EntityMetadata;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\EntityManager\Operation\ExtensionInterface;
+use Magento\Framework\Serialize\Serializer\Json;
+
+/**
+ * Copy gallery data from one product to another
+ */
+class CopyHandler implements ExtensionInterface
+{
+    /**
+     * @var EntityMetadata
+     */
+    private $metadata;
+
+    /**
+     * @var Gallery
+     */
+    private $galleryResourceModel;
+
+    /**
+     * @var ProductAttributeRepositoryInterface
+     */
+    private $attributeRepository;
+
+    /**
+     * @var AttributeValue
+     */
+    private $attributeValue;
+
+    /**
+     * @var Json
+     */
+    private $json;
+
+    /**
+     * @var ProductAttributeInterface
+     */
+    private $attribute;
+
+    /**
+     * @param MetadataPool $metadataPool
+     * @param Gallery $galleryResourceModel
+     * @param ProductAttributeRepositoryInterface $attributeRepository
+     * @param AttributeValue $attributeValue
+     * @param Json $json
+     */
+    public function __construct(
+        MetadataPool $metadataPool,
+        Gallery $galleryResourceModel,
+        ProductAttributeRepositoryInterface $attributeRepository,
+        AttributeValue $attributeValue,
+        Json $json
+    ) {
+        $this->metadata = $metadataPool->getMetadata(ProductInterface::class);
+        $this->galleryResourceModel = $galleryResourceModel;
+        $this->attributeRepository = $attributeRepository;
+        $this->attributeValue = $attributeValue;
+        $this->json = $json;
+    }
+
+    /**
+     * Copy gallery data from one product to another
+     *
+     * @param Product $product
+     * @param array $arguments
+     * @return void
+     */
+    public function execute($product, $arguments = []): void
+    {
+        $fromId = (int) $arguments['original_link_id'];
+        $toId = $product->getData($this->metadata->getLinkField());
+        $attributeId = $this->getAttribute()->getAttributeId();
+        $valueIdMap = $this->galleryResourceModel->duplicate($attributeId, [], $fromId, $toId);
+        $gallery = $this->getMediaGalleryCollection($product);
+
+        if (!empty($gallery['images'])) {
+            $images = [];
+            foreach ($gallery['images'] as $key => $image) {
+                $valueId = $image['value_id'] ?? null;
+                $newKey = $key;
+                if ($valueId !== null) {
+                    $newValueId = $valueId;
+                    if (isset($valueIdMap[$valueId])) {
+                        $newValueId = $valueIdMap[$valueId];
+                    }
+                    if (((int) $valueId) === $key) {
+                        $newKey = $newValueId;
+                    }
+                    $image['value_id'] = $newValueId;
+                }
+                $images[$newKey] = $image;
+            }
+            $gallery['images'] = $images;
+            $attrCode = $this->getAttribute()->getAttributeCode();
+            $product->setData($attrCode, $gallery);
+        }
+
+        //Copy media attribute values from one product to another
+        if (isset($arguments['media_attribute_codes'])) {
+            $values = $this->attributeValue->getValues(
+                ProductInterface::class,
+                $fromId,
+                $arguments['media_attribute_codes']
+            );
+            if ($values) {
+                foreach (array_keys($values) as $key) {
+                    $values[$key][$this->metadata->getLinkField()] = $product->getData($this->metadata->getLinkField());
+                    unset($values[$key]['value_id']);
+                }
+                $this->attributeValue->insertValues(
+                    ProductInterface::class,
+                    $values
+                );
+            }
+        }
+    }
+
+    /**
+     * Get product media gallery collection
+     *
+     * @param Product $product
+     * @return array
+     */
+    private function getMediaGalleryCollection(Product $product): array
+    {
+        $attrCode = $this->getAttribute()->getAttributeCode();
+        $value = $product->getData($attrCode);
+
+        if (is_array($value) && isset($value['images'])) {
+            if (!is_array($value['images']) && strlen($value['images']) > 0) {
+                $value['images'] = $this->json->unserialize($value['images']);
+            }
+
+            if (!is_array($value['images'])) {
+                $value['images'] = [];
+            }
+        }
+
+        return $value;
+    }
+
+    /**
+     * Returns media gallery attribute instance
+     *
+     * @return ProductAttributeInterface
+     */
+    private function getAttribute(): ProductAttributeInterface
+    {
+        if (!$this->attribute) {
+            $this->attribute = $this->attributeRepository->get(
+                ProductInterface::MEDIA_GALLERY
+            );
+        }
+
+        return $this->attribute;
+    }
+}
diff --git a/vendor/magento/module-catalog/Model/Product/Gallery/DeleteHandler.php b/vendor/magento/module-catalog/Model/Product/Gallery/DeleteHandler.php
new file mode 100644
index 00000000000..16adccb29b2
--- /dev/null
+++ b/vendor/magento/module-catalog/Model/Product/Gallery/DeleteHandler.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+declare(strict_types=1);
+
+namespace Magento\Catalog\Model\Product\Gallery;
+
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\ResourceModel\Product\Gallery;
+use Magento\Eav\Model\ResourceModel\AttributeValue;
+use Magento\Framework\EntityManager\EntityMetadata;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\EntityManager\Operation\ExtensionInterface;
+
+/**
+ * Delete all media gallery records for provided product
+ */
+class DeleteHandler implements ExtensionInterface
+{
+    /**
+     * @var EntityMetadata
+     */
+    private $metadata;
+
+    /**
+     * @var Gallery
+     */
+    private $galleryResourceModel;
+
+    /**
+     * @var AttributeValue
+     */
+    private $attributeValue;
+
+    /**
+     * @param MetadataPool $metadataPool
+     * @param Gallery $galleryResourceModel
+     * @param AttributeValue $attributeValue
+     */
+    public function __construct(
+        MetadataPool $metadataPool,
+        Gallery $galleryResourceModel,
+        AttributeValue $attributeValue
+    ) {
+        $this->metadata = $metadataPool->getMetadata(ProductInterface::class);
+        $this->galleryResourceModel = $galleryResourceModel;
+        $this->attributeValue = $attributeValue;
+    }
+
+    /**
+     * Delete all media gallery records for provided product
+     *
+     * @param Product $product
+     * @param array $arguments
+     * @return void
+     */
+    public function execute($product, $arguments = []): void
+    {
+        $valuesId = $this->getMediaGalleryValuesId($product);
+        if ($valuesId) {
+            $this->galleryResourceModel->deleteGallery($valuesId);
+        }
+        if (isset($arguments['media_attribute_codes'])) {
+            $values = $this->attributeValue->getValues(
+                ProductInterface::class,
+                (int) $product->getData($this->metadata->getLinkField()),
+                $arguments['media_attribute_codes']
+            );
+            if ($values) {
+                $this->attributeValue->deleteValues(
+                    ProductInterface::class,
+                    $values
+                );
+            }
+        }
+    }
+
+    /**
+     * Get product media gallery values IDs
+     *
+     * @param Product $product
+     * @return array
+     */
+    private function getMediaGalleryValuesId(Product $product): array
+    {
+        $connection = $this->galleryResourceModel->getConnection();
+        $select = $connection->select()
+            ->from($this->galleryResourceModel->getTable(Gallery::GALLERY_VALUE_TO_ENTITY_TABLE))
+            ->where(
+                $this->metadata->getLinkField() . '=?',
+                $product->getData($this->metadata->getLinkField()),
+                \Zend_Db::INT_TYPE
+            );
+        return $connection->fetchCol($select);
+    }
+}
diff --git a/vendor/magento/module-catalog/Model/ResourceModel/Product/Gallery.php b/vendor/magento/module-catalog/Model/ResourceModel/Product/Gallery.php
index 5ff6207074b..28e637ad056 100644
--- a/vendor/magento/module-catalog/Model/ResourceModel/Product/Gallery.php
+++ b/vendor/magento/module-catalog/Model/ResourceModel/Product/Gallery.php
@@ -452,6 +452,9 @@ class Gallery extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
         // Duplicate per store gallery values
         $select = $this->getConnection()->select()->from(
             $this->getTable(self::GALLERY_VALUE_TABLE)
+        )->where(
+            $linkField . ' = ?',
+            $originalProductId
         )->where(
             'value_id IN(?)',
             array_keys($valueIdMap)
diff --git a/vendor/magento/module-eav/Model/ResourceModel/AttributeValue.php b/vendor/magento/module-eav/Model/ResourceModel/AttributeValue.php
index 305ed202ff2..66404c3ef38 100644
--- a/vendor/magento/module-eav/Model/ResourceModel/AttributeValue.php
+++ b/vendor/magento/module-eav/Model/ResourceModel/AttributeValue.php
@@ -117,7 +117,7 @@ class AttributeValue
      * Delete attribute values
      *
      * @param string $entityType
-     * @param array[][] $values
+     * @param array[] $values
      * Format:
      * array(
      *      0 => array(
@@ -159,12 +159,64 @@ class AttributeValue
         }
     }
 
+    /**
+     * Insert attribute values
+     *
+     * @param string $entityType
+     * @param array[] $values
+     * Format:
+     * array(
+     *      0 => array(
+     *          attribute_id => 11,
+     *          value => 'some long text',
+     *          ...
+     *      ),
+     *      1 => array(
+     *          attribute_id => 22,
+     *          value => 'some short text',
+     *          ...
+     *      )
+     * )
+     */
+    public function insertValues(string $entityType, array $values): void
+    {
+        $metadata = $this->metadataPool->getMetadata($entityType);
+        $connection = $metadata->getEntityConnection();
+        $attributeTables = [];
+        $allAttributes = [];
+
+        foreach ($this->getEntityAttributes($entityType) as $attribute) {
+            $allAttributes[(int) $attribute->getAttributeId()] = $attribute;
+        }
+
+        foreach ($values as $value) {
+            $attribute = $allAttributes[(int) $value['attribute_id']] ?? null;
+            if ($attribute && !$attribute->isStatic()) {
+                $columns = array_keys($value);
+                $columnsHash = implode(',', $columns);
+                $attributeTable = $attribute->getBackend()->getTable();
+                $attributeTables[$attributeTable][$columnsHash][] = array_values($value);
+            }
+        }
+
+        foreach ($attributeTables as $table => $tableData) {
+            foreach ($tableData as $columns => $data) {
+                $connection->insertArray(
+                    $table,
+                    explode(',', $columns),
+                    $data
+                );
+            }
+        }
+    }
+
     /**
      * Get attribute of given entity type
      *
      * @param string $entityType
+     * @return array
      */
-    private function getEntityAttributes(string $entityType)
+    private function getEntityAttributes(string $entityType): array
     {
         $metadata = $this->metadataPool->getMetadata($entityType);
         $eavEntityType = $metadata->getEavEntityType();
