Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 2a604c2

Browse files
committed
Load site's Logo from Cloudinary
1 parent 0a623c7 commit 2a604c2

File tree

2 files changed

+363
-0
lines changed

2 files changed

+363
-0
lines changed
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
<?php
2+
3+
namespace Cloudinary\Cloudinary\Plugin\Theme\Block\Html\Header;
4+
5+
use Cloudinary\Api\Admin\AdminApi;
6+
use Cloudinary\Api\Upload\UploadApi;
7+
use Cloudinary\Cloudinary\Core\CloudinaryImageManager;
8+
use Cloudinary\Cloudinary\Core\ConfigurationInterface;
9+
use Cloudinary\Cloudinary\Core\ConfigurationBuilder;
10+
use Cloudinary\Cloudinary\Core\Image;
11+
use Cloudinary\Cloudinary\Core\UrlGenerator;
12+
use Cloudinary\Configuration\Configuration;
13+
use Magento\Framework\App\Filesystem\DirectoryList;
14+
use Magento\Framework\Filesystem;
15+
use Magento\Framework\UrlInterface;
16+
use Magento\Store\Model\ScopeInterface;
17+
use Magento\Framework\App\Config\ScopeConfigInterface;
18+
use Psr\Log\LoggerInterface;
19+
20+
class Logo
21+
{
22+
/**
23+
* @var ConfigurationInterface
24+
*/
25+
private $configuration;
26+
27+
/**
28+
* @var CloudinaryImageManager
29+
*/
30+
private $cloudinaryImageManager;
31+
32+
/**
33+
* @var ConfigurationBuilder
34+
*/
35+
private $configurationBuilder;
36+
37+
/**
38+
* @var UrlGenerator
39+
*/
40+
private $urlGenerator;
41+
42+
/**
43+
* @var Filesystem
44+
*/
45+
private $filesystem;
46+
47+
/**
48+
* @var ScopeConfigInterface
49+
*/
50+
private $scopeConfig;
51+
52+
/**
53+
* @var UrlInterface
54+
*/
55+
private $urlBuilder;
56+
57+
/**
58+
* @var LoggerInterface
59+
*/
60+
private $logger;
61+
62+
/**
63+
* @param ConfigurationInterface $configuration
64+
* @param CloudinaryImageManager $cloudinaryImageManager
65+
* @param ConfigurationBuilder $configurationBuilder
66+
* @param UrlGenerator $urlGenerator
67+
* @param Filesystem $filesystem
68+
* @param ScopeConfigInterface $scopeConfig
69+
* @param UrlInterface $urlBuilder
70+
* @param LoggerInterface $logger
71+
*/
72+
public function __construct(
73+
ConfigurationInterface $configuration,
74+
CloudinaryImageManager $cloudinaryImageManager,
75+
ConfigurationBuilder $configurationBuilder,
76+
UrlGenerator $urlGenerator,
77+
Filesystem $filesystem,
78+
ScopeConfigInterface $scopeConfig,
79+
UrlInterface $urlBuilder,
80+
LoggerInterface $logger
81+
) {
82+
$this->configuration = $configuration;
83+
$this->cloudinaryImageManager = $cloudinaryImageManager;
84+
$this->configurationBuilder = $configurationBuilder;
85+
$this->urlGenerator = $urlGenerator;
86+
$this->filesystem = $filesystem;
87+
$this->scopeConfig = $scopeConfig;
88+
$this->urlBuilder = $urlBuilder;
89+
$this->logger = $logger;
90+
}
91+
92+
/**
93+
* Plugin to intercept logo loading and serve from Cloudinary
94+
*
95+
* @param \Magento\Theme\Block\Html\Header\Logo $subject
96+
* @param callable $proceed
97+
* @return string
98+
*/
99+
public function aroundGetLogoSrc(
100+
\Magento\Theme\Block\Html\Header\Logo $subject,
101+
callable $proceed
102+
) {
103+
// Check if Cloudinary is enabled
104+
if (!$this->configuration->isEnabled()) {
105+
return $proceed();
106+
}
107+
108+
try {
109+
// Get the original logo URL first
110+
$originalLogoUrl = $proceed();
111+
112+
// Get logo path from configuration
113+
$logoPath = $this->scopeConfig->getValue(
114+
'design/header/logo_src',
115+
ScopeInterface::SCOPE_STORE
116+
);
117+
118+
if (!$logoPath) {
119+
// If no custom logo is set, use the original URL
120+
return $this->processLogoFromUrl($originalLogoUrl);
121+
}
122+
123+
// The logo path configuration doesn't include the 'logo/' prefix, so we need to add it
124+
$fullLogoPath = 'logo/' . $logoPath;
125+
126+
// Create a unique public ID for the logo (without logo/ prefix since we add it later)
127+
$publicId = pathinfo($logoPath, PATHINFO_FILENAME);
128+
129+
// Check if logo exists on Cloudinary
130+
$cloudinaryUrl = $this->checkAndUploadToCloudinary($fullLogoPath, $publicId, $originalLogoUrl);
131+
132+
if ($cloudinaryUrl) {
133+
return $cloudinaryUrl;
134+
}
135+
} catch (\Exception $e) {
136+
$this->logger->error('Cloudinary Logo Plugin Error: ' . $e->getMessage());
137+
}
138+
139+
// Fallback to original logo URL if anything fails
140+
return $proceed();
141+
}
142+
143+
/**
144+
* Check if image exists on Cloudinary and upload if necessary
145+
*
146+
* @param string $logoPath
147+
* @param string $publicId
148+
* @param string $originalUrl
149+
* @return string|null
150+
*/
151+
private function checkAndUploadToCloudinary($logoPath, $publicId, $originalUrl)
152+
{
153+
try {
154+
// Initialize Cloudinary configuration
155+
Configuration::instance($this->configurationBuilder->build());
156+
157+
// Check if the image exists on Cloudinary
158+
$existingUrl = $this->checkCloudinaryImageExists($publicId);
159+
160+
if (!$existingUrl) {
161+
// Image doesn't exist on Cloudinary, upload it
162+
$mediaDirectory = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA);
163+
$logoFullPath = null;
164+
165+
if ($logoPath && $mediaDirectory->isFile($logoPath)) {
166+
$logoFullPath = $mediaDirectory->getAbsolutePath($logoPath);
167+
} else {
168+
// If the file doesn't exist in media, try to download from original URL
169+
$logoFullPath = $this->downloadLogoFromUrl($originalUrl);
170+
if (!$logoFullPath) {
171+
return null;
172+
}
173+
}
174+
175+
// Upload to Cloudinary with specific public_id
176+
$uploader = new UploadApi($this->configuration->getCredentials());
177+
$uploadOptions = array_merge(
178+
$this->configuration->getUploadConfig()->toArray(),
179+
[
180+
'public_id' => 'logo/' . $publicId,
181+
'overwrite' => true,
182+
'resource_type' => 'image'
183+
]
184+
);
185+
186+
$uploadResult = $uploader->upload($logoFullPath, $uploadOptions);
187+
188+
if (isset($uploadResult['secure_url'])) {
189+
// Add Magento plugin metadata
190+
if (isset($uploadResult['public_id'])) {
191+
$metadata = "cld_mag_plugin=1";
192+
$uploader->addContext($metadata, [$uploadResult['public_id']]);
193+
}
194+
195+
$this->logger->info('Logo uploaded to Cloudinary with public_id: ' . $publicId);
196+
197+
// Clean up temp file if it was downloaded
198+
if (strpos($logoFullPath, '/tmp/') !== false) {
199+
@unlink($logoFullPath);
200+
}
201+
202+
return $uploadResult['secure_url'];
203+
}
204+
} else {
205+
// Image exists on Cloudinary, return its URL
206+
$this->logger->info('Logo already exists on Cloudinary with public_id: ' . $publicId);
207+
return $existingUrl;
208+
}
209+
} catch (\Exception $e) {
210+
$this->logger->error('Error uploading logo to Cloudinary: ' . $e->getMessage());
211+
}
212+
213+
return null;
214+
}
215+
216+
/**
217+
* Check if image exists on Cloudinary
218+
*
219+
* @param string $publicId
220+
* @return string|false
221+
*/
222+
private function checkCloudinaryImageExists($publicId)
223+
{
224+
try {
225+
// Initialize AdminApi to check resource existence
226+
$adminApi = new AdminApi($this->configuration->getCredentials());
227+
228+
// Build the full public_id with folder
229+
$fullPublicId = 'logo/' . $publicId;
230+
231+
try {
232+
// Try to get the resource details
233+
$result = $adminApi->asset($fullPublicId, ['resource_type' => 'image']);
234+
235+
if (isset($result['secure_url'])) {
236+
$imageUrl = $result['secure_url'];
237+
238+
// Verify the image is actually accessible by making a HEAD request
239+
if ($this->verifyImageAccessibility($imageUrl)) {
240+
return $imageUrl;
241+
} else {
242+
$this->logger->info('Cloudinary image URL not accessible, will re-upload: ' . $imageUrl);
243+
return false;
244+
}
245+
}
246+
} catch (\Exception $e) {
247+
// Resource doesn't exist, which is expected if not uploaded yet
248+
$this->logger->debug('Image not found on Cloudinary: ' . $fullPublicId);
249+
}
250+
} catch (\Exception $e) {
251+
$this->logger->debug('Error checking Cloudinary image existence: ' . $e->getMessage());
252+
}
253+
254+
return false;
255+
}
256+
257+
/**
258+
* Verify if an image URL is accessible
259+
*
260+
* @param string $url
261+
* @return bool
262+
*/
263+
private function verifyImageAccessibility($url)
264+
{
265+
try {
266+
$context = stream_context_create([
267+
'http' => [
268+
'method' => 'HEAD',
269+
'timeout' => 5,
270+
],
271+
'ssl' => [
272+
'verify_peer' => false,
273+
'verify_peer_name' => false,
274+
]
275+
]);
276+
277+
$headers = @get_headers($url, 1, $context);
278+
279+
if ($headers && isset($headers[0])) {
280+
// Check for successful HTTP status codes (200, 201, etc.)
281+
return strpos($headers[0], '200') !== false || strpos($headers[0], '201') !== false;
282+
}
283+
} catch (\Exception $e) {
284+
$this->logger->debug('Error verifying image accessibility: ' . $e->getMessage());
285+
}
286+
287+
return false;
288+
}
289+
290+
/**
291+
* Process logo from URL when no custom logo is set
292+
*
293+
* @param string $originalUrl
294+
* @return string
295+
*/
296+
private function processLogoFromUrl($originalUrl)
297+
{
298+
try {
299+
// Extract filename from URL
300+
$urlParts = parse_url($originalUrl);
301+
$path = $urlParts['path'] ?? '';
302+
$filename = pathinfo($path, PATHINFO_FILENAME);
303+
304+
if ($filename) {
305+
$publicId = 'logo/' . $filename;
306+
307+
// Check and upload to Cloudinary
308+
$cloudinaryUrl = $this->checkAndUploadToCloudinary('', $publicId, $originalUrl);
309+
310+
if ($cloudinaryUrl) {
311+
return $cloudinaryUrl;
312+
}
313+
}
314+
} catch (\Exception $e) {
315+
$this->logger->error('Error processing logo from URL: ' . $e->getMessage());
316+
}
317+
318+
return $originalUrl;
319+
}
320+
321+
/**
322+
* Download logo from URL to temporary location
323+
*
324+
* @param string $url
325+
* @return string|null
326+
*/
327+
private function downloadLogoFromUrl($url)
328+
{
329+
try {
330+
$tempDir = $this->filesystem->getDirectoryWrite(DirectoryList::TMP);
331+
$extension = pathinfo($url, PATHINFO_EXTENSION) ?: 'svg';
332+
$filename = 'logo_' . uniqid() . '.' . $extension;
333+
$tempPath = $tempDir->getAbsolutePath($filename);
334+
335+
// Create context for HTTPS requests to disable SSL verification for local development
336+
$context = stream_context_create([
337+
'http' => [
338+
'timeout' => 10,
339+
'method' => 'GET',
340+
],
341+
'ssl' => [
342+
'verify_peer' => false,
343+
'verify_peer_name' => false,
344+
]
345+
]);
346+
347+
$logoContent = file_get_contents($url, false, $context);
348+
if ($logoContent) {
349+
file_put_contents($tempPath, $logoContent);
350+
return $tempPath;
351+
}
352+
} catch (\Exception $e) {
353+
$this->logger->error('Error downloading logo from URL: ' . $e->getMessage());
354+
}
355+
356+
return null;
357+
}
358+
}

‎etc/di.xml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@
9999
<type name="Magento\Cms\Block\Widget\Block">
100100
<plugin name="cloudinary_plugin_block_cms_block_widget_block" type="Cloudinary\Cloudinary\Plugin\Cms\Block\Widget\Block"/>
101101
</type>
102+
103+
<type name="Magento\Theme\Block\Html\Header\Logo">
104+
<plugin name="cloudinary_plugin_theme_logo" type="Cloudinary\Cloudinary\Plugin\Theme\Block\Html\Header\Logo"/>
105+
</type>
106+
102107
<type name="Magento\Catalog\Model\ResourceModel\Eav\Attribute">
103108
<plugin name="cloudinary_update_swatch_plugin" type="Cloudinary\Cloudinary\Plugin\AttributeSavePlugin" />
104109
</type>

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /