diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Catalog/ImageFileHandle.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Catalog/ImageFileHandle.php
new file mode 100644
index 00000000000..675511ec720
--- /dev/null
+++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Catalog/ImageFileHandle.php
@@ -0,0 +1,24 @@
+ Mage_Catalog_Model_Product_Image::ON_REMOVAL_KEEP, 'label' => Mage::helper('adminhtml')->__('Keep image file(s) on the filesystem')],
+ ['value' => Mage_Catalog_Model_Product_Image::ON_REMOVAL_DELETE, 'label' => Mage::helper('adminhtml')->__('Permanently deletes image file(s)')],
+ ];
+ }
+}
diff --git a/app/code/core/Mage/Catalog/Helper/Image.php b/app/code/core/Mage/Catalog/Helper/Image.php
index 1bed8d0dc6f..ebc776174b6 100644
--- a/app/code/core/Mage/Catalog/Helper/Image.php
+++ b/app/code/core/Mage/Catalog/Helper/Image.php
@@ -22,6 +22,8 @@ class Mage_Catalog_Helper_Image extends Mage_Core_Helper_Abstract
public const XML_NODE_SKIP_IMAGE_ON_DUPLICATE_ACTION = 'catalog/product_image/images_on_duplicate_action';
+ public const XML_NODE_DELETE_IMAGE_ON_REMOVAL_ACTION = 'catalog/product_image/images_on_removal_action';
+
protected $_moduleName = 'Mage_Catalog';
/**
@@ -657,4 +659,9 @@ public function skipProductImageOnDuplicate(): int
{
return Mage::getStoreConfigAsInt(self::XML_NODE_SKIP_IMAGE_ON_DUPLICATE_ACTION);
}
+
+ public function deleteImageFileOnRemoval(): int
+ {
+ return Mage::getStoreConfigAsInt(self::XML_NODE_DELETE_IMAGE_ON_REMOVAL_ACTION);
+ }
}
diff --git a/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Media.php b/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Media.php
index c17c2c6be07..e128de72a48 100644
--- a/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Media.php
+++ b/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Media.php
@@ -231,7 +231,7 @@ public function afterSave($object)
foreach ($value['images'] as &$image) {
if (!empty($image['removed'])) {
if (isset($image['value_id']) && !isset($picturesInOtherStores[$image['file']])) {
- $toDelete[] = $image['value_id'];
+ $toDelete[] = $image;
}
continue;
@@ -271,7 +271,49 @@ public function afterSave($object)
$this->_getResource()->insertGalleryValueInStore($data);
}
- $this->_getResource()->deleteGallery($toDelete);
+ $valueIdsToDelete = array_map(fn($img) => $img['value_id'], $toDelete);
+ $this->_getResource()->deleteGallery($valueIdsToDelete);
+
+ /** @var Mage_Catalog_Helper_Image $imgHelper */
+ $imgHelper = Mage::helper('catalog/image');
+ if ($imgHelper->deleteImageFileOnRemoval() === Mage_Catalog_Model_Product_Image::ON_REMOVAL_DELETE) {
+ $filesToDelete = array_map(fn($img) => $img['file'], $toDelete);
+
+ $unusedImages = $this->_getResource()->filterUnusedImageFiles($filesToDelete, $object->getId());
+ $io = new Varien_Io_File();
+ foreach ($unusedImages as $file) {
+ $io->rm($this->_getConfig()->getMediaPath($file));
+ }
+ }
+ }
+
+ /**
+ * @param Mage_Catalog_Model_Product $object
+ * @return Mage_Catalog_Model_Product_Attribute_Backend_Media
+ */
+ public function afterDelete($object)
+ {
+ $attrCode = $this->getAttribute()->getAttributeCode();
+ $value = $object->getData($attrCode);
+ if (!is_array($value) || !isset($value['images'])) {
+ return parent::afterDelete($object);
+ }
+
+ /** @var Mage_Catalog_Helper_Image $imgHelper */
+ $imgHelper = Mage::helper('catalog/image');
+
+ if ($imgHelper->deleteImageFileOnRemoval() === Mage_Catalog_Model_Product_Image::ON_REMOVAL_KEEP) {
+ return parent::afterDelete($object);
+ }
+
+ $images = array_map(fn($image) => $image['file'], $value['images']);
+ $unusedImages = $this->_getResource()->filterUnusedImageFiles($images, $object->getId());
+ $io = new Varien_Io_File();
+ foreach ($unusedImages as $file) {
+ $io->rm($this->_getConfig()->getMediaPath($file));
+ }
+
+ return parent::afterDelete($object);
}
/**
diff --git a/app/code/core/Mage/Catalog/Model/Product/Image.php b/app/code/core/Mage/Catalog/Model/Product/Image.php
index be6f9846cc1..0ecc99d9db2 100644
--- a/app/code/core/Mage/Catalog/Model/Product/Image.php
+++ b/app/code/core/Mage/Catalog/Model/Product/Image.php
@@ -41,6 +41,22 @@ class Mage_Catalog_Model_Product_Image extends Mage_Core_Model_Abstract
*/
public const ON_DUPLICATE_SKIP = 1;
+
+ /**
+ * Always ask
+ *
+ * @var int
+ */
+ public const ON_REMOVAL_KEEP = 0;
+
+
+ /**
+ * Always ask
+ *
+ * @var int
+ */
+ public const ON_REMOVAL_DELETE = 1;
+
/**
* Requested width for the scaled image
* @var int
diff --git a/app/code/core/Mage/Catalog/Model/Resource/Product/Attribute/Backend/Media.php b/app/code/core/Mage/Catalog/Model/Resource/Product/Attribute/Backend/Media.php
index 18a04cca65c..48a860545f5 100644
--- a/app/code/core/Mage/Catalog/Model/Resource/Product/Attribute/Backend/Media.php
+++ b/app/code/core/Mage/Catalog/Model/Resource/Product/Attribute/Backend/Media.php
@@ -274,4 +274,24 @@ public function loadGallerySet(array $productIds, $storeId)
$this->_removeDuplicates($result);
return $result;
}
+
+ /**
+ * Filter gallery images that are not used by other products
+ *
+ * @param array $imageFiles
+ * @param int $objectId
+ */
+ public function filterUnusedImageFiles($imageFiles, $objectId): array
+ {
+ // Duplicate product store values
+ $adapter = $this->_getReadAdapter();
+ $select = $adapter->select()
+ ->from($this->getTable(self::GALLERY_TABLE), [
+ new Zend_Db_Expr('DISTINCT value'),
+ ])
+ ->where('entity_id != ?', $objectId)
+ ->where('value IN (?)', $imageFiles);
+
+ return array_diff($imageFiles, $this->_getReadAdapter()->fetchCol($select));
+ }
}
diff --git a/app/code/core/Mage/Catalog/etc/config.xml b/app/code/core/Mage/Catalog/etc/config.xml
index a5a78270ebb..d9bc9c0ad6d 100644
--- a/app/code/core/Mage/Catalog/etc/config.xml
+++ b/app/code/core/Mage/Catalog/etc/config.xml
@@ -811,6 +811,7 @@
2105000-1
+ 0.html
diff --git a/app/code/core/Mage/Catalog/etc/system.xml b/app/code/core/Mage/Catalog/etc/system.xml
index 7c8e4346fd3..2859aaf731a 100644
--- a/app/code/core/Mage/Catalog/etc/system.xml
+++ b/app/code/core/Mage/Catalog/etc/system.xml
@@ -213,6 +213,16 @@
11
+
+
+ Important: image files referenced by other products will not be deleted. Only image files that are no longer associated with any product are eligible for removal.]]>
+ select
+ adminhtml/system_config_source_catalog_imageFileHandle
+ 50
+ 1
+ 1
+ 1
+
diff --git a/app/locale/en_US/Mage_Adminhtml.csv b/app/locale/en_US/Mage_Adminhtml.csv
index 945e5947641..58e918cc92d 100644
--- a/app/locale/en_US/Mage_Adminhtml.csv
+++ b/app/locale/en_US/Mage_Adminhtml.csv
@@ -1321,3 +1321,7 @@
"Duplicate product without images","Duplicate product without images"
"Copy Images on Duplicate","Copy Images on Duplicate"
"'Ask' option only affects Admin interface. Default for programmatical duplication is to persist images.","'Ask' option only affects Admin interface. Default for programmatical duplication is to persist images."
+"Product image file handling on removal","Product image file handling on removal"
+"Defines how product image files are handled when a product is deleted or when an image is removed from the product media gallery.
Important: image files referenced by other products will not be deleted. Only image files that are no longer associated with any product are eligible for removal.","Defines how product image files are handled when a product is deleted or when an image is removed from the product media gallery.
Important: image files referenced by other products will not be deleted. Only image files that are no longer associated with any product are eligible for removal."
+"Keep image file(s) on the filesystem","Keep image file(s) on the filesystem"
+"Permanently deletes image file(s)","Permanently deletes image file(s)"