Skip to content

Commit f90c4f4

Browse files
committed
Merge remote-tracking branch 'upstream/v2.1.3'
2 parents 08f818b + 2e53046 commit f90c4f4

File tree

4 files changed

+227
-69
lines changed

4 files changed

+227
-69
lines changed

Controller/Adminhtml/Ajax/UpdateAdminImage.php

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Cloudinary\Cloudinary\Core\ConfigurationBuilder;
77
use Cloudinary\Cloudinary\Core\Image\ImageFactory;
88
use Cloudinary\Cloudinary\Core\UrlGenerator;
9+
use Cloudinary\Cloudinary\Core\Image;
910
use Cloudinary\Configuration\Configuration;
1011
use Magento\Store\Model\StoreManagerInterface;
1112
use Magento\Framework\UrlInterface;
@@ -14,6 +15,7 @@
1415
use Magento\Backend\App\Action\Context;
1516
use Cloudinary\Asset\Media;
1617
use Cloudinary\Cloudinary\Core\Image\Transformation;
18+
use Psr\Log\LoggerInterface;
1719

1820
class UpdateAdminImage extends Action
1921
{
@@ -27,6 +29,7 @@ class UpdateAdminImage extends Action
2729
protected $resultFactory;
2830
protected $configurationBuilder;
2931
protected $transformation;
32+
protected $logger;
3033
private $_authorised;
3134

3235

@@ -40,6 +43,7 @@ class UpdateAdminImage extends Action
4043
* @param ResultRawFactory $resultFactory
4144
* @param ConfigurationBuilder $configurationBuilder
4245
* @param Transformation $transformation
46+
* @param LoggerInterface $logger
4347
*/
4448
public function __construct(
4549
Context $context,
@@ -50,7 +54,8 @@ public function __construct(
5054
UrlInterface $urlInterface,
5155
ResultRawFactory $resultFactory,
5256
ConfigurationBuilder $configurationBuilder,
53-
Transformation $transformation
57+
Transformation $transformation,
58+
LoggerInterface $logger
5459
) {
5560
parent::__construct($context);
5661
$this->imageFactory = $imageFactory;
@@ -61,6 +66,7 @@ public function __construct(
6166
$this->resultFactory = $resultFactory;
6267
$this->configurationBuilder = $configurationBuilder;
6368
$this->transformation = $transformation;
69+
$this->logger = $logger;
6470
}
6571

6672
protected function _isAllowed()
@@ -83,12 +89,12 @@ private function authorise()
8389
public function execute()
8490
{
8591
$this->authorise();
86-
$result = ['error' => 'Invalid configuration'];
92+
$remoteImageUrl = $this->getRequest()->getParam('remote_image');
93+
$result = $remoteImageUrl ?: ['error' => 'Invalid configuration'];
8794

8895
if ($this->configuration->isEnabled()) {
8996
try {
90-
$remoteImageUrl = $this->getRequest()->getParam('remote_image');
91-
97+
9298
// Validate URL
9399
if (!$remoteImageUrl) {
94100
throw new \InvalidArgumentException('Missing remote_image parameter');
@@ -103,34 +109,24 @@ public function execute()
103109
$cleanUrl = $parsedUrl['scheme'] . '://' . $parsedUrl['host'] . $parsedUrl['path'];
104110
$baseUrl = $this->storeManager->getStore()->getBaseUrl();
105111
$relativePath = str_replace($baseUrl, '', $cleanUrl);
112+
$relativePath = ltrim($relativePath, '/');
106113

107-
// Check if this is a Cloudinary rendition path
108-
if (strpos($relativePath, '.renditions/cloudinary/') !== false) {
109-
$parts = explode('.renditions/cloudinary/', $relativePath);
110-
$filename = end($parts);
111-
112-
// Remove the first cld_ prefix if there are multiple
113-
if (preg_match('/^cld_[a-zA-Z0-9]+_/', $filename)) {
114-
$filename = preg_replace('/^cld_[a-zA-Z0-9]+_/', '', $filename);
115-
}
114+
// Create Image object and use UrlGenerator (same as storefront)
115+
$image = Image::fromPath($remoteImageUrl, $relativePath);
116116

117-
$fileId = 'media/' . $filename;
118-
} else {
119-
$fileId = $relativePath;
120-
}
117+
// Use UrlGenerator which handles all the logic including database mapping
118+
$cloudinaryUrl = $this->urlGenerator->generateFor($image, $this->transformation);
119+
$result = (string)$cloudinaryUrl;
121120

122-
$result = Media::fromParams(
123-
$fileId,
121+
} catch (\Exception $e) {
122+
// Return original URL on error for graceful fallback
123+
$this->logger->error(
124+
'Cloudinary UpdateAdminImage error: ' . $e->getMessage(),
124125
[
125-
'transformation' => $this->transformation->build(),
126-
'secure' => true,
127-
'sign_url' => $this->configuration->getUseSignedUrls(),
128-
'version' => 1
126+
'image_url' => $remoteImageUrl,
127+
'exception' => $e
129128
]
130-
) . '?_i=AB';
131-
132-
} catch (\Exception $e) {
133-
$result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()];
129+
);
134130
}
135131
}
136132

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "cloudinary/cloudinary-magento2",
33
"description": "Cloudinary Magento 2 Integration.",
44
"type": "magento2-module",
5-
"version": "2.1.2",
5+
"version": "2.1.3",
66
"license": "MIT",
77
"require": {
88
"php": ">=7.3",

etc/module.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0"?>
22
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
3-
<module name="Cloudinary_Cloudinary" setup_version="2.1.2">
3+
<module name="Cloudinary_Cloudinary" setup_version="2.1.3">
44
<sequence>
55
<module name="Magento_ProductVideo"/>
66
<module name="Magento_PageBuilder"/>
Lines changed: 202 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,217 @@
1+
/**
2+
* Cloudinary Preview Update Handler
3+
* Manages image preview updates with Cloudinary transformed images in Page Builder
4+
*/
15
define(
2-
['jquery','Magento_PageBuilder/js/events','mage/url'],
3-
function($,_PBEvents, urlBuilder){
4-
'use strict'
6+
['jquery', 'Magento_PageBuilder/js/events'],
7+
function ($, _PBEvents) {
8+
'use strict';
9+
10+
/**
11+
* @param {Object} config - Configuration object
12+
* @param {string} config.ajaxUrl - URL for AJAX requests
13+
* @param {string} config.saveButtonSelector - Selector for save button (default: '#save-button')
14+
* @param {Element} element - DOM element
15+
* @returns {Object} updateHandler instance
16+
*/
517
return function (config, element) {
6-
var updateHandler = {
7-
images: [],
18+
let updateHandler = {
19+
images: {},
20+
pendingRequests: {},
21+
processedImages: new Set(),
22+
saveButtonSelector: config.saveButtonSelector || '#save-button',
23+
eventHandlers: [],
24+
25+
/**
26+
* Initialize the handler and set up event listeners
27+
*/
828
init: function () {
929
let self = this;
10-
_PBEvents.on('image:renderAfter', function (event){
11-
let elem = event.element, key = event.id;
12-
let image = $(elem).find('img');
13-
let src = {'remote_image': image.attr('src')};
14-
self.images.push(src);
30+
31+
// Store event handler references for cleanup
32+
let renderAfterHandler = function (event) {
33+
let elem = event.element,
34+
key = event.id,
35+
image = $(elem).find('img'),
36+
imageSrc = image.attr('src');
37+
38+
// Skip if no image source or already processed
39+
if (!imageSrc || self.processedImages.has(imageSrc)) {
40+
return;
41+
}
42+
43+
self.images[key] = {
44+
key: key,
45+
remote_image: imageSrc
46+
};
1547

1648
self.update(key);
49+
};
50+
51+
let saveButtonHandler = function () {
52+
self.restoreOriginalImages();
53+
};
54+
55+
// Attach event handlers
56+
_PBEvents.on('image:renderAfter', renderAfterHandler);
57+
$(self.saveButtonSelector).on('click', saveButtonHandler);
58+
59+
// Store handlers for cleanup
60+
self.eventHandlers.push({
61+
event: 'image:renderAfter',
62+
handler: renderAfterHandler
1763
});
18-
$('#save-button').on('click', function (e){
19-
self.images.forEach(function(elem){
20-
if (elem.cld_image) {
21-
let cld_src = elem.cld_image;
22-
let img = $('img[src="' + cld_src +'"]');
23-
if (img.length) {
24-
img.attr('src', elem.remote_image);
25-
}
26-
}
27-
});
64+
self.eventHandlers.push({
65+
selector: self.saveButtonSelector,
66+
event: 'click',
67+
handler: saveButtonHandler
2868
});
69+
70+
return self;
2971
},
30-
update: function(key) {
72+
73+
/**
74+
* Restore original images before save
75+
*/
76+
restoreOriginalImages: function () {
3177
let self = this;
32-
this.images.forEach(function(elem,ind){
33-
$.ajax({
34-
url: config.ajaxUrl,
35-
type: 'POST',
36-
dataType: 'json',
37-
data: elem,
38-
success: function(image) {
39-
self.images[ind].cld_image = image;
40-
let img = $('img[src="' + self.images[ind].remote_image +'"]');
41-
if (img.length) {
42-
img.attr('src', self.images[ind].cld_image);
43-
}
44-
},
45-
error: function(xhr, textStatus, errorThrown) {
46-
console.log('Error:', textStatus, errorThrown);
78+
79+
$.each(self.images, function (_, elem) {
80+
if (elem.cld_image) {
81+
// Use safer selector approach
82+
$('img').filter(function () {
83+
return $(this).attr('src') === elem.cld_image;
84+
}).attr('src', elem.remote_image);
85+
}
86+
});
87+
},
88+
89+
/**
90+
* Validate image URL
91+
* @param {string} url - URL to validate
92+
* @returns {boolean} Whether URL is valid
93+
*/
94+
isValidImageUrl: function (url) {
95+
if (!url || typeof url !== 'string') {
96+
return false;
97+
}
98+
99+
// Basic URL validation
100+
try {
101+
let urlObj = new URL(url, window.location.origin);
102+
return /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(urlObj.pathname) ||
103+
url.indexOf('media/') !== -1;
104+
} catch (e) {
105+
return false;
106+
}
107+
},
108+
109+
/**
110+
* Update image with Cloudinary version
111+
* @param {string} key - Image key
112+
*/
113+
update: function (key) {
114+
let self = this,
115+
imageData = self.images[key];
116+
117+
// Validation checks
118+
if (!imageData || !imageData.remote_image) {
119+
return;
120+
}
121+
122+
if (!self.isValidImageUrl(imageData.remote_image)) {
123+
console.warn('Invalid image URL:', imageData.remote_image);
124+
return;
125+
}
126+
127+
// Check if already processing this image URL
128+
let imageUrl = imageData.remote_image;
129+
if (self.processedImages.has(imageUrl)) {
130+
return;
131+
}
132+
133+
// Abort previous request for this key if still pending
134+
if (self.pendingRequests[key]) {
135+
self.pendingRequests[key].abort();
136+
}
137+
138+
// Mark as being processed
139+
self.processedImages.add(imageUrl);
140+
141+
// Make AJAX request and store reference
142+
self.pendingRequests[key] = $.ajax({
143+
url: config.ajaxUrl,
144+
type: 'POST',
145+
dataType: 'json',
146+
data: {
147+
remote_image: imageData.remote_image,
148+
form_key: window.FORM_KEY || ''
149+
},
150+
success: function (image) {
151+
152+
delete self.pendingRequests[key];
153+
154+
// Validate response
155+
if (!image || typeof image !== 'string') {
156+
console.warn('Invalid response for image:', key);
157+
return;
47158
}
48-
});
49159

50-
})
160+
self.images[key].cld_image = image;
161+
162+
// Update image in DOM using safer selector
163+
$('img').filter(function () {
164+
return $(this).attr('src') === imageData.remote_image;
165+
}).attr('src', image);
166+
},
167+
error: function (xhr, textStatus, errorThrown) {
168+
// Clean up pending request reference
169+
delete self.pendingRequests[key];
170+
171+
// Only log if not aborted
172+
if (textStatus !== 'abort') {
173+
console.error('Cloudinary image update failed:', {
174+
key: key,
175+
status: textStatus,
176+
error: errorThrown,
177+
response: xhr.responseText
178+
});
179+
180+
// Remove from processed set to allow retry
181+
self.processedImages.delete(imageUrl);
182+
}
183+
}
184+
});
185+
},
186+
187+
/**
188+
* Cleanup event listeners and abort pending requests
189+
*/
190+
destroy: function () {
191+
let self = this;
192+
193+
// Remove event listeners
194+
self.eventHandlers.forEach(function (handler) {
195+
if (handler.selector) {
196+
$(handler.selector).off(handler.event, handler.handler);
197+
} else if (handler.event) {
198+
_PBEvents.off(handler.event, handler.handler);
199+
}
200+
});
201+
202+
// Abort all pending AJAX requests
203+
$.each(self.pendingRequests, function (_, request) {
204+
request.abort();
205+
});
206+
207+
// Clear data
208+
self.images = {};
209+
self.pendingRequests = {};
210+
self.processedImages.clear();
211+
self.eventHandlers = [];
51212
}
52213
};
214+
53215
return updateHandler.init();
54-
}
55-
});
216+
};
217+
});

0 commit comments

Comments
 (0)