Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 37 additions & 26 deletions app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,19 @@ 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 {
$this->setChild(
'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',
]),
);
}
Expand All @@ -63,19 +63,19 @@ 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',
]),
);

$this->setChild(
'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',
]),
);
}
Expand All @@ -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',
]),
);
}
Expand All @@ -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()->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 !== -1) {
$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',
]),
);
}
Expand Down Expand Up @@ -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,
]);
}
Expand Down Expand Up @@ -229,9 +240,9 @@ public function getDeleteUrl()
/**
* @return string
*/
public function getDuplicateUrl()
public function getDuplicateUrl($skipImages = false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function getDuplicateUrl($skipImages = false)
public function getDuplicateUrl(bool $skipImages = false)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a new method getDuplicateSkipUrl()?

{
return $this->getUrl('*/*/duplicate', ['_current' => true]);
return $this->getUrl('*/*/duplicate', ['_current' => true, 'skipImages' => $skipImages ? 1 : 0]);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

class Mage_Adminhtml_Model_System_Config_Source_Catalog_ImageDuplicate {


public function toOptionArray()
{
return [
['value' => -1, 'label' => Mage::helper('adminhtml')->__('Always ask')],
['value' => 0, 'label' => Mage::helper('adminhtml')->__('Copy images to the new product')],
['value' => 1, 'label' => Mage::helper('adminhtml')->__('Duplicate product without images')],
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,12 @@ public function duplicateAction()
{
$product = $this->_initProduct();
try {
$imgHelper = Mage::helper('catalog/image');

if($imgHelper->skipProductImageOnDuplicate() === -1){
$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()]);
Expand Down
10 changes: 10 additions & 0 deletions app/code/core/Mage/Catalog/Helper/Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -650,4 +652,12 @@ public function validateUploadFile($filePath)

return $mimeType !== null;
}

/**
* @return int
*/
public function skipProductImageOnDuplicate()
{
return Mage::getStoreConfigAsInt(self::XML_NODE_SKIP_IMAGE_ON_DUPLICATE_ACTION);
}
Comment on lines +656 to +662
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* @return int
*/
public function skipProductImageOnDuplicate()
{
return Mage::getStoreConfigAsInt(self::XML_NODE_SKIP_IMAGE_ON_DUPLICATE_ACTION);
}
public function skipProductImageOnDuplicate(): int
{
return Mage::getStoreConfigAsInt(self::XML_NODE_SKIP_IMAGE_ON_DUPLICATE_ACTION);
}

}
12 changes: 11 additions & 1 deletion app/code/core/Mage/Catalog/Model/Product.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
* @method bool getIsChangedWebsites()
* @method bool getIsCustomOptionChanged()
* @method bool getIsDefault()
* @method bool getSkipImagesOnDuplicate()
* @method $this setSkipImagesOnDuplicate(bool $value)
* @method bool getIsDuplicate()
* @method bool getIsMassupdate()
* @method bool getIsRecurring()
Expand Down Expand Up @@ -1365,6 +1367,12 @@ public function duplicate()
->setId(null)
->setStoreId(Mage::app()->getStore()->getId());

if($newProduct->getSkipImagesOnDuplicate() == null && $this->_getImageHelper()->skipProductImageOnDuplicate() === -1){
$newProduct->setSkipImagesOnDuplicate(false);
}else{
$newProduct->setSkipImagesOnDuplicate((bool) $this->_getImageHelper()->skipProductImageOnDuplicate());
}
Comment on lines +1370 to +1374
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if($newProduct->getSkipImagesOnDuplicate() == null && $this->_getImageHelper()->skipProductImageOnDuplicate() === -1){
$newProduct->setSkipImagesOnDuplicate(false);
}else{
$newProduct->setSkipImagesOnDuplicate((bool) $this->_getImageHelper()->skipProductImageOnDuplicate());
}
if (is_null($newProduct->getSkipImagesOnDuplicate()) && $this->_getImageHelper()->skipProductImageOnDuplicate() === -1) { # todo: use constant
$newProduct->setSkipImagesOnDuplicate(false);
} else {
$newProduct->setSkipImagesOnDuplicate($this->_getImageHelper()->skipProductImageOnDuplicate());
}


Mage::dispatchEvent(
'catalog_model_product_duplicate',
['current_product' => $this, 'new_product' => $newProduct],
Expand Down Expand Up @@ -1437,7 +1445,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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,15 @@ public function beforeSave($object)
$value['images'] = Mage::helper('core')->jsonDecode($value['images']);
}

if (!isset($value['values'])) {
if (!isset($value['values']) || $object->getSkipImagesOnDuplicate()) {
$value['values'] = [];
}

if (!is_array($value['values']) && (string) $value['values'] !== '') {
$value['values'] = Mage::helper('core')->jsonDecode($value['values']);
}

if (!is_array($value['images'])) {
if (!is_array($value['images']) || $object->getSkipImagesOnDuplicate()) {
$value['images'] = [];
}

Expand Down Expand Up @@ -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;
}

Expand Down
44 changes: 43 additions & 1 deletion app/code/core/Mage/Catalog/Model/Resource/Product.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
*/
protected $_productCategoryTable;

/**
* Used when duplicating product
*
* @var string
*/
protected $_skipImagesOnDuplicate = false;

Check failure on line 36 in app/code/core/Mage/Catalog/Model/Resource/Product.php

View workflow job for this annotation

GitHub Actions / PHPStan / Analyze

Property Mage_Catalog_Model_Resource_Product::$_skipImagesOnDuplicate (string) does not accept default value of type false.
Comment on lines +31 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* Used when duplicating product
*
* @var string
*/
protected $_skipImagesOnDuplicate = false;
/**
* Used when duplicating product
*/
protected bool $skipImagesOnDuplicate = false;


/**
* Initialize resource
*/
Expand Down Expand Up @@ -565,9 +572,22 @@
{
$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]);
Expand All @@ -583,6 +603,10 @@
->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,
Expand Down Expand Up @@ -719,4 +743,22 @@

return $this->_getReadAdapter()->fetchCol($select);
}

/**
* @param bool $newProductSkipImages
* @return $this
*/
public function setSkipImagesOnDuplicate(bool $newProductSkipImages){
$this->_skipImagesOnDuplicate = $newProductSkipImages;

Check failure on line 752 in app/code/core/Mage/Catalog/Model/Resource/Product.php

View workflow job for this annotation

GitHub Actions / PHPStan / Analyze

Property Mage_Catalog_Model_Resource_Product::$_skipImagesOnDuplicate (string) does not accept bool.
Comment on lines +747 to +752
Copy link
Contributor

@sreichel sreichel Nov 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* @param bool $newProductSkipImages
* @return $this
*/
public function setSkipImagesOnDuplicate(bool $newProductSkipImages){
$this->_skipImagesOnDuplicate = $newProductSkipImages;
/**
* @return $this
*/
public function setSkipImagesOnDuplicate(bool $newProductSkipImages)
{
$this->skipImagesOnDuplicate = $newProductSkipImages;

return $this;
}

/**
* @return bool|string
*/
public function getSkipImagesOnDuplicate(){
return $this->_skipImagesOnDuplicate;
}
Comment on lines +756 to +761
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* @return bool|string
*/
public function getSkipImagesOnDuplicate(){
return $this->_skipImagesOnDuplicate;
}
public function getSkipImagesOnDuplicate(): bool
{
return $this->skipImagesOnDuplicate;
}



Comment on lines +762 to +763
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

}
1 change: 1 addition & 0 deletions app/code/core/Mage/Catalog/etc/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,7 @@
<base_width>1800</base_width>
<small_width>210</small_width>
<max_dimension>5000</max_dimension>
<images_on_duplicate_action>-1</images_on_duplicate_action>
</product_image>
<seo>
<product_url_suffix>.html</product_url_suffix>
Expand Down
10 changes: 10 additions & 0 deletions app/code/core/Mage/Catalog/etc/system.xml
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,16 @@
<show_in_default>1</show_in_default>
<validate>validate-digits validate-greater-than-zero</validate>
</progressive_threshold>
<images_on_duplicate_action translate="label comment">
<label>Skip Images on Duplicate</label>
<comment>'Ask' option only affects Admin interface. Default for programmatical duplication is to persist images.</comment>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_catalog_imageDuplicate</source_model>
<sort_order>50</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
</images_on_duplicate_action>
</fields>
</product_image>
<placeholder translate="label">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,38 @@
return 1;
}

function openDuplicateDialog(keepImagesUrl,skipImagesUrl) {
var html = '<p><small><?php echo $this->__('You can disable this message on'); ?>:<br/> <i><?php echo $this->__('System'); ?> > <?php echo $this->__('Configuration'); ?> > <?php echo $this->__('Catalog Images'); ?> > <?php echo $this->__('Product Image'); ?></i></small></p><br/>';

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:'<?php echo $this->__('Copy the images onto the new product?') ?>',
recenterAuto:false,
hideEffect:Element.hide,
showEffect:Element.show,
id:"duplicate-product",
buttonClass:"form-button",
okLabel:"<?php echo $this->__('Yes'); ?>",
ok: duplicateKeepImages.bind(this),
cancelLabel: "<?php echo $this->__('Duplicate product without images'); ?>",
cancel: duplicateSkipImages.bind(this),
});
}

Event.observe(window, 'load', function() {
var objName = '<?php echo $this->getSelectedTabId() ?>';
if (objName) {
Expand Down
7 changes: 7 additions & 0 deletions app/locale/en_US/Mage_Adminhtml.csv
Original file line number Diff line number Diff line change
Expand Up @@ -1312,3 +1312,10 @@
"{{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 <a href=""%s"">configuration</a>.","{{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 <a href=""%s"">configuration</a>."
"Powered by OpenMage","Powered by OpenMage"
"At least one currency has to be allowed.","At least one currency has to be allowed."
"You can disable this message on","You can disable this message on"
"Copy the images onto the new product?","Copy the images onto the new product?"
"Copy images to the new product","Copy images to the new product"
"Always ask","Always ask"
"Duplicate product without images","Duplicate product without images"
"Skip Images on Duplicate","Skip 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."
Loading