Skip to content

Commit 48ebebb

Browse files
committedFeb 25, 2025
version 2.0.0
2 parents 674f840 + b133a55 commit 48ebebb

11 files changed

+320
-14
lines changed
 

‎Helper/ProductGalleryHelper.php

+47-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
use Cloudinary\Cloudinary\Core\ConfigurationInterface;
66
use Magento\Framework\App\Helper\Context;
7-
7+
use Magento\Theme\Model\Theme\ThemeProvider;
8+
use Magento\Store\Model\StoreManagerInterface;
9+
use Magento\Framework\App\Config\ScopeConfigInterface;
10+
use Magento\Store\Model\ScopeInterface;
811
class ProductGalleryHelper extends \Magento\Framework\App\Helper\AbstractHelper
912
{
1013

@@ -45,15 +48,38 @@ class ProductGalleryHelper extends \Magento\Framework\App\Helper\AbstractHelper
4548
];
4649

4750
/**
48-
* @param Context $context
49-
* @param ConfigurationInterface $configuration
51+
* @var ScopeConfigInterface
52+
*/
53+
protected $scopeConfig;
54+
/**
55+
* @var ThemeList
5056
*/
57+
protected $themeProvider;
58+
59+
protected $storeManager;
60+
61+
5162
public function __construct(
5263
Context $context,
53-
ConfigurationInterface $configuration
64+
ConfigurationInterface $configuration,
65+
StoreManagerInterface $storeManager,
66+
ScopeConfigInterface $scopeConfig,
67+
ThemeProvider $themeProvider
5468
) {
5569
parent::__construct($context);
5670
$this->configuration = $configuration;
71+
$this->storeManager = $storeManager;
72+
$this->scopeConfig = $scopeConfig;
73+
$this->themeProvider = $themeProvider;
74+
}
75+
76+
protected function LogToFile($msg, $file = '/var/log/cloudinary.log')
77+
{
78+
$writer = new \Zend_Log_Writer_Stream(BP . $file);
79+
$logger = new \Zend_Log();
80+
$logger->addWriter($writer);
81+
82+
$logger->info(print_r($msg, true));
5783
}
5884

5985
/**
@@ -178,6 +204,23 @@ public function getCname()
178204
return ($config['cname']) ?? '';
179205
}
180206

207+
/**
208+
* Check if Hyvä Theme is active
209+
*
210+
* @return bool
211+
*/
212+
public function isHyvaThemeEnabled()
213+
{
214+
215+
$themeId = $this->scopeConfig->getValue(
216+
'design/theme/theme_id',
217+
\Magento\Store\Model\ScopeInterface::SCOPE_STORE,
218+
$this->storeManager->getStore()->getId()
219+
);
220+
221+
$theme = $this->themeProvider->getThemeById($themeId);
222+
return $theme && strpos($theme->getCode(), 'Hyva/') === 0;
223+
}
181224

182225

183226

‎Plugin/SwatchImageUrlPlugin.php

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class SwatchImageUrlPlugin
3737

3838
protected $imageFactory;
3939

40+
protected $mediaDirectory;
41+
4042
/**
4143
* @param StoreManagerInterface $storeManager
4244
* @param UrlInterface $urlBuilder

‎composer.json

+1-1
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": "1.20.52",
5+
"version": "2.0.0",
66
"license": "MIT",
77
"require": {
88
"cloudinary/cloudinary_php": ">=2.7 <=2.11.0"

‎etc/module.xml

+1-1
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="1.20.52">
3+
<module name="Cloudinary_Cloudinary" setup_version="2.0.0">
44
<sequence>
55
<module name="Magento_ProductVideo"/>
66
<module name="Magento_PageBuilder"/>

‎view/frontend/layout/catalog_product_view.xml

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
<?xml version="1.0"?>
22
<page layout="1column" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
33
<head>
4-
<link rel="stylesheet" type="text/css" src="https://unpkg.com/cloudinary-video-player@1.10.5/dist/cld-video-player.min.css" src_type="url" />
5-
4+
<css src="https://unpkg.com/cloudinary-video-player/dist/cld-video-player.min.css" rel="stylesheet" src_type="url" />
65
</head>
76
<body>
87
<referenceContainer name="before.body.end">

‎view/frontend/templates/product/gallery.phtml

+156-4
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,160 @@
22
/**
33
* @var \Magento\Catalog\Block\Product\View\Gallery $block
44
*/
5+
$cloudinaryGalleryHelper = $this->helper('Cloudinary\Cloudinary\Helper\ProductGalleryHelper');
56
?>
6-
<div id="<?= /* @escapeNotVerified */ $this->getCldPGid() ?>"
7-
class="cloudinary-product-gallery"
8-
data-mage-init='{"cloudinaryProductGallery": <?= $block->getCloudinaryPGOptions() ?>}'
9-
style="max-width:100%;margin:auto"></div>
7+
8+
<?php if ($cloudinaryGalleryHelper->isHyvaThemeEnabled()): ?>
9+
<script>
10+
document.addEventListener("alpine:init", () => {
11+
Alpine.data("cloudinaryGallery", () => ({
12+
cld_gallery: null,
13+
cld_mediaAssets: [],
14+
15+
init() {
16+
this.loadCloudinary(() => {
17+
this.$nextTick(() => {
18+
this.setupGallery();
19+
this.registerSwatchListener();
20+
});
21+
});
22+
},
23+
24+
loadCloudinary(callback) {
25+
if (window.cloudinary && typeof cloudinary.galleryWidget === "function") {
26+
callback();
27+
return;
28+
}
29+
30+
let script = document.createElement("script");
31+
script.src = "https://product-gallery.cloudinary.com/latest/all.js";
32+
script.async = true;
33+
script.onload = callback;
34+
document.head.appendChild(script);
35+
},
36+
37+
setupGallery() {
38+
let galleryElement = document.querySelector(`[data-selector="cloudinary-product-gallery"]`);
39+
if (!galleryElement) {
40+
setTimeout(() => this.setupGallery(), 500);
41+
return;
42+
}
43+
44+
let cldOptions = galleryElement.getAttribute("data-cloudinary-options");
45+
if (!cldOptions) return;
46+
47+
try {
48+
cldOptions = JSON.parse(cldOptions);
49+
} catch (e) {
50+
return;
51+
}
52+
this.cld_mediaAssets = cldOptions.cloudinaryPGoptions.mediaAssets || [];
53+
54+
let options = {
55+
cloudinaryPGoptions: cldOptions.cloudinaryPGoptions || {},
56+
cldPGid: cldOptions.cldPGid || galleryElement.id,
57+
mediaAssets: this.cld_mediaAssets,
58+
};
59+
60+
window.cloudinary_pg = window.cloudinary_pg || {};
61+
let cldpgid = options.cldPGid;
62+
63+
if (!window.cloudinary_pg[cldpgid]) {
64+
this.cld_gallery = window.cloudinary_pg[cldpgid] = cloudinary.galleryWidget(options.cloudinaryPGoptions);
65+
this.cld_gallery.render();
66+
} else {
67+
this.cld_gallery = window.cloudinary_pg[cldpgid];
68+
}
69+
},
70+
71+
registerSwatchListener() {
72+
document.querySelectorAll("label.swatch-option").forEach((label) => {
73+
if (!label.dataset.listenerAdded) {
74+
label.dataset.listenerAdded = "true";
75+
label.addEventListener("click", () => {
76+
let input = label.querySelector("input.product-option-value-input");
77+
if (input) {
78+
let selectedSwatch = input.value;
79+
let newImages = this.getProductImages(selectedSwatch);
80+
if (newImages.length > 0) {
81+
this.updateCloudinaryGallery(newImages);
82+
}
83+
}
84+
});
85+
}
86+
});
87+
},
88+
89+
getProductImages(selectedSwatch) {
90+
const productIdElement = document.querySelector('input[name="product"]');
91+
if (!productIdElement) return [];
92+
93+
const productId = productIdElement.value;
94+
const functionName = `initConfigurableSwatchOptions_${productId}`;
95+
if (typeof window[functionName] !== "function") {
96+
setTimeout(() => this.getProductImages(selectedSwatch), 1000);
97+
return [];
98+
}
99+
100+
let configData = window[functionName]();
101+
if (!configData?.optionConfig?.images) return [];
102+
103+
let matchingProductId = Object.keys(configData.optionConfig.index).find(
104+
(id) => configData.optionConfig.index[id]["93"] == selectedSwatch
105+
);
106+
107+
return matchingProductId ? configData.optionConfig.images[matchingProductId] : [];
108+
},
109+
110+
extractImageName(url) {
111+
let fileNameWithExtension = url.substring(url.lastIndexOf("/") + 1);
112+
let firstChar = fileNameWithExtension.charAt(0);
113+
let secondChar = fileNameWithExtension.charAt(1);
114+
let publicId = `media/catalog/product/${firstChar}/${secondChar}/${fileNameWithExtension}`;
115+
return { publicId: publicId.replace("?_i=AB", ""), mediaType: "image" };
116+
},
117+
118+
mergeMediaAssets(existingAssets, newAssets) {
119+
const existingPublicIds = existingAssets.map(asset => asset.publicId);
120+
return [
121+
...existingAssets,
122+
...newAssets.filter(newAsset => !existingPublicIds.includes(newAsset.publicId)),
123+
];
124+
},
125+
126+
updateCloudinaryGallery(newImages) {
127+
if (!this.cld_gallery) {
128+
setTimeout(() => this.updateCloudinaryGallery(newImages), 500);
129+
return;
130+
}
131+
132+
if (typeof this.cld_gallery.update !== "function") return;
133+
134+
let newMediaAssets = newImages.map(image => this.extractImageName(image.full));
135+
let updatedAssets = this.mergeMediaAssets(newMediaAssets, this.cld_mediaAssets);
136+
137+
138+
this.$nextTick(() => {
139+
try {
140+
this.cld_gallery.update({ mediaAssets: updatedAssets });
141+
this.cld_gallery.setItem(0);
142+
} catch (error) {}
143+
});
144+
}
145+
}));
146+
});
147+
</script>
148+
149+
<div x-data="cloudinaryGallery"
150+
data-cloudinary-options='<?= $block->getCloudinaryPGOptions() ?>'
151+
id="<?= /* @escapeNotVerified */ $this->getCldPGid() ?>"
152+
data-selector="cloudinary-product-gallery"
153+
class="w-full pt-6 md:pt-0 md:h-auto md:row-start-1 md:row-span-2 md:col-start-1"
154+
></div>
155+
156+
<?php else: ?>
157+
<div id="<?= /* @escapeNotVerified */ $this->getCldPGid() ?>"
158+
class="cloudinary-product-gallery"
159+
data-mage-init='{"cloudinaryProductGallery": <?= $block->getCloudinaryPGOptions() ?>}'
160+
style="max-width:100%;margin:auto"></div>
161+
<?php endif; ?>
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<script type="text/javascript" src="https://unpkg.com/cloudinary-video-player@1.10.5/dist/cld-video-player.min.js" defer></script>
1+
<script type="text/javascript" src="https://unpkg.com/cloudinary-video-player/dist/cld-video-player.min.js" defer></script>

‎view/frontend/templates/product/video/settings.phtml

+6
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,9 @@
1818
<script id="cld_video_settings" type="application/json">
1919
<?= json_encode($block->getVideoSettings()) ?>
2020
</script>
21+
<?php
22+
$cloudinaryGalleryHelper = $this->helper('Cloudinary\Cloudinary\Helper\ProductGalleryHelper');
23+
?>
24+
<?php if ($cloudinaryGalleryHelper->isHyvaThemeEnabled()): ?>
25+
<script src="<?= $block->getViewFileUrl('Cloudinary_Cloudinary::js/hyva/cloudinary-video-support.js') ?>"></script>
26+
<?php endif; ?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
document.addEventListener("alpine:init", () => {
2+
if (typeof initGallery === "function") {
3+
const originalInitGallery = initGallery;
4+
5+
window.initGallery = function () {
6+
const gallery = originalInitGallery();
7+
8+
gallery.ensureYouTubeIframeExists = function () {
9+
let youtubeContainer = document.querySelector("#youtube-player");
10+
11+
if (!youtubeContainer || youtubeContainer.tagName !== "IFRAME") {
12+
const iframe = document.createElement("iframe");
13+
iframe.id = "youtube-player";
14+
iframe.classList.add("w-full", "h-full");
15+
iframe.width = "640";
16+
iframe.height = "360";
17+
iframe.allow = "autoplay; fullscreen";
18+
iframe.frameBorder = "0";
19+
iframe.src = "about:blank";
20+
youtubeContainer?.replaceWith(iframe);
21+
}
22+
};
23+
24+
document.addEventListener("DOMContentLoaded", () => {
25+
gallery.ensureYouTubeIframeExists();
26+
});
27+
28+
const originalGetVideoData = gallery.getVideoData;
29+
gallery.getVideoData = function () {
30+
const videoData = originalGetVideoData.apply(this);
31+
if (videoData && videoData.id && videoData.type) {
32+
return videoData;
33+
}
34+
35+
const activeImage = this.images[this.active];
36+
const videoUrl = activeImage && activeImage.videoUrl;
37+
38+
if (!videoUrl || !videoUrl.includes("cloudinary.com")) {
39+
return false;
40+
}
41+
42+
const transformedUrl = videoUrl.replace("/upload/", "/upload/f_auto,q_auto:good,vc_auto/");
43+
44+
return {
45+
id: transformedUrl,
46+
type: "cloudinary",
47+
url: transformedUrl,
48+
};
49+
};
50+
51+
const originalActivateVideo = gallery.activateVideo;
52+
gallery.activateVideo = function () {
53+
const videoData = this.getVideoData();
54+
if (!videoData || !videoData.id || !videoData.type) {
55+
return;
56+
}
57+
58+
this.ensureYouTubeIframeExists();
59+
60+
if (videoData.type === "cloudinary") {
61+
this.activeVideoType = "youtube";
62+
this.$nextTick(() => {
63+
this.loadCloudinaryIntoYouTubeIframe(videoData);
64+
});
65+
return;
66+
}
67+
68+
if (videoData.type === "youtube") {
69+
this.$nextTick(() => {
70+
this.resetYouTubeIframe(videoData);
71+
});
72+
}
73+
74+
originalActivateVideo.apply(this);
75+
};
76+
77+
gallery.loadCloudinaryIntoYouTubeIframe = function (videoData) {
78+
this.$nextTick(() => {
79+
const youtubeIframe = document.querySelector("#youtube-player");
80+
if (!youtubeIframe || youtubeIframe.tagName !== "IFRAME") {
81+
return;
82+
}
83+
84+
youtubeIframe.src = "";
85+
youtubeIframe.src = `${videoData.url}?autoplay=1&loop=0&controls=1`;
86+
});
87+
};
88+
89+
gallery.resetYouTubeIframe = function (videoData) {
90+
this.$nextTick(() => {
91+
const youtubeIframe = document.querySelector("#youtube-player");
92+
if (!youtubeIframe || youtubeIframe.tagName !== "IFRAME") {
93+
return;
94+
}
95+
96+
youtubeIframe.src = `https://www.youtube.com/embed/${videoData.id}?autoplay=1`;
97+
});
98+
};
99+
100+
return gallery;
101+
};
102+
}
103+
});

0 commit comments

Comments
 (0)