diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit.php index 16c0376aea9..aa18a561f3b 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit.php @@ -41,9 +41,9 @@ protected function _prepareLayout() 'back_button', $this->getLayout()->createBlock('adminhtml/widget_button') ->setData([ - 'label' => Mage::helper('catalog')->__('Back'), - 'onclick' => Mage::helper('core/js')->getSetLocationJs($this->getUrl('*/*/', ['store' => $this->getRequest()->getParam('store', 0)])), - 'class' => 'back', + 'label' => Mage::helper('catalog')->__('Back'), + 'onclick' => Mage::helper('core/js')->getSetLocationJs($this->getUrl('*/*/', ['store' => $this->getRequest()->getParam('store', 0)])), + 'class' => 'back', ]), ); } else { @@ -51,9 +51,9 @@ protected function _prepareLayout() 'back_button', $this->getLayout()->createBlock('adminhtml/widget_button') ->setData([ - 'label' => Mage::helper('catalog')->__('Close Window'), - 'onclick' => 'window.close()', - 'class' => 'cancel', + 'label' => Mage::helper('catalog')->__('Close Window'), + 'onclick' => 'window.close()', + 'class' => 'cancel', ]), ); } @@ -63,9 +63,9 @@ protected function _prepareLayout() 'reset_button', $this->getLayout()->createBlock('adminhtml/widget_button') ->setData([ - 'label' => Mage::helper('catalog')->__('Reset'), - 'onclick' => Mage::helper('core/js')->getSetLocationJs($this->getUrl('*/*/*', ['_current' => true])), - 'class' => 'reset', + 'label' => Mage::helper('catalog')->__('Reset'), + 'onclick' => Mage::helper('core/js')->getSetLocationJs($this->getUrl('*/*/*', ['_current' => true])), + 'class' => 'reset', ]), ); @@ -73,9 +73,9 @@ protected function _prepareLayout() 'save_button', $this->getLayout()->createBlock('adminhtml/widget_button') ->setData([ - 'label' => Mage::helper('catalog')->__('Save'), - 'onclick' => 'productForm.submit()', - 'class' => 'save', + 'label' => Mage::helper('catalog')->__('Save'), + 'onclick' => 'productForm.submit()', + 'class' => 'save', ]), ); } @@ -86,9 +86,9 @@ protected function _prepareLayout() 'save_and_edit_button', $this->getLayout()->createBlock('adminhtml/widget_button') ->setData([ - 'label' => Mage::helper('catalog')->__('Save and Continue Edit'), - 'onclick' => Mage::helper('core/js')->getSaveAndContinueEditJs($this->getSaveAndContinueUrl()), - 'class' => 'save continue', + 'label' => Mage::helper('catalog')->__('Save and Continue Edit'), + 'onclick' => Mage::helper('core/js')->getSaveAndContinueEditJs($this->getSaveAndContinueUrl()), + 'class' => 'save continue', ]), ); } @@ -98,21 +98,32 @@ protected function _prepareLayout() 'delete_button', $this->getLayout()->createBlock('adminhtml/widget_button') ->setData([ - 'label' => Mage::helper('catalog')->__('Delete'), - 'onclick' => Mage::helper('core/js')->getConfirmSetLocationJs($this->getDeleteUrl()), - 'class' => 'delete', + 'label' => Mage::helper('catalog')->__('Delete'), + 'onclick' => Mage::helper('core/js')->getConfirmSetLocationJs($this->getDeleteUrl()), + 'class' => 'delete', ]), ); } - if ($this->getProduct()->isDuplicable()) { + if ($this->getProduct()->isDuplicable() && $this->getProduct()->getId()) { + if ($this->getProduct()->getMediaGalleryImages()->count() === 0) { + $onClickAction = Mage::helper('core/js')->getSetLocationJs($this->getDuplicateUrl(true)); + } else { + $skipImgOnDuplicate = $this->helper('catalog/image')->skipProductImageOnDuplicate(); + $onClickAction = "openDuplicateDialog('" . $this->getDuplicateUrl(false) . "','" . $this->getDuplicateUrl(true) . "'); return false;"; + + if ($skipImgOnDuplicate !== Mage_Catalog_Model_Product_Image::ON_DUPLICATE_ASK) { + $onClickAction = Mage::helper('core/js')->getSetLocationJs($this->getDuplicateUrl((bool) $skipImgOnDuplicate)); + } + } + $this->setChild( 'duplicate_button', $this->getLayout()->createBlock('adminhtml/widget_button') ->setData([ - 'label' => Mage::helper('catalog')->__('Duplicate'), - 'onclick' => Mage::helper('core/js')->getSetLocationJs($this->getDuplicateUrl()), - 'class' => 'add duplicate', + 'label' => Mage::helper('catalog')->__('Duplicate'), + 'onclick' => $onClickAction, + 'class' => 'add duplicate', ]), ); } @@ -191,9 +202,9 @@ public function getSaveUrl() public function getSaveAndContinueUrl() { return $this->getUrl('*/*/save', [ - '_current' => true, - 'back' => 'edit', - 'tab' => '{{tab_id}}', + '_current' => true, + 'back' => 'edit', + 'tab' => '{{tab_id}}', 'active_tab' => null, ]); } @@ -229,9 +240,9 @@ public function getDeleteUrl() /** * @return string */ - public function getDuplicateUrl() + public function getDuplicateUrl(bool $skipImages = false) { - return $this->getUrl('*/*/duplicate', ['_current' => true]); + return $this->getUrl('*/*/duplicate', ['_current' => true, 'skipImages' => $skipImages ? Mage_Catalog_Model_Product_Image::ON_DUPLICATE_SKIP : Mage_Catalog_Model_Product_Image::ON_DUPLICATE_COPY]); } /** diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Catalog/ImageDuplicate.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Catalog/ImageDuplicate.php new file mode 100644 index 00000000000..b698c104a13 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Catalog/ImageDuplicate.php @@ -0,0 +1,25 @@ + Mage_Catalog_Model_Product_Image::ON_DUPLICATE_ASK, 'label' => Mage::helper('adminhtml')->__('Always ask')], + ['value' => Mage_Catalog_Model_Product_Image::ON_DUPLICATE_SKIP, 'label' => Mage::helper('adminhtml')->__('Duplicate product without images')], + ['value' => Mage_Catalog_Model_Product_Image::ON_DUPLICATE_COPY, 'label' => Mage::helper('adminhtml')->__('Duplicate product with images')], + ]; + } +} diff --git a/app/code/core/Mage/Adminhtml/controllers/Catalog/ProductController.php b/app/code/core/Mage/Adminhtml/controllers/Catalog/ProductController.php index c126c65fd69..471c6148b0e 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Catalog/ProductController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Catalog/ProductController.php @@ -822,6 +822,12 @@ public function duplicateAction() { $product = $this->_initProduct(); try { + $imgHelper = Mage::helper('catalog/image'); + + if ($imgHelper->skipProductImageOnDuplicate() === Mage_Catalog_Model_Product_Image::ON_DUPLICATE_ASK) { + $product->setSkipImagesOnDuplicate((bool) $this->getRequest()->getParam('skipImages', true)); + } + $newProduct = $product->duplicate(); $this->_getSession()->addSuccess($this->__('The product has been duplicated.')); $this->_redirect('*/*/edit', ['_current' => true, 'id' => $newProduct->getId()]); diff --git a/app/code/core/Mage/Catalog/Helper/Image.php b/app/code/core/Mage/Catalog/Helper/Image.php index 2ee8fb8303f..1bed8d0dc6f 100644 --- a/app/code/core/Mage/Catalog/Helper/Image.php +++ b/app/code/core/Mage/Catalog/Helper/Image.php @@ -20,6 +20,8 @@ class Mage_Catalog_Helper_Image extends Mage_Core_Helper_Abstract public const XML_NODE_PRODUCT_MAX_DIMENSION = 'catalog/product_image/max_dimension'; + public const XML_NODE_SKIP_IMAGE_ON_DUPLICATE_ACTION = 'catalog/product_image/images_on_duplicate_action'; + protected $_moduleName = 'Mage_Catalog'; /** @@ -650,4 +652,9 @@ public function validateUploadFile($filePath) return $mimeType !== null; } + + public function skipProductImageOnDuplicate(): int + { + return Mage::getStoreConfigAsInt(self::XML_NODE_SKIP_IMAGE_ON_DUPLICATE_ACTION); + } } diff --git a/app/code/core/Mage/Catalog/Model/Product.php b/app/code/core/Mage/Catalog/Model/Product.php index 146feadcd70..25cf8881291 100644 --- a/app/code/core/Mage/Catalog/Model/Product.php +++ b/app/code/core/Mage/Catalog/Model/Product.php @@ -103,6 +103,7 @@ * @method string getShipmentType() * @method string getShortDescription() * @method bool getSkipCheckRequiredOption() + * @method bool getSkipImagesOnDuplicate() * @method string getSmallImage() * @method bool getStickWithinParent() * @method array getStockData() @@ -212,6 +213,7 @@ * @method $this setRequiredOptions(bool $value) * @method $this setShortDescription(string $value) * @method $this setSkipCheckRequiredOption(bool $value) + * @method $this setSkipImagesOnDuplicate(bool $value) * @method $this setSku(string $value) * @method $this setStatus(int $store) * @method $this setStockData(array $value) @@ -1375,6 +1377,17 @@ public function duplicate() ->setId(null) ->setStoreId(Mage::app()->getStore()->getId()); + if (is_bool($this->getSkipImagesOnDuplicate())) { + // when set programmatically or via frontend + $newProduct->setSkipImagesOnDuplicate($this->getSkipImagesOnDuplicate()); + } elseif ($this->_getImageHelper()->skipProductImageOnDuplicate() === Mage_Catalog_Model_Product_Image::ON_DUPLICATE_ASK) { + // when not defined at all, but config is 'ask' + $newProduct->setSkipImagesOnDuplicate(false); + } else { + // when not defined at all, but config is set + $newProduct->setSkipImagesOnDuplicate($this->_getImageHelper()->skipProductImageOnDuplicate()); + } + Mage::dispatchEvent( 'catalog_model_product_duplicate', ['current_product' => $this, 'new_product' => $newProduct], @@ -1447,7 +1460,9 @@ public function duplicate() $newProduct->save(); $this->getOptionInstance()->duplicate($this->getId(), $newProduct->getId()); - $this->getResource()->duplicate($this->getId(), $newProduct->getId()); + $this->getResource() + ->setSkipImagesOnDuplicate($newProduct->getSkipImagesOnDuplicate()) + ->duplicate($this->getId(), $newProduct->getId()); // TODO - duplicate product on all stores of the websites it is associated with /*if ($storeIds = $this->getWebsiteIds()) { 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 9cefe18ed7e..c17c2c6be07 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 @@ -105,7 +105,7 @@ public function beforeSave($object) $value['images'] = Mage::helper('core')->jsonDecode($value['images']); } - if (!isset($value['values'])) { + if (!isset($value['values']) || $object->getSkipImagesOnDuplicate()) { $value['values'] = []; } @@ -113,7 +113,7 @@ public function beforeSave($object) $value['values'] = Mage::helper('core')->jsonDecode($value['values']); } - if (!is_array($value['images'])) { + if (!is_array($value['images']) || $object->getSkipImagesOnDuplicate()) { $value['images'] = []; } @@ -696,7 +696,7 @@ public function duplicate($object) $attrCode = $this->getAttribute()->getAttributeCode(); $mediaGalleryData = $object->getData($attrCode); - if (!isset($mediaGalleryData['images']) || !is_array($mediaGalleryData['images'])) { + if (!isset($mediaGalleryData['images']) || !is_array($mediaGalleryData['images']) || $object->getSkipImagesOnDuplicate()) { return $this; } diff --git a/app/code/core/Mage/Catalog/Model/Product/Image.php b/app/code/core/Mage/Catalog/Model/Product/Image.php index 9da9e03e34b..be6f9846cc1 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Image.php +++ b/app/code/core/Mage/Catalog/Model/Product/Image.php @@ -18,6 +18,29 @@ class Mage_Catalog_Model_Product_Image extends Mage_Core_Model_Abstract { public const DIMENSIONS_SEPARATOR = 'x'; + + /** + * Always ask to copy images on duplicated product + * + * @var int + */ + public const ON_DUPLICATE_ASK = -1; + + /** + * Copy images to the new product + * + * @var int + */ + public const ON_DUPLICATE_COPY = 0; + + + /** + * Always ask + * + * @var int + */ + public const ON_DUPLICATE_SKIP = 1; + /** * Requested width for the scaled image * @var int diff --git a/app/code/core/Mage/Catalog/Model/Resource/Product.php b/app/code/core/Mage/Catalog/Model/Resource/Product.php index 1cc50fd8269..d633f643360 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Product.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Product.php @@ -28,6 +28,11 @@ class Mage_Catalog_Model_Resource_Product extends Mage_Catalog_Model_Resource_Ab */ protected $_productCategoryTable; + /** + * Used when duplicating product + */ + protected bool $skipImagesOnDuplicate = false; + /** * Initialize resource */ @@ -565,9 +570,22 @@ public function duplicate($oldId, $newId) { $adapter = $this->_getWriteAdapter(); $eavTables = ['datetime', 'decimal', 'int', 'text', 'varchar']; - + $mediaImageAttributeSkipIds = []; $adapter = $this->_getWriteAdapter(); + if ($this->getSkipImagesOnDuplicate()) { + + /** + * @var int $attributeId + * @var Mage_Eav_Model_Entity_Attribute_Abstract $attribute + */ + foreach ($this->getAttributesById() as $attributeId => $attribute) { + if ($attribute->getFrontendInput() == 'media_image') { + $mediaImageAttributeSkipIds[$attribute->getBackendType()][] = $attributeId; + } + } + } + // duplicate EAV store values foreach ($eavTables as $suffix) { $tableName = $this->getTable(['catalog/product', $suffix]); @@ -583,6 +601,10 @@ public function duplicate($oldId, $newId) ->where('entity_id = ?', $oldId) ->where('store_id > ?', 0); + if (isset($mediaImageAttributeSkipIds[$suffix])) { + $select->where('attribute_id NOT IN (?)', $mediaImageAttributeSkipIds[$suffix]); + } + $adapter->query($adapter->insertFromSelect( $select, $tableName, @@ -719,4 +741,18 @@ public function getCategoryIdsWithAnchors($object) return $this->_getReadAdapter()->fetchCol($select); } + + /** + * @return $this + */ + public function setSkipImagesOnDuplicate(bool $newProductSkipImages) + { + $this->skipImagesOnDuplicate = $newProductSkipImages; + return $this; + } + + public function getSkipImagesOnDuplicate(): bool + { + return $this->skipImagesOnDuplicate; + } } diff --git a/app/code/core/Mage/Catalog/etc/config.xml b/app/code/core/Mage/Catalog/etc/config.xml index 07adebaca45..a5a78270ebb 100644 --- a/app/code/core/Mage/Catalog/etc/config.xml +++ b/app/code/core/Mage/Catalog/etc/config.xml @@ -810,6 +810,7 @@ 1800 210 5000 + -1 .html diff --git a/app/code/core/Mage/Catalog/etc/system.xml b/app/code/core/Mage/Catalog/etc/system.xml index add69a01d34..7c8e4346fd3 100644 --- a/app/code/core/Mage/Catalog/etc/system.xml +++ b/app/code/core/Mage/Catalog/etc/system.xml @@ -203,6 +203,16 @@ 1 validate-digits validate-greater-than-zero + + + 'Ask' option only affects Admin interface. Default for programmatical duplication is to persist images. + select + adminhtml/system_config_source_catalog_imageDuplicate + 50 + 1 + 1 + 1 + diff --git a/app/design/adminhtml/default/default/template/catalog/product/edit.phtml b/app/design/adminhtml/default/default/template/catalog/product/edit.phtml index 5fb4f6e9490..d118f0c4cd3 100644 --- a/app/design/adminhtml/default/default/template/catalog/product/edit.phtml +++ b/app/design/adminhtml/default/default/template/catalog/product/edit.phtml @@ -92,6 +92,39 @@ return 1; } + function openDuplicateDialog(keepImagesUrl,skipImagesUrl) { + var html = '

__('Do you want to use the same images on the duplicate product?'); ?>

'; + html += '

__('You can disable this message on'); ?>:
__('System'); ?> > __('Configuration'); ?> > __('Catalog Images'); ?> > __('Product Image'); ?>


'; + + function duplicateKeepImages(dialogWindow) { + dialogWindow.close(); + setLocation(keepImagesUrl); + } + function duplicateSkipImages(dialogWindow) { + dialogWindow.close(); + setLocation(skipImagesUrl); + } + + Dialog.confirm(html, { + width: 450, + height: 120, + draggable:true, + closable:true, + className:"magento", + windowClassName:"popup-window", + title:'__('Current product has images') ?>', + recenterAuto:false, + hideEffect:Element.hide, + showEffect:Element.show, + id:"duplicate-product", + buttonClass:"form-button", + okLabel:"__('Duplicate with images'); ?>", + ok: duplicateKeepImages.bind(this), + cancelLabel: "__('Duplicate without images'); ?>", + cancel: duplicateSkipImages.bind(this), + }); + } + Event.observe(window, 'load', function() { var objName = 'getSelectedTabId() ?>'; if (objName) { diff --git a/app/locale/en_US/Mage_Adminhtml.csv b/app/locale/en_US/Mage_Adminhtml.csv index 0262cbb17b8..945e5947641 100644 --- a/app/locale/en_US/Mage_Adminhtml.csv +++ b/app/locale/en_US/Mage_Adminhtml.csv @@ -1312,3 +1312,12 @@ "{{base_url}} is not recommended to use in a production environment to declare the Base Unsecure URL / Base Secure URL. It is highly recommended to change this value in your Magento configuration.","{{base_url}} is not recommended to use in a production environment to declare the Base Unsecure URL / Base Secure URL. It is highly recommended to change this value in your Magento configuration." "Powered by OpenMage","Powered by OpenMage" "At least one currency has to be allowed.","At least one currency has to be allowed." +"Do you want to use the same images on the duplicate product?","Do you want to use the same images on the duplicate product?" +"You can disable this message on","You can disable this message on" +"Current product has images","Current product has images" +"Duplicate with images","Duplicate with images" +"Duplicate without images","Duplicate without images" +"Always ask","Always ask" +"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."