diff --git a/Api/ProductGalleryManagementInterface.php b/Api/ProductGalleryManagementInterface.php
new file mode 100644
index 00000000..23a30260
--- /dev/null
+++ b/Api/ProductGalleryManagementInterface.php
@@ -0,0 +1,64 @@
+mediaLibraryHelper = $mediaLibraryHelper;
+ $this->productMetadata = $productMetadata;
+ }
+
+ /**
+ * Get Cloudinary media library widget options
+ *
+ * @param bool $multiple Allow multiple
+ * @param bool $refresh Refresh options
+ * @return string
+ */
+ public function getCloudinaryMediaLibraryWidgetOptions($multiple = false, $refresh = false)
+ {
+ if (!($cloudinaryMLoptions = $this->mediaLibraryHelper->getCloudinaryMLOptions($multiple, $refresh))) {
+ return null;
+ }
+
+ try {
+ if (version_compare($this->productMetadata->getVersion(), '2.3.5', '<=')) {
+ $imageUploadUrl = $this->_urlBuilder->addSessionParam()->getUrl('cloudinary/cms_wysiwyg_images/upload', ['type' => $this->_getMediaType()]);
+ } else {
+ $imageUploadUrl = $this->_urlBuilder->getUrl('cloudinary/cms_wysiwyg_images/upload', ['type' => $this->_getMediaType()]);
+ }
+
+ //Try to add session param on Magento versions prior to 2.3.5
+
+ } catch (\Exception $e) {
+ //Catch deprecation error on Magento 2.3.5 and above
+ throw new \Exception($e->getMessage());
+ }
+
+ return $this->_jsonEncoder->encode(
+ [
+ 'cldMLid' => 'wysiwyg_media_gallery',
+ 'imageUploaderUrl' => $imageUploadUrl,
+ 'triggerSelector' => '.media-gallery-modal',
+ 'triggerEvent' => 'fileuploaddone',
+ 'cloudinaryMLoptions' => $cloudinaryMLoptions,
+ 'addTmpExtension' => false,
+ 'cloudinaryMLshowOptions' => $this->mediaLibraryHelper->getCloudinaryMLshowOptions("image"),
+ ]
+ );
+ }
+
+ /**
+ * Return current media type based on request or data
+ *
+ * @return string
+ */
+ protected function _getMediaType()
+ {
+ if ($this->hasData('media_type')) {
+ return $this->_getData('media_type');
+ }
+ return $this->getRequest()->getParam('type');
+ }
+}
diff --git a/Block/Adminhtml/Form/Field/Free.php b/Block/Adminhtml/Form/Field/Free.php
index 46428ba9..de9e3577 100644
--- a/Block/Adminhtml/Form/Field/Free.php
+++ b/Block/Adminhtml/Form/Field/Free.php
@@ -10,6 +10,7 @@
class Free extends Field
{
+ const XML_PATH_GLOBAL_TRANSFORMATION = 'cloudinary/transformations/cloudinary_free_transform_global';
/**
* @var ConfigurationInterface
*/
@@ -21,10 +22,10 @@ class Free extends Field
private $model;
/**
- * @param Context $context
+ * @param Context $context
* @param ConfigurationInterface $configuration
- * @param FreeBackendModel $model
- * @param array $data
+ * @param FreeBackendModel $model
+ * @param array $data
*/
public function __construct(
Context $context,
@@ -47,8 +48,12 @@ protected function _beforeToHtml()
return $this;
}
+ public function isCanPreviewTransformations() {
+ return $this->_scopeConfig->getValue(self::XML_PATH_GLOBAL_TRANSFORMATION);
+ }
+
/**
- * @param AbstractElement $element
+ * @param AbstractElement $element
* @return string
*/
protected function _getElementHtml(AbstractElement $element)
diff --git a/Block/Adminhtml/Product/Edit/NewVideo.php b/Block/Adminhtml/Product/Edit/NewVideo.php
index d762b3a3..15eb5bdd 100644
--- a/Block/Adminhtml/Product/Edit/NewVideo.php
+++ b/Block/Adminhtml/Product/Edit/NewVideo.php
@@ -5,37 +5,65 @@
*/
namespace Cloudinary\Cloudinary\Block\Adminhtml\Product\Edit;
+use Cloudinary\Cloudinary\Core\ConfigurationBuilder;
+use Cloudinary\Cloudinary\Core\ConfigurationInterface;
+use Magento\Backend\Block\Template\Context;
+use Magento\Framework\Data\FormFactory;
+use Magento\Framework\Json\EncoderInterface;
+use Magento\Framework\Registry;
+use Magento\Framework\UrlInterface;
+use Magento\ProductVideo\Helper\Media;
+
/**
* @SuppressWarnings(PHPMD.DepthOfInheritance)
*/
class NewVideo extends \Magento\ProductVideo\Block\Adminhtml\Product\Edit\NewVideo
{
+ /**
+ * @var array|null
+ */
protected $_cloudinaryConfig;
/**
- * @var \Cloudinary\Cloudinary\Core\ConfigurationBuilder
+ * @var ConfigurationInterface
+ */
+ private $configuration;
+
+ /**
+ * @var ConfigurationBuilder
*/
protected $_cloudinaryConfigurationBuilder;
/**
- * @param \Magento\Backend\Block\Template\Context $context
- * @param \Magento\Framework\Registry $registry
- * @param \Magento\Framework\Data\FormFactory $formFactory
- * @param \Magento\ProductVideo\Helper\Media $mediaHelper
- * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder
- * @param \Cloudinary\Cloudinary\Core\ConfigurationBuilder $cloudinaryConfigurationBuilder
- * @param array $data
+ * @method __construct
+ * @param Context $context
+ * @param Registry $registry
+ * @param FormFactory $formFactory
+ * @param Media $mediaHelper
+ * @param EncoderInterface $jsonEncoder
+ * @param ConfigurationInterface $configuration
+ * @param ConfigurationBuilder $cloudinaryConfigurationBuilder
+ * @param array $data
*/
public function __construct(
- \Magento\Backend\Block\Template\Context $context,
- \Magento\Framework\Registry $registry,
- \Magento\Framework\Data\FormFactory $formFactory,
- \Magento\ProductVideo\Helper\Media $mediaHelper,
- \Magento\Framework\Json\EncoderInterface $jsonEncoder,
- \Cloudinary\Cloudinary\Core\ConfigurationBuilder $cloudinaryConfigurationBuilder,
+ Context $context,
+ Registry $registry,
+ FormFactory $formFactory,
+ Media $mediaHelper,
+ EncoderInterface $jsonEncoder,
+ ConfigurationInterface $configuration,
+ ConfigurationBuilder $cloudinaryConfigurationBuilder,
array $data = []
) {
- parent::__construct($context, $registry, $formFactory, $mediaHelper, $jsonEncoder, $data);
+ parent::__construct(
+ $context,
+ $registry,
+ $formFactory,
+ $mediaHelper,
+ $jsonEncoder,
+ $data
+ );
+ $this->configuration = $configuration;
$this->_cloudinaryConfigurationBuilder = $cloudinaryConfigurationBuilder;
}
@@ -43,13 +71,14 @@ protected function getCloudinaryConfig()
{
if (is_null($this->_cloudinaryConfig)) {
$this->_cloudinaryConfig = $this->_cloudinaryConfigurationBuilder->build();
- if (!$this->_cloudinaryConfig['api_key'] || !$this->_cloudinaryConfig['api_secret'] || !$this->_cloudinaryConfig['cloud_name']) {
+ if (!$this->_cloudinaryConfig['cloud']['api_key'] || !$this->_cloudinaryConfig['cloud']['api_secret'] || !$this->_cloudinaryConfig['cloud']['cloud_name']) {
$this->_cloudinaryConfig = false;
} else {
- $this->_cloudinaryConfig['api_url'] = "https://api.cloudinary.com/v1_1/{$this->_cloudinaryConfig['cloud_name']}/";
+ $this->_cloudinaryConfig['cloud']['api_url'] = "https://api.cloudinary.com/v1_1/{$this->_cloudinaryConfig['cloud']['cloud_name']}/";
}
}
- return $this->_cloudinaryConfig;
+
+ return $this->_cloudinaryConfig['cloud'];
}
/**
@@ -66,7 +95,7 @@ public function getWidgetOptions()
'htmlId' => $this->getHtmlId(),
'youTubeApiKey' => $this->mediaHelper->getYouTubeApiKey(),
'videoSelector' => $this->videoSelector,
- 'cloudinaryPlaceholder' => $this->getViewFileUrl('Cloudinary_Cloudinary::images/cloudinary_logo_for_white_bg.jpg')
+ 'cloudinaryPlaceholder' => $this->getPlaceholderUrl(),
]
);
}
@@ -78,6 +107,10 @@ public function getWidgetOptions()
*/
protected function getNoteVideoUrl()
{
+ if (!$this->configuration->isModuleEnabled()) {
+ return parent::getNoteVideoUrl();
+ }
+
$result = __('Supported: Vimeo');
$messages = "";
if ($this->mediaHelper->getYouTubeApiKey() === null) {
@@ -85,11 +118,13 @@ protected function getNoteVideoUrl()
} else {
$result .= __(', YouTube');
}
+
if (!$this->getCloudinaryConfig()) {
$messages .= __(' *To add Cloudinary video, please enter your Cloudinary Account Credentials first.', $this->getCloudinaryConfigUrl());
} else {
$result .= __(', Cloudinary');
}
+
return $result . $messages;
}
@@ -107,4 +142,24 @@ protected function getCloudinaryConfigUrl()
]
);
}
+
+ /**
+ * @return string
+ */
+ protected function getPlaceholderUrl()
+ {
+ $storeManager = $this->configuration->getStoreManager();
+ $configPaths = [
+ 'catalog/placeholder/image_placeholder',
+ 'catalog/placeholder/small_image_placeholder',
+ 'catalog/placeholder/thumbnail_placeholder',
+ ];
+ foreach ($configPaths as $configPath) {
+ if (($path = $storeManager->getStore()->getConfig($configPath))) {
+ return $storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA) . 'catalog/product/placeholder/' . $path;
+ break;
+ }
+ }
+ return $this->getViewFileUrl('Cloudinary_Cloudinary::images/cloudinary_cloud_glyph_blue.png');
+ }
}
diff --git a/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php b/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php
new file mode 100644
index 00000000..9ece3fec
--- /dev/null
+++ b/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php
@@ -0,0 +1,147 @@
+
+ *
+ * @method \Magento\Framework\Data\Form\Element\AbstractElement getElement()
+ */
+namespace Cloudinary\Cloudinary\Block\Adminhtml\Product\Helper\Form\Gallery;
+
+use Cloudinary\Cloudinary\Helper\MediaLibraryHelper;
+use Cloudinary\Cloudinary\Model\ProductSpinsetMapFactory;
+use Magento\Backend\Block\Template\Context;
+use Magento\Catalog\Model\Product\Media\Config;
+use Magento\Framework\Json\DecoderInterface;
+use Magento\Framework\Json\EncoderInterface;
+
+/**
+ * Block for gallery content.
+ */
+class Content extends \Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery\Content
+{
+ /**
+ * @var string
+ */
+ protected $_template = 'Cloudinary_Cloudinary::catalog/product/helper/gallery.phtml';
+
+ /**
+ * @var DecoderInterface
+ */
+ protected $_jsonDecoder;
+
+ /**
+ * @var MediaLibraryHelper
+ */
+ protected $_mediaLibraryHelper;
+
+ /**
+ * @var ProductSpinsetMapFactory
+ */
+ protected $_productSpinsetMapFactory;
+
+ /**
+ * @method __construct
+ * @param Context $context
+ * @param EncoderInterface $jsonEncoder
+ * @param DecoderInterface $jsonDecoder
+ * @param Config $mediaConfig
+ * @param MediaLibraryHelper $mediaLibraryHelper
+ * @param ProductSpinsetMapFactory $productSpinsetMapFactory
+ * @param array $data
+ */
+ public function __construct(
+ Context $context,
+ EncoderInterface $jsonEncoder,
+ DecoderInterface $jsonDecoder,
+ Config $mediaConfig,
+ MediaLibraryHelper $mediaLibraryHelper,
+ ProductSpinsetMapFactory $productSpinsetMapFactory,
+ array $data = []
+ ) {
+ parent::__construct($context, $jsonEncoder, $mediaConfig, $data);
+ $this->_jsonDecoder = $jsonDecoder;
+ $this->_mediaLibraryHelper = $mediaLibraryHelper;
+ $this->_productSpinsetMapFactory = $productSpinsetMapFactory;
+ }
+
+ /**
+ * Get Cloudinary media library widget options
+ *
+ * @param bool $multiple Allow multiple
+ * @param bool $refresh Refresh options
+ * @return string
+ */
+ public function getCloudinaryMediaLibraryWidgetOptions($multiple = true, $refresh = false)
+ {
+ if (!($cloudinaryMLoptions = $this->_mediaLibraryHelper->getCloudinaryMLOptions($multiple, $refresh))) {
+ return null;
+ }
+
+ try {
+ //Try to add session param on Magento versions prior to 2.3.5
+ $imageUploadUrl = $this->_urlBuilder->addSessionParam()->getUrl('cloudinary/ajax/retrieveImage');
+ } catch (\Exception $e) {
+ //Catch deprecation error on Magento 2.3.5 and above
+ $imageUploadUrl = $this->_urlBuilder->getUrl('cloudinary/ajax/retrieveImage');
+ }
+
+ return $this->_jsonEncoder->encode(
+ [
+ 'htmlId' => $this->getHtmlId(),
+ 'cldMLid' => 'product_gallery_' . $this->getHtmlId(),
+ 'imageUploaderUrl' => $imageUploadUrl,
+ 'triggerSelector' => '#media_gallery_content',
+ 'triggerEvent' => 'addItem',
+ 'useDerived' => false,
+ 'addTmpExtension' => true,
+ 'cloudinaryMLoptions' => $cloudinaryMLoptions,
+ 'cloudinaryMLshowOptions' => $this->_mediaLibraryHelper->getCloudinaryMLshowOptions(null),
+ ]
+ );
+ }
+
+ /**
+ * Escape a string for the HTML attribute context
+ *
+ * @param string $string
+ * @param boolean $escapeSingleQuote
+ * @return string
+ */
+ public function escapeHtmlAttr($string, $escapeSingleQuote = true)
+ {
+ if (method_exists($this->_escaper, 'escapeHtmlAttr')) {
+ return $this->_escaper->escapeHtmlAttr($string, $escapeSingleQuote);
+ }
+ if ($escapeSingleQuote) {
+ $escaper = new \Zend\Escaper\Escaper();
+ return $escaper->escapeHtmlAttr((string) $string);
+ }
+ return htmlspecialchars((string)$string, ENT_COMPAT, 'UTF-8', false);
+ }
+
+ /**
+ * Returns image json
+ *
+ * @return string
+ */
+ public function getImagesJson()
+ {
+ $images = $this->_jsonDecoder->decode(parent::getImagesJson());
+ if ($images) {
+ foreach ($images as &$image) {
+ if ($image['media_type'] === 'image') {
+ $cldspinset = $this->_productSpinsetMapFactory->create()->getCollection()->addFieldToFilter("image_name", $image['file'])->setPageSize(1)->getFirstItem();
+ $image['cldspinset'] = $cldspinset ? $cldspinset->getCldspinset() : "";
+ }
+ }
+ return $this->_jsonEncoder->encode($images);
+ }
+ return '[]';
+ }
+}
diff --git a/Block/Adminhtml/System/Config/AutoUploadMapping.php b/Block/Adminhtml/System/Config/AutoUploadMapping.php
new file mode 100644
index 00000000..55837d21
--- /dev/null
+++ b/Block/Adminhtml/System/Config/AutoUploadMapping.php
@@ -0,0 +1,97 @@
+configuration = $configuration;
+ parent::__construct($context, $data);
+ }
+
+ /**
+ * Remove scope label
+ *
+ * @param AbstractElement $element
+ * @return string
+ */
+ public function render(AbstractElement $element)
+ {
+ $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue();
+ return parent::render($element);
+ }
+
+ /**
+ * Return element html
+ *
+ * @param AbstractElement $element
+ * @return string
+ */
+ protected function _getElementHtml(AbstractElement $element)
+ {
+ return $this->_toHtml();
+ }
+
+ /**
+ * Return ajax url for collect button
+ *
+ * @return string
+ */
+ public function getAjaxUrl()
+ {
+ return $this->getUrl('cloudinary/ajax_system_config/autoUploadMapping');
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEnabled()
+ {
+ return $this->configuration->isEnabled();
+ }
+
+ /**
+ * Generate collect button html
+ *
+ * @return string
+ */
+ public function getButtonHtml()
+ {
+ $button = $this->getLayout()->createBlock(
+ 'Magento\Backend\Block\Widget\Button'
+ )->setData(
+ [
+ 'id' => 'auto-upload-mapping-btn',
+ 'label' => __('Map media directory'),
+ 'disabled' => !$this->configuration->isEnabled(),
+ ]
+ );
+
+ return $button->toHtml();
+ }
+}
diff --git a/Block/Adminhtml/System/Config/Form/Field/ColorPicker.php b/Block/Adminhtml/System/Config/Form/Field/ColorPicker.php
new file mode 100644
index 00000000..0928b2cc
--- /dev/null
+++ b/Block/Adminhtml/System/Config/Form/Field/ColorPicker.php
@@ -0,0 +1,43 @@
+getElementHtml();
+ $value = $element->getData('value');
+
+ $html .= '';
+
+ return $html;
+ }
+}
diff --git a/Block/Adminhtml/System/Config/ModuleVersion.php b/Block/Adminhtml/System/Config/ModuleVersion.php
new file mode 100644
index 00000000..22ca29f7
--- /dev/null
+++ b/Block/Adminhtml/System/Config/ModuleVersion.php
@@ -0,0 +1,54 @@
+configuration = $configuration;
+ parent::__construct($context, $data);
+ }
+
+ /**
+ * Remove scope label
+ *
+ * @param AbstractElement $element
+ * @return string
+ */
+ public function render(AbstractElement $element)
+ {
+ $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue();
+ return parent::render($element);
+ }
+
+ /**
+ * Return element html
+ *
+ * @param AbstractElement $element
+ * @return string
+ */
+ protected function _getElementHtml(AbstractElement $element)
+ {
+ return "
{$this->configuration->getModuleVersion()}
";
+ }
+}
diff --git a/Block/Lazyload.php b/Block/Lazyload.php
new file mode 100644
index 00000000..93b91dbe
--- /dev/null
+++ b/Block/Lazyload.php
@@ -0,0 +1,61 @@
+configuration = $configuration;
+ $this->jsonEncoder = $jsonEncoder;
+ parent::__construct($context, $data);
+ }
+
+ /**
+ * @method isEnabledLazyload
+ * @return boolean
+ */
+ public function isEnabledLazyload()
+ {
+ return $this->configuration->isEnabled() && $this->configuration->isEnabledLazyload();
+ }
+
+ /**
+ * @method getLazyloadOptions
+ * @param boolean $json
+ * @return string|array
+ */
+ public function getLazyloadOptions($json = true)
+ {
+ $options = [
+ 'threshold' => $this->configuration->getLazyloadThreshold(),
+ 'effect' => $this->configuration->getLazyloadEffect(),
+ 'placeholder' => $this->configuration->getLazyloadPlaceholder(),
+ ];
+ return $json ? $this->jsonEncoder->encode($options) : $options;
+ }
+}
diff --git a/Block/Scripts.php b/Block/Scripts.php
new file mode 100644
index 00000000..033b441f
--- /dev/null
+++ b/Block/Scripts.php
@@ -0,0 +1,57 @@
+configuration = $configuration;
+ $this->_helper = $mediaHelper;
+ }
+
+ /**
+ * @method isEnabledLazyload
+ * @return boolean
+ */
+ public function isEnabled()
+ {
+ return $this->configuration->isEnabled();
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getCname()
+ {
+ return $this->_helper->getCname();
+ }
+
+}
diff --git a/Block/VideoSettings.php b/Block/VideoSettings.php
new file mode 100644
index 00000000..2c83e762
--- /dev/null
+++ b/Block/VideoSettings.php
@@ -0,0 +1,113 @@
+configuration = $configuration;
+ parent::__construct($context, $data);
+ }
+
+ /**
+ * @return array
+ */
+ public function getVideoSettings() {
+ $settings = [
+ 'player_type' => 'default'
+ ];
+ $allSettings = $this->configuration->getAllVideoSettings();
+ $videoFreeParamsArray = [];
+ $sourceTypes = null;
+ $videoPlayerEnabled = isset($allSettings['enabled']) && (bool)$allSettings['enabled'];
+ $videoFreeParams = isset($allSettings['video_free_params']) ? $allSettings['video_free_params'] : null;
+ if (!empty($videoFreeParams) && $videoFreeParams != "{}") {
+ $videoFreeParamsArray = json_decode($videoFreeParams, true);
+ $videoFreeParamsArray['player']['cloudName'] = $this->configuration->getCloud();
+ $settings['settings'] = $videoFreeParamsArray['player'];
+ $source = isset($videoFreeParamsArray['source']) ? $videoFreeParamsArray['source'] : null;
+ $settings['source'] = $source ?? [];
+ }
+
+ if (empty($videoFreeParamsArray)) {
+ $videoFreeParams = false;
+ }
+
+ // additional params
+ if ($this->configuration->isEnabled() && $videoPlayerEnabled) {
+ $settings['player_type'] = 'cloudinary';
+ if (!$videoFreeParams) {
+
+ $transformation = [];
+
+ $autoplay = isset($allSettings['autoplay']) ? $allSettings['autoplay'] : 'never';
+ $controls = isset($allSettings['controls']) ? $allSettings['controls'] : null;
+ $isLoop = isset ($allSettings['loop']) ? (bool) $allSettings['loop'] : false;
+ $playerSettings = [
+ "cloudName" => $this->configuration->getCloud(),
+ 'controls' => ($controls == 'all'),
+ 'autoplay' => ($autoplay != 'never'),
+ 'loop' => $isLoop,
+ 'chapters' => false
+ ];
+
+ $playerSettings['muted'] = false;
+
+ if ($autoplay) {
+ $playerSettings['autoplayMode'] = $autoplay;
+ if ($autoplay != 'never') {
+ $playerSettings['muted'] = true;
+ }
+ }
+
+ $streamMode = isset($allSettings['stream_mode']) ? $allSettings['stream_mode'] : null;
+
+ if ($streamMode == 'optimization') {
+ $streamModeFormat = isset($allSettings['stream_mode_format']) ? $allSettings['stream_mode_format'] : null;
+ $streamModeQuality = isset($allSettings['stream_mode_quality']) ? $allSettings['stream_mode_quality'] : null;
+ $progressiveSourceTypes = isset($allSettings['progressive_sourcetypes']) ? $allSettings['progressive_sourcetypes'] : null;
+
+ if ($streamModeFormat == 'none' && $progressiveSourceTypes){
+ $sourceTypes = explode(',',$progressiveSourceTypes);
+ }
+ if ($streamModeQuality){
+ $transformation[]= $streamModeQuality;
+ }
+ }
+ if ($streamMode == 'abr') {
+ $sourceTypes = isset($allSettings['source_types']) ? [$allSettings['source_types']] : null;
+ }
+
+ $settings = [
+ 'player_type' => ($this->configuration->isEnabledProductGallery()) ? 'default' : 'cloudinary',
+ 'settings' => $playerSettings
+ ];
+
+ if ($transformation && is_array($transformation)) {
+ $settings['transformation'] = implode(',',$transformation);
+ }
+
+ if ($sourceTypes) {
+ $settings['source'] = ['sourceTypes' => $sourceTypes];
+ }
+ }
+ }
+
+ return $settings;
+ }
+}
diff --git a/Command/DownloadImages.php b/Command/DownloadImages.php
new file mode 100644
index 00000000..4b7c1f22
--- /dev/null
+++ b/Command/DownloadImages.php
@@ -0,0 +1,157 @@
+Are you sure you want to override local files (y/n)[n]?";
+
+ private $_override = false;
+
+ private $_includeSync = false;
+
+ /**
+ * @var ObjectManagerInterface
+ */
+ private $objectManager;
+
+ /**
+ * @var OutputLogger
+ */
+ private $outputLogger;
+
+ /**
+ * @var Registry
+ */
+ private $coreRegistry;
+
+ /**
+ * @var BatchDownloader
+ */
+ private $batchDownloader;
+
+ /**
+ * @method __construct
+ * @param ObjectManagerInterface $objectManager
+ * @param OutputLogger $outputLogger
+ * @param Registry $coreRegistry
+ */
+ public function __construct(
+ ObjectManagerInterface $objectManager,
+ OutputLogger $outputLogger,
+ Registry $coreRegistry
+ ) {
+ parent::__construct('cloudinary:download:all');
+
+ $this->objectManager = $objectManager;
+ $this->outputLogger = $outputLogger;
+ $this->coreRegistry = $coreRegistry;
+ }
+
+ /**
+ * Configure the command
+ *
+ * @return void
+ */
+ protected function configure()
+ {
+ $this->setName('cloudinary:download:all');
+ $this->setDescription('Download images from Cloudinary to the local pub/media dir. Optionally, Include synchronization state and download everything');
+ $this->setDefinition([
+ new InputOption(
+ self::OVERRIDE,
+ '-o',
+ InputOption::VALUE_NONE,
+ 'Override local images if already exists'
+ ),
+ new InputOption(
+ self::FORCE,
+ '-f',
+ InputOption::VALUE_NONE,
+ 'Force download even if Cloudinary is disabled'
+ ),
+ new InputOption(
+ self::ENV,
+ '-e',
+ InputOption::VALUE_OPTIONAL,
+ 'Cloudinary environment variable that will be used during the process',
+ null
+ ),
+ new InputOption(
+ self::INCLUDE_SYNC,
+ '-i',
+ InputOption::VALUE_NONE,
+ 'Include synchronization state and download all images',
+ )
+ ]);
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ *
+ * @return init
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+
+ try {
+ $this->batchDownloader = $this->objectManager
+ ->get(\Cloudinary\Cloudinary\Model\BatchDownloader::class);
+
+ if ($input->getOption(self::OVERRIDE) && $this->confirmQuestion(self::OVERRIDE_CONFIRM_MESSAGE, $input, $output)) {
+ $this->_override = true;
+ }
+ if (($env = $input->getOption(self::ENV))) {
+ $this->coreRegistry->register(Configuration::CONFIG_PATH_ENVIRONMENT_VARIABLE, $env);
+ }
+ if ($input->getOption(self::FORCE)) {
+ $this->coreRegistry->register(Configuration::CONFIG_PATH_ENABLED, true);
+ }
+ if ($input->getOption(self::INCLUDE_SYNC)) {
+ $this->_includeSync = true;
+ }
+ $this->outputLogger->setOutput($output);
+ $this->batchDownloader->downloadUnsynchronisedImages($this->outputLogger, $this->_override, $this->_includeSync);
+
+ return 1;
+
+ } catch (\Exception $e) {
+ $output->writeln($e->getMessage());
+ return 0;
+ }
+ }
+
+ /**
+ * @method confirmQuestion
+ * @param string $message
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return bool
+ */
+ private function confirmQuestion(string $message, InputInterface $input, OutputInterface $output)
+ {
+ $confirmationQuestion = new ConfirmationQuestion($message, false);
+ return (bool)$this->getHelper('question')->ask($input, $output, $confirmationQuestion);
+ }
+}
diff --git a/Command/ProductGalleryApiQueueProcess.php b/Command/ProductGalleryApiQueueProcess.php
new file mode 100644
index 00000000..bef4f6b1
--- /dev/null
+++ b/Command/ProductGalleryApiQueueProcess.php
@@ -0,0 +1,70 @@
+objectManager = $objectManager;
+ $this->appState = $appState;
+ }
+
+ /**
+ * Configure the command
+ *
+ * @return void
+ */
+ protected function configure()
+ {
+ $this->setName('cloudinary:product-gallery-api-queue:process');
+ $this->setDescription('Process queued items for product gallery API');
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ *
+ * @return void
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $this->job = $this->objectManager
+ ->get(\Cloudinary\Cloudinary\Cron\ProductGalleryApiQueue::class);
+
+ $this->appState->setAreaCode(\Magento\Framework\App\Area::AREA_CRONTAB);
+ return $this->job
+ ->setOutput($output)
+ ->execute();
+ }
+}
diff --git a/Command/ResetAll.php b/Command/ResetAll.php
index 4ab9816e..0a3ca01d 100644
--- a/Command/ResetAll.php
+++ b/Command/ResetAll.php
@@ -2,15 +2,16 @@
namespace Cloudinary\Cloudinary\Command;
+use Cloudinary\Cloudinary\Helper\Reset;
+use Magento\Framework\ObjectManagerInterface;
+use Magento\User\Model\User;
+use Magento\User\Model\UserFactory;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Question\ConfirmationQuestion;
-use Symfony\Component\Console\Helper\QuestionHelper;
-use Magento\User\Model\UserFactory;
-use Magento\User\Model\User;
-use Cloudinary\Cloudinary\Helper\Reset;
+use Symfony\Component\Console\Question\Question;
class ResetAll extends Command
{
@@ -36,6 +37,11 @@ class ResetAll extends Command
const COMPLETE_MESSAGE1 = 'All Cloudinary module data has been reset.';
const COMPLETE_MESSAGE2 = 'Please clear your configuration cache to ensure changes take effect.';
+ /**
+ * @var ObjectManagerInterface
+ */
+ private $objectManager;
+
/**
* @var UserFactory
*/
@@ -46,12 +52,18 @@ class ResetAll extends Command
*/
private $resetHelper;
-
- public function __construct(UserFactory $userFactory, Reset $resetHelper)
- {
+ /**
+ * @method __construct
+ * @param ObjectManagerInterface $objectManager
+ * @param UserFactory $userFactory
+ */
+ public function __construct(
+ ObjectManagerInterface $objectManager,
+ UserFactory $userFactory
+ ) {
parent::__construct('cloudinary:reset');
+ $this->objectManager = $objectManager;
$this->userFactory = $userFactory;
- $this->resetHelper = $resetHelper;
}
protected function configure()
@@ -60,36 +72,40 @@ protected function configure()
}
/**
- * @param InputInterface $input
- * @param OutputInterface $output
+ * @param InputInterface $input
+ * @param OutputInterface $output
*
- * @return void
+ * @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
+ $this->resetHelper = $this->objectManager
+ ->get(\Cloudinary\Cloudinary\Helper\Reset::class);
+
$this->displayPreActionMessage($output);
$helper = $this->getHelper('question');
if (!$this->confirmActionStart($input, $output, $helper)) {
- return;
+ return 0;
}
$adminUser = $this->getAdminUser($this->readAdminName($input, $output, $helper));
if (!$adminUser->getId()) {
$output->writeln(self::ADMIN_USER_NOT_FOUND);
- return;
+ return 0;
}
$adminPassword = $this->readAdminPassword($input, $output, $helper);
if (!$this->authenticate($adminUser, $adminPassword)) {
$output->writeln(self::ADMIN_PASSWORD_INCORRECT);
- return;
+ return 0;
}
$this->resetHelper->resetModule();
$output->writeln(self::COMPLETE_MESSAGE1);
$output->writeln(self::COMPLETE_MESSAGE2);
+ return 1;
}
/**
@@ -101,7 +117,7 @@ private function displayPreActionMessage(OutputInterface $output)
$output->writeln(sprintf(self::WARNING_FORMAT, self::PRE_ACTION_WARNING2));
array_map(
- function($line) use ($output) {
+ function ($line) use ($output) {
$output->writeln(sprintf('%s ', $line));
},
self::PRE_ACTION_MESSAGES
@@ -109,9 +125,9 @@ function($line) use ($output) {
}
/**
- * @param InputInterface $input
- * @param OutputInterface $output
- * @param QuestionHelper $helper
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @param QuestionHelper $helper
* @return bool
*/
private function confirmActionStart(InputInterface $input, OutputInterface $output, QuestionHelper $helper)
@@ -122,9 +138,9 @@ private function confirmActionStart(InputInterface $input, OutputInterface $outp
}
/**
- * @param InputInterface $input
- * @param OutputInterface $output
- * @param QuestionHelper $helper
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @param QuestionHelper $helper
* @return string
*/
private function readAdminName(InputInterface $input, OutputInterface $output, QuestionHelper $helper)
@@ -135,7 +151,7 @@ private function readAdminName(InputInterface $input, OutputInterface $output, Q
}
/**
- * @param string $username
+ * @param string $username
* @return User
*/
private function getAdminUser($username)
@@ -144,9 +160,9 @@ private function getAdminUser($username)
}
/**
- * @param InputInterface $input
- * @param OutputInterface $output
- * @param QuestionHelper $helper
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @param QuestionHelper $helper
* @return string
*/
private function readAdminPassword(InputInterface $input, OutputInterface $output, QuestionHelper $helper)
@@ -158,8 +174,8 @@ private function readAdminPassword(InputInterface $input, OutputInterface $outpu
}
/**
- * @param User $user
- * @param string $password
+ * @param User $user
+ * @param string $password
* @return bool
*/
private function authenticate(User $user, $password)
diff --git a/Command/StopMigration.php b/Command/StopMigration.php
index ff85b4b1..454b9ab1 100644
--- a/Command/StopMigration.php
+++ b/Command/StopMigration.php
@@ -3,28 +3,34 @@
namespace Cloudinary\Cloudinary\Command;
use Cloudinary\Cloudinary\Model\MigrationTask;
+use Magento\Framework\ObjectManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class StopMigration extends Command
{
- const NOP_MESSAGE = 'No upload running to stop.';
- const STOPPED_MESSAGE = 'Upload manually stopped.';
-
+ const NOP_MESSAGE = 'No upload/download running to stop.';
+ const STOPPED_MESSAGE = 'Upload/Download manually stopped.';
+
+ /**
+ * @var ObjectManagerInterface
+ */
+ private $objectManager;
+
/**
* @var MigrationTask
*/
private $migrationTask;
/**
- * @param MigrationTask $migrationTask
+ * @param ObjectManagerInterface $objectManager
*/
- public function __construct(MigrationTask $migrationTask)
+ public function __construct(ObjectManagerInterface $objectManager)
{
- parent::__construct('cloudinary:upload:stop');
+ parent::__construct('cloudinary:migration:stop');
- $this->migrationTask = $migrationTask;
+ $this->objectManager = $objectManager;
}
/**
@@ -34,22 +40,26 @@ public function __construct(MigrationTask $migrationTask)
*/
protected function configure()
{
- $this->setDescription('Stops any currently running upload.');
+ $this->setDescription('Stops any currently running upload/download.');
}
/**
- * @param InputInterface $input
- * @param OutputInterface $output
+ * @param InputInterface $input
+ * @param OutputInterface $output
*
- * @return void
+ * @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
+ $this->migrationTask = $this->objectManager
+ ->get(\Cloudinary\Cloudinary\Model\MigrationTask::class);
+
if ($this->migrationTask->hasStarted()) {
$this->migrationTask->stop();
$output->writeln(self::STOPPED_MESSAGE);
} else {
$output->writeln(self::NOP_MESSAGE);
}
+ return 1;
}
}
diff --git a/Command/UploadImages.php b/Command/UploadImages.php
index 9f7f7bb4..557e3313 100644
--- a/Command/UploadImages.php
+++ b/Command/UploadImages.php
@@ -3,17 +3,28 @@
namespace Cloudinary\Cloudinary\Command;
use Cloudinary\Cloudinary\Model\BatchUploader;
+use Cloudinary\Cloudinary\Model\Configuration;
use Cloudinary\Cloudinary\Model\Logger\OutputLogger;
+use Magento\Framework\ObjectManagerInterface;
+use Magento\Framework\Registry;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class UploadImages extends Command
{
+ /**#@+
+ * Keys and shortcuts for input arguments and options
+ */
+ const FORCE = 'force';
+ const ENV = 'env';
+ /**#@- */
+
/**
- * @var BatchUploader
+ * @var ObjectManagerInterface
*/
- private $batchUploader;
+ private $objectManager;
/**
* @var OutputLogger
@@ -21,40 +32,83 @@ class UploadImages extends Command
private $outputLogger;
/**
- * @param BatchUploader $batchUploader
+ * @var Registry
*/
- public function __construct(BatchUploader $batchUploader, OutputLogger $outputLogger)
- {
+ private $coreRegistry;
+
+ /**
+ * @var BatchUploader
+ */
+ private $batchUploader;
+
+ /**
+ * @method __construct
+ * @param ObjectManagerInterface $objectManager
+ * @param OutputLogger $outputLogger
+ * @param Registry $coreRegistry
+ */
+ public function __construct(
+ ObjectManagerInterface $objectManager,
+ OutputLogger $outputLogger,
+ Registry $coreRegistry
+ ) {
parent::__construct('cloudinary:upload:all');
- $this->batchUploader = $batchUploader;
+ $this->objectManager = $objectManager;
$this->outputLogger = $outputLogger;
+ $this->coreRegistry = $coreRegistry;
}
/**
* Configure the command
- *
+ *
* @return void
*/
protected function configure()
{
$this->setName('cloudinary:upload:all');
$this->setDescription('Upload unsynchronised images');
+ $this->setDefinition([
+ new InputOption(
+ self::FORCE,
+ '-f',
+ InputOption::VALUE_NONE,
+ 'Force upload even if Cloudinary is disabled'
+ ),
+ new InputOption(
+ self::ENV,
+ '-e',
+ InputOption::VALUE_OPTIONAL,
+ 'Cloudinary environment variable that will be used during the process',
+ null
+ ),
+ ]);
}
/**
- * @param InputInterface $input
- * @param OutputInterface $output
+ * @param InputInterface $input
+ * @param OutputInterface $output
*
- * @return void
+ * @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
try {
+ $this->batchUploader = $this->objectManager
+ ->get(\Cloudinary\Cloudinary\Model\BatchUploader::class);
+
+ if (($env = $input->getOption(self::ENV))) {
+ $this->coreRegistry->register(Configuration::CONFIG_PATH_ENVIRONMENT_VARIABLE, $env);
+ }
+ if ($input->getOption(self::FORCE)) {
+ $this->coreRegistry->register(Configuration::CONFIG_PATH_ENABLED, true);
+ }
$this->outputLogger->setOutput($output);
$this->batchUploader->uploadUnsynchronisedImages($this->outputLogger);
+ return 1;
} catch (\Exception $e) {
$output->writeln($e->getMessage());
+ return 0;
}
}
}
diff --git a/Controller/Adminhtml/Ajax/Free/Image.php b/Controller/Adminhtml/Ajax/Free/Image.php
index 4740dd13..2784a18d 100644
--- a/Controller/Adminhtml/Ajax/Free/Image.php
+++ b/Controller/Adminhtml/Ajax/Free/Image.php
@@ -2,13 +2,13 @@
namespace Cloudinary\Cloudinary\Controller\Adminhtml\Ajax\Free;
-use Cloudinary\Cloudinary\Core\Image\Transformation\Freeform;
+use Cloudinary\Cloudinary\Core\ConfigurationInterface;
use Cloudinary\Cloudinary\Core\Image\Transformation;
+use Cloudinary\Cloudinary\Core\Image\Transformation\Freeform;
+use Cloudinary\Cloudinary\Model\Config\Backend\Free as FreeBackendModel;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\Result\JsonFactory;
-use Cloudinary\Cloudinary\Model\Config\Backend\Free as FreeBackendModel;
-use Cloudinary\Cloudinary\Core\ConfigurationInterface;
class Image extends Action
{
@@ -31,9 +31,9 @@ class Image extends Action
private $configuration;
/**
- * @param Context $context
- * @param JsonFactory $resultJsonFactory
- * @param FreeBackendModel $model
+ * @param Context $context
+ * @param JsonFactory $resultJsonFactory
+ * @param FreeBackendModel $model
* @param ConfigurationInterface $configuration
*/
public function __construct(
@@ -71,12 +71,12 @@ public function execute()
}
/**
- * @param string $freeTransform
+ * @param string $freeTransform
* @return Transformation
*/
private function defaultTransformWithFreeTransform($freeTransform)
{
- $transformation = $this->configuration->getDefaultTransformation();
+ $transformation = $this->configuration->getDefaultTransformation(true);
if ($freeTransform) {
$transformation->withFreeform(Freeform::fromString($freeTransform));
diff --git a/Controller/Adminhtml/Ajax/Free/Sample.php b/Controller/Adminhtml/Ajax/Free/Sample.php
index 48b82655..460f5a3c 100644
--- a/Controller/Adminhtml/Ajax/Free/Sample.php
+++ b/Controller/Adminhtml/Ajax/Free/Sample.php
@@ -2,13 +2,13 @@
namespace Cloudinary\Cloudinary\Controller\Adminhtml\Ajax\Free;
-use Cloudinary\Cloudinary\Core\Image\Transformation\Freeform;
+use Cloudinary\Cloudinary\Core\ConfigurationInterface;
use Cloudinary\Cloudinary\Core\Image\Transformation;
+use Cloudinary\Cloudinary\Core\Image\Transformation\Freeform;
+use Cloudinary\Cloudinary\Model\Config\Backend\Free as FreeBackendModel;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\Result\JsonFactory;
-use Cloudinary\Cloudinary\Model\Config\Backend\Free as FreeBackendModel;
-use Cloudinary\Cloudinary\Core\ConfigurationInterface;
class Sample extends Action
{
@@ -31,9 +31,9 @@ class Sample extends Action
private $configuration;
/**
- * @param Context $context
- * @param JsonFactory $resultJsonFactory
- * @param FreeBackendModel $model
+ * @param Context $context
+ * @param JsonFactory $resultJsonFactory
+ * @param FreeBackendModel $model
* @param ConfigurationInterface $configuration
*/
public function __construct(
@@ -70,13 +70,13 @@ public function execute()
}
/**
- * @param string $freeTransform
+ * @param string $freeTransform
* @return Transformation
*/
private function defaultTransformWithFreeTransform($freeTransform)
{
return $this->configuration->getDefaultTransformation()
- ->withFreeform(Freeform::fromString($freeTransform));
+ ->withFreeform(Freeform::fromString($freeTransform), false);
}
/**
diff --git a/Controller/Adminhtml/Ajax/RetrieveImage.php b/Controller/Adminhtml/Ajax/RetrieveImage.php
new file mode 100644
index 00000000..04929f7e
--- /dev/null
+++ b/Controller/Adminhtml/Ajax/RetrieveImage.php
@@ -0,0 +1,413 @@
+resultRawFactory = $resultRawFactory;
+ $this->mediaConfig = $mediaConfig;
+ $this->fileSystem = $fileSystem;
+ $this->imageAdapter = $imageAdapterFactory->create();
+ $this->curl = $curl;
+ $this->fileUtility = $fileUtility;
+ $this->fileProcessor = $fileProcessor;
+ $this->extensionValidator = $extensionValidator;
+ $this->protocolValidator = $protocolValidator;
+ $this->storeManager = $storeManager;
+ $this->configuration = $configuration;
+ $this->mediaLibraryMapFactory = $mediaLibraryMapFactory;
+ $this->assetRepository = $assetRepository;
+ }
+
+ /**
+ * @return \Magento\Framework\Controller\Result\Raw
+ */
+ public function execute()
+ {
+ try {
+ $localUniqFilePath = $this->remoteFileUrl = $this->getRequest()->getParam('remote_image');
+ if (
+ $this->configuration->isEnabledCachePlaceholder() &&
+ strpos($this->getBaseTmpMediaPath(), '/category') === false
+ ) {
+ $customPlaceholder = $this->configuration->getCustomPlaceholderPath();
+ if ($customPlaceholder && file_exists($customPlaceholder)) {
+ $image = file_get_contents($customPlaceholder);
+ $sourceFilePath = $customPlaceholder;
+ } else {
+ $asset = $this->assetRepository->createAsset('Cloudinary_Cloudinary::images/cloudinary_placeholder.jpg');
+ $sourceFilePath = $asset->getSourceFile();
+ $image = file_get_contents($sourceFilePath);
+ }
+ } else {
+ $image = file_get_contents($localUniqFilePath);
+ }
+ $this->validateRemoteFile($this->remoteFileUrl);
+ $this->parsedRemoteFileUrl = $this->configuration->parseCloudinaryUrl($this->remoteFileUrl);
+ $this->parsedRemoteFileUrl["transformations_string"] = $this->getRequest()->getParam('asset')["free_transformation"];
+ $assetParsedRemoteFileUrl = $this->configuration->parseCloudinaryUrl($this->getRequest()->getParam('asset')["asset_url"]);
+ $this->parsedRemoteFileUrl["type"] = $assetParsedRemoteFileUrl['type'];
+ $this->parsedRemoteFileUrl["thumbnail_url"] = $assetParsedRemoteFileUrl['thumbnail_url'];
+ $baseTmpMediaPath = $this->getBaseTmpMediaPath();
+
+ if ($this->configuration->isEnabledLocalMapping()) {
+ $this->cldUniqid = $this->configuration->generateCLDuniqid();
+ $localUniqFilePath = $this->configuration->addUniquePrefixToBasename($localUniqFilePath, $this->cldUniqid);
+ }
+
+ $localUniqFilePath = $this->appendNewFileName($baseTmpMediaPath . $this->getLocalTmpFileName($localUniqFilePath));
+ $this->validateFileExtensions($localUniqFilePath);
+
+ // reads the image and save it locally
+ if (!$this->configuration->isEnabledCachePlaceholder()) {
+ // Only download the real Cloudinary image if not using placeholder
+ $this->retrieveRemoteImage($this->remoteFileUrl, $localUniqFilePath);
+ } else if ((strpos($this->getBaseTmpMediaPath(), '/category') !== false)) {
+ $this->retrieveRemoteImage($this->remoteFileUrl, $localUniqFilePath);
+ } else {
+ // Save the already-read placeholder image manually
+ $this->fileUtility->saveFile($localUniqFilePath, $image);
+ $this->usingPlaceholderFallback = true;
+ }
+
+ $localFileFullPath = $this->appendAbsoluteFileSystemPath($localUniqFilePath);
+ $this->imageAdapter->validateUploadFile($localFileFullPath);
+ $result = $this->appendResultSaveRemoteImage($localUniqFilePath, $baseTmpMediaPath);
+ if ($this->configuration->isEnabledLocalMapping()) {
+ $this->saveCloudinaryMapping();
+ }
+ } catch (\Exception $e) {
+ $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()];
+ $fileWriter = $this->fileSystem->getDirectoryWrite(DirectoryList::MEDIA);
+ if (isset($localFileFullPath) && $fileWriter->isExist($localFileFullPath)) {
+ $fileWriter->delete($localFileFullPath);
+ }
+ }
+ /** @var \Magento\Framework\Controller\Result\Raw $response */
+ $response = $this->resultRawFactory->create();
+ $response->setHeader('Content-type', 'text/plain');
+ $response->setContents(json_encode($result));
+ return $response;
+ }
+
+ protected function getBaseTmpMediaPath()
+ {
+ $baseTmpMediaPath = false;
+ switch ($this->getRequest()->getParam('type')) {
+ case 'design_config_fileUploader':
+ $baseTmpMediaPath = 'tmp/' . FileProcessor::FILE_DIR;
+ break;
+ case 'pagebuilder_contenttype':
+ $baseTmpMediaPath = PageBuilderContentTypeUpload::UPLOAD_DIR;
+ break;
+ case 'category_image':
+ $baseTmpMediaPath = 'catalog/tmp/category';
+ break;
+ default:
+ $baseTmpMediaPath = $this->mediaConfig->getBaseTmpMediaPath();
+ break;
+ }
+ if (!$baseTmpMediaPath) {
+ throw new LocalizedException(__("Empty baseTmpMediaPath"));
+ }
+ return $baseTmpMediaPath;
+ }
+
+ protected function getLocalTmpFileName($remoteFileUrl)
+ {
+ $localFileName = Uploader::getCorrectFileName(basename($remoteFileUrl));
+ switch ($this->getRequest()->getParam('type')) {
+ case 'pagebuilder_contenttype':
+ case 'design_config_fileUploader':
+ case 'category_image':
+ $localTmpFileName = DIRECTORY_SEPARATOR . $localFileName;
+ break;
+ default:
+ $localTmpFileName = Uploader::getDispretionPath($localFileName) . DIRECTORY_SEPARATOR . $localFileName;
+ break;
+ }
+ return $localTmpFileName;
+ }
+
+ /**
+ * Validate remote file
+ *
+ * @throws LocalizedException
+ *
+ * @return $this
+ */
+ private function validateRemoteFile()
+ {
+ if (!$this->protocolValidator->isValid($this->remoteFileUrl)) {
+ throw new LocalizedException(
+ __("Protocol isn't allowed")
+ );
+ }
+ return $this;
+ }
+
+ /**
+ * Invalidates files that have script extensions.
+ *
+ * @param string $filePath
+ * @throws ValidatorException
+ * @return void
+ */
+ private function validateFileExtensions($filePath)
+ {
+ $extension = pathinfo($filePath, PATHINFO_EXTENSION);
+ if (!$this->extensionValidator->isValid($extension)) {
+ throw new ValidatorException(__('Disallowed file type.'));
+ }
+ }
+
+ /**
+ * @param string $localUniqFilePath
+ * @return mixed
+ */
+ protected function appendResultSaveRemoteImage($localUniqFilePath, $baseTmpMediaPath)
+ {
+ $tmpFileName = $localUniqFilePath;
+ if (substr($tmpFileName, 0, strlen($baseTmpMediaPath)) == $baseTmpMediaPath) {
+ $tmpFileName = substr($tmpFileName, strlen($baseTmpMediaPath));
+ }
+ $result['name'] = basename($localUniqFilePath);
+ $result['type'] = $this->imageAdapter->getMimeType();
+ $result['error'] = 0;
+ $result['size'] = filesize($this->appendAbsoluteFileSystemPath($localUniqFilePath));
+ $result['url'] = $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA) . $localUniqFilePath;
+ $result['tmp_name'] = $this->appendAbsoluteFileSystemPath($localUniqFilePath);
+ $result['file'] = $tmpFileName;
+ $result['using_placeholder_fallback'] = (bool) $this->usingPlaceholderFallback;
+
+ return $result;
+ }
+
+ /**
+ * Trying to get remote image to save it locally
+ *
+ * @param string $fileUrl
+ * @param string $localFilePath
+ * @return void
+ * @throws LocalizedException
+ */
+ protected function retrieveRemoteImage($fileUrl, $localFilePath)
+ {
+ $this->curl->setConfig(['header' => false]);
+ $this->curl->write('GET', $fileUrl);
+
+ $image = $this->curl->read();
+
+ if (empty($image) && $this->getRequest()->getParam('asset')["resource_type"] === 'video') {
+ //Fallback for video thumbnail image, use placeholder or store logo
+ $this->usingPlaceholderFallback = true;
+ $this->curl->close();
+ $this->curl->setConfig(['header' => false, 'verifypeer' => false, 'verifyhost' => 0]);
+ $this->curl->write('GET', $this->getPlaceholderUrl());
+ $image = $this->curl->read();
+ }
+
+ if (empty($image)) {
+ $this->usingPlaceholderFallback = false;
+ throw new LocalizedException(
+ __('The preview image information is unavailable. Check your connection and try again.')
+ );
+ }
+ $this->fileUtility->saveFile($localFilePath, $image);
+ }
+
+ /**
+ * @param string $localFilePath
+ * @return string
+ */
+ protected function appendNewFileName($localFilePath)
+ {
+ $destinationFile = $this->appendAbsoluteFileSystemPath($localFilePath);
+ $fileName = Uploader::getNewFileName($destinationFile);
+ $fileInfo = pathinfo($localFilePath);
+ return $fileInfo['dirname'] . DIRECTORY_SEPARATOR . $fileName;
+ }
+
+ /**
+ * @param string $localTmpFile
+ * @return string
+ */
+ protected function appendAbsoluteFileSystemPath($localTmpFile)
+ {
+ /** @var \Magento\Framework\Filesystem\Directory\Read $mediaDirectory */
+ $mediaDirectory = $this->fileSystem->getDirectoryRead(DirectoryList::MEDIA);
+ $pathToSave = $mediaDirectory->getAbsolutePath();
+ return $pathToSave . $localTmpFile;
+ }
+
+ /**
+ * @return string
+ */
+ private function saveCloudinaryMapping()
+ {
+ return $this->mediaLibraryMapFactory->create()
+ ->setCldUniqid($this->cldUniqid)
+ ->setCldPublicId(($this->parsedRemoteFileUrl["type"] === "video") ? $this->parsedRemoteFileUrl["thumbnail_url"] : $this->parsedRemoteFileUrl["publicId"] . '.' . $this->parsedRemoteFileUrl["extension"])
+ ->setFreeTransformation($this->parsedRemoteFileUrl["transformations_string"])
+ ->save();
+ }
+
+ /**
+ * @return string
+ */
+ private function getPlaceholderUrl()
+ {
+ $configPaths = [
+ 'catalog/placeholder/image_placeholder',
+ 'catalog/placeholder/small_image_placeholder',
+ 'catalog/placeholder/thumbnail_placeholder',
+ ];
+ foreach ($configPaths as $configPath) {
+ if (($path = $this->storeManager->getStore()->getConfig($configPath))) {
+ return $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA) . 'catalog/product/placeholder/' . $path;
+ break;
+ }
+ }
+ return $this->_view->getLayout()->createBlock("Magento\Theme\Block\Html\Header\Logo")->getViewFileUrl('Cloudinary_Cloudinary::images/cloudinary_cloud_glyph_blue.png');
+ }
+}
diff --git a/Controller/Adminhtml/Ajax/System/Config/AutoUploadMapping.php b/Controller/Adminhtml/Ajax/System/Config/AutoUploadMapping.php
new file mode 100644
index 00000000..724cfb8b
--- /dev/null
+++ b/Controller/Adminhtml/Ajax/System/Config/AutoUploadMapping.php
@@ -0,0 +1,131 @@
+jsonResultFactory = $jsonResultFactory;
+ $this->requestProcessor = $requestProcessor;
+ $this->messageManager = $messageManager;
+ $this->configuration = $configuration;
+ $this->cacheTypeList = $cacheTypeList;
+ $this->appConfig = $config;
+ }
+
+ /**
+ * @return ResultInterface
+ */
+ public function execute()
+ {
+ try {
+ $this->validateAjaxRequest();
+ $this->cleanConfigCache();
+
+ if ($this->configuration->isEnabled()) {
+ if (!$this->requestProcessor->handle(DirectoryList::MEDIA, $this->configuration->getMediaBaseUrl(), true)) {
+ throw new \Exception(self::AUTO_UPLOAD_SETUP_FAIL_MESSAGE);
+ }
+ }
+ } catch (\Exception $e) {
+ return $this->jsonResultFactory->create()
+ ->setHttpResponseCode(500)
+ ->setData(['error' => 1, 'message' => "ERROR during the mapping process: " . $e->getMessage(), 'errorcode' => $e->getCode()]);
+ }
+
+ return $this->jsonResultFactory->create()
+ ->setHttpResponseCode(\Magento\Framework\Webapi\Response::HTTP_OK)
+ ->setData(['error' => 0, 'message' => 'Successfully mapped media directory!']);
+ }
+
+ protected function cleanConfigCache()
+ {
+ $this->cacheTypeList->cleanType(\Magento\Framework\App\Cache\Type\Config::TYPE_IDENTIFIER);
+ $this->appConfig->reinit();
+ return $this;
+ }
+
+ protected function _isAllowed()
+ {
+ return $this->_authorization->isAllowed('Cloudinary_Cloudinary::config_cloudinary');
+ }
+
+ /**
+ * @throws \Exception
+ */
+ private function validateAjaxRequest()
+ {
+ if (!$this->getRequest()->isAjax()) {
+ throw new \Exception(self::NON_AJAX_REQUEST);
+ }
+ }
+}
diff --git a/Controller/Adminhtml/Ajax/UpdateAdminImage.php b/Controller/Adminhtml/Ajax/UpdateAdminImage.php
new file mode 100644
index 00000000..c7379ff1
--- /dev/null
+++ b/Controller/Adminhtml/Ajax/UpdateAdminImage.php
@@ -0,0 +1,138 @@
+imageFactory = $imageFactory;
+ $this->urlGenerator = $urlGenerator;
+ $this->configuration = $configuration;
+ $this->storeManager = $storeManager;
+ $this->urlInterface = $urlInterface;
+ $this->resultFactory = $resultFactory;
+ $this->configurationBuilder = $configurationBuilder;
+ $this->transformation = $transformation;
+ $this->logger = $logger;
+ }
+
+ protected function _isAllowed()
+ {
+ return $this->_authorization->isAllowed(self::ADMIN_RESOURCE);
+ }
+
+ private function authorise()
+ {
+ if (!$this->_authorised && $this->configuration->isEnabled()) {
+ Configuration::instance($this->configurationBuilder->build());
+ BaseApiClient::$userPlatform = $this->configuration->getUserPlatform();
+ $this->_authorised = true;
+ }
+ }
+
+ /**
+ * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\Result\Raw|\Magento\Framework\Controller\ResultInterface
+ */
+ public function execute()
+ {
+ $this->authorise();
+ $remoteImageUrl = $this->getRequest()->getParam('remote_image');
+ $result = $remoteImageUrl ?: ['error' => 'Invalid configuration'];
+
+ if ($this->configuration->isEnabled()) {
+ try {
+
+ // Validate URL
+ if (!$remoteImageUrl) {
+ throw new \InvalidArgumentException('Missing remote_image parameter');
+ }
+
+ $parsedUrl = parse_url($remoteImageUrl);
+
+ if (!$parsedUrl || !isset($parsedUrl['scheme'], $parsedUrl['host'], $parsedUrl['path'])) {
+ throw new \InvalidArgumentException('Invalid image URL');
+ }
+
+ $cleanUrl = $parsedUrl['scheme'] . '://' . $parsedUrl['host'] . $parsedUrl['path'];
+ $baseUrl = $this->storeManager->getStore()->getBaseUrl();
+ $relativePath = str_replace($baseUrl, '', $cleanUrl);
+ $relativePath = ltrim($relativePath, '/');
+
+ // Create Image object and use UrlGenerator (same as storefront)
+ $image = Image::fromPath($remoteImageUrl, $relativePath);
+
+ // Use UrlGenerator which handles all the logic including database mapping
+ $cloudinaryUrl = $this->urlGenerator->generateFor($image, $this->transformation);
+ $result = (string)$cloudinaryUrl;
+
+ } catch (\Exception $e) {
+ // Return original URL on error for graceful fallback
+ $this->logger->error(
+ 'Cloudinary UpdateAdminImage error: ' . $e->getMessage(),
+ [
+ 'image_url' => $remoteImageUrl,
+ 'exception' => $e
+ ]
+ );
+ }
+ }
+
+ $response = $this->resultFactory->create();
+ $response->setHeader('Content-type', 'application/json');
+ $response->setContents(json_encode($result));
+ return $response;
+ }
+}
diff --git a/Controller/Adminhtml/Cms/Wysiwyg/Images/Upload.php b/Controller/Adminhtml/Cms/Wysiwyg/Images/Upload.php
new file mode 100644
index 00000000..238c8548
--- /dev/null
+++ b/Controller/Adminhtml/Cms/Wysiwyg/Images/Upload.php
@@ -0,0 +1,358 @@
+directoryList = $directoryList;
+ $this->mediaConfig = $mediaConfig;
+ $this->fileSystem = $fileSystem;
+ $this->imageAdapter = $imageAdapterFactory->create();
+ $this->curl = $curl;
+ $this->fileUtility = $fileUtility;
+ $this->extensionValidator = $extensionValidator;
+ $this->protocolValidator = $protocolValidator;
+ $this->configuration = $configuration;
+ $this->mediaLibraryMapFactory = $mediaLibraryMapFactory;
+ $this->mediaGalleryUploader = $mediaGalleryUploader;
+ $this->mediaAsset = $mediaAsset;
+ $this->mediaAssetSave = $mediaAssetSave;
+ }
+
+ /**
+ * Files upload processing.
+ *
+ * @return \Magento\Framework\Controller\ResultInterface
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ public function execute()
+ {
+ try {
+ $this->_initAction();
+ $path = ($this->getStorage()->getSession()->getCurrentPath()) ?? null;
+
+ if (!$path){
+ $path = $this->directoryList->getRoot() .'/pub/'. DirectoryList::MEDIA .'/cloudinary';
+ }
+
+ if (!$this->validatePath($path, DirectoryList::MEDIA)) {
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __('Directory %1 is not under storage root path.', $path)
+ );
+ }
+ $localFileName = $this->remoteFileUrl = $this->getRequest()->getParam('remote_image');
+ $this->validateRemoteFile($this->remoteFileUrl);
+ $this->parsedRemoteFileUrl = $this->configuration->parseCloudinaryUrl($this->remoteFileUrl);
+ $this->parsedRemoteFileUrl["transformations_string"] = $this->getRequest()->getParam('asset')["free_transformation"];
+ if ($this->configuration->isEnabledLocalMapping()) {
+ $this->cldUniqid = $this->configuration->generateCLDuniqid();
+ $localFileName = $this->configuration->addUniquePrefixToBasename($localFileName, $this->cldUniqid);
+ }
+ $localFileName = Uploader::getCorrectFileName(basename($localFileName));
+ $localFilePath = $this->appendNewFileName($path . DIRECTORY_SEPARATOR . $localFileName);
+ $this->validateRemoteFileExtensions($localFilePath);
+
+ $this->retrieveRemoteImage($this->remoteFileUrl, $localFilePath);
+ $this->getStorage()->resizeFile($localFilePath, true);
+ $this->imageAdapter->validateUploadFile($localFilePath);
+ $result = $this->appendResultSaveRemoteImage($localFilePath);
+ $asset = $this->getRequest()->getParam('asset');
+ $reg = preg_match('/^(.*)\/media\//',$localFilePath,$substruct);
+ $newPath = str_replace($substruct[0],'',$localFilePath);
+ $ma = $this->mediaAsset->create(
+ [
+ //'id' => 2020,
+ 'path' => $newPath,
+ 'description' => $localFileName,
+ 'contentType' => $asset['resource_type'],
+ 'title' => $localFileName,
+ 'source' => 'Cloudinary',
+ 'width' => $asset['width'],
+ 'height' => $asset['height'],
+ 'size' => $asset['bytes']
+ ]
+ );
+ $this->mediaAssetSave->execute([$ma]);
+ // $mgu = $this->mediaGalleryUploader->execute($path, $type);
+ if ($this->configuration->isEnabledLocalMapping()) {
+ $this->saveCloudinaryMapping();
+ }
+ } catch (\Exception $e) {
+ $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()];
+ }
+ /** @var \Magento\Framework\Controller\Result\Json $resultJson */
+ $resultJson = $this->resultJsonFactory->create();
+
+ return $resultJson->setData($result);
+ }
+
+ /**
+ * Validate remote file
+ *
+ * @throws LocalizedException
+ *
+ * @return $this
+ */
+ private function validateRemoteFile()
+ {
+ if (!$this->protocolValidator->isValid($this->remoteFileUrl)) {
+ throw new LocalizedException(
+ __("Protocol isn't allowed")
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Validate path.
+ *
+ * Gets real path for directory provided in parameters and compares it with specified root directory.
+ * Will return TRUE if real path of provided value contains root directory path and FALSE if not.
+ * Throws the \Magento\Framework\Exception\FileSystemException in case when directory path is absent
+ * in Directories configuration.
+ *
+ * @param string $path
+ * @param string $directoryConfig
+ * @return bool
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ private function validatePath($path, $directoryConfig = DirectoryList::MEDIA)
+ {
+ $directory = $this->fileSystem->getDirectoryWrite($directoryConfig);
+ $realPath = $directory->getDriver()->getRealPathSafety($path);
+ $root = $this->directoryList->getPath($directoryConfig);
+
+ return strpos($realPath, $root) === 0;
+ }
+
+ /**
+ * Invalidates files that have script extensions.
+ *
+ * @param string $filePath
+ * @throws \Magento\Framework\Exception\ValidatorException
+ * @return void
+ */
+ private function validateRemoteFileExtensions($filePath)
+ {
+ $extension = pathinfo($filePath, PATHINFO_EXTENSION);
+ $allowedExtensions = (array) $this->getStorage()->getAllowedExtensions($this->getRequest()->getParam('type'));
+ if (!$this->extensionValidator->isValid($extension) || !in_array($extension, $allowedExtensions)) {
+ throw new \Magento\Framework\Exception\ValidatorException(__('Disallowed file type.'));
+ }
+ }
+
+ /**
+ * @param string $filePath
+ * @return mixed
+ */
+ protected function appendResultSaveRemoteImage($filePath)
+ {
+ $fileInfo = pathinfo($filePath);
+ $result['name'] = $fileInfo['basename'];
+ $result['type'] = $this->imageAdapter->getMimeType();
+ $result['error'] = 0;
+ $result['size'] = filesize($filePath);
+ $result['url'] = $this->getRequest()->getParam('remote_image');
+ $result['file'] = $filePath;
+ return $result;
+ }
+
+ /**
+ * Trying to get remote image to save it locally
+ *
+ * @param string $fileUrl
+ * @param string $localFilePath
+ * @return void
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ protected function retrieveRemoteImage($fileUrl, $localFilePath)
+ {
+ $this->curl->setConfig(['header' => false]);
+ $this->curl->write('GET', $fileUrl);
+ $image = $this->curl->read();
+ if (empty($image)) {
+ throw new LocalizedException(
+ __('The preview image information is unavailable. Check your connection and try again.')
+ );
+ }
+ $this->fileUtility->saveFile($localFilePath, $image);
+ }
+
+ /**
+ * @param string $localFilePath
+ * @return string
+ */
+ protected function appendNewFileName($localFilePath)
+ {
+ $fileName = Uploader::getNewFileName($localFilePath);
+ $fileInfo = pathinfo($localFilePath);
+ return $fileInfo['dirname'] . DIRECTORY_SEPARATOR . $fileName;
+ }
+
+ /**
+ * @param string $localTmpFile
+ * @return string
+ */
+ protected function appendAbsoluteFileSystemPath($localTmpFile)
+ {
+ /** @var \Magento\Framework\Filesystem\Directory\Read $mediaDirectory */
+ $mediaDirectory = $this->fileSystem->getDirectoryRead(DirectoryList::MEDIA);
+ $pathToSave = $mediaDirectory->getAbsolutePath();
+ return $pathToSave . $localTmpFile;
+ }
+
+ /**
+ * @return string
+ */
+ private function saveCloudinaryMapping()
+ {
+ return $this->mediaLibraryMapFactory->create()
+ ->setCldUniqid($this->cldUniqid)
+ ->setCldPublicId(($this->parsedRemoteFileUrl["type"] === "video") ? $this->parsedRemoteFileUrl["thumbnail_url"] : $this->parsedRemoteFileUrl["publicId"] . '.' . $this->parsedRemoteFileUrl["extension"])
+ ->setFreeTransformation($this->parsedRemoteFileUrl["transformations_string"])
+ ->save();
+ }
+}
diff --git a/Controller/Adminhtml/PageBuilder/MediaGallery/Upload.php b/Controller/Adminhtml/PageBuilder/MediaGallery/Upload.php
new file mode 100644
index 00000000..6d72df94
--- /dev/null
+++ b/Controller/Adminhtml/PageBuilder/MediaGallery/Upload.php
@@ -0,0 +1,58 @@
+logger = $logger;
+ }
+
+ public function execute()
+ {
+ $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON);
+ try{
+ $params = $this->getRequest()->getParams();
+
+ } catch (NoSuchEntityException $e) {
+ $responseCode = self::HTTP_OK;
+ $responseContent = [];
+ } catch (\Exception $e) {
+ $responseCode = self::HTTP_INTERNAL_ERROR;
+ $this->logger->critical($e);
+ $responseContent = [
+ 'success' => false,
+ 'message' => __('An error occurred on attempt to retrieve asset information.'),
+ ];
+ }
+
+ $resultJson->setHttpResponseCode($responseCode);
+ $resultJson->setData($responseContent);
+
+ return $resultJson;
+ }
+}
diff --git a/Core/AutoUploadMapping/ApiClient.php b/Core/AutoUploadMapping/ApiClient.php
index 082ec6e6..c35afcf2 100644
--- a/Core/AutoUploadMapping/ApiClient.php
+++ b/Core/AutoUploadMapping/ApiClient.php
@@ -2,9 +2,10 @@
namespace Cloudinary\Cloudinary\Core\AutoUploadMapping;
-use Cloudinary;
-use Cloudinary\Api;
-use Cloudinary\Api\Response;
+use Cloudinary\Api\Admin\AdminApi;
+use Cloudinary\Api\ApiResponse;
+use Cloudinary\Api\BaseApiClient;
+use Cloudinary\Cloudinary;
use Cloudinary\Cloudinary\Core\ConfigurationBuilder;
use Cloudinary\Cloudinary\Core\ConfigurationInterface;
@@ -14,6 +15,15 @@ class ApiClient
const FOLDER_KEY = 'folder';
const URL_KEY = 'template';
+ private $cloudinarySDK;
+
+ private $api;
+
+ /**
+ * @var bool
+ */
+ private $_authorised;
+
/**
* @var ConfigurationInterface
*/
@@ -30,25 +40,19 @@ class ApiClient
private $errors = [];
/**
- * ApiClient constructor.
* @param ConfigurationInterface $configuration
* @param ConfigurationBuilder $configurationBuilder
*/
public function __construct(
ConfigurationInterface $configuration,
- ConfigurationBuilder $configurationBuilder,
- Api $api
+ ConfigurationBuilder $configurationBuilder
) {
$this->configuration = $configuration;
$this->configurationBuilder = $configurationBuilder;
- $this->api = $api;
- if ($this->configuration->isEnabled()) {
- $this->authorise();
- }
}
/**
- * @param ConfigurationInterface $configuration
+ * @param ConfigurationInterface $configuration
* @return ApiClient
*/
public static function fromConfiguration(ConfigurationInterface $configuration)
@@ -56,31 +60,31 @@ public static function fromConfiguration(ConfigurationInterface $configuration)
return new ApiClient(
$configuration,
new ConfigurationBuilder($configuration),
- new Api()
+ new AdminApi()
);
}
/**
- * @param string $folder
- * @param string $url
+ * @param string $folder
+ * @param string $url
* @return bool
*/
public function prepareMapping($folder, $url)
{
try {
+ $this->authorise();
+ $existingMappings = $this->parseFetchMappingsResponse($this->api->uploadMappings());
- $existingMappings = $this->parseFetchMappingsResponse($this->api->upload_mappings());
if ($this->hasMapping($existingMappings, $folder)) {
if (!$this->mappingMatches($existingMappings, $folder, $url)) {
- $this->api->update_upload_mapping($folder, [self::URL_KEY => $url]);
+ $this->api->updateUploadMapping($folder, [self::URL_KEY => $url]);
}
} else {
- $this->api->create_upload_mapping($folder, [self::URL_KEY => $url]);
+ $this->api->createUploadMapping($folder, [self::URL_KEY => $url]);
}
return true;
-
} catch (\Exception $e) {
$this->errors[] = $e;
return false;
@@ -88,12 +92,13 @@ public function prepareMapping($folder, $url)
}
/**
- * @param Response $response
+ * @param ApiResponse $response
* @return array
* @throws \Exception
*/
- private function parseFetchMappingsResponse(Response $response)
+ private function parseFetchMappingsResponse(ApiResponse $response)
{
+ $response = (array)$response;
if (!array_key_exists(self::MAPPINGS_KEY, $response) || !is_array($response[self::MAPPINGS_KEY])) {
throw new \Exception('Illegal mapping response');
}
@@ -102,23 +107,23 @@ private function parseFetchMappingsResponse(Response $response)
}
/**
- * @param array $mappings
- * @param string $folder
+ * @param array $mappings
+ * @param string $folder
* @return array
*/
private function filterMappings(array $mappings, $folder)
{
return array_filter(
$mappings,
- function(array $mapping) use ($folder) {
+ function (array $mapping) use ($folder) {
return $mapping[self::FOLDER_KEY] == $folder;
}
);
}
/**
- * @param array $mappings
- * @param string $folder
+ * @param array $mappings
+ * @param string $folder
* @return bool
*/
private function hasMapping(array $mappings, $folder)
@@ -127,27 +132,35 @@ private function hasMapping(array $mappings, $folder)
}
/**
- * @param array $existingMappings
- * @param string $folder
- * @param string $url
+ * @param array $existingMappings
+ * @param string $folder
+ * @param string $url
* @return bool
*/
private function mappingMatches(array $existingMappings, $folder, $url)
{
return count(
- array_filter(
- $this->filterMappings($existingMappings, $folder),
- function(array $mapping) use ($url) {
- return $mapping[self::URL_KEY] == $url;
- }
- )
- ) > 0;
+ array_filter(
+ $this->filterMappings($existingMappings, $folder),
+ function (array $mapping) use ($url) {
+ return $mapping[self::URL_KEY] == $url;
+ }
+ )
+ ) > 0;
}
private function authorise()
{
- Cloudinary::config($this->configurationBuilder->build());
- Cloudinary::$USER_PLATFORM = $this->configuration->getUserPlatform();
+ if (!$this->_authorised && $this->configuration->isEnabled()) {
+ // $config = new Configuration($this->configurationBuilder->build());
+ $this->cloudinarySDK = new Cloudinary($this->configurationBuilder->build());
+ $this->api = $this->cloudinarySDK->adminApi();
+
+ BaseApiClient::$userPlatform = $this->configuration->getUserPlatform();
+ $this->_authorised = true;
+
+ return $this;
+ }
}
/**
diff --git a/Core/AutoUploadMapping/RequestProcessor.php b/Core/AutoUploadMapping/RequestProcessor.php
index b5508150..e6897ea7 100644
--- a/Core/AutoUploadMapping/RequestProcessor.php
+++ b/Core/AutoUploadMapping/RequestProcessor.php
@@ -16,7 +16,7 @@ class RequestProcessor
/**
* @param AutoUploadConfigurationInterface $configuration
- * @param ApiClient $apiClient
+ * @param ApiClient $apiClient
*/
public function __construct(
AutoUploadConfigurationInterface $configuration,
@@ -27,13 +27,14 @@ public function __construct(
}
/**
- * @param string $folder
- * @param string $url
+ * @param string $folder
+ * @param string $url
+ * @param bool $force
* @return bool
*/
- public function handle($folder, $url)
+ public function handle($folder, $url, $force = false)
{
- if ($this->configuration->isActive() == $this->configuration->getRequestState()) {
+ if ($this->configuration->isActive() == $this->configuration->getRequestState() && !$force) {
return true;
}
@@ -47,8 +48,8 @@ public function handle($folder, $url)
}
/**
- * @param string $folder
- * @param string $url
+ * @param string $folder
+ * @param string $url
* @return bool
*/
private function handleActiveRequest($folder, $url)
diff --git a/Core/Cloud.php b/Core/Cloud.php
index a5b3ee27..3338a6d4 100644
--- a/Core/Cloud.php
+++ b/Core/Cloud.php
@@ -4,10 +4,8 @@
class Cloud
{
-
private $cloudName;
-
private function __construct($cloudName)
{
$this->cloudName = (string)$cloudName;
diff --git a/Core/CloudinaryImageManager.php b/Core/CloudinaryImageManager.php
index 2a11c7c0..e2eaba01 100644
--- a/Core/CloudinaryImageManager.php
+++ b/Core/CloudinaryImageManager.php
@@ -7,6 +7,7 @@
/**
* Class CloudinaryImageManager
+ *
* @package Cloudinary\Cloudinary\Core
*/
class CloudinaryImageManager
@@ -26,45 +27,67 @@ class CloudinaryImageManager
*/
private $synchronisationRepository;
+ /**
+ * @var ConfigurationInterface
+ */
+ private $configuration;
+
/**
* CloudinaryImageManager constructor.
*
- * @param ImageProvider $cloudinaryImageProvider
+ * @param ImageProvider $cloudinaryImageProvider
* @param SynchroniseAssetsRepositoryInterface $synchronisationRepository
+ * @param ConfigurationInterface $configuration
*/
public function __construct(
ImageProvider $cloudinaryImageProvider,
- SynchroniseAssetsRepositoryInterface $synchronisationRepository
+ SynchroniseAssetsRepositoryInterface $synchronisationRepository,
+ ConfigurationInterface $configuration
) {
$this->cloudinaryImageProvider = $cloudinaryImageProvider;
$this->synchronisationRepository = $synchronisationRepository;
+ $this->configuration = $configuration;
}
/**
- * @param Image $image
- * @param OutputInterface|null $output
+ * @param Image $image
+ * @param OutputInterface|null $output
* @throws \Exception
*/
- public function uploadAndSynchronise(Image $image, OutputInterface $output = null, $retryAttempt = 0)
+ public function uploadAndSynchronise(Image $image, ?OutputInterface $output = null, $retryAttempt = 0)
{
+ $uploadResult = null;
+ if (!$this->configuration->isEnabled() || !$this->configuration->hasEnvironmentVariable()) {
+ return;
+ }
+
try {
+
$this->report($output, sprintf(self::MESSAGE_UPLOADING_IMAGE, $image));
- $this->cloudinaryImageProvider->upload($image);
+ $uploadResult = $this->cloudinaryImageProvider->upload($image);
} catch (FileExists $e) {
$this->report($output, sprintf(self::MESSAGE_UPLOADED_EXISTS, $image));
+ $uploadResult['file_exist'] = preg_replace('/\.[^.]+$/', '', $image->getRelativePath());
+
} catch (\Exception $e) {
- if ($retryAttempt < self::MAXIMUM_RETRY_ATTEMPTS) {
- $retryAttempt++;
- $this->report($output, sprintf(self::MESSAGE_RETRY, $e->getMessage(), $retryAttempt));
- usleep(rand(10, 1000) * 1000);
- $this->uploadAndSynchronise($image, $output, $retryAttempt);
- return;
- }
+ if ($e->getMessage() === FileExists::DEFAULT_MESSAGE) {
+ $this->report($output, sprintf(self::MESSAGE_UPLOADED_EXISTS, $image));
+ $uploadResult['file_exist'] = preg_replace('/\.[^.]+$/', '', $image->getRelativePath());
+ } else {
+ if ($retryAttempt < self::MAXIMUM_RETRY_ATTEMPTS) {
+ $retryAttempt++;
+ $this->report($output, sprintf(self::MESSAGE_RETRY, $e->getMessage(), $retryAttempt));
+ usleep(rand(10, 1000) * 1000);
+ $this->uploadAndSynchronise($image, $output, $retryAttempt);
+ return $uploadResult;
+ }
- throw $e;
+ throw $e;
+ }
}
$this->synchronisationRepository->saveAsSynchronized($image->getRelativePath());
+ return $uploadResult;
}
/**
@@ -72,15 +95,19 @@ public function uploadAndSynchronise(Image $image, OutputInterface $output = nul
*/
public function removeAndUnSynchronise(Image $image)
{
+ if (!$this->configuration->isEnabled() || !$this->configuration->hasEnvironmentVariable()) {
+ return;
+ }
+
$this->cloudinaryImageProvider->delete($image);
$this->synchronisationRepository->removeSynchronised($image->getRelativePath());
}
/**
* @param OutputInterface|null $output
- * @param string $message
+ * @param string $message
*/
- private function report(OutputInterface $output = null, $message = '')
+ private function report(?OutputInterface $output = null, $message = '')
{
if ($output) {
$output->writeln($message);
diff --git a/Core/CloudinaryImageProvider.php b/Core/CloudinaryImageProvider.php
index cbfe3560..adcd804c 100644
--- a/Core/CloudinaryImageProvider.php
+++ b/Core/CloudinaryImageProvider.php
@@ -2,16 +2,22 @@
namespace Cloudinary\Cloudinary\Core;
-use Cloudinary;
-use Cloudinary\Uploader;
+use Cloudinary\Api\BaseApiClient;
+use Cloudinary\Api\Upload\UploadApi;
+use Cloudinary\Asset\Media;
use Cloudinary\Cloudinary\Core\Exception\ApiError;
use Cloudinary\Cloudinary\Core\Image\Transformation;
-use Cloudinary\Cloudinary\Core\Security;
-use Cloudinary\Cloudinary\Core\Image\Transformation\Format;
-use Cloudinary\Cloudinary\Core\Image\Transformation\FetchFormat;
+use Cloudinary\Cloudinary\Model\MediaLibraryMapFactory;
+use Cloudinary\Configuration\Configuration;
+use Magento\Catalog\Model\Product\Media\Config as ProductMediaConfig;
class CloudinaryImageProvider implements ImageProvider
{
+ /**
+ * @var bool
+ */
+ private $_authorised;
+
/**
* @var ConfigurationInterface
*/
@@ -28,59 +34,76 @@ class CloudinaryImageProvider implements ImageProvider
private $configurationBuilder;
/**
- * @var CredentialValidator
+ * @var ProductMediaConfig
+ */
+ private $productMediaConfig;
+
+ /**
+ * @var MediaLibraryMapFactory
+ */
+ private $mediaLibraryMapFactory;
+
+ /**
+ * @var $uploader
*/
- private $credentialValidator;
+ protected $uploader;
/**
* @param ConfigurationInterface $configuration
* @param ConfigurationBuilder $configurationBuilder
* @param UploadResponseValidator $uploadResponseValidator
- * @param CredentialValidator $credentialValidator
+ * @param ProductMediaConfig $productMediaConfig
+ * @param MediaLibraryMapFactory $mediaLibraryMapFactory
*/
public function __construct(
ConfigurationInterface $configuration,
ConfigurationBuilder $configurationBuilder,
UploadResponseValidator $uploadResponseValidator,
- CredentialValidator $credentialValidator
+ ProductMediaConfig $productMediaConfig,
+ MediaLibraryMapFactory $mediaLibraryMapFactory
) {
$this->configuration = $configuration;
$this->uploadResponseValidator = $uploadResponseValidator;
$this->configurationBuilder = $configurationBuilder;
- $this->credentialValidator = $credentialValidator;
- if ($configuration->isEnabled()) {
- $this->authorise();
- }
+ $this->productMediaConfig = $productMediaConfig;
+ $this->mediaLibraryMapFactory = $mediaLibraryMapFactory;
}
/**
- * @param ConfigurationInterface $configuration
+ * @param ConfigurationInterface $configuration
* @return CloudinaryImageProvider
*/
- public static function fromConfiguration(ConfigurationInterface $configuration){
+ public static function fromConfiguration(ConfigurationInterface $configuration)
+ {
return new CloudinaryImageProvider(
$configuration,
new ConfigurationBuilder($configuration),
- new UploadResponseValidator(),
- new CredentialValidator()
+ new UploadResponseValidator()
);
}
/**
- * @param Image $image
+ * @param Image $image
* @return mixed
*/
public function upload(Image $image)
{
+ $this->authorise();
if (!$this->configuration->isEnabled()) {
return false;
}
try {
- $uploadResult = Uploader::upload(
+ $this->uploader = new UploadApi($this->configuration->getCredentials());
+ $uploadResult = $this->uploader->upload(
(string)$image,
$this->configuration->getUploadConfig()->toArray() + [ "folder" => $image->getRelativeFolder()]
);
+ if (isset($uploadResult['public_id'])) {
+ $publicIds = [$uploadResult['public_id']];
+ $metadata = "cld_mag_plugin=1";
+ $this->uploader->addContext($metadata, $publicIds);
+ }
return $this->uploadResponseValidator->validateResponse($image, $uploadResult);
} catch (\Exception $e) {
ApiError::throwWith($image, $e->getMessage());
@@ -88,35 +111,83 @@ public function upload(Image $image)
}
/**
- * @param Image $image
- * @param Transformation $transformation
+ * @param Image $image
+ * @param Transformation $transformation
* @return Image
*/
public function retrieveTransformed(Image $image, Transformation $transformation)
{
- return Image::fromPath(
- \cloudinary_url($image->getId(), ['transformation' => $transformation->build(), 'secure' => true]),
- $image->getRelativePath()
- );
+ $this->authorise();
+ $imageId = $image->getId();
+
+ if ($this->configuration->isEnabledLocalMapping()) {
+ //Look for a match on the mapping table:
+ preg_match('/(cld_[A-Za-z0-9]{13}_).+$/i', $imageId, $cldUniqid);
+ if ($cldUniqid && isset($cldUniqid[1])) {
+ $mapped = $this->mediaLibraryMapFactory->create()->getCollection()->addFieldToFilter("cld_uniqid", $cldUniqid[1])->setPageSize(1)->getFirstItem();
+ if ($mapped && ($origPublicId = $mapped->getCldPublicId())) {
+ if (preg_match('/http(s?)\:\/\//i', $origPublicId)) { // If the image is a thumbnail the publicId woud be the full URL
+ return Image::fromPath($origPublicId);
+ }
+ if (($freeTransformation = $mapped->getFreeTransformation()) && \strpos($imageId, $this->productMediaConfig->getBaseMediaUrl()) === 0) {
+ $transformation->withFreeform($freeTransformation, false);
+ }
+ $imageId = $origPublicId;
+ }
+ }
+ }
+
+ //Generate the CLD URL:
+ $imagePath = Media::fromParams(
+ $imageId,
+ [
+ 'transformation' => $transformation->build(),
+ 'secure' => true,
+ 'sign_url' => $this->configuration->getUseSignedUrls(),
+ 'version' => 1
+ ]
+ ) . '?_i=AB';
+
+ if (!$this->configuration->isEnabledProductGallery()) {
+ //Handle with use-root-path if necessary:
+ if ($this->configuration->getUseRootPath()) {
+ if (\strpos($imagePath, "cloudinary.com/{$this->configuration->getCloud()}/image/upload/") !== false) {
+ $imagePath = str_replace("cloudinary.com/{$this->configuration->getCloud()}/image/upload/", "cloudinary.com/{$this->configuration->getCloud()}/", $imagePath);
+ } elseif (\strpos($imagePath, "cloudinary.com/image/upload/") !== false) {
+ $imagePath = str_replace("cloudinary.com/image/upload/", "cloudinary.com/", $imagePath);
+ }
+ }
+
+ //Remove version number if necessary:
+ if ($this->configuration->getRemoveVersionNumber()) {
+ $regex = '/\/v[0-9]{1,10}\/' . preg_quote(ltrim($imageId, '/'), '/') . '$/';
+ $imagePath = preg_replace($regex, '/' . ltrim($imageId, '/'), $imagePath);
+ }
+ }
+
+ return Image::fromPath($imagePath, $image->getRelativePath());
}
/**
- * @param Image $image
+ * @param Image $image
* @return Image
*/
public function retrieve(Image $image)
{
+ $this->authorise();
return $this->retrieveTransformed($image, $this->configuration->getDefaultTransformation());
}
/**
- * @param Image $image
+ * @param Image $image
* @return bool
*/
public function delete(Image $image)
{
+ $this->authorise();
if ($this->configuration->isEnabled()) {
- Uploader::destroy($image->getIdWithoutExtension());
+ $this->uploader = new UploadApi($this->configuration->getCredentials());
+ return $this->uploader->destroy($image->getIdWithoutExtension());
}
}
@@ -125,12 +196,26 @@ public function delete(Image $image)
*/
public function validateCredentials()
{
- return $this->credentialValidator->validate($this->configuration->getCredentials());
+ try {
+ $this->authorise();
+ $pingValidation = $this->api->ping();
+ if (!(isset($pingValidation["status"]) && $pingValidation["status"] === "ok")) {
+ return false;
+ //throw new ValidatorException(__(self::CREDENTIALS_CHECK_UNSURE));
+ }
+ } catch (\Exception $e) {
+ return false;
+ }
+
+ return true;
}
private function authorise()
{
- Cloudinary::config($this->configurationBuilder->build());
- Cloudinary::$USER_PLATFORM = $this->configuration->getUserPlatform();
+ if (!$this->_authorised && $this->configuration->isEnabled()) {
+ Configuration::instance($this->configurationBuilder->build());
+ BaseApiClient::$userPlatform = $this->configuration->getUserPlatform();
+ $this->_authorised = true;
+ }
}
}
diff --git a/Core/ConfigurationBuilder.php b/Core/ConfigurationBuilder.php
index 21e8f50d..9b50eaad 100644
--- a/Core/ConfigurationBuilder.php
+++ b/Core/ConfigurationBuilder.php
@@ -1,6 +1,7 @@
configuration = $configuration;
}
+ /**
+ * @return Configuration instance
+ */
public function build()
{
- $config = [
- "cloud_name" => (string)$this->configuration->getCloud(),
- "api_key" => (string)$this->configuration->getCredentials()->getKey(),
- "api_secret" => (string)$this->configuration->getCredentials()->getSecret()
- ];
-
- if ($this->configuration->getCdnSubdomainStatus()) {
- $config['cdn_subdomain'] = true;
+
+ $reg = $this->configuration->getCoreRegistry();
+ $credentials = $this->configuration->getCredentials();
+ if ($credentials) {
+ $cloud = [
+ 'cloud_name' => $credentials['cloud_name'],
+ 'api_key' => $credentials['api_key'],
+ 'api_secret' => $credentials['api_secret']
+ ];
+
+ $url = array_diff($credentials, $cloud);
+
+ $config = array('cloud' => $cloud);
+
+ if ($url && is_array($url)) {
+ $config['url'] = $url;
+ }
+
+ if ($this->configuration->getCdnSubdomainStatus()) {
+ $config['cloud']['cdn_subdomain'] = true;
+ }
+
+ return $config;
}
- return $config;
+ return $this;
}
}
diff --git a/Core/ConfigurationInterface.php b/Core/ConfigurationInterface.php
index a362195e..e2c6a1db 100644
--- a/Core/ConfigurationInterface.php
+++ b/Core/ConfigurationInterface.php
@@ -2,8 +2,6 @@
namespace Cloudinary\Cloudinary\Core;
-use Cloudinary\Cloudinary\Core\Cloud;
-use Cloudinary\Cloudinary\Core\Credentials;
use Cloudinary\Cloudinary\Core\Image\Transformation;
interface ConfigurationInterface
@@ -49,7 +47,7 @@ public function isEnabled();
public function getFormatsToPreserve();
/**
- * @param string $file
+ * @param string $file
*
* @return string
*/
diff --git a/Core/CredentialValidator.php b/Core/CredentialValidator.php
index f6b0f152..99290b98 100644
--- a/Core/CredentialValidator.php
+++ b/Core/CredentialValidator.php
@@ -13,6 +13,5 @@ public function validate(Credentials $credentials)
$request = new ValidateRemoteUrlRequest($signedValidationUrl);
return $request->validate();
-
}
}
diff --git a/Core/Credentials.php b/Core/Credentials.php
index d30567d6..545eb97d 100644
--- a/Core/Credentials.php
+++ b/Core/Credentials.php
@@ -2,23 +2,21 @@
namespace Cloudinary\Cloudinary\Core;
-
use Cloudinary\Cloudinary\Core\Security\Key;
use Cloudinary\Cloudinary\Core\Security\Secret;
class Credentials
{
-
private $key;
private $secret;
- private function __construct(Key $key,Secret $secret)
+ private function __construct(Key $key, Secret $secret)
{
$this->key = $key;
$this->secret = $secret;
}
- public static function fromKeyAndSecret(Key $key,Secret $secret)
+ public static function fromKeyAndSecret(Key $key, Secret $secret)
{
return new Credentials($key, $secret);
}
diff --git a/Core/Exception/FileExists.php b/Core/Exception/FileExists.php
index 2c515534..6bba7785 100644
--- a/Core/Exception/FileExists.php
+++ b/Core/Exception/FileExists.php
@@ -4,5 +4,5 @@
class FileExists extends MigrationError
{
- const DEFAULT_MESSAGE = 'File already exists (cloudinary is case insensitive!!).';
+ const DEFAULT_MESSAGE = 'File already exists (cloudinary is case insensitive).';
}
diff --git a/Core/Exception/InvalidCredentials.php b/Core/Exception/InvalidCredentials.php
index 6f36aaba..129c95fe 100644
--- a/Core/Exception/InvalidCredentials.php
+++ b/Core/Exception/InvalidCredentials.php
@@ -6,5 +6,4 @@
class InvalidCredentials extends Exception
{
-
}
diff --git a/Core/Exception/MigrationError.php b/Core/Exception/MigrationError.php
index 6ede19f7..f6bb259e 100644
--- a/Core/Exception/MigrationError.php
+++ b/Core/Exception/MigrationError.php
@@ -7,6 +7,7 @@
/**
* Class MigrationError
+ *
* @package Cloudinary\Cloudinary\Core\Exception
*/
class MigrationError extends Exception
@@ -35,8 +36,8 @@ public function getImage()
}
/**
- * @param Image $image
- * @param string $message
+ * @param Image $image
+ * @param string $message
* @throws MigrationError
*/
public static function throwWith(Image $image, $message = '')
diff --git a/Core/Image/ImageFactory.php b/Core/Image/ImageFactory.php
index aa409379..cd76f4ad 100644
--- a/Core/Image/ImageFactory.php
+++ b/Core/Image/ImageFactory.php
@@ -3,7 +3,6 @@
namespace Cloudinary\Cloudinary\Core\Image;
use Cloudinary\Cloudinary\Core\ConfigurationInterface;
-use Cloudinary\Cloudinary\Core\Image\SynchronizationCheck;
use Cloudinary\Cloudinary\Core\Image;
class ImageFactory
@@ -20,8 +19,9 @@ class ImageFactory
/**
* ImageFactory constructor.
+ *
* @param ConfigurationInterface $configuration
- * @param SynchronizationCheck $synchronizationChecker
+ * @param SynchronizationCheck $synchronizationChecker
*/
public function __construct(ConfigurationInterface $configuration, SynchronizationCheck $synchronizationChecker)
{
@@ -30,7 +30,7 @@ public function __construct(ConfigurationInterface $configuration, Synchronizati
}
/**
- * @param $imagePath
+ * @param $imagePath
* @return Image
*/
public function build($imagePath, callable $localPathGenerator)
diff --git a/Core/Image/Transformation.php b/Core/Image/Transformation.php
index 8ed3b574..f477142c 100644
--- a/Core/Image/Transformation.php
+++ b/Core/Image/Transformation.php
@@ -2,16 +2,18 @@
namespace Cloudinary\Cloudinary\Core\Image;
+use Cloudinary\Cloudinary\Core\Image\Transformation\Crop;
+use Cloudinary\Cloudinary\Core\Image\Transformation\DefaultImage;
use Cloudinary\Cloudinary\Core\Image\Transformation\Dimensions;
use Cloudinary\Cloudinary\Core\Image\Transformation\Dpr;
use Cloudinary\Cloudinary\Core\Image\Transformation\FetchFormat;
+use Cloudinary\Cloudinary\Core\Image\Transformation\Freeform;
use Cloudinary\Cloudinary\Core\Image\Transformation\Gravity;
use Cloudinary\Cloudinary\Core\Image\Transformation\Quality;
-use Cloudinary\Cloudinary\Core\Image\Transformation\Crop;
-use Cloudinary\Cloudinary\Core\Image\Transformation\Freeform;
class Transformation
{
+ private $defaultImage;
private $gravity;
private $dimensions;
private $crop;
@@ -23,14 +25,23 @@ class Transformation
public function __construct()
{
- $this->crop = 'pad';
+ $this->crop = 'lpad';
$this->flags = [];
}
+ public function withDefaultImage(DefaultImage $defaultImage)
+ {
+ $defaultImageStr = trim((string)$defaultImage);
+ if (!empty($defaultImageStr)) {
+ $this->defaultImage = $defaultImageStr;
+ }
+ return $this;
+ }
+
public function withGravity(Gravity $gravity)
{
$this->gravity = $gravity;
- $this->crop = ((string)$gravity) ? 'crop' : 'pad';
+ $this->crop = ((string)$gravity) ? 'crop' : 'lpad';
return $this;
}
@@ -64,9 +75,9 @@ public function withDpr(Dpr $dpr)
return $this;
}
- public function withFreeform(Freeform $freeform)
+ public function withFreeform(Freeform $freeform, $append = true)
{
- $this->freeform = $freeform;
+ $this->freeform = trim(($append) ? $this->freeform . "," . $freeform : $freeform, ",");
return $this;
}
@@ -87,13 +98,14 @@ public function build()
['raw_transformation' => (string)$this->freeform],
[
'fetch_format' => (string)$this->fetchFormat,
- 'quality' => (string)$this->quality,
+ 'quality' => (string)$this->quality ?: null,
'crop' => (string)$this->crop,
- 'gravity' => (string)$this->gravity ?: null,
+ 'gravity' => (string)$this->gravity ?: '',
'width' => $this->dimensions ? $this->dimensions->getWidth() : null,
'height' => $this->dimensions ? $this->dimensions->getHeight() : null,
'dpr' => (string)$this->dpr,
- 'flags' => $this->flags
+ 'flags' => $this->flags,
+ 'default_image' => $this->defaultImage ?? '',
]
];
}
diff --git a/Core/Image/Transformation/Crop.php b/Core/Image/Transformation/Crop.php
index 16095333..ef55cc3c 100644
--- a/Core/Image/Transformation/Crop.php
+++ b/Core/Image/Transformation/Crop.php
@@ -5,7 +5,9 @@
class Crop
{
const PAD = 'pad';
+ const LPAD = 'lpad';
const FIT = 'fit';
+ const LIMIT = 'limit';
private $value;
@@ -24,11 +26,21 @@ public static function pad()
return new Crop(self::PAD);
}
+ public static function lpad()
+ {
+ return new Crop(self::LPAD);
+ }
+
public static function fit()
{
return new Crop(self::FIT);
}
+ public static function limit()
+ {
+ return new Crop(self::LIMIT);
+ }
+
public function __toString()
{
return $this->value;
diff --git a/Core/Image/Transformation/DefaultImage.php b/Core/Image/Transformation/DefaultImage.php
new file mode 100644
index 00000000..2ee83912
--- /dev/null
+++ b/Core/Image/Transformation/DefaultImage.php
@@ -0,0 +1,28 @@
+value = $value;
+ }
+
+ public function __toString()
+ {
+ return $this->value;
+ }
+
+ public static function fromString($value)
+ {
+ return new DefaultImage($value);
+ }
+
+ public static function null()
+ {
+ return new DefaultImage(null);
+ }
+}
diff --git a/Core/Image/Transformation/Dimensions.php b/Core/Image/Transformation/Dimensions.php
index 0bc23b5b..93370be0 100644
--- a/Core/Image/Transformation/Dimensions.php
+++ b/Core/Image/Transformation/Dimensions.php
@@ -33,9 +33,10 @@ public static function squareMissingDimension(Dimensions $dimensions)
{
if (!$dimensions->getWidth()) {
return Dimensions::square($dimensions->getHeight());
- } else if (!$dimensions->getHeight()) {
+ } elseif (!$dimensions->getHeight()) {
return Dimensions::square($dimensions->getWidth());
}
+
return $dimensions;
}
diff --git a/Core/Image/Transformation/Freeform.php b/Core/Image/Transformation/Freeform.php
index 5d234737..b64388cf 100644
--- a/Core/Image/Transformation/Freeform.php
+++ b/Core/Image/Transformation/Freeform.php
@@ -11,6 +11,7 @@ class Freeform
/**
* Freeform constructor.
+ *
* @param string $urlParameters
*/
public function __construct($urlParameters)
@@ -19,7 +20,7 @@ public function __construct($urlParameters)
}
/**
- * @param string $value
+ * @param string $value
* @return Freeform
*/
public static function fromString($value)
diff --git a/Core/Image/Transformation/Gravity.php b/Core/Image/Transformation/Gravity.php
index 9ee45377..46a53295 100644
--- a/Core/Image/Transformation/Gravity.php
+++ b/Core/Image/Transformation/Gravity.php
@@ -26,5 +26,3 @@ public static function null()
return new Gravity(null);
}
}
-
-
diff --git a/Core/Migration/BatchUploader.php b/Core/Migration/BatchUploader.php
index 443a1e45..d1c4b4c4 100644
--- a/Core/Migration/BatchUploader.php
+++ b/Core/Migration/BatchUploader.php
@@ -57,11 +57,12 @@ class BatchUploader
/**
* BatchUploader constructor.
+ *
* @param ImageProvider $imageProvider
- * @param Task $migrationTask
- * @param Logger $logger
- * @param string $baseMediaPath
- * @param callable $exceptionCallback
+ * @param Task $migrationTask
+ * @param Logger $logger
+ * @param string $baseMediaPath
+ * @param callable $exceptionCallback
*/
public function __construct(
ImageProvider $imageProvider,
@@ -84,17 +85,18 @@ public function uploadImages(array $images)
{
$this->countMigrated = 0;
foreach ($images as $image) {
-
if ($this->migrationTask->hasBeenStopped()) {
break;
}
+
$this->uploadImage($image);
}
+
$this->logger->notice(sprintf(self::MESSAGE_STATUS, $this->countMigrated, $this->countFailed));
}
/**
- * @param Synchronizable $image
+ * @param Synchronizable $image
* @return string
*/
private function getAbsolutePath(Synchronizable $image)
@@ -104,7 +106,7 @@ private function getAbsolutePath(Synchronizable $image)
/**
* @param Synchronizable $image
- * @param int $retryAttempt
+ * @param int $retryAttempt
*/
private function uploadImage(Synchronizable $image, $retryAttempt = 0)
{
@@ -149,8 +151,8 @@ private function notify(\Exception $e)
}
/**
- * @param $retryMessage
- * @param \Exception $e
+ * @param $retryMessage
+ * @param \Exception $e
* @return \Exception
*/
private function addRetryMessage($retryMessage, \Exception $e)
@@ -160,12 +162,13 @@ private function addRetryMessage($retryMessage, \Exception $e)
} else {
$e = new \Exception($e->getMessage() . $retryMessage);
}
+
return $e;
}
/**
- * @param \Exception $e
- * @param $message
+ * @param \Exception $e
+ * @param $message
* @return string
*/
private function buildUploadErrorMessage(\Exception $e, $message)
diff --git a/Core/Migration/Logger.php b/Core/Migration/Logger.php
index 5303c770..24a60084 100644
--- a/Core/Migration/Logger.php
+++ b/Core/Migration/Logger.php
@@ -4,11 +4,11 @@
interface Logger
{
- public function warning($message, array $context = array());
+ public function warning($message, array $context = []);
- public function notice($message, array $context = array());
+ public function notice($message, array $context = []);
- public function error($message, array $context = array());
+ public function error($message, array $context = []);
public function debugLog($message);
}
diff --git a/Core/Security/ApiSignature.php b/Core/Security/ApiSignature.php
index 531c9422..cb5bc1cb 100644
--- a/Core/Security/ApiSignature.php
+++ b/Core/Security/ApiSignature.php
@@ -2,11 +2,10 @@
namespace Cloudinary\Cloudinary\Core\Security;
-use Cloudinary;
+use Cloudinary\Cloudinary;
class ApiSignature
{
-
private $apiSignature;
private function __construct(Secret $secret, array $params)
@@ -14,7 +13,7 @@ private function __construct(Secret $secret, array $params)
$this->apiSignature = Cloudinary::api_sign_request($params, (string) $secret);
}
- public static function fromSecretAndParams(Secret $secret, array $params = array())
+ public static function fromSecretAndParams(Secret $secret, array $params = [])
{
return new ApiSignature($secret, $params);
}
diff --git a/Core/Security/CloudinaryEnvironmentVariable.php b/Core/Security/CloudinaryEnvironmentVariable.php
index 3a8a3fe3..0c0f1ab5 100644
--- a/Core/Security/CloudinaryEnvironmentVariable.php
+++ b/Core/Security/CloudinaryEnvironmentVariable.php
@@ -2,46 +2,81 @@
namespace Cloudinary\Cloudinary\Core\Security;
-use Cloudinary;
-use Cloudinary\Cloudinary\Core\Cloud;
+use Cloudinary\Cloudinary;
use Cloudinary\Cloudinary\Core\Credentials;
+use Cloudinary\Configuration\Configuration;
class CloudinaryEnvironmentVariable implements EnvironmentVariable
{
-
+ /**
+ * @var array|string|string[]
+ */
private $environmentVariable;
+ /**
+ * @var $configuration
+ */
+ private $configuration;
+
+ /**
+ * @param $environmentVariable
+ * @throws Cloudinary\Core\Exception\InvalidCredentials
+ */
private function __construct($environmentVariable)
{
$this->environmentVariable = (string)$environmentVariable;
try {
- Cloudinary::config_from_url(str_replace('CLOUDINARY_URL=', '', $environmentVariable));
- } catch (\Exception $e){
+ //Cloudinary::config_from_url(str_replace('CLOUDINARY_URL=', '', $environmentVariable));
+ $this->environmentVariable = str_replace('CLOUDINARY_URL=', '', $environmentVariable);
+ } catch (\Exception $e) {
throw new \Cloudinary\Cloudinary\Core\Exception\InvalidCredentials('Cloudinary config creation from environment variable failed');
}
}
+ /**
+ * @param $environmentVariable
+ * @return CloudinaryEnvironmentVariable
+ * @throws Cloudinary\Core\Exception\InvalidCredentials
+ */
public static function fromString($environmentVariable)
{
return new CloudinaryEnvironmentVariable($environmentVariable);
}
+ /**
+ * @return string
+ */
public function getCloud()
{
- return Cloud::fromName(Cloudinary::config_get('cloud_name'));
+ if (!$this->configuration) {
+ if (!$this->environmentVariable) {
+ return false;
+ }
+ $this->configuration = new Configuration($this->environmentVariable);
+ }
+ return $this->configuration->cloud->cloudName;
}
+ /**
+ * @return Credentials
+ */
public function getCredentials()
{
+ if (!$this->configuration) {
+ $this->configuration = new Configuration($this->environmentVariable);
+ }
+
return Credentials::fromKeyAndSecret(
- Key::fromString(Cloudinary::config_get('api_key')),
- Secret::fromString(Cloudinary::config_get('api_secret'))
+ Key::fromString($this->configuration->cloud->apiKey),
+ Secret::fromString($this->configuration->cloud->apiSecret)
);
}
+ /**
+ * @return array|string|string[]
+ */
public function __toString()
{
return $this->environmentVariable;
}
-
}
diff --git a/Core/Security/ConsoleUrl.php b/Core/Security/ConsoleUrl.php
index 1dcdf528..601cf471 100644
--- a/Core/Security/ConsoleUrl.php
+++ b/Core/Security/ConsoleUrl.php
@@ -4,7 +4,6 @@
class ConsoleUrl
{
-
private $consoleUrl;
const CLOUDINARY_CONSOLE_BASE_URL = 'https://cloudinary.com/console/';
diff --git a/Core/Security/Key.php b/Core/Security/Key.php
index cd138703..1ab658bf 100644
--- a/Core/Security/Key.php
+++ b/Core/Security/Key.php
@@ -4,7 +4,6 @@
class Key
{
-
private $key;
private function __construct($key)
@@ -21,5 +20,4 @@ public function __toString()
{
return $this->key;
}
-
}
diff --git a/Core/Security/Secret.php b/Core/Security/Secret.php
index e07e4902..67296ab2 100644
--- a/Core/Security/Secret.php
+++ b/Core/Security/Secret.php
@@ -4,7 +4,6 @@
class Secret
{
-
private $secret;
private function __construct($secret)
diff --git a/Core/Security/SignedConsoleUrl.php b/Core/Security/SignedConsoleUrl.php
index 57483dbb..8fcf1bac 100644
--- a/Core/Security/SignedConsoleUrl.php
+++ b/Core/Security/SignedConsoleUrl.php
@@ -6,12 +6,11 @@
class SignedConsoleUrl
{
-
private $signedConsoleUrl;
private function __construct(ConsoleUrl $url, Credentials $credentials)
{
- $params = array("timestamp" => time(), "mode" => "check");
+ $params = ["timestamp" => time(), "mode" => "check"];
$params["signature"] = (string)ApiSignature::fromSecretAndParams($credentials->getSecret(), $params);
$params["api_key"] = (string)$credentials->getKey();
$query = http_build_query($params);
diff --git a/Core/SynchroniseAssetsRepositoryInterface.php b/Core/SynchroniseAssetsRepositoryInterface.php
index f5d2f278..df903f58 100644
--- a/Core/SynchroniseAssetsRepositoryInterface.php
+++ b/Core/SynchroniseAssetsRepositoryInterface.php
@@ -5,13 +5,13 @@
interface SynchroniseAssetsRepositoryInterface
{
/**
- * @param string $imagePath
+ * @param string $imagePath
* @return mixed
*/
public function saveAsSynchronized($imagePath);
/**
- * @param string g$imagePath
+ * @param string g $imagePath
* @return mixed
*/
public function removeSynchronised($imagePath);
diff --git a/Core/UploadConfig.php b/Core/UploadConfig.php
index 92e8c8dd..db43cab4 100644
--- a/Core/UploadConfig.php
+++ b/Core/UploadConfig.php
@@ -8,7 +8,6 @@
namespace Cloudinary\Cloudinary\Core;
-
class UploadConfig
{
/**
@@ -68,6 +67,6 @@ public function toArray()
"use_filename" => $this->useFilename,
"unique_filename" => $this->uniqueFilename,
"overwrite" => $this->overwrite,
- ] ;
+ ];
}
}
diff --git a/Core/UrlGenerator.php b/Core/UrlGenerator.php
index 1884baab..3958098b 100644
--- a/Core/UrlGenerator.php
+++ b/Core/UrlGenerator.php
@@ -2,7 +2,6 @@
namespace Cloudinary\Cloudinary\Core;
-use Cloudinary\Cloudinary\Core\ImageInterface;
use Cloudinary\Cloudinary\Core\Image\LocalImage;
use Cloudinary\Cloudinary\Core\Image\Transformation;
use Cloudinary\Cloudinary\Core\Image\Transformation\Dimensions;
@@ -21,7 +20,7 @@ class UrlGenerator
/**
* @param ConfigurationInterface $configuration
- * @param ImageProvider $imageProvider
+ * @param ImageProvider $imageProvider
*/
public function __construct(ConfigurationInterface $configuration, ImageProvider $imageProvider)
{
@@ -35,7 +34,7 @@ public function __construct(ConfigurationInterface $configuration, ImageProvider
*
* @return string
*/
- public function generateFor(ImageInterface $image, Transformation $transformation = null)
+ public function generateFor(ImageInterface $image, ?Transformation $transformation = null)
{
if ($image instanceof LocalImage) {
return (string)$image;
@@ -48,7 +47,7 @@ public function generateFor(ImageInterface $image, Transformation $transformatio
}
/**
- * @param Image $image
+ * @param Image $image
* @param Dimensions $dimensions
*
* @return string
diff --git a/Core/ValidateRemoteUrlRequest.php b/Core/ValidateRemoteUrlRequest.php
index 747f8210..5014f9c1 100644
--- a/Core/ValidateRemoteUrlRequest.php
+++ b/Core/ValidateRemoteUrlRequest.php
@@ -4,7 +4,6 @@
class ValidateRemoteUrlRequest
{
-
private $curlHandler;
public function __construct($url)
@@ -20,6 +19,7 @@ public function validate()
if ($result->responseCode == 200 && is_null($result->error)) {
return true;
}
+
return false;
}
diff --git a/Cron/ProductGalleryApiQueue.php b/Cron/ProductGalleryApiQueue.php
new file mode 100644
index 00000000..8a29c3e1
--- /dev/null
+++ b/Cron/ProductGalleryApiQueue.php
@@ -0,0 +1,197 @@
+configuration = $configuration;
+ $this->cldProductGalleryManagement = $cldProductGalleryManagement;
+ $this->jsonHelper = $jsonHelper;
+ $this->productVideoFactory = $productVideoFactory;
+ $this->productGalleryApiQueueFactory = $productGalleryApiQueueFactory;
+ $this->notifierPool = $notifierPool;
+ }
+
+ public function execute()
+ {
+
+ if ($this->configuration->isEnabled() && $this->configuration->isEnabledProductgalleryApiQueue()) {
+ try {
+ $queuedItems = $this->productGalleryApiQueueFactory->create()->getCollection()
+ ->addFieldToFilter("success", 0)
+ ->addFieldToFilter("tryouts", ['lt' => $this->configuration->getProductgalleryApiQueueMaxTryouts()])
+ ->setOrder('created_at', 'asc');
+ if (($_limit = $this->configuration->getProductgalleryApiQueueLimit())) {
+ $queuedItems->setPageSize($_limit);
+ }
+
+ foreach ($queuedItems as $item) {
+ try {
+ $fullItemData = $this->jsonHelper->jsonDecode($item->getFullItemData());
+ $this->processOutput("ProductGalleryApiQueue::execute() - Processing item ID: {$item->getId()} ...", "debug", ['full_item_data' => $fullItemData]);
+ $item->setTryouts($item->getTryouts() + 1);
+ $this->cldProductGalleryManagement->addGalleryItem(
+ $fullItemData["url"],
+ $fullItemData["sku"],
+ $fullItemData["publicId"],
+ $fullItemData["roles"],
+ $fullItemData["label"],
+ $fullItemData["disabled"],
+ $fullItemData["cldspinset"]
+ );
+ $item->setSuccess(1);
+ $item->setSuccessAt(date('Y-m-d H:i:s'));
+ $item->setMessage('success');
+ $item->setHasErrors(0);
+ } catch (\Exception $e) {
+ $item->setSuccess(0);
+ $item->setMessage("[ERROR]\n" . $e->getMessage() . "\n" . $e->getTraceAsString());
+ $item->setHasErrors(1);
+
+ $this->processOutput("ProductGalleryApiQueue::execute() - Exception during product-gallery API queued item processing: " . $e->getMessage(), 'error', ['trace' => $e->getTraceAsString(), 'queued_item' => $item->getData()]);
+ if (!($this->output instanceof OutputInterface) && $item->getTryouts() >= $this->configuration->getProductgalleryApiQueueMaxTryouts() && count($this->adminNotificationErrors) < 7) {
+ $this->adminNotificationErrors[] = [
+ "message" => $e->getMessage(),
+ "tryouts" => $item->getTryouts(),
+ "item_data" => $fullItemData
+ ];
+ }
+ }
+
+ $item->save();
+ $this->processOutput("ProductGalleryApiQueue::execute() - Processing item ID: {$item->getId()} - Done.", "debug");
+ }
+ } catch (\Exception $e) {
+ $this->processOutput("ProductGalleryApiQueue::execute() - Exception during product-gallery API queue processing: " . $e->getMessage(), 'error', ['trace' => $e->getTraceAsString()]);
+ if (!($this->output instanceof OutputInterface) && $item->getTryouts() >= $this->configuration->getProductgalleryApiQueueMaxTryouts() && count($this->adminNotificationErrors) < 7) {
+ $this->adminNotificationErrors[] = [
+ "message" => $e->getMessage(),
+ "details" => $e->getTraceAsString()
+ ];
+ }
+ }
+ if ($this->adminNotificationErrors) {
+ $adminNotificationErrors = $this->jsonHelper->jsonEncode(array_slice($this->adminNotificationErrors, 0, 5));
+ if (count($this->adminNotificationErrors) > 5) {
+ $adminNotificationErrors .= " ... [this message is too long, check the log for the rest] ";
+ }
+ $this->addAdminNotification("[Cloudinary] An error occurred during the background processing of the product-gallery API queue! *More detailes can be found on the Cloudinary log file (var/log/cloudinary_cloudinary.log)", $adminNotificationErrors, 'critical');
+ }
+ }
+
+ return 1;
+ }
+
+ /**
+ * @method setOutput
+ * @param OutputInterface $output
+ */
+ public function setOutput(OutputInterface $output)
+ {
+ $this->output = $output;
+ return $this;
+ }
+
+ /**
+ * @method getOutput
+ * @return OutputInterface
+ */
+ public function getOutput()
+ {
+ return $this->output;
+ }
+
+ /**
+ * Process output messages (log to system.log / output to terminal)
+ * @method _processOutput
+ * @return $this
+ */
+ protected function processOutput($message, $type = "info", $data = [])
+ {
+ if ($this->output instanceof OutputInterface) {
+ //Output to terminal
+ $outputType = ($type === "error") ? $type : "info";
+ $this->output->writeln('<' . $outputType . '>' . json_encode($message) . '' . $outputType . '>');
+ if ($data) {
+ $this->output->writeln('' . json_encode($data) . ' ');
+ }
+ } else {
+ //Log to var/log/cloudinary_cloudinary.log
+ $this->configuration->log($message, $data);
+ }
+
+ return $this;
+ }
+
+ private function addAdminNotification(string $title, $description = "", $type = 'critical')
+ {
+ $method = 'add' . ucfirst($type);
+ $this->notifierPool->{$method}($title, $description);
+ return $this;
+ }
+}
diff --git a/Cron/VideoDataImport.php b/Cron/VideoDataImport.php
new file mode 100644
index 00000000..4ecb2072
--- /dev/null
+++ b/Cron/VideoDataImport.php
@@ -0,0 +1,80 @@
+configuration = $configuration;
+ $this->cloudinaryResourcesManagement = $cloudinaryResourcesManagement;
+ $this->jsonHelper = $jsonHelper;
+ $this->productVideoFactory = $productVideoFactory;
+ }
+
+ public function execute()
+ {
+ if ($this->configuration->isEnabled()) {
+ $productVideosCollection = $this->productVideoFactory->create()->getCollection()
+ ->addFieldToSelect('*')
+ ->addFieldToFilter('provider', 'cloudinary')
+ ->addFieldToFilter('title', ['null' => true])
+ ->setOrder('value_id', 'asc')
+ ->setPageSize($this->configuration->getScheduledVideoDataImportLimit());
+ foreach ($productVideosCollection as $video) {
+ $parsedRemoteFileUrl = $this->configuration->parseCloudinaryUrl($video->getUrl());
+ $title = $description = $parsedRemoteFileUrl["publicId"];
+
+ $videoData = (array) $this->jsonHelper->jsonDecode($this->cloudinaryResourcesManagement->setId($parsedRemoteFileUrl["publicId"])->getVideo());
+ if (!$videoData["error"]) {
+ $videoData["context"] = new DataObject((isset($videoData["data"]["context"])) ? (array)$videoData["data"]["context"] : []);
+ $title = $videoData["context"]->getData('caption') ?: $videoData["context"]->getData('alt');
+ $description = $videoData["context"]->getData('description') ?: $videoData["context"]->getData('alt');
+ }
+ $title = $title ?: $parsedRemoteFileUrl["publicId"];
+ $description = preg_replace('/( |<([^>]+)>)/i', '', $description ?: $title);
+
+ $video->setTitle((string) $title)
+ ->setDescription((string) $description)
+ ->save();
+ }
+ }
+ }
+}
diff --git a/Helper/MediaLibraryHelper.php b/Helper/MediaLibraryHelper.php
new file mode 100644
index 00000000..30d01f59
--- /dev/null
+++ b/Helper/MediaLibraryHelper.php
@@ -0,0 +1,111 @@
+configuration = $configuration;
+ }
+
+ /**
+ * @method getCloudinaryMLOptions
+ * @param bool $multiple Allow multiple
+ * @param bool $refresh Refresh options
+ * @return array
+ */
+ public function getCloudinaryMLOptions($multiple = false, $refresh = true)
+ {
+ if ((is_null($this->cloudinaryMLoptions) || $refresh) && $this->configuration->isEnabled()) {
+ $this->cloudinaryMLoptions = [];
+ $this->timestamp = time();
+ $this->credentials = $this->configuration->getCredentials();
+ if (!$this->credentials["cloud_name"] || !$this->credentials["api_key"] || !$this->credentials["api_secret"]) {
+ $this->credentials = null;
+ } else {
+ $this->cloudinaryMLoptions = [
+ 'cloud_name' => $this->credentials["cloud_name"],
+ 'api_key' => $this->credentials["api_key"],
+ 'cms_type' => 'magento',
+ //'default_transformations' => [['quality' => 'auto'],['format' => 'auto']],
+ 'integration' => [
+ 'type' => 'magento_plugin',
+ 'version' => $this->configuration->getModuleVersion(),
+ 'platform' => "{$this->configuration->getMagentoPlatformName()} {$this->configuration->getMagentoPlatformEdition()} {$this->configuration->getMagentoPlatformVersion()}"
+ ]
+ ];
+ }
+ }
+ if ($this->cloudinaryMLoptions) {
+ $this->cloudinaryMLoptions['multiple'] = $multiple;
+ }
+
+ return $this->cloudinaryMLoptions;
+ }
+
+ /**
+ * @method getCloudinaryMLshowOptions
+ * @param string|null $resourceType
+ * @param string $path
+ * @return [type]
+ */
+ public function getCloudinaryMLshowOptions($resourceType = null, $path = "")
+ {
+ $options = [];
+ if ($resourceType || $resourceType) {
+ $options["folder"] = [
+ "path" => $path,
+ "resource_type" => $resourceType,
+ ];
+ }
+ return $options;
+ }
+
+ /**
+ * @return null
+ */
+ public function getCname()
+ {
+ return $this->configuration->isEnabled() && isset($this->configuration->getCredentials()['cname']) ? $this->configuration->getCredentials()['cname'] : null;
+ }
+}
diff --git a/Helper/Product/Free.php b/Helper/Product/Free.php
index 57750788..0febc6c4 100644
--- a/Helper/Product/Free.php
+++ b/Helper/Product/Free.php
@@ -2,10 +2,10 @@
namespace Cloudinary\Cloudinary\Helper\Product;
-use Cloudinary\Cloudinary\Core\Image\Transformation\Freeform;
use Cloudinary\Cloudinary\Core\ConfigurationInterface;
-use Magento\Catalog\Model\Product;
+use Cloudinary\Cloudinary\Core\Image\Transformation\Freeform;
use Cloudinary\Cloudinary\Model\Config\Backend\Free as FreeModel;
+use Magento\Catalog\Model\Product;
class Free
{
@@ -20,7 +20,7 @@ class Free
private $configuration;
/**
- * @param FreeModel $freeModel
+ * @param FreeModel $freeModel
* @param ConfigurationInterface $configuration
*/
public function __construct(FreeModel $freeModel, ConfigurationInterface $configuration)
@@ -43,8 +43,8 @@ public function validate($imageName, $transform)
}
/**
- * @param string $id
- * @param array $images
+ * @param string $id
+ * @param array $images
* @return string
*/
public function getImageNameForId($id, array $images)
@@ -53,7 +53,7 @@ public function getImageNameForId($id, array $images)
}
/**
- * @param Product $product
+ * @param Product $product
* @return array
*/
public function getMediaGalleryImages(Product $product)
@@ -68,8 +68,8 @@ public function getMediaGalleryImages(Product $product)
}
/**
- * @param array|null $data
- * @param array|null $isUpdated
+ * @param array|null $data
+ * @param array|null $isUpdated
* @return array
*/
public function filterUpdated($data, $isUpdated)
@@ -80,7 +80,7 @@ public function filterUpdated($data, $isUpdated)
return array_filter(
$data,
- function($id) use ($isUpdated) {
+ function ($id) use ($isUpdated) {
return $isUpdated[$id] === '1';
},
ARRAY_FILTER_USE_KEY
diff --git a/Helper/ProductGalleryHelper.php b/Helper/ProductGalleryHelper.php
new file mode 100644
index 00000000..1807f1d2
--- /dev/null
+++ b/Helper/ProductGalleryHelper.php
@@ -0,0 +1,215 @@
+ 'string',
+ 'themeProps_onPrimary' => 'string',
+ 'themeProps_active' => 'string',
+ 'themeProps_onActive' => 'string',
+ 'transition' => 'string',
+ 'aspectRatio' => 'string',
+ 'navigation' => 'string',
+ 'zoom' => 'bool',
+ 'zoomProps_type' => 'string',
+ 'zoomPropsViewerPosition' => 'string',
+ 'zoomProps_trigger' => 'string',
+ 'carouselLocation' => 'string',
+ 'carouselOffset' => 'float',
+ 'carouselStyle' => 'string',
+ 'thumbnailProps_width' => 'float',
+ 'thumbnailProps_height' => 'float',
+ 'thumbnailProps_navigationShape' => 'string',
+ 'thumbnailProps_selectedStyle' => 'string',
+ 'thumbnailProps_selectedBorderPosition' => 'string',
+ 'thumbnailProps_selectedBorderWidth' => 'float',
+ 'thumbnailProps_mediaSymbolShape' => 'string',
+ 'indicatorProps_shape' => 'string',
+ ];
+
+ /**
+ * @param Context $context
+ * @param ConfigurationInterface $configuration
+ * @param DesignInterface $viewDesign
+ */
+ public function __construct(
+ Context $context,
+ ConfigurationInterface $configuration,
+ DesignInterface $viewDesign
+ ) {
+ parent::__construct($context);
+ $this->configuration = $configuration;
+ $this->viewDesign = $viewDesign;
+ }
+
+ /**
+ * Check if Hyvä Theme is active
+ *
+ * @return bool
+ */
+ public function isHyvaThemeEnabled(): bool
+ {
+ $theme = $this->viewDesign->getDesignTheme();
+
+ while ($theme) {
+ if (strpos($theme->getCode(), 'Hyva/') === 0) {
+ return true;
+ }
+ $theme = $theme->getParentTheme();
+ }
+
+ return false;
+ }
+
+ /**
+ * @method getCloudinaryPGOptions
+ * @param bool $refresh Refresh options
+ * @param bool $ignoreDisabled Get te options even if the module or the product gallery are disabled
+ * @return array|null
+ */
+ public function getCloudinaryPGOptions($refresh = false, $ignoreDisabled = false)
+ {
+ if ((is_null($this->cloudinaryPGoptions) || $refresh) &&
+ ($ignoreDisabled || ($this->configuration->isEnabled() && $this->configuration->isEnabledProductGallery()))
+ ) {
+ $this->cloudinaryPGoptions = $this->configuration->getProductGalleryAll();
+
+ if ($this->configuration->isEnabledCldVideo()) {
+ $transformation = [];
+ $videoSettings = $this->configuration->getAllVideoSettings();
+ $videoFreeParams = $videoSettings['video_free_params'] ?? null;
+ $videoControls = $videoSettings['controls'] ?? "none";
+
+ if ($videoFreeParams) {
+ $config = json_decode($videoFreeParams, true);
+ $config = array_shift($config);
+ unset($config['cloudName']);
+ }
+ $config['playerType'] = 'cloudinary';
+
+ if (!$videoFreeParams || $videoFreeParams == "{}") {
+ $config = [
+ 'playerType' => 'cloudinary',
+ 'controls' => $videoControls,
+ 'chapters' => false,
+ 'muted' => false
+ ];
+ $autoplayMode = $videoSettings['autoplay'] ?? null;
+ $config['autoplayMode'] = $autoplayMode;
+ if ($autoplayMode && $autoplayMode != 'never') {
+ $config['autoplay'] = true;
+ $config['muted'] = true;
+ } else {
+ $config['autoplay'] = false;
+ }
+
+ $streamMode = $videoSettings['stream_mode'] ?? null;
+
+ if ($streamMode == 'optimization') {
+ $streamModeFormat = $videoSettings['stream_mode_format'] ?? null;
+ $streamModeQuality = $videoSettings['stream_mode_quality'] ?? null;
+ if ($streamModeFormat) {
+ $transformation[] = $streamModeFormat;
+ }
+
+ if ($streamModeQuality) {
+ $transformation[] = $streamModeQuality;
+ }
+ }
+ if ($streamMode == 'abr') {
+ if (isset($videoSettings['source_types'])) {
+ $config['sourceTypes'] = ['auto'];
+ $transformation[] = 'f_' . $videoSettings['source_types'];
+ }
+ }
+
+ if ($transformation && is_array($transformation)) {
+ $config['transformation'] = implode(',', $transformation);
+ }
+ }
+ $this->cloudinaryPGoptions['videoProps'] = $config;
+ }
+
+ foreach ($this->cloudinaryPGoptions as $key => $value) {
+ // Change casting
+ if (isset($this->_casting[$key])) {
+ \settype($value, $this->_casting[$key]);
+ $this->cloudinaryPGoptions[$key] = $value;
+ }
+ // Build options hierarchy
+ $path = explode("_", $key);
+ $_path = $path[0];
+ if (in_array($_path, ['themeProps', 'zoomProps', 'thumbnailProps', 'indicatorProps'])) {
+ if (!isset($this->cloudinaryPGoptions[$_path])) {
+ $this->cloudinaryPGoptions[$_path] = [];
+ }
+ array_shift($path);
+ $path = implode("_", $path);
+ $this->cloudinaryPGoptions[$_path][$path] = $value;
+ unset($this->cloudinaryPGoptions[$key]);
+ }
+ }
+ if (isset($this->cloudinaryPGoptions['enabled'])) {
+ unset($this->cloudinaryPGoptions['enabled']);
+ }
+ if (isset($this->cloudinaryPGoptions['custom_free_params'])) {
+ $customFreeParams = (array) json_decode($this->cloudinaryPGoptions['custom_free_params'], true);
+ $this->cloudinaryPGoptions = array_replace_recursive($this->cloudinaryPGoptions, $customFreeParams);
+ unset($this->cloudinaryPGoptions['custom_free_params']);
+ }
+ $this->cloudinaryPGoptions['cloudName'] = $this->getCloudName();
+ $this->cloudinaryPGoptions['cname'] = $this->getCname();
+ $this->cloudinaryPGoptions['queryParam'] = 'AB';
+ }
+
+ return $this->cloudinaryPGoptions;
+ }
+
+ /**
+ * @method getCloudName
+ * @return string
+ */
+ public function getCloudName()
+ {
+ return (string)$this->configuration->getCloud();
+ }
+
+ /**
+ * @return string
+ */
+ public function getCname()
+ {
+ $config = $this->configuration->getCredentials();
+ return ($config['cname']) ?? '';
+ }
+
+ /**
+ * @return bool
+ */
+ public function canDisplayProductGallery()
+ {
+ return ($this->configuration->isEnabled() && $this->configuration->isEnabledProductGallery()) ? true : false;
+ }
+}
diff --git a/Helper/Reset.php b/Helper/Reset.php
index 2ec8a002..267ef1a4 100644
--- a/Helper/Reset.php
+++ b/Helper/Reset.php
@@ -25,8 +25,8 @@ class Reset
/**
* @param ResourceConnection $connection
- * @param Synchronisation $synchronisation
- * @param Transformation $transformation
+ * @param Synchronisation $synchronisation
+ * @param Transformation $transformation
*/
public function __construct(
ResourceConnection $connection,
diff --git a/LICENSE.txt b/LICENSE.txt
index 49525fd9..5ef82b5c 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,48 +1,7 @@
+Copyright 2018 Cloudinary
-Open Software License ("OSL") v. 3.0
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work:
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-Licensed under the Open Software License version 3.0
-
- 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following:
-
- 1. to reproduce the Original Work in copies, either alone or as part of a collective work;
-
- 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work;
-
- 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License;
-
- 4. to perform the Original Work publicly; and
-
- 5. to display the Original Work publicly.
-
- 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works.
-
- 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work.
-
- 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license.
-
- 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c).
-
- 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.
-
- 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer.
-
- 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation.
-
- 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c).
-
- 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.
-
- 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License.
-
- 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.
-
- 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.
-
- 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
-
- 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.
-
- 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.
\ No newline at end of file
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/LICENSE_AFL.txt b/LICENSE_AFL.txt
deleted file mode 100644
index f39d641b..00000000
--- a/LICENSE_AFL.txt
+++ /dev/null
@@ -1,48 +0,0 @@
-
-Academic Free License ("AFL") v. 3.0
-
-This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work:
-
-Licensed under the Academic Free License version 3.0
-
- 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following:
-
- 1. to reproduce the Original Work in copies, either alone or as part of a collective work;
-
- 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work;
-
- 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License;
-
- 4. to perform the Original Work publicly; and
-
- 5. to display the Original Work publicly.
-
- 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works.
-
- 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work.
-
- 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license.
-
- 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c).
-
- 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.
-
- 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer.
-
- 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation.
-
- 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c).
-
- 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.
-
- 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License.
-
- 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.
-
- 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.
-
- 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
-
- 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.
-
- 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.
diff --git a/Model/Api/ProductGalleryManagement.php b/Model/Api/ProductGalleryManagement.php
new file mode 100644
index 00000000..a7e2c6f5
--- /dev/null
+++ b/Model/Api/ProductGalleryManagement.php
@@ -0,0 +1,924 @@
+configuration = $configuration;
+ $this->request = $request;
+ $this->jsonHelper = $jsonHelper;
+ $this->productRepository = $productRepository;
+ $this->mediaConfig = $mediaConfig;
+ $this->fileSystem = $fileSystem;
+ $this->imageAdapter = $imageAdapterFactory->create();
+ $this->curl = $curl;
+ $this->fileUtility = $fileUtility;
+ $this->fileProcessor = $fileProcessor;
+ $this->extensionValidator = $extensionValidator;
+ $this->protocolValidator = $protocolValidator;
+ $this->storeManager = $storeManager;
+ $this->productImageFinder = $productImageFinder;
+ $this->cloudinaryImageManager = $cloudinaryImageManager;
+ $this->cloudinaryResourcesManagement = $cloudinaryResourcesManagement;
+ $this->transformationFactory = $transformationFactory;
+ $this->mediaGalleryProcessor = $mediaGalleryProcessor;
+ $this->mediaLibraryMapFactory = $mediaLibraryMapFactory;
+ $this->productGalleryApiQueueFactory = $productGalleryApiQueueFactory;
+ $this->appEmulation = $appEmulation;
+ $this->resourceConnection = $resourceConnection;
+ $this->assetRepository = $assetRepository;
+ }
+
+ /**
+ * Remove Gallery Item
+ * @param $product
+ * @param $url
+ * @param $removeAllGallery
+ * @return int|void
+ * @throws NoSuchEntityException .
+ */
+ private function removeGalleryItem($product, $url, $removeAllGallery)
+ {
+ $unlinked = 0;
+ if ($product) {
+ $mediaGalleryEntries = $product->getMediaGalleryEntries();
+
+ if (is_array($mediaGalleryEntries)) {
+ foreach ($mediaGalleryEntries as $key => $entry) {
+ $image = $entry->getFile();
+ $url = preg_replace('/\?.*/', '', $url);
+ $image = $this->storeManager->getStore()->getBaseUrl() . 'pub/media/catalog/product' . $image;
+ $basename = basename($image);
+ $ext = pathinfo($image, PATHINFO_EXTENSION);
+ // removing unique id from original filename
+ $pattern = '/^' . $this->configuration::CLD_UNIQID_PREFIX . '.*?_/';
+ $basename = preg_replace($pattern, '', $basename);
+ $filename = basename($url);
+ $filename = preg_replace($pattern, '', $filename);
+ // $filename = $url . '.' . $ext;
+
+ if ($basename == $filename || $removeAllGallery) {
+ unset($mediaGalleryEntries[$key]);
+ $this->mediaGalleryProcessor->removeImage($product, $image);
+ $unlinked++;
+ }
+ }
+ }
+ if ($unlinked) {
+ $product->setMediaGalleryEntries($mediaGalleryEntries);
+ try {
+ $product = $this->productRepository->save($product);
+ } catch (\Exception $e) {
+ $message = ['type' => 'error', 'message' => 'Falied Delete Image Error: ' . $e->getMessage() . ' line ' . $e->getLine()];
+ }
+ }
+ return $unlinked;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeProductMedia($sku, $urls, $delete_all_gallery = 0)
+ {
+ $result = [
+ "passed" => 0,
+ "failed" => [
+ "count" => 0,
+ "urls" => []
+ ]
+ ];
+
+ $urls = (array) $urls;
+
+ try {
+ $product = $this->productRepository->get($sku);
+ $this->checkEnvHeader();
+ $this->checkEnabled();
+
+ foreach ($urls as $i => $url) {
+ $unlinked = $this->removeGalleryItem($product, $url, $delete_all_gallery);
+ }
+
+ $result["passed"] = $unlinked;
+ } catch (\Exception $e) {
+ $result["failed"]["count"]++;
+ $result["error"] = $e->getMessage();
+ $result["failed"]["public_id"][] = $url;
+ }
+
+ return $this->jsonHelper->jsonEncode($result);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addProductMedia($sku, $urls)
+ {
+ $result = [
+ "passed" => 0,
+ "failed" => [
+ "count" => 0,
+ "urls" => []
+ ]
+ ];
+ try {
+ $this->checkEnvHeader();
+ $this->checkEnabled();
+ $urls = (array)$urls;
+ foreach ($urls as $i => $url) {
+ try {
+ $url = (array)$url;
+ $this->processOrQueue(
+ (isset($url["url"])) ? $url["url"] : null,
+ $sku,
+ (isset($url["publicId"])) ? $url["publicId"] : null,
+ (isset($url["roles"])) ? $url["roles"] : null,
+ (isset($url["label"])) ? $url["label"] : null,
+ (isset($url["disabled"])) ? $url["disabled"] : null,
+ (isset($url["cldspinset"])) ? $url["cldspinset"] : null
+ );
+ $result["passed"]++;
+ } catch (\Exception $e) {
+ $result["failed"]["count"]++;
+ $url["error"] = $e->getMessage();
+ $result["failed"]["urls"][] = $url;
+ }
+ }
+ } catch (\Exception $e) {
+ $result["error"] = 1;
+ $result["message"] = $e->getMessage();
+ }
+
+ if ($result["passed"] && !$result["failed"]["count"]) {
+ $result["message"] = $this->configuration->isEnabledProductgalleryApiQueue() ? "All items have been added to queue." : "success";
+ }
+
+ return $this->jsonHelper->jsonEncode($result);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getProductMedia($sku)
+ {
+ return $this->_getProductMedia($sku);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getProductsMedia($skus)
+ {
+ return $this->_getProductMedia($skus);
+ }
+
+ /**
+ * [_getProductMedia description]
+ * @method _getProductMedia
+ * @param mixed $sku
+ * @return string (json result)
+ */
+ private function _getProductMedia($sku)
+ {
+ $result = ["data" => []];
+
+ try {
+ $this->checkEnvHeader();
+ $this->checkEnabled();
+ if (is_array($sku) || is_object($sku)) {
+ foreach ($sku as $key => $_sku) {
+ $result['data'][$_sku] = $this->getProductCldUrlsBySku($_sku);
+ }
+ } else {
+ $result['data'] = $this->getProductCldUrlsBySku($sku);
+ }
+ } catch (\Exception $e) {
+ $result["error"] = 1;
+ $result["message"] = $e->getMessage();
+ }
+
+ return $this->jsonHelper->jsonEncode($result);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addItem($url = null, $sku = null, $publicId = null, $roles = null, $label = null, $disabled = 0, $cldspinset = null)
+ {
+ return $this->addItems([[
+ "url" => $url,
+ "sku" => $sku,
+ "publicId" => $publicId,
+ "roles" => $roles,
+ "label" => $label,
+ "disabled" => $disabled,
+ "cldspinset" => $cldspinset
+ ]]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addItems($items)
+ {
+ $result = [
+ "errors" => 0,
+ "items" => [],
+ "message" => ""
+ ];
+ try {
+ $this->checkEnvHeader();
+ $this->checkEnabled();
+ $items = (array)$items;
+ foreach ($items as $i => $item) {
+ try {
+ $item = $result["items"][$i] = (array)$item;
+ $result["items"][$i]["error"] = 0;
+ $result["items"][$i]["message"] = $this->configuration->isEnabledProductgalleryApiQueue() ? "The item was added to the queue." : "success";
+ $this->processOrQueue(
+ (isset($item["url"])) ? $item["url"] : null,
+ (isset($item["sku"])) ? $item["sku"] : null,
+ (isset($item["publicId"])) ? $item["publicId"] : null,
+ (isset($item["roles"])) ? $item["roles"] : null,
+ (isset($item["label"])) ? $item["label"] : null,
+ (isset($item["disabled"])) ? $item["disabled"] : null,
+ (isset($item["cldspinset"])) ? $item["cldspinset"] : null
+ );
+ } catch (\Exception $e) {
+ $result["errors"]++;
+ $result["items"][$i]["error"] = 1;
+ $result["items"][$i]["message"] = $e->getMessage();
+ if ($this->mapped && $this->mapped->getId()) {
+ $this->mapped->delete();
+ }
+ }
+ }
+ } catch (\Exception $e) {
+ $result["errors"]++;
+ $result["message"] = "\n{$e->getMessage()}";
+ }
+
+ if (!$result["errors"]) {
+ $result["message"] = $this->configuration->isEnabledProductgalleryApiQueue() ? "All items have been added to queue." : "success";
+ } else {
+ $result["message"] = "error" . $result["message"];
+ }
+
+ return $this->jsonHelper->jsonEncode($result);
+ }
+
+ /**
+ * @method processOrQueue
+ * @param string $url
+ * @param string $sku
+ * @param string|null $publicId
+ * @param string|null $roles
+ * @param string|null $label
+ * @param bool|int|null $disabled
+ * @param string $cldspinset
+ */
+ public function processOrQueue($url, $sku, $publicId = null, $roles = null, $label = null, $disabled = 0, $cldspinset = null)
+ {
+ if (!$url && !$cldspinset) {
+ throw new LocalizedException(
+ __("The `url` field is mandatory when not passing `cldspinset`.")
+ );
+ }
+ if (!$sku) {
+ throw new LocalizedException(
+ __("The `sku` field is mandatory.")
+ );
+ }
+ if ($this->configuration->isEnabledProductgalleryApiQueue()) {
+ $fullItemData = $this->jsonHelper->jsonEncode([
+ "url" => $url,
+ "sku" => $sku,
+ "publicId" => $publicId,
+ "roles" => $roles,
+ "label" => $label,
+ "disabled" => $disabled,
+ "cldspinset" => $cldspinset
+ ]);
+ return $this->productGalleryApiQueueFactory->create()
+ ->setSku($sku)
+ ->setFullItemData($fullItemData)
+ ->save();
+ } else {
+ return $this->addGalleryItem($url, $sku, $publicId, $roles, $label, $disabled, $cldspinset);
+ }
+ }
+ /**
+ * @method addGalleryItem
+ * @param string $url
+ * @param string $sku
+ * @param string|null $publicId
+ * @param string|null $roles
+ * @param string|null $label
+ * @param bool|int|null $disabled
+ * @param string $cldspinset
+ * @return $this
+ */
+ public function addGalleryItem($url, $sku, $publicId = null, $roles = null, $label = null, $disabled = 0, $cldspinset = null)
+ {
+ try {
+ $this->emulateAdminhtmlArea();
+
+ $this->cldUniqid = $this->mapped = null;
+
+ if ($cldspinset) {
+ $imageData = (array) $this->jsonHelper->jsonDecode($this->cloudinaryResourcesManagement->setId($cldspinset)->setMaxResults(1)->getResourcesByTag());
+ if (!$imageData || $imageData["error"] || !$imageData["data"] || !$imageData["data"][0] || $imageData["data"][0]["resource_type"] !== "image") {
+ throw new LocalizedException(
+ __("No spin set exists for the given tag. Ensure you have uploaded it to Cloudinary correctly, or try again with a different tag name.")
+ );
+ } else {
+ $imageData["data"] = (array) $imageData["data"][0];
+ $url = $url ?: $imageData["data"]["secure_url"];
+ }
+ }
+ if (!$url) {
+ throw new LocalizedException(
+ __("The `url` field is mandatory.")
+ );
+ }
+
+ $this->parsedRemoteFileUrl = $this->configuration->parseCloudinaryUrl($url, $publicId);
+
+ if (!$this->parsedRemoteFileUrl["version"] && !$publicId) {
+ throw new LocalizedException(
+ __("The `publicId` field is mandatory for Cloudinary URLs that doesn't contain a version number.")
+ );
+ }
+
+ $roles = ($roles) ? array_map('trim', (is_string($roles) ? explode(',', $roles) : (array) $roles)) : null;
+ $product = $this->productRepository->get($sku);
+
+ $isEnabledPlaceholderCache = $this->configuration->isEnabledCachePlaceholder();
+
+ $result = $this->retrieveImage($this->parsedRemoteFileUrl['thumbnail_url'] ?: $this->parsedRemoteFileUrl['transformationless_url']);
+
+
+ $result["file"] = $this->mediaGalleryProcessor->addImage(
+ $product,
+ $result["tmp_name"],
+ $roles,
+ true,
+ false
+ );
+
+ $mediaGalleryData = $product->getMediaGallery();
+ $galItem = array_pop($mediaGalleryData["images"]);
+
+ if ($this->parsedRemoteFileUrl["type"] === "video") {
+ $videoData = (array) $this->jsonHelper->jsonDecode($this->cloudinaryResourcesManagement->setId($this->parsedRemoteFileUrl["publicId"])->getVideo());
+ $videoData["title"] = $label;
+ $videoData["description"] = "";
+ if (!$videoData["error"]) {
+ $videoData["context"] = new DataObject((isset($videoData["data"]["context"])) ? (array)$videoData["data"]["context"] : []);
+ $videoData["title"] = $videoData["title"] ? $videoData["title"] : ($videoData["context"]->getData('caption') ?: $videoData["context"]->getData('alt'));
+ $videoData["description"] = $videoData["context"]->getData('description') ?: $videoData["context"]->getData('alt');
+ }
+ $videoData["title"] = $videoData["title"] ?: $this->parsedRemoteFileUrl["publicId"];
+ $videoData["description"] = preg_replace('/( |<([^>]+)>)/i', '', $videoData["description"] ?: $videoData["title"]);
+
+ $galItem = array_merge($galItem, [
+ "media_type" => "external-video",
+ "video_provider" => "cloudinary",
+ "disabled" => $disabled ? 1 : 0,
+ "label" => $videoData["title"],
+ "video_url" => $this->parsedRemoteFileUrl["orig_url"],
+ "video_title" => $videoData["title"],
+ "video_description" => $videoData["description"],
+ ]);
+ }
+
+ if ($this->parsedRemoteFileUrl["type"] === "image") {
+ if (!$label) {
+ $imageData = $imageData ?: (array) $this->jsonHelper->jsonDecode($this->cloudinaryResourcesManagement->setId($this->parsedRemoteFileUrl["publicId"])->getImage());
+ if (!$imageData["error"]) {
+ $imageData["context"] = new DataObject((isset($imageData["data"]["context"])) ? (array)$imageData["data"]["context"] : []);
+ $label = $imageData["context"]->getData('caption') ?: $imageData["context"]->getData('alt');
+ }
+ $label = $label ?: "";
+ }
+ $galItem = array_merge($galItem, [
+ "disabled" => $disabled ? 1 : 0,
+ "label" => $label,
+ "cldspinset" => $cldspinset,
+ ]);
+ }
+
+ $mediaGalleryData["images"][] = $galItem;
+ $product->setData('media_gallery', $mediaGalleryData);
+
+ $product->save();
+ $mediaGalleryData = $product->getMediaGallery();
+ $galItem = array_pop($mediaGalleryData["images"]);
+
+ if ($this->parsedRemoteFileUrl["type"] === "image" && $this->parsedRemoteFileUrl['transformations_string']) {
+ $this->transformationFactory->create()
+ ->setImageName($galItem["file"])
+ ->setFreeTransformation($this->parsedRemoteFileUrl['transformations_string'])
+ ->save();
+ }
+
+ if ($this->parsedRemoteFileUrl["type"] === "image" && $cldspinset) {
+ $this->resourceConnection->getConnection()
+ ->insertOnDuplicate($this->resourceConnection->getTableName('cloudinary_product_spinset_map'), [
+ 'image_name' => $galItem['file'],
+ 'cldspinset' => $cldspinset
+ ], ['image_name', 'cldspinset']);
+ }
+
+ if ($this->configuration->isEnabledLocalMapping()) {
+ $this->saveCloudinaryMapping();
+ }
+ } catch (\Exception $e) {
+ $this->stopEnvironmentEmulation();
+ throw $e;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $remoteFileUrl
+ * @return array
+ */
+ private function retrieveImage($remoteFileUrl)
+ {
+ try {
+ $this->validateRemoteFile($remoteFileUrl);
+ $baseTmpMediaPath = $this->mediaConfig->getBaseTmpMediaPath();
+ if ($this->configuration->isEnabledLocalMapping()) {
+ $this->cldUniqid = $this->configuration->generateCLDuniqid();
+ $localUniqFilePath = $this->configuration->addUniquePrefixToBasename($remoteFileUrl, $this->cldUniqid);
+ }
+ if ($this->configuration->isEnabledCachePlaceholder()) {
+ $customPlaceholderPath = $this->configuration->getCustomPlaceholderPath();
+
+ if ($customPlaceholderPath && file_exists($customPlaceholderPath)) {
+ $image = file_get_contents($customPlaceholderPath);
+ } else {
+ // Fallback to module default image
+ $asset = $this->assetRepository->createAsset('Cloudinary_Cloudinary::images/cloudinary_placeholder.jpg');
+ $image = $asset->getContent();
+ }
+
+ }
+ $localUniqFilePath = $this->appendNewFileName($baseTmpMediaPath . $this->getLocalTmpFileName($localUniqFilePath));
+ $this->validateRemoteFileExtensions($localUniqFilePath);
+
+ if (!$this->configuration->isEnabledCachePlaceholder()) {
+ $this->retrieveRemoteImage($remoteFileUrl, $localUniqFilePath);
+ } else {
+ $this->fileUtility->saveFile($localUniqFilePath, $image);
+ }
+ $localFileFullPath = $this->appendAbsoluteFileSystemPath($localUniqFilePath);
+ $this->imageAdapter->validateUploadFile($localFileFullPath);
+ $result = $this->appendResultSaveRemoteImage($localUniqFilePath, $baseTmpMediaPath);
+ } catch (\Exception $e) {
+ $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()];
+ $fileWriter = $this->fileSystem->getDirectoryWrite(DirectoryList::MEDIA);
+ if (isset($localFileFullPath) && $fileWriter->isExist($localFileFullPath)) {
+ $fileWriter->delete($localFileFullPath);
+ }
+ throw $e;
+ }
+ return $result;
+ }
+
+ /**
+ * @method checkEnvHeader
+ * @return $this
+ */
+ private function checkEnvHeader()
+ {
+ if (($envVar = $this->request->getHeader('CLD-ENV-VAR'))) {
+ $this->configuration->setRegistryEnabled(true);
+ $this->configuration->setRegistryEnvVar($envVar);
+ }
+ return $this;
+ }
+
+ /**
+ * @method checkEnabled
+ * @return $this
+ */
+ private function checkEnabled()
+ {
+ if (!$this->configuration->isEnabled()) {
+ throw new LocalizedException(
+ __("Cloudinary module is disabled. Please enable it first in order to use this API.")
+ );
+ }
+ return $this;
+ }
+
+ private function getLocalTmpFileName($remoteFileUrl)
+ {
+ $localFileName = Uploader::getCorrectFileName(basename($remoteFileUrl));
+ return Uploader::getDispretionPath($localFileName) . DIRECTORY_SEPARATOR . $localFileName;
+ }
+
+ /**
+ * Validate remote file
+ *
+ * @param string $remoteFileUrl
+ * @throws LocalizedException
+ *
+ * @return $this
+ */
+ private function validateRemoteFile($remoteFileUrl)
+ {
+ if (!$this->protocolValidator->isValid($remoteFileUrl)) {
+ throw new LocalizedException(
+ __("Protocol isn't allowed")
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Invalidates files that have script extensions.
+ *
+ * @param string $filePath
+ * @throws ValidatorException
+ * @return void
+ */
+ private function validateRemoteFileExtensions($filePath)
+ {
+ $extension = pathinfo($filePath, PATHINFO_EXTENSION);
+ if (!$this->extensionValidator->isValid($extension)) {
+ throw new ValidatorException(__('Disallowed file type.'));
+ }
+ }
+
+ /**
+ * @param string $localUniqFilePath
+ * @return mixed
+ */
+ private function appendResultSaveRemoteImage($localUniqFilePath, $baseTmpMediaPath)
+ {
+ $tmpFileName = $localUniqFilePath;
+ if (substr($tmpFileName, 0, strlen($baseTmpMediaPath)) == $baseTmpMediaPath) {
+ $tmpFileName = substr($tmpFileName, strlen($baseTmpMediaPath));
+ }
+ $result['name'] = basename($localUniqFilePath);
+ $result['type'] = $this->imageAdapter->getMimeType();
+ $result['error'] = 0;
+ $result['size'] = filesize($this->appendAbsoluteFileSystemPath($localUniqFilePath));
+ $result['url'] = $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA) . $localUniqFilePath;
+ $result['tmp_name'] = $this->appendAbsoluteFileSystemPath($localUniqFilePath);
+ $result['file'] = $tmpFileName;
+ return $result;
+ }
+
+ /**
+ * Trying to get remote image to save it locally
+ *
+ * @param string $fileUrl
+ * @param string $localFilePath
+ * @return void
+ * @throws LocalizedException
+ */
+ private function retrieveRemoteImage($fileUrl, $localFilePath)
+ {
+ $this->curl->setConfig(['header' => false]);
+ $this->curl->write('GET', $fileUrl);
+
+ $image = $this->curl->read();
+ if (empty($image)) {
+ throw new LocalizedException(
+ __('The preview image information is unavailable. Check your connection and try again.')
+ );
+ }
+ $this->fileUtility->saveFile($localFilePath, $image);
+ }
+
+ /**
+ * @param string $localFilePath
+ * @return string
+ */
+ private function appendNewFileName($localFilePath)
+ {
+ $destinationFile = $this->appendAbsoluteFileSystemPath($localFilePath);
+ $fileName = Uploader::getNewFileName($destinationFile);
+ $fileInfo = pathinfo($localFilePath);
+ return $fileInfo['dirname'] . DIRECTORY_SEPARATOR . $fileName;
+ }
+
+ /**
+ * @param string $localTmpFile
+ * @return string
+ */
+ private function appendAbsoluteFileSystemPath($localTmpFile)
+ {
+ /** @var \Magento\Framework\Filesystem\Directory\Read $mediaDirectory */
+ $mediaDirectory = $this->fileSystem->getDirectoryRead(DirectoryList::MEDIA);
+ $pathToSave = $mediaDirectory->getAbsolutePath();
+ return $pathToSave . $localTmpFile;
+ }
+
+ private function saveCloudinaryMapping()
+ {
+ return $this->mapped = $this->mediaLibraryMapFactory->create()
+ ->setCldUniqid($this->cldUniqid)
+ ->setCldPublicId(($this->parsedRemoteFileUrl["type"] === "video") ? $this->parsedRemoteFileUrl["thumbnail_url"] : $this->parsedRemoteFileUrl["publicId"] . '.' . $this->parsedRemoteFileUrl["extension"])
+ ->setFreeTransformation($this->parsedRemoteFileUrl["transformations_string"])
+ ->save();
+ }
+
+ /**
+ * @method getProductMediaBySku
+ * @param string $sku
+ * @return array
+ */
+ private function getProductCldUrlsBySku($sku)
+ {
+ $urls = [
+ 'image' => null,
+ 'small_image' => null,
+ 'thumbnail' => null,
+ 'media_gallery' => [],
+ ];
+ try {
+ $product = $this->productRepository->get($sku);
+ foreach ($product->getMediaGalleryImages() as $gallItem) {
+ $urls['media_gallery'][] = $gallItem->getUrl();
+ if ($product->getData('image') === $gallItem->getFile()) {
+ $urls['image'] = $gallItem->getUrl();
+ }
+ if ($product->getData('small_image') === $gallItem->getFile()) {
+ $urls['small_image'] = $gallItem->getUrl();
+ }
+ if ($product->getData('thumbnail') === $gallItem->getFile()) {
+ $urls['thumbnail'] = $gallItem->getUrl();
+ }
+ }
+ } catch (\Exception $e) {
+ $urls = [
+ 'error' => 1,
+ 'message' => $e->getMessage(),
+ ];
+ }
+ return $urls;
+ }
+
+ ///////////////////////////////
+ // App Environment Emulation //
+ ///////////////////////////////
+
+ /**
+ * Start environment emulation of the specified store
+ *
+ * Function returns information about initial store environment and emulates environment of another store
+ *
+ * @param integer $storeId
+ * @param string $area
+ * @param bool $force A true value will ensure that environment is always emulated, regardless of current store
+ * @return $this
+ */
+ private function startEnvironmentEmulation($storeId, $area = Area::AREA_FRONTEND, $force = false)
+ {
+ $this->stopEnvironmentEmulation();
+ $this->appEmulation->startEnvironmentEmulation($storeId, $area, $force);
+ return $this;
+ }
+
+ /**
+ * Stop environment emulation
+ *
+ * Function restores initial store environment
+ *
+ * @return $this
+ */
+ private function stopEnvironmentEmulation()
+ {
+ $this->appEmulation->stopEnvironmentEmulation();
+ return $this;
+ }
+
+ /**
+ * @method emulateAdminArea
+ * @param boolean $force
+ * @return $this
+ */
+ private function emulateAdminhtmlArea($force = true)
+ {
+ $this->startEnvironmentEmulation(0, Area::AREA_ADMINHTML, $force);
+ return $this;
+ }
+}
diff --git a/Model/Api/ResourcesManagement.php b/Model/Api/ResourcesManagement.php
index c16dad41..3efaec11 100644
--- a/Model/Api/ResourcesManagement.php
+++ b/Model/Api/ResourcesManagement.php
@@ -2,13 +2,19 @@
namespace Cloudinary\Cloudinary\Model\Api;
-use Cloudinary;
-use Cloudinary\Api;
+use Cloudinary\Api\BaseApiClient;
+use Cloudinary\Cloudinary;
+use Cloudinary\Api\Admin\AdminApi;
use Cloudinary\Cloudinary\Core\ConfigurationBuilder;
use Cloudinary\Cloudinary\Core\ConfigurationInterface;
+use Magento\Framework\App\Request\Http;
+use Magento\Framework\Json\EncoderInterface;
class ResourcesManagement implements \Cloudinary\Cloudinary\Api\ResourcesManagementInterface
{
+ private $initialized;
+ private $id;
+ private $maxResults;
protected $_resourceType = "image";
protected $_resourceData = [];
@@ -17,74 +23,120 @@ class ResourcesManagement implements \Cloudinary\Cloudinary\Api\ResourcesManagem
*/
private $_configuration;
+
+ private $_cloudinarySDK;
+
/**
* @var ConfigurationBuilder
*/
private $_configurationBuilder;
/**
- * @var Cloudinary\Api
+ * @var Cloudinary\Api\Admin\AdminApi
*/
private $_api;
/**
- * @var \Magento\Framework\App\Request\Http
+ * @var Http
*/
private $_request;
+ /**
+ * @var EncoderInterface
+ */
+ private $_jsonEncoder;
+
/**
* ApiClient constructor.
+ *
* @param ConfigurationInterface $configuration
- * @param ConfigurationBuilder $configurationBuilder
+ * @param ConfigurationBuilder $configurationBuilder
+ * @param AdminApi $api
+ * @param Http $request
+ * @param EncoderInterface $jsonEncoder
*/
public function __construct(
ConfigurationInterface $configuration,
ConfigurationBuilder $configurationBuilder,
- Api $api,
- \Magento\Framework\App\Request\Http $request
+ Http $request,
+ EncoderInterface $jsonEncoder
) {
$this->_configuration = $configuration;
$this->_configurationBuilder = $configurationBuilder;
- $this->_api = $api;
$this->_request = $request;
- if ($this->_configuration->isEnabled()) {
- $this->_authorise();
+ $this->_jsonEncoder = $jsonEncoder;
+ }
+
+ private function initialize()
+ {
+ if (!$this->initialized) {
+ $this->initialized = true;
+ if (($id = $this->_request->getParam("id"))) {
+ $this->setId(\rawurldecode($id));
+ }
+ if (($maxResults = $this->_request->getParam("max_results"))) {
+ $this->setMaxResults($maxResults);
+ }
+ if ($this->_configuration->isEnabled()) {
+ $this->_cloudinarySDK = new Cloudinary($this->_configurationBuilder->build());
+ $this->_api = $this->_cloudinarySDK->adminApi();
+ BaseApiClient::$userPlatform = $this->_configuration->getUserPlatform();
+ }
}
+ return $this;
+ }
+
+ public function setId($id)
+ {
+ $this->id = $id;
+ return $this;
}
- private function _authorise()
+ public function getId()
{
- Cloudinary::config($this->_configurationBuilder->build());
- Cloudinary::$USER_PLATFORM = $this->_configuration->getUserPlatform();
+ return $this->id;
}
- public function _sendJsonResponse($response)
+ public function setMaxResults($maxResults)
{
- header('Content-Type: application/json');
- echo json_encode($response);
- die;
+ $this->maxResults = $maxResults;
+ return $this;
+ }
+
+ public function getMaxResults()
+ {
+ return $this->maxResults;
}
/**
* Get details of a single resource
+ *
* @method _getResourceData
* @return string (json encoded data)
*/
protected function _getResourceData()
{
try {
- $this->_resourceData = $this->_api->resource($this->_request->getParam("id"), [
- "resource_type" => $this->_resourceType
- ]);
- $this->_sendJsonResponse([
- "error" => 0,
- "data" => $this->_resourceData
- ]);
+ $this->initialize();
+ $this->_resourceData = $this->_api->asset(
+ $this->getId(),
+ [
+ "resource_type" => $this->_resourceType
+ ]
+ );
+ return $this->_jsonEncoder->encode(
+ [
+ "error" => 0,
+ "data" => $this->_resourceData
+ ]
+ );
} catch (\Exception $e) {
- $this->_sendJsonResponse([
- "error" => 1,
- "message" => $e->getMessage()
- ]);
+ return $this->_jsonEncoder->encode(
+ [
+ "error" => 1,
+ "message" => $e->getMessage()
+ ]
+ );
}
}
@@ -94,7 +146,7 @@ protected function _getResourceData()
public function getImage()
{
$this->_resourceType = "image";
- $this->_getResourceData();
+ return $this->_getResourceData();
}
/**
@@ -103,6 +155,36 @@ public function getImage()
public function getVideo()
{
$this->_resourceType = "video";
- $this->_getResourceData();
+ return $this->_getResourceData();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getResourcesByTag()
+ {
+ try {
+ $this->initialize();
+ $resources = $this->_api->assetsByTag(
+ $this->getId(),
+ [
+ "resource_type" => $this->_resourceType,
+ "max_results" => (int) $this->maxResults || null
+ ]
+ )['resources'];
+ return $this->_jsonEncoder->encode(
+ [
+ "error" => 0,
+ "data" => $resources
+ ]
+ );
+ } catch (\Exception $e) {
+ return $this->_jsonEncoder->encode(
+ [
+ "error" => 1,
+ "message" => $e->getMessage()
+ ]
+ );
+ }
}
}
diff --git a/Model/AutoUploadMapping/AutoUploadConfiguration.php b/Model/AutoUploadMapping/AutoUploadConfiguration.php
index 697c8a52..3d3f7ffd 100644
--- a/Model/AutoUploadMapping/AutoUploadConfiguration.php
+++ b/Model/AutoUploadMapping/AutoUploadConfiguration.php
@@ -2,9 +2,9 @@
namespace Cloudinary\Cloudinary\Model\AutoUploadMapping;
+use Cloudinary\Cloudinary\Core\AutoUploadMapping\AutoUploadConfigurationInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Config\Storage\WriterInterface;
-use Cloudinary\Cloudinary\Core\AutoUploadMapping\AutoUploadConfigurationInterface;
class AutoUploadConfiguration implements AutoUploadConfigurationInterface
{
@@ -69,7 +69,7 @@ public function setRequestState($state)
/**
* @param string $key
- * @param bool $state
+ * @param bool $state
*/
private function setFlag($key, $state)
{
diff --git a/Model/BatchDownloader.php b/Model/BatchDownloader.php
new file mode 100644
index 00000000..dd079c03
--- /dev/null
+++ b/Model/BatchDownloader.php
@@ -0,0 +1,391 @@
+_configuration = $configuration;
+ $this->_configurationBuilder = $configurationBuilder;
+ $this->_migrationTask = $migrationTask;
+ $this->_api = $api;
+ $this->_directoryList = $directoryList;
+ $this->_curl = $curl;
+ $this->_fileSystem = $fileSystem;
+ $this->_fileUtility = $fileUtility;
+ $this->_protocolValidator = $protocolValidator;
+ $this->_extensionValidator = $extensionValidator;
+ $this->_synchronizationChecker = $synchronizationChecker;
+ $this->_synchronisationRepository = $synchronisationRepository;
+ }
+
+ private function _authorise()
+ {
+
+ // $config = new Configuration($this->configurationBuilder->build());
+ $this->_cloudinarySdk = new Cloudinary($this->_configurationBuilder->build());
+
+ $this->_api = $this->_cloudinarySdk->adminApi();
+ \Cloudinary\Api\BaseApiClient::$userPlatform = $this->_configuration->getUserPlatform();
+ $this->_authorised = true;
+
+ return $this;
+
+ }
+
+ /**
+ * Find unsynchronised images and upload them to cloudinary
+ *
+ * @param OutputInterface|null $output
+ * @param bool $override
+ * @return bool
+ * @throws \Exception
+ */
+ public function downloadUnsynchronisedImages(?OutputInterface $output = null, $override = false, $includeSync = false)
+ {
+ if (!$this->_configuration->isEnabled(false)) {
+ throw new \Exception("Cloudinary seems to be disabled. Please enable it first or pass -f in order to force it on the CLI");
+ }
+ if (!$this->_configuration->hasEnvironmentVariable()) {
+ throw new \Exception("Cloudinary environment variable seems to be missing. Please configure it first or pass it to the command as `-e` in order to use on the CLI");
+ }
+
+ $this->_authorise();
+
+ //= Config
+ $this->_output = $output;
+ $this->_override = (bool) $override;
+ $baseMediaPath = $this->_directoryList->getPath(DirectoryList::MEDIA);
+ $directoryInstance = $this->_fileSystem->getDirectoryWrite(DirectoryList::MEDIA);
+
+ if ($includeSync) {
+ $this->displayMessage('== [Notice] == Process started with include-synchronization flag. ');
+ }
+
+ //= Checking migration lock / Start migration
+ if (!$this->validateMigrationLock()) {
+ return false;
+ } else {
+ $this->_migrationTask->start();
+ }
+
+ do {
+ try {
+ $this->_iteration++;
+ $this->displayMessage('Iteration #' . $this->_iteration . ' ');
+
+ $response = $this->getResources($this->_nextCursor);
+ $response->setResourcesCount(count($response->getResources()));
+ if ($response->getResourcesCount() > 0) {
+ $this->displayMessage('Found ' . $response->getResourcesCount() . ' image(s) to on this round. ' . (($response->getNextCursor()) ? '*More Rounds Expected*' : '*Last Round*'));
+ foreach ($response->getResources() as $i => &$resource) {
+ try {
+ //= Checking migration status
+ if ($this->_migrationTask->hasBeenStopped()) {
+ $this->displayMessage(self::MESSAGE_DOWNLOAD_INTERRUPTED);
+ return false;
+ }
+
+ //= Preparations & Validations
+ $resource = new DataObject($resource);
+ $this->displayMessage('= [Processing] Image ' . ($i+1) . '/' . $response->getResourcesCount() . ' ');
+ $resource->setPublicId(preg_replace('/^' . preg_quote(DirectoryList::MEDIA . DIRECTORY_SEPARATOR, '/') . '/', '', $resource->getPublicId()) . '.' . $resource->getFormat());
+ $this->displayMessage('=== [Processing] Public ID: ' . $resource->getPublicId() . ' ');
+ $remoteFileUrl = $resource->getSecureUrl();
+ $this->validateRemoteFile($remoteFileUrl);
+ $localFileName = $resource->getPublicId(); //Uploader::getCorrectFileName($resource->getPublicId());
+ $localFilePath = $baseMediaPath . DIRECTORY_SEPARATOR . $localFileName;
+ $this->validateRemoteFileExtensions($localFilePath);
+
+ //= Checking if already exists
+ $skipDownload = false;
+ $this->displayMessage('=== [Processing] Local path: ' . $localFilePath . ' ');
+ if ($directoryInstance->isFile($localFilePath)) {
+ $this->displayMessage('=== [Processing] Image already exists locally. ');
+ if ($this->_override) {
+ $this->displayMessage('=== [Processing] *Overriding* ');
+ } else {
+ $skipDownload = true;
+ }
+ }
+
+ //= Downloading image / Skipping
+ if ($skipDownload) {
+ $this->displayMessage('=== [Processing] Skipping download. ');
+ } else {
+ $this->displayMessage('=== [Processing] Downloading image... ');
+ $this->_curl->setConfig(['header' => false]);
+ $this->_curl->write('GET', $remoteFileUrl);
+ $image = $this->_curl->read();
+ if (empty($image)) {
+ throw new LocalizedException(
+ __('The preview image information is unavailable. Check your connection and try again.')
+ );
+ }
+ $this->displayMessage('=== [Processing] Saving... ');
+ $this->_fileUtility->saveFile($localFilePath, $image, $this->_override);
+ if (!$directoryInstance->isFile($localFilePath)) {
+ throw new LocalizedException(__("Image not saved."));
+ }
+ $this->displayMessage('=== [Processing] Saved. ');
+ }
+
+ //Flagging as syncronized
+ $resource->setImage(Image::fromPath($localFilePath, $localFileName));
+ if ($includeSync || $resource->getImage()->getRelativePath() && !$this->_synchronizationChecker->isSynchronized($resource->getImage()->getRelativePath())) {
+ $this->displayMessage('=== [Processing] Flagging As Syncronized... ');
+ $this->_synchronisationRepository->saveAsSynchronized($resource->getImage()->getRelativePath());
+ } else {
+ $this->displayMessage('=== [Processing] Image already syncronized or auto-upload-mapping is enabled. ');
+ }
+
+ //= Success
+ $this->displayMessage('= [Success] ');
+ } catch (\Exception $e) {
+ $this->displayMessage('= [Error] ' . $e->getMessage() . ' ');
+ continue;
+ }
+ }
+ } else {
+ $this->displayMessage('' . self::DONE_MESSAGE . ' ');
+ break;
+ }
+ if (($this->_nextCursor = $response->getNextCursor()) && (int)$this->_rateLimitRemaining <= self::API_REQUEST_STOP_ON_REMAINING_RATE_LIMIT) {
+ $this->displayMessage('' . sprintf(self::WAIT_FOR_RATE_LIMIT_RESET_MESSAGE, date('Y-m-d H:i:s', ($this->_rateLimitResetAt + 10))) . ' ');
+ time_sleep_until($this->_rateLimitResetAt + 10);
+ }
+ sleep(self::API_REQUESTS_SLEEP_BEFORE_NEXT_CALL); //Wait between each API call.
+ } catch (\Exception $e) {
+ $this->displayMessage('' . $e->getMessage() . ' ');
+ break;
+ }
+ } while ($this->_nextCursor);
+
+ $this->_migrationTask->stop();
+ return true;
+ }
+
+ /**
+ * @method getResources
+ * @param mixed $nextCursor
+ * @return DataObject
+ */
+ private function getResources(?string $nextCursor = null): DataObject
+ {
+ $response = $this->_api->assets([
+ 'resource_type' => 'image',
+ 'type' => 'upload',
+ 'prefix' => DirectoryList::MEDIA . DIRECTORY_SEPARATOR,
+ 'max_results' => self::API_REQUEST_MAX_RESULTS,
+ 'next_cursor' => $nextCursor,
+ ]);
+
+ $this->_rateLimitResetAt = $response->rateLimitResetAt ?? null;
+ $this->_rateLimitAllowed = $response->rateLimitAllowed ?? null;
+ $this->_rateLimitRemaining = $response->rateLimitRemaining ?? null;
+
+ // Avoid modifying $response directly to prevent dynamic property creation
+ $responseArray = (array) $response;
+ if (isset($responseArray['resources']) && is_array($responseArray['resources'])) {
+ $responseArray['resources'] = array_values($responseArray['resources']);
+ }
+
+ return new DataObject($responseArray);
+ }
+
+ /**
+ * @param OutputInterface $output
+ * @param string $message
+ */
+ private function displayMessage($message)
+ {
+ if ($this->_output) {
+ $this->_output->writeln($message);
+ }
+ }
+
+ /**
+ * @param OutputInterface $output
+ * @return bool
+ */
+ private function validateMigrationLock()
+ {
+ if ($this->_migrationTask->hasStarted()) {
+ $this->displayMessage(self::ERROR_MIGRATION_ALREADY_RUNNING);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Validate remote file
+ *
+ * @param string $remoteFileUrl
+ * @throws LocalizedException
+ *
+ * @return $this
+ */
+ private function validateRemoteFile($remoteFileUrl)
+ {
+ if (!$this->_protocolValidator->isValid($remoteFileUrl)) {
+ throw new LocalizedException(
+ __("Protocol isn't allowed")
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Invalidates files that have script extensions.
+ *
+ * @param string $filePath
+ * @throws \Magento\Framework\Exception\ValidatorException
+ * @return void
+ */
+ private function validateRemoteFileExtensions($filePath)
+ {
+ $extension = pathinfo($filePath, PATHINFO_EXTENSION);
+ if (!$this->_extensionValidator->isValid($extension)) {
+ throw new \Magento\Framework\Exception\ValidatorException(__('Disallowed file type.'));
+ }
+ }
+}
diff --git a/Model/BatchUploader.php b/Model/BatchUploader.php
index dea838f1..3af658ec 100644
--- a/Model/BatchUploader.php
+++ b/Model/BatchUploader.php
@@ -2,14 +2,14 @@
namespace Cloudinary\Cloudinary\Model;
+use Cloudinary\Cloudinary\Core\AutoUploadMapping\AutoUploadConfigurationInterface;
use Cloudinary\Cloudinary\Core\CloudinaryImageManager;
use Cloudinary\Cloudinary\Core\Image;
use Symfony\Component\Console\Output\OutputInterface;
-use Cloudinary\Cloudinary\Core\AutoUploadMapping\AutoUploadConfigurationInterface;
class BatchUploader
{
- const ERROR_MIGRATION_ALREADY_RUNNING = 'Cannot start upload - a migration is in progress or was interrupted. If you are sure a migration is not running elsewhere run the cloudinary:upload:stop command before attempting another upload.';
+ const ERROR_MIGRATION_ALREADY_RUNNING = 'Cannot start upload - a migration is in progress or was interrupted. If you are sure a migration is not running elsewhere run the cloudinary:migration:stop command before attempting another upload.';
const ERROR_AUTO_UPLOAD1 = 'Manual migration is not required when auto upload mapping is enabled.';
const ERROR_AUTO_UPLOAD2 = 'Please disable auto upload mapping and refresh the configuration cache ' .
'if you wish to perform a manual migration.';
@@ -44,9 +44,9 @@ class BatchUploader
private $autoUploadConfiguration;
/**
- * @param ImageRepository $imageRepository
- * @param Configuration $configuration
- * @param MigrationTask $migrationTask
+ * @param ImageRepository $imageRepository
+ * @param Configuration $configuration
+ * @param MigrationTask $migrationTask
* @param CloudinaryImageManager $cloudinaryImageManager
*/
public function __construct(
@@ -66,12 +66,19 @@ public function __construct(
/**
* Find unsynchronised images and upload them to cloudinary
*
- * @param OutputInterface|null $output
+ * @param OutputInterface|null $output
* @return bool
* @throws \Exception
*/
- public function uploadUnsynchronisedImages(OutputInterface $output = null)
+ public function uploadUnsynchronisedImages(?OutputInterface $output = null)
{
+ if (!$this->configuration->isEnabled(false)) {
+ throw new \Exception("Cloudinary seems to be disabled. Please enable it first or pass -f in order to force it on the CLI");
+ }
+ if (!$this->configuration->hasEnvironmentVariable()) {
+ throw new \Exception("Cloudinary environment variable seems to be missing. Please configure it first or pass it to the command as `-e` in order to use on the CLI");
+ }
+
if (!$this->validateAutoUploadMapping($output) || !$this->validateMigrationLock($output)) {
return false;
}
@@ -85,6 +92,7 @@ public function uploadUnsynchronisedImages(OutputInterface $output = null)
$this->displayMessage($output, self::MESSAGE_UPLOAD_INTERRUPTED);
return false;
}
+
$this->uploadAndSynchronise($image, $output);
}
@@ -92,7 +100,6 @@ public function uploadUnsynchronisedImages(OutputInterface $output = null)
$this->displayMessage($output, sprintf(self::MESSAGE_UPLOAD_COMPLETE, count($images)));
return true;
-
} catch (\Exception $e) {
$this->migrationTask->stop();
throw $e;
@@ -101,7 +108,7 @@ public function uploadUnsynchronisedImages(OutputInterface $output = null)
/**
* @param OutputInterface $output
- * @param string $message
+ * @param string $message
*/
private function displayMessage(OutputInterface $output, $message)
{
@@ -111,7 +118,7 @@ private function displayMessage(OutputInterface $output, $message)
}
/**
- * @param Image $image
+ * @param Image $image
* @param OutputInterface $output
*/
private function uploadAndSynchronise(Image $image, OutputInterface $output)
@@ -124,7 +131,7 @@ private function uploadAndSynchronise(Image $image, OutputInterface $output)
}
/**
- * @param OutputInterface $output
+ * @param OutputInterface $output
* @return bool
*/
private function validateAutoUploadMapping(OutputInterface $output)
@@ -139,7 +146,7 @@ private function validateAutoUploadMapping(OutputInterface $output)
}
/**
- * @param OutputInterface $output
+ * @param OutputInterface $output
* @return bool
*/
private function validateMigrationLock(OutputInterface $output)
diff --git a/Model/Config/Backend/Credentials.php b/Model/Config/Backend/Credentials.php
index 15e8ed62..308b2abe 100644
--- a/Model/Config/Backend/Credentials.php
+++ b/Model/Config/Backend/Credentials.php
@@ -2,17 +2,18 @@
namespace Cloudinary\Cloudinary\Model\Config\Backend;
+use Cloudinary\Api\BaseApiClient;
+use Cloudinary\Cloudinary;
+use Cloudinary\Cloudinary\Core\ConfigurationBuilder;
+use Cloudinary\Cloudinary\Core\ConfigurationInterface;
+use Cloudinary\Cloudinary\Core\Exception\InvalidCredentials;
use Magento\Config\Model\Config\Backend\Encrypted;
use Magento\Framework\App\Cache\TypeListInterface;
+use Magento\Framework\App\Config\ReinitableConfigInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\Encryption\EncryptorInterface;
use Magento\Framework\Exception\ValidatorException;
-use Cloudinary\Cloudinary\Core\CredentialValidator;
-use Cloudinary\Cloudinary\Core\ConfigurationInterface;
-use Cloudinary\Cloudinary\Core\Security\CloudinaryEnvironmentVariable;
-use Cloudinary\Cloudinary\Core\Credentials as CredentialsValue;
-use Cloudinary\Cloudinary\Core\Exception\InvalidCredentials;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\Registry;
@@ -25,26 +26,49 @@ class Credentials extends Encrypted
const CLOUDINARY_ENABLED_PATH = 'groups/cloud/fields/cloudinary_enabled/value';
/**
- * @var CredentialValidator
+ * @var ConfigurationInterface
*/
- private $credentialValidator;
+ private $configuration;
/**
- * @var ConfigurationInterface
+ * @var Cloudinary
*/
- private $configuration;
+ private $cloudinarySdk;
+
+ /**
+ * @var ConfigurationBuilder
+ */
+ private $configurationBuilder;
+
+ /**
+ * @var Cloudinary\Api\Admin\AdminApi
+ */
+ private $api;
+
+ /**
+ * Application config
+ *
+ * @var ScopeConfigInterface
+ */
+ protected $appConfig;
/**
- * @param Context $context
- * @param Registry $registry
- * @param ScopeConfigInterface $config
- * @param TypeListInterface $cacheTypeList
- * @param EncryptorInterface $encryptor
- * @param CredentialValidator $credentialValidator
- * @param ConfigurationInterface $configuration
- * @param AbstractResource $resource
- * @param AbstractDb $resourceCollection
- * @param array $data
+ * @var EncryptorInterface
+ */
+ private $encryptor;
+
+ /**
+ * @param Context $context
+ * @param Registry $registry
+ * @param ScopeConfigInterface $config
+ * @param TypeListInterface $cacheTypeList
+ * @param EncryptorInterface $encryptor
+ * @param ConfigurationInterface $configuration
+ * @param AbstractResource $resource
+ * @param AbstractDb $resourceCollection
+ * @param ConfigurationBuilder $configurationBuilder
+ * @param ReinitableConfigInterface $appConfig
+ * @param array $data
*/
public function __construct(
Context $context,
@@ -52,14 +76,17 @@ public function __construct(
ScopeConfigInterface $config,
TypeListInterface $cacheTypeList,
EncryptorInterface $encryptor,
- CredentialValidator $credentialValidator,
ConfigurationInterface $configuration,
- AbstractResource $resource = null,
- AbstractDb $resourceCollection = null,
+ ConfigurationBuilder $configurationBuilder,
+ ReinitableConfigInterface $appConfig,
+ ?AbstractResource $resource = null,
+ ?AbstractDb $resourceCollection = null,
array $data = []
) {
- $this->credentialValidator = $credentialValidator;
$this->configuration = $configuration;
+ $this->configurationBuilder = $configurationBuilder;
+ $this->appConfig = $appConfig;
+ $this->encryptor = $encryptor;
parent::__construct(
$context,
@@ -73,56 +100,99 @@ public function __construct(
);
}
+ /**
+ * @return void
+ * @throws ValidatorException
+ */
public function beforeSave()
{
$rawValue = $this->getValue();
parent::beforeSave();
- if (!$rawValue) {
- throw new ValidatorException(__(self::CREDENTIALS_CHECK_MISSING));
- }
+ $this->cacheTypeList->cleanType(\Magento\Framework\App\Cache\Type\Config::TYPE_IDENTIFIER);
+ $this->appConfig->reinit();// $decrypted = $this->encryptor->decrypt($var);
- if ($this->isSaveAllowed()) {
- $this->validate($this->getCredentialsFromEnvironmentVariable($rawValue));
- } else {
- $this->validate($this->getCredentialsFromConfig());
+ if ($rawValue || $this->configuration->isEnabled(false)) {
+ if (!$rawValue) {
+ throw new ValidatorException(__(self::CREDENTIALS_CHECK_MISSING));
+ }
+
+ if ($this->isSaveAllowed()) {
+ if (stripos($rawValue, 'cloudinary://') !== false) {
+ $this->validate($this->getCredentialsFromEnvironmentVariable($rawValue));
+ } else {
+ $this->validate($this->getCredentialsFromEnvironmentVariable($this->encryptor->decrypt($rawValue)));
+ }
+ } else {
+ $field = $this->configuration->getCredentials();
+ $this->validate($field);
+ }
}
}
/**
- * @param CredentialsValue $credentials
+ * @param $credentials
* @throws ValidatorException
*/
- private function validate(CredentialsValue $credentials)
+ private function validate($credentials)
{
- if (!$this->credentialValidator->validate($credentials)) {
+ $this->_authorise($credentials);
+ $pingValidation = $this->cloudinarySdk->adminApi()->ping();
+ if (!(isset($pingValidation["status"]) && $pingValidation["status"] === "ok")) {
throw new ValidatorException(__(self::CREDENTIALS_CHECK_UNSURE));
}
}
/**
- * @param string $environmentVariable
+ * @param string $environmentVariable
* @throws ValidatorException
- * @return CredentialsValue
+ * @return array
*/
private function getCredentialsFromEnvironmentVariable($environmentVariable)
{
try {
- return CloudinaryEnvironmentVariable::fromString($environmentVariable)->getCredentials();
- } catch (InvalidCredentials $e) {
+ // Cloudinary::config_from_url(str_replace('CLOUDINARY_URL=', '', $environmentVariable));
+ $environmentVariable = str_replace('CLOUDINARY_URL=', '', $environmentVariable);
+ $uri = parse_url($environmentVariable);
+ if (!isset($uri["scheme"]) || strtolower($uri["scheme"]) !== "cloudinary") {
+ throw new \InvalidArgumentException("Invalid CLOUDINARY_URL scheme. Expecting to start with 'cloudinary://'");
+ }
+ $q_params = [];
+ if (isset($uri["query"])) {
+ parse_str($uri["query"], $q_params);
+ }
+ $private_cdn = isset($uri["path"]) && $uri["path"] != "/";
+ $credentials = array_merge(
+ $q_params,
+ [
+ "cloud_name" => $uri["host"],
+ "api_key" => $uri["user"],
+ "api_secret" => $uri["pass"],
+ "private_cdn" => $private_cdn,
+ ]
+ );
+
+ if (isset($credentials['cname'])) {
+ $credentials['secure'] = true;
+ $credentials['secure_distribution'] = $credentials['cname'];
+ }
+
+ return $credentials;
+
+ } catch (\Exception $e) {
throw new ValidatorException(__(self::CREDENTIALS_CHECK_FAILED));
}
}
/**
* @throws ValidatorException
- * @return CredentialsValue
+ * @return array
*/
private function getCredentialsFromConfig()
{
try {
- return $this->configuration->getCredentials();
+ return $this->getCredentialsFromEnvironmentVariable($this->cloudinarySdk->toString());
} catch (InvalidCredentials $e) {
throw new ValidatorException(__(self::CREDENTIALS_CHECK_FAILED));
}
@@ -135,4 +205,14 @@ private function isModuleActiveInFormData()
{
return $this->getDataByPath(self::CLOUDINARY_ENABLED_PATH) === '1';
}
+
+ /**
+ * @param $credentials
+ */
+ private function _authorise($credentials)
+ {
+ $this->cloudinarySdk = new Cloudinary($credentials);
+ BaseApiClient::$userPlatform = $this->configuration->getUserPlatform();
+ return $this;
+ }
}
diff --git a/Model/Config/Backend/Free.php b/Model/Config/Backend/Free.php
index 1c00b162..fc187526 100644
--- a/Model/Config/Backend/Free.php
+++ b/Model/Config/Backend/Free.php
@@ -11,19 +11,25 @@
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\Exception\ValidatorException;
-use Magento\Framework\HTTP\ZendClient;
+use Magento\Framework\App\ProductMetadataInterface;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\Phrase;
use Magento\Framework\Registry;
-use Magento\Framework\UrlInterface;
-use Zend_Http_Response;
+use Laminas\Http\Response as LaminasResponse;
class Free extends \Magento\Framework\App\Config\Value
{
const ERROR_FORMAT = 'Incorrect custom transform - %1';
const ERROR_DEFAULT = 'please update';
+ /**
+ * @var ProductMetadataInterface
+ */
+ protected $productMetadata;
+
+ protected $clientType;
+
/**
* @var ConfigurationInterface
*/
@@ -35,20 +41,20 @@ class Free extends \Magento\Framework\App\Config\Value
private $cloudinaryImageProvider;
/**
- * @var ZendClient
+ * @var LaminasClient|\Magento\Framework\HTTP\ZendClient
*/
- private $zendClient;
+ private $httpClient;
/**
* @param Context $context
* @param Registry $registry
* @param ScopeConfigInterface $config
* @param TypeListInterface $cacheTypeList
- * @param ConfigurationInterface $configuration,
+ * @param ConfigurationInterface $configuration
* @param CloudinaryImageProvider $cloudinaryImageProvider
- * @param ZendClient $zendClient
- * @param AbstractResource $resource
- * @param AbstractDb $resourceCollection
+ * @param ProductMetadataInterface $productMetadata
+ * @param AbstractResource|null $resource
+ * @param AbstractDb|null $resourceCollection
* @param array $data
*/
public function __construct(
@@ -58,35 +64,43 @@ public function __construct(
TypeListInterface $cacheTypeList,
ConfigurationInterface $configuration,
CloudinaryImageProvider $cloudinaryImageProvider,
- ZendClient $zendClient,
- AbstractResource $resource = null,
- AbstractDb $resourceCollection = null,
+ ProductMetadataInterface $productMetadata,
+ ?AbstractResource $resource = null,
+ ?AbstractDb $resourceCollection = null,
array $data = []
) {
$this->configuration = $configuration;
$this->cloudinaryImageProvider = $cloudinaryImageProvider;
- $this->zendClient = $zendClient;
+ $this->productMetadata = $productMetadata;
+ // fix for magento versions 2.4.6 or newer
+ if (version_compare($productMetadata->getVersion(), '2.4.6', '>=')) {
+ $this->httpClient = new \Magento\Framework\HTTP\LaminasClient();
+ $this->clientType = 'laminas';
+ } else {
+ $this->httpClient = new \Magento\Framework\HTTP\ZendClient();
+ $this->clientType = 'zend';
+ }
+
parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data);
}
+
public function beforeSave()
{
if ($this->hasAccountConfigured() && $this->getValue()) {
-
$transform = $this->configuration
->getDefaultTransformation()
->withFreeform(Freeform::fromString($this->getValue()));
$this->validate($this->sampleImageUrl($transform));
-
}
parent::beforeSave();
}
/**
- * @param string $url
+ * @param string $url
* @throws ValidatorException
*/
public function validate($url)
@@ -99,30 +113,45 @@ public function validate($url)
throw new ValidatorException(__(self::ERROR_FORMAT, self::ERROR_DEFAULT));
}
- if ($response->isError()) {
+ $isError = ($this->clientType == 'laminas') ? (!$response->isSuccess()) : $response->isError();
+
+ if ($isError) {
throw new ValidatorException($this->formatError($response));
}
}
+
+ protected function getHeader($response) {
+ return ($this->clientType == 'laminas') ? $response->getHeaders()->get('x-cld-error') : $response->getHeader('x-cld-error');
+ }
+
/**
- * @param Zend_Http_Response $response
+ * @param $response
* @return Phrase
*/
- public function formatError(Zend_Http_Response $response)
+ public function formatError($response)
{
+ $status = ($this->clientType == 'laminas') ? $response->getStatusCode() : $response->getStatus();
+ $res = ($this->clientType == 'laminas') ? $this->getHeader($response)->getFieldValue() : $this->getHeader($response);
return __(
self::ERROR_FORMAT,
- $response->getStatus() == 400 ? $response->getHeader('x-cld-error') : self::ERROR_DEFAULT
+ $status == 400 ? $res : self::ERROR_DEFAULT
);
}
/**
- * @param string $url
- * @return Zend_Http_Response
+ * @param $url
+ * @return mixed
*/
public function httpRequest($url)
{
- return $this->zendClient->setUri($url)->request(ZendClient::GET);
+ if ($this->clientType == 'laminas') {
+ $client = $this->httpClient->setUri($url)->setMethod(\Laminas\Http\Request::METHOD_GET);
+ return $client->send();
+ } else {
+ return $this->httpClient->setUri($url)->request('GET');
+ }
+
}
/**
@@ -134,7 +163,7 @@ public function hasAccountConfigured()
}
/**
- * @param Transformation $transformation
+ * @param Transformation $transformation
* @return string
*/
public function sampleImageUrl(Transformation $transformation)
@@ -146,8 +175,8 @@ public function sampleImageUrl(Transformation $transformation)
}
/**
- * @param String $filename
- * @param Transformation $transformation
+ * @param String $filename
+ * @param Transformation $transformation
* @return string
*/
public function namedImageUrl($filename, Transformation $transformation)
diff --git a/Model/Config/Backend/ProductGalleryCustomFreeParams.php b/Model/Config/Backend/ProductGalleryCustomFreeParams.php
new file mode 100644
index 00000000..06eb0e83
--- /dev/null
+++ b/Model/Config/Backend/ProductGalleryCustomFreeParams.php
@@ -0,0 +1,83 @@
+ 'No error',
+ JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
+ JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)',
+ JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
+ JSON_ERROR_SYNTAX => 'Syntax error',
+ JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded'
+ ];
+
+ /**
+ * @var ManagerInterface
+ */
+ private $messageManager;
+
+ /**
+ * Application config
+ *
+ * @var ScopeConfigInterface
+ */
+ protected $appConfig;
+
+ public function __construct(
+ Context $context,
+ Registry $registry,
+ ScopeConfigInterface $config,
+ TypeListInterface $cacheTypeList,
+ ManagerInterface $messageManager,
+ ReinitableConfigInterface $appConfig,
+ ?AbstractResource $resource = null,
+ ?AbstractDb $resourceCollection = null,
+ array $data = []
+ ) {
+ $this->messageManager = $messageManager;
+ $this->appConfig = $appConfig;
+
+ parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data);
+ }
+
+ public function beforeSave()
+ {
+ $rawValue = $this->getValue();
+
+ parent::beforeSave();
+
+ $this->cacheTypeList->cleanType(\Magento\Framework\App\Cache\Type\Config::TYPE_IDENTIFIER);
+ $this->appConfig->reinit();
+
+ if ($rawValue) {
+ $data = json_decode($rawValue);
+ if ($data === null || $data === false) {
+ $this->setValue('{}');
+ try {
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ $this->messageManager->addError(self::BAD_JSON_ERROR_MESSAGE . ' (' . self::JSON_ERRORS[json_last_error()] . ')');
+ }
+ } catch (\Exception $e) {
+ $this->messageManager->addError(self::BAD_JSON_ERROR_MESSAGE);
+ }
+ } else {
+ $this->setValue(json_encode((array)$data));
+ }
+ } else {
+ $this->setValue('{}');
+ }
+ }
+}
diff --git a/Model/Config/Backend/SwatchUpload.php b/Model/Config/Backend/SwatchUpload.php
new file mode 100644
index 00000000..bb95d07c
--- /dev/null
+++ b/Model/Config/Backend/SwatchUpload.php
@@ -0,0 +1,110 @@
+swatchCollectionFactory = $swatchCollectionFactory;
+ $this->directoryList = $directoryList;
+ $this->_configuration = $configuration;
+ $this->_cldImageManager = $cldImageManager;
+ parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data);
+ }
+
+
+ public function getMediaPath($filepath)
+ {
+ return $this->directoryList->getPath(DirectoryList::MEDIA) . DIRECTORY_SEPARATOR . self::SWATCH_MEDIA_PATH . $filepath;
+ }
+ /**
+ * After saving the configuration, upload swatch images to the CDN
+ *
+ * @return $this
+ * @throws LocalizedException
+ */
+ public function afterSave()
+ {
+ $originalValue = $this->getOldValue();
+ // Check if the CDN setting is enabled
+ if ($this->getvalue() && $this->getValue() != $originalValue) {
+ // Get all swatches of type 2 (visual swatches)
+ $swatchCollection = $this->swatchCollectionFactory->create()
+ ->addFieldToFilter('type', ['eq' => Swatch::SWATCH_TYPE_VISUAL_IMAGE]);
+
+ foreach ($swatchCollection as $swatch) {
+ $file = $swatch->getData('value');
+ $imagePath = $this->getMediaPath($file);
+ $image = $this->_configuration->getMediaBaseUrl() . self::SWATCH_MEDIA_PATH . $swatch->getValue();
+ try {
+ $cldImage = $this->_cldImageManager->uploadAndSynchronise(
+ Image::fromPath($imagePath)
+ );
+ } catch (\Exception $e) {
+ throw new ValidatorException(self::ERROR_DEFAULT);
+ }
+ }
+ }
+
+ return parent::afterSave();
+ }
+
+}
diff --git a/Model/Config/Backend/VideoSettingsFreeParams.php b/Model/Config/Backend/VideoSettingsFreeParams.php
new file mode 100644
index 00000000..305ef684
--- /dev/null
+++ b/Model/Config/Backend/VideoSettingsFreeParams.php
@@ -0,0 +1,142 @@
+ 'No error',
+ JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
+ JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)',
+ JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
+ JSON_ERROR_SYNTAX => 'Syntax error',
+ JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded'
+ ];
+
+ /**
+ * @var ManagerInterface
+ */
+ private $messageManager;
+
+ /**
+ * Application config
+ *
+ * @var ScopeConfigInterface
+ */
+ protected $appConfig;
+
+ public function __construct(
+ Context $context,
+ Registry $registry,
+ ScopeConfigInterface $config,
+ TypeListInterface $cacheTypeList,
+ ManagerInterface $messageManager,
+ ReinitableConfigInterface $appConfig,
+ ?AbstractResource $resource = null,
+ ?AbstractDb $resourceCollection = null,
+ array $data = []
+ ) {
+ $this->messageManager = $messageManager;
+ $this->appConfig = $appConfig;
+
+ parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data);
+ }
+
+
+ protected function jsonDecode($jsonString)
+ {
+ $json = preg_replace('/\r|\n/','',trim($jsonString));
+ $json = str_replace(
+ array('"', "'"),
+ array('\"', '"'),
+ $json
+ );
+
+ $start = strpos($json, 'logoImageUrl');
+ // Find the end of the URL (first comma after the start of logoImageUrl)
+ $end = strpos($json, ',', $start);
+ $logoUrl = null;
+ if ($start) {
+
+ $logoUrl = substr($json, $start, $end - $start);
+ $json = substr($json, 0, $start) . substr($json, $end + 1);
+ }
+ $linkStart = strpos($json, 'logoOnclickUrl');
+ $linkEnd = strpos($json, ',',$linkStart);
+ $linkUrl = null;
+ if ($linkStart) {
+ $linkUrl = substr($json, $linkStart, $linkEnd - $linkStart);
+ $json = substr($json, 0, $linkStart) . substr($json, $linkEnd +1);
+ }
+
+ // Extract the logoImageUrl part
+
+ $json = preg_replace('/(\w+):/', '"$1":', $json);
+ $arr = json_decode($json, true);
+
+ if (is_array($arr)) {
+ if ($logoUrl) {
+ $url = str_replace("logoImageUrl:", "", $logoUrl);
+ $url = str_replace('"', "", $url);
+ $arr['player']['logoImageUrl'] = $url;
+ }
+ if ($linkUrl) {
+ $url = str_replace("logoOnclickUrl:", "", $linkUrl);
+ $url = str_replace('"', "", $url);
+ $arr['player']['logoOnclickUrl'] = $url;
+ }
+ $json = json_encode($arr);
+ }
+ // $json = str_replace("'", '"', $json);
+ return (json_decode($json)) ? $json : $jsonString;
+ }
+
+ function isJson($string) {
+ json_decode($string);
+ return json_last_error() === JSON_ERROR_NONE;
+ }
+
+ public function beforeSave()
+ {
+ $rawValue = $this->getValue();
+
+ parent::beforeSave();
+
+ $this->cacheTypeList->cleanType(\Magento\Framework\App\Cache\Type\Config::TYPE_IDENTIFIER);
+ $this->appConfig->reinit();
+
+ if ($rawValue) {
+
+ $data = $this->jsonDecode($rawValue);
+ if ($data === null || $data === false) {
+ $this->setValue('{}');
+ try {
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ $this->messageManager->addError(self::BAD_JSON_ERROR_MESSAGE . ' (' . self::JSON_ERRORS[json_last_error()] . ')');
+ }
+ } catch (\Exception $e) {
+ $this->messageManager->addError(self::BAD_JSON_ERROR_MESSAGE);
+ }
+ } else {
+ if ($this->isJson($data)) {
+
+ return $this->setValue($data);
+ }
+ $this->setValue('{}');
+ }
+ } else {
+ $this->setValue('{}');
+ }
+ }
+}
diff --git a/Model/Config/Source/Dropdown/CmsBlocks.php b/Model/Config/Source/Dropdown/CmsBlocks.php
new file mode 100644
index 00000000..f979d100
--- /dev/null
+++ b/Model/Config/Source/Dropdown/CmsBlocks.php
@@ -0,0 +1,32 @@
+blockFactory = $blockFactory;
+ }
+
+ public function toOptionArray()
+ {
+ $options = [];
+ foreach ($this->blockFactory->create()->getCollection()->setOrder('title', 'asc') as $block) {
+ $options[] = [
+ 'value' => $block->getIdentifier(),
+ 'label' => $block->getTitle(),
+ ];
+ }
+ return $options;
+ }
+}
diff --git a/Model/Config/Source/Dropdown/Dpr.php b/Model/Config/Source/Dropdown/Dpr.php
index 1726467c..38ebdc5e 100644
--- a/Model/Config/Source/Dropdown/Dpr.php
+++ b/Model/Config/Source/Dropdown/Dpr.php
@@ -8,15 +8,15 @@ class Dpr implements OptionSourceInterface
{
public function toOptionArray()
{
- return array(
- array(
+ return [
+ [
'value' => '1.0',
'label' => '1.0',
- ),
- array(
+ ],
+ [
'value' => '2.0',
'label' => '2.0',
- ),
- );
+ ],
+ ];
}
}
diff --git a/Model/Config/Source/Dropdown/FreeTransformBehavior.php b/Model/Config/Source/Dropdown/FreeTransformBehavior.php
new file mode 100644
index 00000000..2ed10c1e
--- /dev/null
+++ b/Model/Config/Source/Dropdown/FreeTransformBehavior.php
@@ -0,0 +1,22 @@
+ 'add',
+ 'label' => 'Add',
+ ],
+ [
+ 'value' => 'override',
+ 'label' => 'Override',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/Gravity.php b/Model/Config/Source/Dropdown/Gravity.php
index 3bc9eb69..4e315014 100644
--- a/Model/Config/Source/Dropdown/Gravity.php
+++ b/Model/Config/Source/Dropdown/Gravity.php
@@ -8,63 +8,47 @@ class Gravity implements OptionSourceInterface
{
public function toOptionArray()
{
- return array(
- array(
+ return [
+ [
'value' => '',
'label' => 'Magento\'s Default',
- ),
- array(
- 'value' => 'face',
- 'label' => 'Face',
- ),
- array(
- 'value' => 'faces',
- 'label' => 'Faces',
- ),
- array(
+ ],
+ [
'value' => 'north_west',
'label' => 'North West',
- ),
- array(
+ ],
+ [
'value' => 'north',
'label' => 'North',
- ),
- array(
+ ],
+ [
'value' => 'north_east',
'label' => 'North East',
- ),
- array(
+ ],
+ [
'value' => 'east',
'label' => 'East',
- ),
- array(
+ ],
+ [
'value' => 'center',
'label' => 'Center',
- ),
- array(
+ ],
+ [
'value' => 'west',
'label' => 'West',
- ),
- array(
+ ],
+ [
'value' => 'south_west',
'label' => 'South West',
- ),
- array(
+ ],
+ [
'value' => 'south',
'label' => 'South',
- ),
- array(
+ ],
+ [
'value' => 'south_east',
'label' => 'South East',
- ),
- array(
- 'value' => 'face:center',
- 'label' => 'Face (Center)',
- ),
- array(
- 'value' => 'faces:center',
- 'label' => 'Faces (Center)',
- ),
- );
+ ],
+ ];
}
}
diff --git a/Model/Config/Source/Dropdown/Lazyload/Effect.php b/Model/Config/Source/Dropdown/Lazyload/Effect.php
new file mode 100644
index 00000000..e5731be2
--- /dev/null
+++ b/Model/Config/Source/Dropdown/Lazyload/Effect.php
@@ -0,0 +1,22 @@
+ 'show',
+ 'label' => 'Show',
+ ],
+ [
+ 'value' => 'fadeIn',
+ 'label' => 'Fade In',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/Lazyload/Placeholder.php b/Model/Config/Source/Dropdown/Lazyload/Placeholder.php
new file mode 100644
index 00000000..60db2632
--- /dev/null
+++ b/Model/Config/Source/Dropdown/Lazyload/Placeholder.php
@@ -0,0 +1,39 @@
+ 'blur',
+ 'label' => 'Blur',
+ ],
+ [
+ 'value' => 'pixelate',
+ 'label' => 'Pixelate',
+ ],
+ [
+ 'value' => 'predominant-color',
+ 'label' => 'Predominant color',
+ ],
+ [
+ 'value' => 'vectorize',
+ 'label' => 'Vectorize',
+ ],
+ ];
+
+ /*
+ export const placeholderImageOptions = {
+ 'vectorize': {effect: 'vectorize', quality: 1},
+ 'pixelate': {effect: 'pixelate', quality: 1, fetch_format: 'auto'},
+ 'blur': {effect: 'blur:2000', quality: 1, fetch_format: 'auto'},
+ 'predominant-color': predominantColorTransform
+ };
+ */
+ }
+}
diff --git a/Model/Config/Source/Dropdown/ProductGallery/AspectRatio.php b/Model/Config/Source/Dropdown/ProductGallery/AspectRatio.php
new file mode 100644
index 00000000..3f8dbbdd
--- /dev/null
+++ b/Model/Config/Source/Dropdown/ProductGallery/AspectRatio.php
@@ -0,0 +1,62 @@
+ 'square',
+ 'label' => 'Square',
+ ],
+ [
+ 'value' => '1:1',
+ 'label' => '1:1',
+ ],
+ [
+ 'value' => '3:4',
+ 'label' => '3:4',
+ ],
+ [
+ 'value' => '4:3',
+ 'label' => '4:3',
+ ],
+ [
+ 'value' => '4:6',
+ 'label' => '4:6',
+ ],
+ [
+ 'value' => '6:4',
+ 'label' => '6:4',
+ ],
+ [
+ 'value' => '5:7',
+ 'label' => '5:7',
+ ],
+ [
+ 'value' => '7:5',
+ 'label' => '7:5',
+ ],
+ [
+ 'value' => '5:8',
+ 'label' => '5:8',
+ ],
+ [
+ 'value' => '8:5',
+ 'label' => '8:5',
+ ],
+ [
+ 'value' => '9:16',
+ 'label' => '9:16',
+ ],
+ [
+ 'value' => '16:9',
+ 'label' => '16:9',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/ProductGallery/CarouselLocation.php b/Model/Config/Source/Dropdown/ProductGallery/CarouselLocation.php
new file mode 100644
index 00000000..5f8fde64
--- /dev/null
+++ b/Model/Config/Source/Dropdown/ProductGallery/CarouselLocation.php
@@ -0,0 +1,30 @@
+ 'top',
+ 'label' => 'Top',
+ ],
+ [
+ 'value' => 'right',
+ 'label' => 'Right',
+ ],
+ [
+ 'value' => 'left',
+ 'label' => 'Left',
+ ],
+ [
+ 'value' => 'bottom',
+ 'label' => 'Bottom',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/ProductGallery/CarouselStyle.php b/Model/Config/Source/Dropdown/ProductGallery/CarouselStyle.php
new file mode 100644
index 00000000..b5da76c2
--- /dev/null
+++ b/Model/Config/Source/Dropdown/ProductGallery/CarouselStyle.php
@@ -0,0 +1,26 @@
+ 'none',
+ 'label' => 'None',
+ ],
+ [
+ 'value' => 'thumbnails',
+ 'label' => 'Thumbnails',
+ ],
+ [
+ 'value' => 'indicators',
+ 'label' => 'Indicators',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/ProductGallery/IndicatorsShape.php b/Model/Config/Source/Dropdown/ProductGallery/IndicatorsShape.php
new file mode 100644
index 00000000..0f5e17ed
--- /dev/null
+++ b/Model/Config/Source/Dropdown/ProductGallery/IndicatorsShape.php
@@ -0,0 +1,26 @@
+ 'round',
+ 'label' => 'Round',
+ ],
+ [
+ 'value' => 'square',
+ 'label' => 'Square',
+ ],
+ [
+ 'value' => 'radius',
+ 'label' => 'Radius',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/ProductGallery/Navigation.php b/Model/Config/Source/Dropdown/ProductGallery/Navigation.php
new file mode 100644
index 00000000..c893ef00
--- /dev/null
+++ b/Model/Config/Source/Dropdown/ProductGallery/Navigation.php
@@ -0,0 +1,26 @@
+ 'none',
+ 'label' => 'None',
+ ],
+ [
+ 'value' => 'always',
+ 'label' => 'Always',
+ ],
+ [
+ 'value' => 'mouseover',
+ 'label' => 'Mouseover',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/ProductGallery/ThumbnailsMediaSymbolShape.php b/Model/Config/Source/Dropdown/ProductGallery/ThumbnailsMediaSymbolShape.php
new file mode 100644
index 00000000..707b88c3
--- /dev/null
+++ b/Model/Config/Source/Dropdown/ProductGallery/ThumbnailsMediaSymbolShape.php
@@ -0,0 +1,30 @@
+ 'none',
+ 'label' => 'None',
+ ],
+ [
+ 'value' => 'round',
+ 'label' => 'Round',
+ ],
+ [
+ 'value' => 'square',
+ 'label' => 'Square',
+ ],
+ [
+ 'value' => 'radius',
+ 'label' => 'Radius',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/ProductGallery/ThumbnailsNavigationShape.php b/Model/Config/Source/Dropdown/ProductGallery/ThumbnailsNavigationShape.php
new file mode 100644
index 00000000..71955793
--- /dev/null
+++ b/Model/Config/Source/Dropdown/ProductGallery/ThumbnailsNavigationShape.php
@@ -0,0 +1,34 @@
+ 'none',
+ 'label' => 'None',
+ ],
+ [
+ 'value' => 'round',
+ 'label' => 'Round',
+ ],
+ [
+ 'value' => 'square',
+ 'label' => 'Square',
+ ],
+ [
+ 'value' => 'radius',
+ 'label' => 'Radius',
+ ],
+ [
+ 'value' => 'rectangle',
+ 'label' => 'Rectangle',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/ProductGallery/ThumbnailsSelectedBorderPosition.php b/Model/Config/Source/Dropdown/ProductGallery/ThumbnailsSelectedBorderPosition.php
new file mode 100644
index 00000000..cec73748
--- /dev/null
+++ b/Model/Config/Source/Dropdown/ProductGallery/ThumbnailsSelectedBorderPosition.php
@@ -0,0 +1,42 @@
+ 'top',
+ 'label' => 'Top',
+ ],
+ [
+ 'value' => 'bottom',
+ 'label' => 'Bottom',
+ ],
+ [
+ 'value' => 'left',
+ 'label' => 'Left',
+ ],
+ [
+ 'value' => 'right',
+ 'label' => 'Right',
+ ],
+ [
+ 'value' => 'top-bottom',
+ 'label' => 'Top-Bottom',
+ ],
+ [
+ 'value' => 'left-right',
+ 'label' => 'Left-Right',
+ ],
+ [
+ 'value' => 'all',
+ 'label' => 'All',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/ProductGallery/ThumbnailsSelectedStyle.php b/Model/Config/Source/Dropdown/ProductGallery/ThumbnailsSelectedStyle.php
new file mode 100644
index 00000000..10f70142
--- /dev/null
+++ b/Model/Config/Source/Dropdown/ProductGallery/ThumbnailsSelectedStyle.php
@@ -0,0 +1,26 @@
+ 'border',
+ 'label' => 'Border',
+ ],
+ [
+ 'value' => 'gradient',
+ 'label' => 'Gradient',
+ ],
+ [
+ 'value' => 'all',
+ 'label' => 'All',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/ProductGallery/Transition.php b/Model/Config/Source/Dropdown/ProductGallery/Transition.php
new file mode 100644
index 00000000..73057641
--- /dev/null
+++ b/Model/Config/Source/Dropdown/ProductGallery/Transition.php
@@ -0,0 +1,26 @@
+ 'none',
+ 'label' => 'None',
+ ],
+ [
+ 'value' => 'fade',
+ 'label' => 'Fade',
+ ],
+ [
+ 'value' => 'slide',
+ 'label' => 'Slide',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/ProductGallery/ZoomTrigger.php b/Model/Config/Source/Dropdown/ProductGallery/ZoomTrigger.php
new file mode 100644
index 00000000..ae902c66
--- /dev/null
+++ b/Model/Config/Source/Dropdown/ProductGallery/ZoomTrigger.php
@@ -0,0 +1,22 @@
+ 'click',
+ 'label' => 'Click',
+ ],
+ [
+ 'value' => 'hover',
+ 'label' => 'Hover',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/ProductGallery/ZoomType.php b/Model/Config/Source/Dropdown/ProductGallery/ZoomType.php
new file mode 100644
index 00000000..ca334692
--- /dev/null
+++ b/Model/Config/Source/Dropdown/ProductGallery/ZoomType.php
@@ -0,0 +1,26 @@
+ 'inline',
+ 'label' => 'Inline',
+ ],
+ [
+ 'value' => 'flyout',
+ 'label' => 'Flyout',
+ ],
+ [
+ 'value' => 'popup',
+ 'label' => 'Popup',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/ProductGallery/ZoomViewerPosition.php b/Model/Config/Source/Dropdown/ProductGallery/ZoomViewerPosition.php
new file mode 100644
index 00000000..f9eea465
--- /dev/null
+++ b/Model/Config/Source/Dropdown/ProductGallery/ZoomViewerPosition.php
@@ -0,0 +1,30 @@
+ 'top',
+ 'label' => 'Top',
+ ],
+ [
+ 'value' => 'right',
+ 'label' => 'Right',
+ ],
+ [
+ 'value' => 'left',
+ 'label' => 'Left',
+ ],
+ [
+ 'value' => 'bottom',
+ 'label' => 'Bottom',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/Quality.php b/Model/Config/Source/Dropdown/Quality.php
index 1b0488da..8d40bcb5 100644
--- a/Model/Config/Source/Dropdown/Quality.php
+++ b/Model/Config/Source/Dropdown/Quality.php
@@ -8,47 +8,51 @@ class Quality implements OptionSourceInterface
{
public function toOptionArray()
{
- return array(
- array(
+ return [
+ [
+ 'value' => '',
+ 'label' => 'Magento\'s Default',
+ ],
+ [
'value' => '20',
'label' => '20%',
- ),
- array(
+ ],
+ [
'value' => '30',
'label' => '30%',
- ),
- array(
+ ],
+ [
'value' => '40',
'label' => '40%',
- ),
- array(
+ ],
+ [
'value' => '50',
'label' => '50%',
- ),
- array(
+ ],
+ [
'value' => '60',
'label' => '60%',
- ),
- array(
+ ],
+ [
'value' => '70',
'label' => '70%',
- ),
- array(
+ ],
+ [
'value' => '80',
'label' => '80%',
- ),
- array(
+ ],
+ [
'value' => '90',
'label' => '90%',
- ),
- array(
+ ],
+ [
'value' => '100',
'label' => '100%',
- ),
- array(
+ ],
+ [
'value' => 'auto',
'label' => 'Auto',
- )
- );
+ ]
+ ];
}
}
diff --git a/Model/Config/Source/Dropdown/Video/ABR/Comment.php b/Model/Config/Source/Dropdown/Video/ABR/Comment.php
new file mode 100644
index 00000000..21cded0f
--- /dev/null
+++ b/Model/Config/Source/Dropdown/Video/ABR/Comment.php
@@ -0,0 +1,33 @@
+urlInterface = $urlInterface;
+ }
+
+ public function getCommentText($elementValue)
+ {
+
+ if ($elementValue == 'optimization') {
+ $comment = '';
+ }
+ return $comment;
+ }
+}
diff --git a/Model/Config/Source/Dropdown/Video/Autoplay.php b/Model/Config/Source/Dropdown/Video/Autoplay.php
new file mode 100644
index 00000000..687beee3
--- /dev/null
+++ b/Model/Config/Source/Dropdown/Video/Autoplay.php
@@ -0,0 +1,30 @@
+
+ * Date: 14/01/2024
+ * Time: 14:22
+ */
+class Autoplay implements OptionSourceInterface
+{
+ public function toOptionArray()
+ {
+ return [
+ [
+ 'value' => 'never',
+ 'label' => 'Off',
+ ],
+ [
+ 'value' => 'always',
+ 'label' => 'Always',
+ ],
+ [
+ 'value' => 'on-scroll',
+ 'label' => 'On-scroll',
+ ]
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/Video/Autoplay/Comment.php b/Model/Config/Source/Dropdown/Video/Autoplay/Comment.php
new file mode 100644
index 00000000..214082cf
--- /dev/null
+++ b/Model/Config/Source/Dropdown/Video/Autoplay/Comment.php
@@ -0,0 +1,39 @@
+
+ * Date: 07/03/2024
+ * Time: 14:04
+ */
+class Comment implements CommentInterface
+{
+ /**
+ * @var UrlInterface
+ */
+ protected $urlInterface;
+
+ /**
+ * @param UrlInterface $urlInterface
+ */
+ public function __construct(
+ UrlInterface $urlInterface
+ ) {
+ $this->urlInterface = $urlInterface;
+ }
+
+ public function getCommentText($elementValue)
+ {
+ $url = 'https://cloudinary.com/glossary/video-autoplay';
+
+ $comment = '';
+
+ return $comment;
+ }
+}
diff --git a/Model/Config/Source/Dropdown/Video/Controls.php b/Model/Config/Source/Dropdown/Video/Controls.php
new file mode 100644
index 00000000..b31c4545
--- /dev/null
+++ b/Model/Config/Source/Dropdown/Video/Controls.php
@@ -0,0 +1,30 @@
+
+ * Date: 14/01/2024
+ * Time: 14:22
+ */
+class Controls implements OptionSourceInterface
+{
+ public function toOptionArray()
+ {
+ return [
+ [
+ 'value' => 'all',
+ 'label' => 'All',
+ ],
+ [
+ 'value' => 'play',
+ 'label' => 'Play buttons',
+ ],
+ [
+ 'value' => 'none',
+ 'label' => 'None',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/Video/SourceTypes.php b/Model/Config/Source/Dropdown/Video/SourceTypes.php
new file mode 100644
index 00000000..8eba91a4
--- /dev/null
+++ b/Model/Config/Source/Dropdown/Video/SourceTypes.php
@@ -0,0 +1,25 @@
+ 'webm/vp9',
+ 'label' => 'WebM/VP9',
+ ],
+ [
+ 'value' => 'mp4/h265',
+ 'label' => 'MP4/H.265',
+ ],
+ [
+ 'value' => 'mp4/h264',
+ 'label' => 'MP4/H.264',
+ ]
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/Video/StreamMode.php b/Model/Config/Source/Dropdown/Video/StreamMode.php
new file mode 100644
index 00000000..38daeedf
--- /dev/null
+++ b/Model/Config/Source/Dropdown/Video/StreamMode.php
@@ -0,0 +1,26 @@
+
+ * Date: 14/01/2024
+ * Time: 14:22
+ */
+class StreamMode implements OptionSourceInterface
+{
+ public function toOptionArray()
+ {
+ return [
+ [
+ 'value' => 'optimization',
+ 'label' => 'Progressive mode (i.e MP4/Webm)',
+ ],
+ [
+ 'value' => 'abr',
+ 'label' => 'ABR',
+ ]
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/Video/StreamProtocol.php b/Model/Config/Source/Dropdown/Video/StreamProtocol.php
new file mode 100644
index 00000000..a8fc7fdc
--- /dev/null
+++ b/Model/Config/Source/Dropdown/Video/StreamProtocol.php
@@ -0,0 +1,27 @@
+
+ * Date: 10/03/2024
+ * Time: 17:54
+ */
+class StreamProtocol implements OptionSourceInterface
+{
+ public function toOptionArray()
+ {
+ return [
+ [
+ 'value' => 'dash',
+ 'label' => 'Dynamic adaptive streaming over HTTP (MPEG-DASH)',
+ ],
+ [
+ 'value' => 'hls',
+ 'label' => 'HTTP live streaming (HLS)',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/Video/Type.php b/Model/Config/Source/Dropdown/Video/Type.php
new file mode 100644
index 00000000..b9921983
--- /dev/null
+++ b/Model/Config/Source/Dropdown/Video/Type.php
@@ -0,0 +1,26 @@
+
+ * Date: 14/01/2024
+ * Time: 14:22
+ */
+class Type implements OptionSourceInterface
+{
+ public function toOptionArray()
+ {
+ return [
+ [
+ 'value' => 'native',
+ 'label' => 'Default player',
+ ],
+ [
+ 'value' => 'cloudinary',
+ 'label' => 'Cloudinary player',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/Video/VideoFormat.php b/Model/Config/Source/Dropdown/Video/VideoFormat.php
new file mode 100644
index 00000000..b73ec721
--- /dev/null
+++ b/Model/Config/Source/Dropdown/Video/VideoFormat.php
@@ -0,0 +1,21 @@
+ 'none',
+ 'label' => 'None',
+ ],
+ [
+ 'value' => 'auto',
+ 'label' => 'Auto',
+ ],
+ ];
+ }
+}
diff --git a/Model/Config/Source/Dropdown/Video/VideoQuality.php b/Model/Config/Source/Dropdown/Video/VideoQuality.php
new file mode 100644
index 00000000..530e0e2f
--- /dev/null
+++ b/Model/Config/Source/Dropdown/Video/VideoQuality.php
@@ -0,0 +1,37 @@
+ 'none',
+ 'label' => 'Not set',
+ ],
+ [
+ 'value' => 'q_auto',
+ 'label' => 'Auto',
+ ],
+ [
+ 'value' => 'q_auto:best',
+ 'label' => 'Auto best',
+ ],
+ [
+ 'value' => 'q_auto:good',
+ 'label' => 'Auto good',
+ ],
+ [
+ 'value' => 'q_auto:eco',
+ 'label' => 'Auto eco',
+ ],
+ [
+ 'value' => 'q_auto:low',
+ 'label' => 'Auto low',
+ ]
+ ];
+ }
+}
diff --git a/Model/Configuration.php b/Model/Configuration.php
index fad16c2c..805acffe 100644
--- a/Model/Configuration.php
+++ b/Model/Configuration.php
@@ -8,6 +8,7 @@
use Cloudinary\Cloudinary\Core\Credentials;
use Cloudinary\Cloudinary\Core\Exception\InvalidCredentials;
use Cloudinary\Cloudinary\Core\Image\Transformation;
+use Cloudinary\Cloudinary\Core\Image\Transformation\DefaultImage;
use Cloudinary\Cloudinary\Core\Image\Transformation\Dpr;
use Cloudinary\Cloudinary\Core\Image\Transformation\FetchFormat;
use Cloudinary\Cloudinary\Core\Image\Transformation\Freeform;
@@ -15,27 +16,115 @@
use Cloudinary\Cloudinary\Core\Image\Transformation\Quality;
use Cloudinary\Cloudinary\Core\Security\CloudinaryEnvironmentVariable;
use Cloudinary\Cloudinary\Core\UploadConfig;
+use Cloudinary\Cloudinary\Model\Logger as CloudinaryLogger;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Config\Storage\WriterInterface;
use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\App\ProductMetadataInterface;
use Magento\Framework\Encryption\EncryptorInterface;
+use Magento\Framework\Message\ManagerInterface;
+use Magento\Framework\Module\ModuleListInterface;
+use Magento\Framework\Registry;
+use Magento\Framework\UrlInterface;
+use Magento\Store\Model\StoreManagerInterface;
+use Psr\Log\LoggerInterface;
+use Magento\Framework\Filesystem;
class Configuration implements ConfigurationInterface
{
+ const MODULE_NAME = 'Cloudinary_Cloudinary';
+
+ //= Basics
const CONFIG_PATH_ENABLED = 'cloudinary/cloud/cloudinary_enabled';
- const USER_PLATFORM_TEMPLATE = 'CloudinaryMagento/%s (Magento %s)';
const CONFIG_PATH_ENVIRONMENT_VARIABLE = 'cloudinary/setup/cloudinary_environment_variable';
- const CONFIG_CDN_SUBDOMAIN = 'cloudinary/configuration/cloudinary_cdn_subdomain';
- const CONFIG_DEFAULT_GRAVITY = 'cloudinary/transformations/cloudinary_gravity';
- const CONFIG_DEFAULT_QUALITY = 'cloudinary/transformations/cloudinary_image_quality';
- const CONFIG_DEFAULT_DPR = 'cloudinary/transformations/cloudinary_image_dpr';
- const CONFIG_DEFAULT_FETCH_FORMAT = 'cloudinary/transformations/cloudinary_fetch_format';
- const CONFIG_GLOBAL_FREEFORM = 'cloudinary/transformations/cloudinary_free_transform_global';
+ const CONFIG_PATH_CDN_SUBDOMAIN = 'cloudinary/configuration/cloudinary_cdn_subdomain';
+
+ //= Transformations
+ const CONFIG_PATH_DEFAULT_GRAVITY = 'cloudinary/transformations/cloudinary_gravity';
+ const CONFIG_PATH_DEFAULT_QUALITY = 'cloudinary/transformations/cloudinary_image_quality';
+ const CONFIG_PATH_DEFAULT_DPR = 'cloudinary/transformations/cloudinary_image_dpr';
+ const CONFIG_PATH_DEFAULT_FETCH_FORMAT = 'cloudinary/transformations/cloudinary_fetch_format';
+ const CONFIG_PATH_DEFAULT_IMAGE = 'cloudinary/transformations/cloudinary_default_image';
+ const CONFIG_PATH_GLOBAL_FREEFORM = 'cloudinary/transformations/cloudinary_free_transform_global';
+ const CONFIG_PATH_GLOBAL_FREEFORM_PRODUCTS = 'cloudinary/transformations/cloudinary_free_transform_global_products';
+ const CONFIG_PATH_GLOBAL_FREEFORM_PRODUCTS_BEHAVIOR = 'cloudinary/transformations/cloudinary_free_transform_global_products_behavior';
+
+ //= Lazyload
+ const XML_PATH_LAZYLOAD_ENABLED = 'cloudinary/lazyload/enabled';
+ const XML_PATH_LAZYLOAD_AUTO_REPLACE_CMS_BLOCKS = 'cloudinary/lazyload/auto_replace_cms_blocks';
+ const XML_PATH_LAZYLOAD_IGNORED_CMS_BLOCKS = 'cloudinary/lazyload/ignored_cms_blocks';
+ const XML_PATH_LAZYLOAD_THRESHOLD = 'cloudinary/lazyload/threshold';
+ const XML_PATH_LAZYLOAD_EFFECT = 'cloudinary/lazyload/effect';
+ const XML_PATH_LAZYLOAD_PLACEHOLDER = 'cloudinary/lazyload/placeholder';
+
+ //= Advanced
+ const CONFIG_PATH_REMOVE_VERSION_NUMBER = 'cloudinary/advanced/remove_version_number';
+ const CONFIG_PATH_USE_ROOT_PATH = 'cloudinary/advanced/use_root_path';
+ const CONFIG_PATH_USE_SIGNED_URLS = 'cloudinary/advanced/use_signed_urls';
+ const CONFIG_PATH_ENABLE_LOCAL_MAPPING = 'cloudinary/advanced/enable_local_mapping';
+ const CONFIG_PATH_SCHEDULED_VIDEO_DATA_IMPORT_LIMIT = 'cloudinary/advanced/cloudinary_scheduled_video_data_import_limit';
+ const CONFIG_PATH_PG_API_QUEUE_ENABLED = 'cloudinary/advanced/product_gallery_api_queue_enabled';
+ const CONFIG_PATH_PG_API_QUEUE_LIMIT = 'cloudinary/advanced/product_gallery_api_queue_limit';
+ const CONFIG_PATH_PG_API_QUEUE_MAX_TRYOUTS = 'cloudinary/advanced/product_gallery_api_queue_max_tryouts';
+ const CONFIG_PATH_ENABLE_PRODUCT_FREE_TRANSFORMATIONS = 'cloudinary/advanced/enable_product_free_transformations';
+ const CONFIG_PATH_LOAD_SWATCHES_FROM_CLOUDINARY = 'cloudinary/advanced/load_cloudinary_swatches';
+
+ const CONFIG_PATH_ENABLE_CACHE_PLACEHOLDER = 'cloudinary/advanced/cache_placeholder_enable';
+
+ const CONFIG_PATH_CUSTOM_PLACEHOLDER_IMAGE = 'cloudinary/advanced/custom_placeholder_image';
+
+
+ //= Product Gallery
+ const CONFIG_PATH_PG_ALL = 'cloudinary/product_gallery';
+ const CONFIG_PATH_PG_ENABLED = 'cloudinary/product_gallery/enabled';
+ const CONFIG_PATH_PG_THEMEPROPS_PRIMARY = 'cloudinary/product_gallery/themeProps_primary';
+ const CONFIG_PATH_PG_THEMEPROPS_ONPRIMARY = 'cloudinary/product_gallery/themeProps_onPrimary';
+ const CONFIG_PATH_PG_THEMEPROPS_ACTIVE = 'cloudinary/product_gallery/themeProps_active';
+ const CONFIG_PATH_PG_THEMEPROPS_ONACTIVE = 'cloudinary/product_gallery/themeProps_onActive';
+ const CONFIG_PATH_PG_TRANSITION = 'cloudinary/product_gallery/transition';
+ const CONFIG_PATH_PG_ASPECT_RATIO = 'cloudinary/product_gallery/aspectRatio';
+ const CONFIG_PATH_PG_ZOOMPROPS_NAVIGATION = 'cloudinary/product_gallery/navigation';
+ const CONFIG_PATH_PG_ZOOM = 'cloudinary/product_gallery/zoom';
+ const CONFIG_PATH_PG_ZOOMPROPS_TYPE = 'cloudinary/product_gallery/zoomProps_type';
+ const CONFIG_PATH_PG_ZOOMPROPS_POSITION = 'cloudinary/product_gallery/zoomPropsViewerPosition';
+ const CONFIG_PATH_PG_ZOOMPROPS_TRIGGER = 'cloudinary/product_gallery/zoomProps_trigger';
+ const CONFIG_PATH_PG_CAROUSEL_LOCATION = 'cloudinary/product_gallery/carouselLocation';
+ const CONFIG_PATH_PG_CAROUSEL_OFFSET = 'cloudinary/product_gallery/carouselOffset';
+ const CONFIG_PATH_PG_CAROUSEL_STYLE = 'cloudinary/product_gallery/carouselStyle';
+ const CONFIG_PATH_PG_THUMBNAILPROPS_WIDTH = 'cloudinary/product_gallery/thumbnailProps_width';
+ const CONFIG_PATH_PG_THUMBNAILPROPS_HEIGHT = 'cloudinary/product_gallery/thumbnailProps_height';
+ const CONFIG_PATH_PG_THUMBNAILPROPS_NAVIGATION_SHAPE = 'cloudinary/product_gallery/thumbnailProps_navigationShape';
+ const CONFIG_PATH_PG_THUMBNAILPROPS_SELECTED_STYLE = 'cloudinary/product_gallery/thumbnailProps_selectedStyle';
+ const CONFIG_PATH_PG_THUMBNAILPROPS_SELECTED_BORDER_POSITION = 'cloudinary/product_gallery/thumbnailProps_selectedBorderPosition';
+ const CONFIG_PATH_PG_THUMBNAILPROPS_SELECTED_BORDER_WIDTH = 'cloudinary/product_gallery/thumbnailProps_selectedBorderWidth';
+ const CONFIG_PATH_PG_THUMBNAILPROPS_MEDIA_ICON_SHAPE = 'cloudinary/product_gallery/thumbnailProps_mediaSymbolShape';
+ const CONFIG_PATH_PG_INDICATORPROPS_SHAPE = 'cloudinary/product_gallery/indicatorProps_shape';
+ const CONFIG_PATH_PG_CUSTOM_FREE_PARAMS = 'cloudinary/product_gallery/custom_free_params';
+
+ // Video Settings
+
+ const CONFIG_PATH_CLD_VIDEO_SETTINGS_ALL = 'cloudinary/cld_video';
+ const CONFIG_PATH_CLD_VIDEO_ENABLED = 'cloudinary/cld_video/enabled';
+ const CONFIG_PATH_CLD_VIDEO_PLAYER_USE_ABR = 'cloudinary/cld_video/use_abr';
+ const CONFIG_PATH_CLD_VIDEO_PLAYER_SHOW_CONTROLS = 'cloudinary/cld_video/controls';
+ const CONFIG_PATH_CLD_VIDEO_PLAYER_LOOP = 'cloudinary/cld_video/loop';
+ const CONFIG_PATH_CLD_VIDEO_PLAYER_AUTOPLAY = 'cloudinary/cld_video/autoplay';
+
+
+ //= Others
+ const CONFIG_PATH_SECURE_BASE_URL = "web/secure/base_url";
+ const CONFIG_PATH_UNSECURE_BASE_URL = "web/unsecure/base_url";
+ const CONFIG_PATH_USE_SECURE_IN_FRONTEND = "web/secure/use_in_frontend";
+
+ const USER_PLATFORM_TEMPLATE = 'CloudinaryMagento/%s (Magento %s)';
const USE_FILENAME = true;
const UNIQUE_FILENAME = false;
const OVERWRITE = false;
const SCOPE_ID_ONE = 1;
const SCOPE_ID_ZERO = 0;
+ const CLD_UNIQID_PREFIX = 'cld_';
+
+ const LAZYLOAD_DATA_PLACEHOLDER = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC';
/**
* @var ScopeConfigInterface
@@ -53,7 +142,7 @@ class Configuration implements ConfigurationInterface
private $decryptor;
/**
- * @var CloudinaryEnvironmentVariable
+ * @var
*/
private $environmentVariable;
@@ -62,24 +151,101 @@ class Configuration implements ConfigurationInterface
*/
private $autoUploadConfiguration;
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ /**
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
+
+ /**
+ * @var ModuleListInterface
+ */
+ private $moduleList;
+
+ /**
+ * @var ProductMetadataInterface
+ */
+ private $productMetadata;
+
+ /**
+ * @var CloudinaryLogger
+ */
+ private $cloudinaryLogger;
+
+ /**
+ * @var Registry
+ */
+ private $coreRegistry;
+
+ /**
+ * @var ManagerInterface
+ */
+ private $messageManager;
+
+ protected $directoryList;
+
+ private $filesystem;
+
/**
* @param ScopeConfigInterface $configReader
* @param WriterInterface $configWriter
* @param EncryptorInterface $decryptor
* @param AutoUploadConfigurationInterface $autoUploadConfiguration
+ * @param LoggerInterface $logger
+ * @param StoreManagerInterface $storeManager
+ * @param ModuleListInterface $moduleList
+ * @param ProductMetadataInterface $productMetadata
+ * @param Logger $cloudinaryLogger
+ * @param Registry $coreRegistry
+ * @param ManagerInterface $messageManager
+ * @param Filesystem $filesystem
*/
public function __construct(
ScopeConfigInterface $configReader,
WriterInterface $configWriter,
EncryptorInterface $decryptor,
AutoUploadConfigurationInterface $autoUploadConfiguration,
- \Psr\Log\LoggerInterface $logger
+ LoggerInterface $logger,
+ StoreManagerInterface $storeManager,
+ ModuleListInterface $moduleList,
+ ProductMetadataInterface $productMetadata,
+ CloudinaryLogger $cloudinaryLogger,
+ Registry $coreRegistry,
+ ManagerInterface $messageManager,
+ Filesystem $filesystem,
) {
$this->configReader = $configReader;
$this->configWriter = $configWriter;
$this->decryptor = $decryptor;
$this->autoUploadConfiguration = $autoUploadConfiguration;
$this->logger = $logger;
+ $this->storeManager = $storeManager;
+ $this->moduleList = $moduleList;
+ $this->productMetadata = $productMetadata;
+ $this->cloudinaryLogger = $cloudinaryLogger;
+ $this->coreRegistry = $coreRegistry;
+ $this->messageManager = $messageManager;
+ $this->filesystem = $filesystem;
+ }
+
+ /**
+ * @return StoreManagerInterface
+ */
+ public function getStoreManager()
+ {
+ return $this->storeManager;
+ }
+
+ /**
+ * @return Registry
+ */
+ public function getCoreRegistry()
+ {
+ return $this->coreRegistry;
}
/**
@@ -91,24 +257,65 @@ public function getCloud()
}
/**
- * @return Credentials
+ * @return (array) Credentials
*/
public function getCredentials()
{
- return $this->getEnvironmentVariable()->getCredentials();
+
+ $rawValue = $this->configReader->getValue(self::CONFIG_PATH_ENVIRONMENT_VARIABLE, \Magento\Store\Model\ScopeInterface::SCOPE_STORE);
+ $value = $this->decryptor->decrypt($rawValue);
+ $environmentVariable = str_replace('CLOUDINARY_URL=', '', $value);
+ $uri = parse_url($environmentVariable);
+ if (!isset($uri["scheme"]) || strtolower($uri["scheme"]) !== "cloudinary") {
+ throw new \InvalidArgumentException("Invalid CLOUDINARY_URL scheme. Expecting to start with 'cloudinary://'");
+ }
+ $q_params = [];
+ if (isset($uri["query"])) {
+ parse_str($uri["query"], $q_params);
+ }
+ $private_cdn = isset($uri["path"]) && $uri["path"] != "/";
+
+ $credentials = array_merge(
+ $q_params,
+ [
+ "cloud_name" => $uri["host"],
+ "api_key" => $uri["user"],
+ "api_secret" => $uri["pass"],
+ "private_cdn" => $private_cdn,
+ ]
+ );
+
+ if (isset($credentials['cname'])) {
+ $credentials['secure'] = true;
+ $credentials['secure_distribution'] = $credentials['cname'];
+ }
+
+ return $credentials;
+
+
}
/**
+ * @param bool $isProduct
* @return Transformation
*/
- public function getDefaultTransformation()
+ public function getDefaultTransformation($isProduct = false)
{
+ if ($isProduct && ($globalFreeform = $this->getDefaultGlobalFreeformProducts())) {
+ if ($this->getDefaultGlobalFreeformProductsBehavior() === 'add') {
+ $globalFreeform = $this->getDefaultGlobalFreeform() . ',' . $globalFreeform;
+ }
+ } else {
+ $globalFreeform = $this->getDefaultGlobalFreeform();
+ }
+
return Transformation::builder()
->withGravity(Gravity::fromString($this->getDefaultGravity()))
->withQuality(Quality::fromString($this->getImageQuality()))
->withFetchFormat(FetchFormat::fromString($this->getFetchFormat()))
- ->withFreeform(Freeform::fromString($this->getDefaultGlobalFreeform()))
- ->withDpr(Dpr::fromString($this->getImageDpr()));
+ ->withFreeform(Freeform::fromString($globalFreeform))
+ ->withDpr(Dpr::fromString($this->getImageDpr()))
+ ->withDefaultImage(DefaultImage::fromString($this->getCloudinaryDefaultImage()));
}
/**
@@ -116,7 +323,23 @@ public function getDefaultTransformation()
*/
private function getDefaultGlobalFreeform()
{
- return (string) $this->configReader->getValue(self::CONFIG_GLOBAL_FREEFORM);
+ return (string) $this->configReader->getValue(self::CONFIG_PATH_GLOBAL_FREEFORM);
+ }
+
+ /**
+ * @return string
+ */
+ private function getDefaultGlobalFreeformProducts()
+ {
+ return (string) $this->configReader->getValue(self::CONFIG_PATH_GLOBAL_FREEFORM_PRODUCTS);
+ }
+
+ /**
+ * @return string
+ */
+ private function getDefaultGlobalFreeformProductsBehavior()
+ {
+ return (string) $this->configReader->getValue(self::CONFIG_PATH_GLOBAL_FREEFORM_PRODUCTS_BEHAVIOR);
}
/**
@@ -124,7 +347,7 @@ private function getDefaultGlobalFreeform()
*/
public function getCdnSubdomainStatus()
{
- return $this->configReader->isSetFlag(self::CONFIG_CDN_SUBDOMAIN);
+ return $this->configReader->isSetFlag(self::CONFIG_PATH_CDN_SUBDOMAIN);
}
/**
@@ -132,7 +355,7 @@ public function getCdnSubdomainStatus()
*/
public function getUserPlatform()
{
- return sprintf(self::USER_PLATFORM_TEMPLATE, '1.6.0', '2.0.0');
+ return sprintf(self::USER_PLATFORM_TEMPLATE, $this->getModuleVersion(), $this->getMagentoPlatformVersion());
}
/**
@@ -143,12 +366,19 @@ public function getUploadConfig()
return UploadConfig::fromBooleanValues(self::USE_FILENAME, self::UNIQUE_FILENAME, self::OVERWRITE);
}
+ /**
+ * @return bool
+ */
+ public function isModuleEnabled()
+ {
+ return (bool) $this->configReader->getValue(self::CONFIG_PATH_ENABLED);
+ }
/**
* @return boolean
*/
- public function isEnabled()
+ public function isEnabled($checkEnvVar = true)
{
- return $this->hasEnvironmentVariable() && $this->configReader->isSetFlag(self::CONFIG_PATH_ENABLED);
+ return ($this->hasEnvironmentVariable() || !$checkEnvVar) && ($this->coreRegistry->registry(self::CONFIG_PATH_ENABLED) || $this->configReader->isSetFlag(self::CONFIG_PATH_ENABLED));
}
public function enable()
@@ -170,12 +400,20 @@ public function getFormatsToPreserve()
}
/**
- * @param string $file
+ * @return array
+ */
+ public function getSupportedVideoFormats()
+ {
+ return ['mp4', 'webm', 'ogv', 'mov', 'wmv'];
+ }
+
+ /**
+ * @param string $file
* @return string
*/
public function getMigratedPath($file)
{
- return $this->autoUploadConfiguration->isActive() ? sprintf('%s/%s', DirectoryList::MEDIA, $file) : $file;
+ return preg_match("#^" . preg_quote(DirectoryList::MEDIA . DIRECTORY_SEPARATOR, '/') . "#i", $file) ? $file : sprintf('%s/%s', DirectoryList::MEDIA, $file);
}
/**
@@ -183,7 +421,7 @@ public function getMigratedPath($file)
*/
public function getDefaultGravity()
{
- return (string) $this->configReader->getValue(self::CONFIG_DEFAULT_GRAVITY);
+ return (string) $this->configReader->getValue(self::CONFIG_PATH_DEFAULT_GRAVITY);
}
/**
@@ -191,7 +429,15 @@ public function getDefaultGravity()
*/
public function getFetchFormat()
{
- return $this->configReader->isSetFlag(self::CONFIG_DEFAULT_FETCH_FORMAT) ? FetchFormat::FETCH_FORMAT_AUTO : '';
+ return $this->configReader->isSetFlag(self::CONFIG_PATH_DEFAULT_FETCH_FORMAT) ? FetchFormat::FETCH_FORMAT_AUTO : '';
+ }
+
+ /**
+ * @return string
+ */
+ public function getCloudinaryDefaultImage()
+ {
+ return (string) $this->configReader->getValue(self::CONFIG_PATH_DEFAULT_IMAGE);
}
/**
@@ -199,7 +445,7 @@ public function getFetchFormat()
*/
public function getImageQuality()
{
- return $this->configReader->getValue(self::CONFIG_DEFAULT_QUALITY);
+ return (string) $this->configReader->getValue(self::CONFIG_PATH_DEFAULT_QUALITY);
}
/**
@@ -207,7 +453,7 @@ public function getImageQuality()
*/
public function getImageDpr()
{
- return $this->configReader->getValue(self::CONFIG_DEFAULT_DPR);
+ return $this->configReader->getValue(self::CONFIG_PATH_DEFAULT_DPR);
}
/**
@@ -215,25 +461,437 @@ public function getImageDpr()
*/
public function hasEnvironmentVariable()
{
- return (bool)$this->configReader->getValue(self::CONFIG_PATH_ENVIRONMENT_VARIABLE);
+ return $this->coreRegistry->registry(self::CONFIG_PATH_ENVIRONMENT_VARIABLE) ?: (bool)$this->configReader->getValue(self::CONFIG_PATH_ENVIRONMENT_VARIABLE);
}
/**
- * @return CloudinaryEnvironmentVariable
+ * @return ManagerInterface
*/
- private function getEnvironmentVariable()
+ public function getEnvironmentVariable()
{
if (is_null($this->environmentVariable)) {
try {
- $this->environmentVariable = CloudinaryEnvironmentVariable::fromString(
- $this->decryptor->decrypt(
- $this->configReader->getValue(self::CONFIG_PATH_ENVIRONMENT_VARIABLE)
- )
- );
+ $field = $this->coreRegistry->registry(self::CONFIG_PATH_ENVIRONMENT_VARIABLE);
+ $value = $field ?: $this->decryptor->decrypt($this->configReader->getValue(self::CONFIG_PATH_ENVIRONMENT_VARIABLE));
+ if (!$value) {
+ $this->messageManager->addError('Invalid Cloudinary credentails. please check your credentials and try again.');
+ }
+ $this->environmentVariable = CloudinaryEnvironmentVariable::fromString($value);
} catch (InvalidCredentials $invalidConfigException) {
$this->logger->critical($invalidConfigException);
}
}
+
return $this->environmentVariable;
}
+
+ /**
+ * @return bool
+ */
+ public function isEnabledProductGallery()
+ {
+ return (bool) $this->configReader->getValue(self::CONFIG_PATH_PG_ENABLED);
+ }
+
+ /**
+ * @return array
+ */
+ public function getProductGalleryAll()
+ {
+ return (array) $this->configReader->getValue(self::CONFIG_PATH_PG_ALL);
+ }
+
+
+ public function getAllVideoSettings()
+ {
+ return (array) $this->configReader->getValue(self::CONFIG_PATH_CLD_VIDEO_SETTINGS_ALL);
+ }
+
+ public function isLoadSwatchesFromCloudinary()
+ {
+ return (bool) $this->configReader->getValue(self::CONFIG_PATH_LOAD_SWATCHES_FROM_CLOUDINARY);
+ }
+
+ public function isEnabledCachePlaceholder()
+ {
+ return (bool) $this->configReader->getValue(self::CONFIG_PATH_ENABLE_CACHE_PLACEHOLDER);
+ }
+
+ public function getCustomPlaceholderPath(): ?string
+ {
+ $fileName = $this->configReader->getValue(self::CONFIG_PATH_CUSTOM_PLACEHOLDER_IMAGE);
+
+ if (!$fileName) {
+ return null;
+ }
+
+ $mediaDirectory = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA);
+ $filePath = 'cloudinary_placeholder/' . ltrim($fileName, '/');
+
+ if ($mediaDirectory->isExist($filePath)) {
+ return $mediaDirectory->getAbsolutePath($filePath);
+ }
+
+ return null;
+ }
+
+ public function isEnabledLazyload()
+ {
+ return (bool) $this->configReader->getValue(self::XML_PATH_LAZYLOAD_ENABLED);
+ }
+
+ public function isLazyloadAutoReplaceCmsBlocks()
+ {
+ return (bool) $this->configReader->getValue(self::XML_PATH_LAZYLOAD_AUTO_REPLACE_CMS_BLOCKS);
+ }
+
+ /**
+ * @return array
+ */
+ public function getLazyloadIgnoredCmsBlocksArray()
+ {
+ $value = ($this->configReader->getValue(self::XML_PATH_LAZYLOAD_IGNORED_CMS_BLOCKS))
+ ? (array) explode(',', $this->configReader->getValue(self::XML_PATH_LAZYLOAD_IGNORED_CMS_BLOCKS))
+ : [];
+
+ return $value;
+ }
+
+ public function getLazyloadThreshold()
+ {
+ return (int) $this->configReader->getValue(self::XML_PATH_LAZYLOAD_THRESHOLD);
+ }
+
+ public function getLazyloadEffect()
+ {
+ return (string) $this->configReader->getValue(self::XML_PATH_LAZYLOAD_EFFECT);
+ }
+
+ public function getLazyloadPlaceholder()
+ {
+ return (string) $this->configReader->getValue(self::XML_PATH_LAZYLOAD_PLACEHOLDER);
+ }
+
+ /**
+ * @return Freeform
+ */
+ public function getLazyloadPlaceholderFreeform($placeholderType = null)
+ {
+ $placeholderType = $placeholderType ?: $this->getLazyloadPlaceholder();
+ switch ($placeholderType) {
+ case 'pixelate':
+ $freeTransform = 'q_1,e_pixelate';
+ break;
+
+ case 'predominant-color':
+ $freeTransform = '$currWidth_w,$currHeight_h/w_iw_div_2,ar_1,c_pad,b_auto/c_crop,w_10,h_10,g_north_east/w_$currWidth,h_$currHeight,c_fill/q_1';
+ break;
+
+ case 'vectorize':
+ $freeTransform = 'q_1,e_vectorize:3:0.1';
+ break;
+
+ case 'blur':
+ default:
+ $freeTransform = 'q_1,e_blur:2000';
+ break;
+ }
+ return Freeform::fromString($freeTransform);
+ //return Transformation::builder()->withFreeform(Freeform::fromString($freeTransform));
+ }
+
+ /**
+ * @return bool
+ */
+ public function getRemoveVersionNumber()
+ {
+ return (bool) $this->configReader->getValue(self::CONFIG_PATH_REMOVE_VERSION_NUMBER);
+ }
+
+ /**
+ * @return bool
+ */
+ public function getUseRootPath()
+ {
+ return (bool) $this->configReader->getValue(self::CONFIG_PATH_USE_ROOT_PATH);
+ }
+
+ /**
+ * @return bool
+ */
+ public function getUseSignedUrls()
+ {
+ return (bool) $this->configReader->getValue(self::CONFIG_PATH_USE_SIGNED_URLS);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEnabledLocalMapping()
+ {
+ return (bool) $this->configReader->getValue(self::CONFIG_PATH_ENABLE_LOCAL_MAPPING);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEnabledProductFreeTransformations()
+ {
+ return (bool) $this->configReader->getValue(self::CONFIG_PATH_ENABLE_PRODUCT_FREE_TRANSFORMATIONS);
+ }
+
+ /**
+ * @return bool
+ */
+ public function getScheduledVideoDataImportLimit()
+ {
+ return (int) $this->configReader->getValue(self::CONFIG_PATH_SCHEDULED_VIDEO_DATA_IMPORT_LIMIT);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEnabledProductgalleryApiQueue()
+ {
+ return (bool) $this->configReader->getValue(self::CONFIG_PATH_PG_API_QUEUE_ENABLED);
+ }
+
+ /**
+ * @return bool
+ */
+ public function getProductgalleryApiQueueLimit()
+ {
+ $return = (int) $this->configReader->getValue(self::CONFIG_PATH_PG_API_QUEUE_LIMIT);
+ if ($return < 0) {
+ return 0;
+ }
+ return $return;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getProductgalleryApiQueueMaxTryouts()
+ {
+ $return = (int) $this->configReader->getValue(self::CONFIG_PATH_PG_API_QUEUE_MAX_TRYOUTS);
+ if ($return > 20) {
+ return 20;
+ }
+ if ($return < 1) {
+ return 5;
+ }
+ return $return;
+ }
+
+ /**
+ * @method getMediaBaseUrl
+ * @return string
+ */
+ public function getMediaBaseUrl()
+ {
+ return $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA);
+ }
+
+ public function getModuleVersion()
+ {
+ return $this->moduleList->getOne(self::MODULE_NAME)['setup_version'];
+ }
+
+ public function getMagentoPlatformName()
+ {
+ return $this->productMetadata->getName();
+ }
+
+ public function getMagentoPlatformEdition()
+ {
+ return $this->productMetadata->getEdition();
+ }
+
+ public function getMagentoPlatformVersion()
+ {
+ return $this->productMetadata->getVersion();
+ }
+
+ public function isEnabledCldVideo()
+ {
+ return $this->configReader->getValue(self::CONFIG_PATH_CLD_VIDEO_ENABLED);
+ }
+
+ public function getAbrMode()
+ {
+ return $this->configReader->getValue(self::CONFIG_PATH_CLD_VIDEO_PLAYER_USE_ABR);
+ }
+
+ public function isCldVideoLoop()
+ {
+ return (bool) $this->configReader->getValue(self::CONFIG_PATH_CLD_VIDEO_PLAYER_LOOP);
+ }
+
+ public function showCldVideoControls()
+ {
+ return (bool) $this->configReader->getValue(self::CONFIG_PATH_CLD_VIDEO_PLAYER_SHOW_CONTROLS);
+ }
+
+ public function getCldVideoAutoplayMode()
+ {
+ return $this->configReader->getValue(self::CONFIG_PATH_CLD_VIDEO_PLAYER_AUTOPLAY);
+ }
+
+ public function mediaRelativePath($filepath)
+ {
+ $pubPath = DirectoryList::getPath(DirectoryList::PUB) . DIRECTORY_SEPARATOR;
+ return (strpos($filepath, $pubPath) === 0) ? str_replace($pubPath, '', $filepath) : $filepath;
+ }
+
+ /**
+ * Parse Cloudinary URL
+ * @method parseCloudinaryUrl
+ * @param string $url
+ * @param string|null $publicId
+ * @return array
+ */
+ public function parseCloudinaryUrl($url, $publicId = null)
+ {
+ $parsedUrlParts = $this->mbParseUrl($url);
+ $url = preg_replace('/\?.*/', '', $url);
+
+ $parsed = [
+ "orig_url" => $url,
+ "scheme" => isset($parsedUrlParts["scheme"]) ? $parsedUrlParts["scheme"] : null,
+ "host" => isset($parsedUrlParts["host"]) ? $parsedUrlParts["host"] : null,
+ "path" => isset($parsedUrlParts["path"]) ? $parsedUrlParts["path"] : null,
+ "query" => isset($parsedUrlParts["query"]) ? $parsedUrlParts["query"] : null,
+ "extension" => \pathinfo($url, PATHINFO_EXTENSION),
+ "type" => null,
+ "cloudName" => null,
+ "version" => null,
+ "publicId" => ltrim((string) $publicId, '/') ?: null,
+ "transformations_string" => null,
+ "transformations" => [],
+ "transformationless_url" => $url,
+ "versionless_url" => $url,
+ "versionless_transformationless_url" => $url,
+ "thumbnail_url" => null,
+ ];
+
+ $_url = ltrim($parsed["path"], '/');
+ $_url = preg_replace('/\.[^.]+$/', '', $_url);
+
+ preg_match('/\/v[0-9]{1,10}\//', $_url, $version);
+ if ($version && isset($version[0])) {
+ $parsed["version"] = trim($version[0], '/');
+ }
+
+ if (!$parsed["publicId"] && $parsed["version"]) {
+ $parsed["publicId"] = preg_replace('/.+\/v[0-9]{1,10}\//', '', $_url);
+ }
+
+ $_url = preg_replace('/(\/|\/v[0-9]{1,10}\/)' . \preg_quote((string) $parsed["publicId"], '/') . '$/', '', $_url);
+ $_url = explode('/', $_url);
+
+ $slug = \array_shift($_url);
+ if (\in_array($slug, ["image","video"])) {
+ $parsed["type"] = $slug;
+ } else {
+ $parsed["cloudName"] = $slug;
+ }
+
+ $slug = \array_shift($_url);
+ $parsed["type"] = ($parsed["cloudName"] && $slug === "video") ? "video" : "image";
+
+ if (isset($parsed['extension'])) {
+ $parsed['type'] = (in_array($parsed['extension'], $this->getSupportedVideoFormats())) ? 'video' : 'image';
+ }
+
+ $slug = \array_shift($_url);
+ $parsed["transformations_string"] = ($slug === 'upload' ? '' : $slug) . implode('/', $_url);
+
+ if ($parsed["transformations_string"]) {
+ $parsed["transformations"] = explode(',', \str_replace('/', ',', $parsed["transformations_string"]));
+ $parsed["transformationless_url"] = preg_replace('/\/' . \preg_quote($parsed["transformations_string"], '/') . '\//', '/', $url, 1);
+ }
+
+ $parsed["versionless_url"] = preg_replace('/\/v[0-9]{1,10}\//', '/', $url, 1);
+ $parsed["versionless_transformationless_url"] = preg_replace('/\/v[0-9]{1,10}\//', '/', $parsed["transformationless_url"], 1);
+
+ if ($parsed["type"] === "video") {
+ $parsed["thumbnail_url"] = preg_replace('/\.[^.]+$/', '', $url);
+ $parsed["thumbnail_url"] = preg_replace('/\/v[0-9]{1,10}\//', '/', $parsed["thumbnail_url"]);
+ $parsed["thumbnail_url"] = preg_replace('/\/(' . \preg_quote((string) $parsed["publicId"], '/') . ')$/', '/so_auto/$1.jpg', $parsed["thumbnail_url"]);
+ }
+
+ return $parsed;
+ }
+
+ /**
+ * UTF-8 aware parse_url() replacement.
+ *
+ * @return array
+ */
+ public function mbParseUrl($url, $component = -1)
+ {
+ $enc_url = preg_replace_callback(
+ '%[^:/@?&=#]+%usD',
+ function ($matches) {
+ return rawurlencode($matches[0]);
+ },
+ $url
+ );
+ $parts = parse_url($enc_url, $component);
+ if ($parts === false) {
+ throw new \InvalidArgumentException('Malformed URL: ' . $url);
+ }
+ if (is_array($parts)) {
+ foreach ($parts as $name => $value) {
+ $parts[$name] = rawurldecode($value);
+ }
+ } else {
+ $parts = rawurldecode($parts);
+ }
+ return $parts;
+ }
+
+ public function generateCLDuniqid()
+ {
+ return strtolower(uniqid(self::CLD_UNIQID_PREFIX)) . '_';
+ }
+
+ public function addUniquePrefixToBasename($filename, $uniqid = null)
+ {
+ $uniqid = $uniqid ? $uniqid : $this->generateCLDuniqid();
+ return dirname($filename) . '/' . $uniqid . basename($filename);
+ }
+
+ /**
+ * Log to var/log/cloudinary_cloudinary.log
+ * @method log
+ * @param mixed $message
+ * @param array $data
+ * @return $this
+ */
+ public function log($message, $data = [], $prefix = '[Cloudinary Log] ')
+ {
+ $this->cloudinaryLogger->info($prefix . json_encode($message), $data);
+ return $this;
+ }
+
+ /**
+ * @method setRegistryEnabled
+ * @param string|null $val
+ */
+ public function setRegistryEnabled($val)
+ {
+ $this->coreRegistry->register(self::CONFIG_PATH_ENABLED, $val);
+ return $this;
+ }
+
+ /**
+ * @method setRegistryEnvVar
+ * @param bool $val
+ */
+ public function setRegistryEnvVar($val)
+ {
+ $this->coreRegistry->register(self::CONFIG_PATH_ENVIRONMENT_VARIABLE, ($val ? true : false));
+ return $this;
+ }
}
diff --git a/Model/Framework/File/Uploader.php b/Model/Framework/File/Uploader.php
new file mode 100644
index 00000000..5c433475
--- /dev/null
+++ b/Model/Framework/File/Uploader.php
@@ -0,0 +1,47 @@
+ 180) {
+ throw new \InvalidArgumentException('Filename is too long; must be 180 characters or less');
+ }
+
+ if (preg_match('/^_+$/', $fileInfo['filename'])) {
+ $fileName = 'file.' . $fileInfo['extension'];
+ }
+
+ return $fileName;
+ }
+}
diff --git a/Model/GraphQLResolver/ProductAttributeCldResolver.php b/Model/GraphQLResolver/ProductAttributeCldResolver.php
new file mode 100644
index 00000000..95974b06
--- /dev/null
+++ b/Model/GraphQLResolver/ProductAttributeCldResolver.php
@@ -0,0 +1,99 @@
+productGalleryManagement = $productGalleryManagement;
+ $this->productRepository = $productRepository;
+ $this->urlBuilder = $urlBuilder;
+ $this->storeManager = $storeManager;
+ $this->_configuration = $configuration;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value = null, ?array $args = null)
+ {
+ $sku = $value['sku'] ?? null;
+ if ($sku) {
+ $this->_sku = $sku;
+ }
+
+ $attributeCodes = $args['attribute_codes'] ?? null;
+ if ($attributeCodes && is_array($attributeCodes)) {
+ $this->checkEnabled();
+ $product = $this->productRepository->get($this->_sku);
+ $mediaAttributes = [];
+ foreach ($attributeCodes as $attributeCode) {
+ $attrValue = $product->getData($attributeCode);
+ if ($attrValue) {
+ foreach ($product->getMediaGalleryImages() as $gallItem) {
+ if ($attrValue == $gallItem->getFile()) {
+ $mediaAttributes[] = [
+ 'attribute_code' => $attributeCode,
+ 'url' => $gallItem->getUrl()
+ ];
+ }
+ }
+
+ }
+ }
+ return $mediaAttributes;
+ }
+ $productMediaStr = $this->productGalleryManagement->getProductMedia($this->_sku);
+ $jsonDecoder = new \Magento\Framework\Serialize\Serializer\Json();
+ $productMedia = $jsonDecoder->unserialize($productMediaStr);
+
+ return $productMedia['data'];
+ }
+
+
+ private function checkEnabled() {
+ if (!$this->_configuration->isEnabled()) {
+ throw new LocalizedException(
+ __("Cloudinary module is disabled. Please enable it first in order to use this API.")
+ );
+ }
+ return $this;
+ }
+ }
diff --git a/Model/ImageRepository.php b/Model/ImageRepository.php
index 999ca17f..48ec2bdc 100644
--- a/Model/ImageRepository.php
+++ b/Model/ImageRepository.php
@@ -10,11 +10,17 @@
/**
* Class ImageRepository
+ *
* @package Cloudinary\Cloudinary\Model
*/
class ImageRepository
{
- private $allowedImgExtensions = ['JPG', 'PNG', 'GIF', 'BMP', 'TIFF', 'EPS', 'PSD', 'SVG', 'WebP'];
+ private $allowedImgExtensions = ['JPG', 'JPEG', 'PNG', 'GIF', 'BMP', 'TIFF', 'EPS', 'PSD', 'SVG', 'WebP'];
+
+ /**
+ * @var Filesystem
+ */
+ private $filesystem;
/**
* @var ReadInterface
@@ -27,11 +33,11 @@ class ImageRepository
private $synchronizationChecker;
/**
- * @param Filesystem $filesystem
+ * @param Filesystem $filesystem
*/
public function __construct(Filesystem $filesystem, SynchronizationCheck $synchronizationChecker)
{
- $this->mediaDirectory = $filesystem->getDirectoryRead(DirectoryList::MEDIA);
+ $this->filesystem = $filesystem;
$this->synchronizationChecker = $synchronizationChecker;
}
@@ -40,11 +46,22 @@ public function __construct(Filesystem $filesystem, SynchronizationCheck $synchr
*/
public function findUnsynchronisedImages()
{
+ $this->mediaDirectory = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA);
+ if ($this->mediaDirectory->getAbsolutePath() !== ($mediaRealPath = realpath($this->mediaDirectory->getAbsolutePath()))) {
+ $this->mediaDirectory = $this->filesystem->getDirectoryReadByPath($mediaRealPath);
+ }
+
$images = [];
foreach ($this->getRecursiveIterator($this->mediaDirectory->getAbsolutePath()) as $item) {
$absolutePath = $item->getRealPath();
- $relativePath = $this->mediaDirectory->getRelativePath($item->getRealPath());
+ if (strpos(basename($absolutePath), '.') === 0) {
+ continue;
+ }
+ $relativePath = $this->mediaDirectory->getRelativePath($absolutePath);
+ if (!preg_match("#^" . preg_quote(DirectoryList::MEDIA . DIRECTORY_SEPARATOR, '/') . "#i", $relativePath)) {
+ $relativePath = DirectoryList::MEDIA . DIRECTORY_SEPARATOR . $relativePath;
+ }
if ($this->isValidImageFile($item) && !$this->synchronizationChecker->isSynchronized($relativePath)) {
$images[] = Image::fromPath($absolutePath, $relativePath);
}
@@ -54,19 +71,19 @@ public function findUnsynchronisedImages()
}
/**
- * @param $directory
+ * @param $directory
* @return \RecursiveIteratorIterator
*/
private function getRecursiveIterator($directory)
{
return new \RecursiveIteratorIterator(
- new \RecursiveDirectoryIterator($directory),
+ new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::SELF_FIRST
);
}
/**
- * @param $item
+ * @param $item
* @return bool
*/
private function isValidImageFile($item)
diff --git a/Model/Logger.php b/Model/Logger.php
new file mode 100644
index 00000000..26ad6387
--- /dev/null
+++ b/Model/Logger.php
@@ -0,0 +1,6 @@
+logger = $logger;
}
-
/**
* @param OutputInterface $output
*/
@@ -34,7 +29,6 @@ public function setOutput(OutputInterface $output)
{
$this->output = $output;
}
-
/**
* @return OutputInterface
*/
@@ -43,10 +37,8 @@ private function getOutput()
if (!$this->output) {
throw new \RuntimeException('Undefined output source on OutputLogger');
}
-
return $this->output;
}
-
/**
* @param string|array $messages The message as an array of lines or a single string
*/
@@ -60,7 +52,6 @@ public function log($messages)
$this->logger->notice($messages);
}
}
-
/**
* Writes a message to the output.
*
@@ -73,7 +64,6 @@ public function write($messages, $newline = false, $options = 0)
$this->getOutput()->write($messages, $newline, $options);
$this->log($messages);
}
-
/**
* Writes a message to the output and adds a newline at the end.
*
@@ -85,7 +75,6 @@ public function writeln($messages, $options = 0)
$this->getOutput()->writeln($messages, $options);
$this->log($messages);
}
-
/**
* Sets the verbosity of the output.
*
@@ -95,57 +84,51 @@ public function setVerbosity($level)
{
$this->getOutput()->setVerbosity($level);
}
-
/**
* Gets the current verbosity of the output.
*
* @return int The current level of verbosity (one of the VERBOSITY constants)
*/
- public function getVerbosity()
+ public function getVerbosity(): int
{
return $this->getOutput()->getVerbosity();
}
-
/**
* Returns whether verbosity is quiet (-q).
*
* @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise
*/
- public function isQuiet()
+ public function isQuiet(): bool
{
return $this->getOutput()->isQuiet();
}
-
/**
* Returns whether verbosity is verbose (-v).
*
* @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise
*/
- public function isVerbose()
+ public function isVerbose(): bool
{
return $this->getOutput()->isVerbose();
}
-
/**
* Returns whether verbosity is very verbose (-vv).
*
* @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise
*/
- public function isVeryVerbose()
+ public function isVeryVerbose(): bool
{
return $this->getOutput()->isVeryVerbose();
}
-
/**
* Returns whether verbosity is debug (-vvv).
*
* @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise
*/
- public function isDebug()
+ public function isDebug(): bool
{
return $this->getOutput()->isDebug();
}
-
/**
* Sets the decorated flag.
*
@@ -155,17 +138,15 @@ public function setDecorated($decorated)
{
$this->getOutput()->setDecorated($decorated);
}
-
/**
* Gets the decorated flag.
*
* @return bool true if the output will decorate messages, false otherwise
*/
- public function isDecorated()
+ public function isDecorated(): bool
{
return $this->getOutput()->isDecorated();
}
-
/**
* Sets output formatter.
*
@@ -175,13 +156,12 @@ public function setFormatter(OutputFormatterInterface $formatter)
{
$this->getOutput()->setFormatter($formatter);
}
-
/**
* Returns current output formatter instance.
*
* @return OutputFormatterInterface
*/
- public function getFormatter()
+ public function getFormatter(): OutputFormatterInterface
{
return $this->getOutput()->getFormatter();
}
diff --git a/Model/MediaLibraryMap.php b/Model/MediaLibraryMap.php
new file mode 100644
index 00000000..f129c224
--- /dev/null
+++ b/Model/MediaLibraryMap.php
@@ -0,0 +1,11 @@
+_init(\Cloudinary\Cloudinary\Model\ResourceModel\MediaLibraryMap::class);
+ }
+}
diff --git a/Model/Observer/CatalogProductImportBunchSaveAfter.php b/Model/Observer/CatalogProductImportBunchSaveAfter.php
new file mode 100644
index 00000000..75f40426
--- /dev/null
+++ b/Model/Observer/CatalogProductImportBunchSaveAfter.php
@@ -0,0 +1,107 @@
+requestProcessor = $requestProcessor;
+ $this->messageManager = $messageManager;
+ $this->configuration = $configuration;
+ $this->cacheTypeList = $cacheTypeList;
+ $this->appConfig = $config;
+ }
+
+ /**
+ * @param Observer $observer
+ */
+ public function execute(Observer $observer)
+ {
+ //Clear config cache if needed
+ $this->changedPaths = (array) $observer->getEvent()->getChangedPaths();
+ if (count(
+ array_intersect(
+ $this->changedPaths,
+ [
+ \Cloudinary\Cloudinary\Model\Configuration::CONFIG_PATH_ENABLED,
+ \Cloudinary\Cloudinary\Model\Configuration::CONFIG_PATH_ENVIRONMENT_VARIABLE,
+ \Cloudinary\Cloudinary\Model\AutoUploadMapping\AutoUploadConfiguration::REQUEST_PATH
+ ]
+ )
+ ) > 0
+ ) {
+ $this->cleanConfigCache();
+ $this->appConfig->reinit();
+ }
+
+ if (!$this->configuration->isEnabled()) {
+ return $this;
+ }
+
+ if (!$this->requestProcessor->handle(DirectoryList::MEDIA, $this->configuration->getMediaBaseUrl(), true)) {
+ $this->messageManager->addErrorMessage(self::AUTO_UPLOAD_SETUP_FAIL_MESSAGE);
+ }
+ }
+
+ protected function cleanConfigCache()
+ {
+ try {
+ $this->cacheTypeList->cleanType(\Magento\Framework\App\Cache\Type\Config::TYPE_IDENTIFIER);
+ } catch (\Exception $e) {
+ $this->messageManager->addNoticeMessage(__('For some reason, Cloudinary couldn\'t clear your config cache, please clear the cache manually. (Exception message: %1)', $e->getMessage()));
+ }
+ return $this;
+ }
+}
diff --git a/Model/Observer/Configuration.php b/Model/Observer/Configuration.php
deleted file mode 100644
index b0a72746..00000000
--- a/Model/Observer/Configuration.php
+++ /dev/null
@@ -1,64 +0,0 @@
-requestProcessor = $requestProcessor;
- $this->messageManager = $messageManager;
- }
-
- /**
- * @param Observer $observer
- */
- public function execute(Observer $observer)
- {
- if (!$this->requestProcessor->handle('media', $this->getMediaBaseUrl())) {
- $this->messageManager->addErrorMessage(self::AUTO_UPLOAD_SETUP_FAIL_MESSAGE);
- }
- }
-
- /**
- * @return string
- */
- function getMediaBaseUrl() {
- /** @var \Magento\Framework\ObjectManagerInterface $om */
- $om = ObjectManager::getInstance();
-
- /** @var \Magento\Store\Model\StoreManagerInterface $storeManager */
- $storeManager = $om->get('Magento\Store\Model\StoreManagerInterface');
-
- /** @var \Magento\Store\Api\Data\StoreInterface|\Magento\Store\Model\Store $currentStore */
- $currentStore = $storeManager->getStore();
-
- return $currentStore->getBaseUrl(UrlInterface::URL_TYPE_MEDIA);
- }
-}
diff --git a/Model/Observer/DeleteProductImage.php b/Model/Observer/DeleteProductImage.php
index 83afd0c6..25808968 100644
--- a/Model/Observer/DeleteProductImage.php
+++ b/Model/Observer/DeleteProductImage.php
@@ -20,7 +20,7 @@ class DeleteProductImage implements ObserverInterface
private $cloudinaryImageManager;
/**
- * @param ProductImageFinder $productImageFinder
+ * @param ProductImageFinder $productImageFinder
* @param CloudinaryImageManager $cloudinaryImageManager
*/
public function __construct(
@@ -32,7 +32,7 @@ public function __construct(
}
/**
- * @param Observer $observer
+ * @param Observer $observer
*/
public function execute(Observer $observer)
{
diff --git a/Model/Observer/ProductGalleryChangeTemplate.php b/Model/Observer/ProductGalleryChangeTemplate.php
new file mode 100644
index 00000000..c3245527
--- /dev/null
+++ b/Model/Observer/ProductGalleryChangeTemplate.php
@@ -0,0 +1,42 @@
+configuration = $configuration;
+ }
+
+ /**
+ * @param mixed $observer
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ * @return void
+ */
+ public function execute(\Magento\Framework\Event\Observer $observer)
+ {
+ if (!$this->configuration->isEnabled()) {
+ return $this;
+ }
+ $observer->getBlock()->setTemplate('Cloudinary_Cloudinary::catalog/product/helper/gallery.phtml');
+ }
+}
diff --git a/Model/Observer/SaveProductTransform.php b/Model/Observer/SaveProductTransform.php
index 496ee79e..18033da4 100644
--- a/Model/Observer/SaveProductTransform.php
+++ b/Model/Observer/SaveProductTransform.php
@@ -4,6 +4,7 @@
use Cloudinary\Cloudinary\Helper\Product\Free as Helper;
use Cloudinary\Cloudinary\Model\TransformationFactory;
+use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
@@ -20,17 +21,28 @@ class SaveProductTransform implements ObserverInterface
private $transformationFactory;
/**
- * @param Helper $helper
- * @param TransformationFactory $transformationFactory
+ * @var ResourceConnection
*/
- public function __construct(Helper $helper, TransformationFactory $transformationFactory)
- {
+ private $resourceConnection;
+
+ /**
+ * @method __construct
+ * @param Helper $helper
+ * @param TransformationFactory $transformationFactor
+ * @param ResourceConnection $resourceConnection
+ */
+ public function __construct(
+ Helper $helper,
+ TransformationFactory $transformationFactory,
+ ResourceConnection $resourceConnection
+ ) {
$this->helper = $helper;
$this->transformationFactory = $transformationFactory;
+ $this->resourceConnection = $resourceConnection;
}
/**
- * @param Observer $observer
+ * @param Observer $observer
*/
public function execute(Observer $observer)
{
@@ -42,9 +54,22 @@ public function execute(Observer $observer)
$product->getCloudinaryFreeTransformChanges()
);
+ foreach ($mediaGalleryImages as $gallItemId => $gallItem) {
+ if (isset($gallItem['cldspinset']) && $gallItem['media_type'] === 'image') {
+ $this->resourceConnection->getConnection()
+ ->insertOnDuplicate($this->resourceConnection->getTableName('cloudinary_product_spinset_map'), [
+ 'image_name' => $gallItem['file'],
+ 'cldspinset' => $gallItem['cldspinset']
+ ], ['image_name', 'cldspinset']);
+ }
+ }
foreach ($changedTransforms as $id => $transform) {
$this->storeFreeTransformation($this->helper->getImageNameForId($id, $mediaGalleryImages), $transform);
}
+
+ foreach ($changedTransforms as $id => $transform) {
+ $this->helper->validate($this->helper->getImageNameForId($id, $mediaGalleryImages), $transform);
+ }
}
/**
diff --git a/Model/Observer/UploadProductImage.php b/Model/Observer/UploadProductImage.php
index fb92612b..103bc8fd 100644
--- a/Model/Observer/UploadProductImage.php
+++ b/Model/Observer/UploadProductImage.php
@@ -20,7 +20,7 @@ class UploadProductImage implements ObserverInterface
private $cloudinaryImageManager;
/**
- * @param ProductImageFinder $productImageFinder
+ * @param ProductImageFinder $productImageFinder
* @param CloudinaryImageManager $cloudinaryImageManager
*/
public function __construct(
@@ -32,14 +32,32 @@ public function __construct(
}
/**
- * @param Observer $observer
+ * @param Observer $observer
*/
public function execute(Observer $observer)
{
$product = $observer->getEvent()->getProduct();
foreach ($this->productImageFinder->findNewImages($product) as $image) {
- $this->cloudinaryImageManager->uploadAndSynchronise($image);
+ if (!$this->isCloudinaryImage($image)) {
+ $this->cloudinaryImageManager->uploadAndSynchronise($image);
+ }
}
}
+ /**
+ * Check if image sourced from Cloudinary media
+ * @param $image
+ * return bool
+ */
+ protected function isCloudinaryImage($image)
+ {
+ $file = $image->getRelativePath();
+
+ // Skip if it's our known placeholder
+ if (strpos($file, 'cloudinary_placeholder.jpg') !== false) {
+ return true;
+ }
+
+ return strpos($file, 'c/l/cld_') !== false;
+ }
}
diff --git a/Model/ProductGalleryApiQueue.php b/Model/ProductGalleryApiQueue.php
new file mode 100644
index 00000000..ec9f4d8a
--- /dev/null
+++ b/Model/ProductGalleryApiQueue.php
@@ -0,0 +1,11 @@
+_init(\Cloudinary\Cloudinary\Model\ResourceModel\ProductGalleryApiQueue::class);
+ }
+}
diff --git a/Model/ProductImageFinder.php b/Model/ProductImageFinder.php
index fda8cba0..b6d3eb7c 100644
--- a/Model/ProductImageFinder.php
+++ b/Model/ProductImageFinder.php
@@ -10,6 +10,7 @@
/**
* Class ProductImageFinder
+ *
* @package Cloudinary\Cloudinary\Model
*/
class ProductImageFinder
@@ -48,16 +49,19 @@ public function findDeletedImages(Product $product)
}
/**
- * @param Product $product
+ * @param Product $product
* @param ImageFilter $filter
*
* @return \Cloudinary\Cloudinary\Core\Image[]
*/
private function find(Product $product, ImageFilter $filter)
{
- return array_map($this->imageCreator, array_filter(
- $product->getMediaGallery('images') ?: [],
- $filter
- ));
+ return array_map(
+ $this->imageCreator,
+ array_filter(
+ $product->getMediaGallery('images') ?: [],
+ $filter
+ )
+ );
}
}
diff --git a/Model/ProductImageFinder/DeletedImageFilter.php b/Model/ProductImageFinder/DeletedImageFilter.php
index 32e8f0c0..ac8193ea 100644
--- a/Model/ProductImageFinder/DeletedImageFilter.php
+++ b/Model/ProductImageFinder/DeletedImageFilter.php
@@ -12,4 +12,4 @@ public function __invoke($imageData)
{
return isset($imageData['removed']) && $imageData['removed'] == 1;
}
-}
\ No newline at end of file
+}
diff --git a/Model/ProductImageFinder/ImageCreator.php b/Model/ProductImageFinder/ImageCreator.php
index b3eef664..72f78eaf 100644
--- a/Model/ProductImageFinder/ImageCreator.php
+++ b/Model/ProductImageFinder/ImageCreator.php
@@ -10,6 +10,7 @@
/**
* Class ImageCreator
+ *
* @package Cloudinary\Cloudinary\Model\ProductImageFinder
*/
class ImageCreator
@@ -27,7 +28,7 @@ class ImageCreator
/**
* ImageCreator constructor.
*
- * @param Filesystem $filesystem
+ * @param Filesystem $filesystem
* @param MediaConfig $mediaConfig
*/
public function __construct(Filesystem $filesystem, MediaConfig $mediaConfig)
@@ -52,4 +53,3 @@ public function __invoke(array $imageData)
);
}
}
-
diff --git a/Model/ProductImageFinder/ImageFilter.php b/Model/ProductImageFinder/ImageFilter.php
index 48670a6a..99080201 100644
--- a/Model/ProductImageFinder/ImageFilter.php
+++ b/Model/ProductImageFinder/ImageFilter.php
@@ -4,13 +4,14 @@
/**
* Interface ImageFilter
+ *
* @package Cloudinary\Cloudinary\Model\ProductImageFinder
*/
interface ImageFilter
{
/**
- * @param $imageData
+ * @param $imageData
* @return boolean
*/
public function __invoke($imageData);
-}
\ No newline at end of file
+}
diff --git a/Model/ProductImageFinder/NewImageFilter.php b/Model/ProductImageFinder/NewImageFilter.php
index 14be07dd..f07db2a7 100644
--- a/Model/ProductImageFinder/NewImageFilter.php
+++ b/Model/ProductImageFinder/NewImageFilter.php
@@ -3,16 +3,17 @@
/**
* Class NewImageFinder
+ *
* @package Cloudinary\Cloudinary\Model\ProductImageFinder
*/
class NewImageFilter implements ImageFilter
{
/**
- * @param $imageData
+ * @param $imageData
* @return bool
*/
public function __invoke($imageData)
{
return !empty($imageData['new_file']);
}
-}
\ No newline at end of file
+}
diff --git a/Model/ProductSpinsetMap.php b/Model/ProductSpinsetMap.php
new file mode 100644
index 00000000..439dd942
--- /dev/null
+++ b/Model/ProductSpinsetMap.php
@@ -0,0 +1,11 @@
+_init(\Cloudinary\Cloudinary\Model\ResourceModel\ProductSpinsetMap::class);
+ }
+}
diff --git a/Model/ProductVideo.php b/Model/ProductVideo.php
new file mode 100644
index 00000000..d73e7171
--- /dev/null
+++ b/Model/ProductVideo.php
@@ -0,0 +1,11 @@
+_init(\Cloudinary\Cloudinary\Model\ResourceModel\ProductVideo::class);
+ }
+}
diff --git a/Model/ResourceModel/MediaLibraryMap.php b/Model/ResourceModel/MediaLibraryMap.php
new file mode 100644
index 00000000..bdfdc7c7
--- /dev/null
+++ b/Model/ResourceModel/MediaLibraryMap.php
@@ -0,0 +1,16 @@
+_init('cloudinary_media_library_map', 'id');
+ }
+}
diff --git a/Model/ResourceModel/MediaLibraryMap/Collection.php b/Model/ResourceModel/MediaLibraryMap/Collection.php
new file mode 100644
index 00000000..9838de6a
--- /dev/null
+++ b/Model/ResourceModel/MediaLibraryMap/Collection.php
@@ -0,0 +1,17 @@
+_init(
+ \Cloudinary\Cloudinary\Model\MediaLibraryMap::class,
+ \Cloudinary\Cloudinary\Model\ResourceModel\MediaLibraryMap::class
+ );
+ }
+}
diff --git a/Model/ResourceModel/ProductGalleryApiQueue.php b/Model/ResourceModel/ProductGalleryApiQueue.php
new file mode 100644
index 00000000..70d3230c
--- /dev/null
+++ b/Model/ResourceModel/ProductGalleryApiQueue.php
@@ -0,0 +1,16 @@
+_init('cloudinary_product_gallery_api_queue', 'id');
+ }
+}
diff --git a/Model/ResourceModel/ProductGalleryApiQueue/Collection.php b/Model/ResourceModel/ProductGalleryApiQueue/Collection.php
new file mode 100644
index 00000000..ab916fc5
--- /dev/null
+++ b/Model/ResourceModel/ProductGalleryApiQueue/Collection.php
@@ -0,0 +1,17 @@
+_init(
+ \Cloudinary\Cloudinary\Model\ProductGalleryApiQueue::class,
+ \Cloudinary\Cloudinary\Model\ResourceModel\ProductGalleryApiQueue::class
+ );
+ }
+}
diff --git a/Model/ResourceModel/ProductSpinsetMap.php b/Model/ResourceModel/ProductSpinsetMap.php
new file mode 100644
index 00000000..d89b82d8
--- /dev/null
+++ b/Model/ResourceModel/ProductSpinsetMap.php
@@ -0,0 +1,16 @@
+_init('cloudinary_product_spinset_map', 'id');
+ }
+}
diff --git a/Model/ResourceModel/ProductSpinsetMap/Collection.php b/Model/ResourceModel/ProductSpinsetMap/Collection.php
new file mode 100644
index 00000000..81e0a297
--- /dev/null
+++ b/Model/ResourceModel/ProductSpinsetMap/Collection.php
@@ -0,0 +1,17 @@
+_init(
+ \Cloudinary\Cloudinary\Model\ProductSpinsetMap::class,
+ \Cloudinary\Cloudinary\Model\ResourceModel\ProductSpinsetMap::class
+ );
+ }
+}
diff --git a/Model/ResourceModel/ProductVideo.php b/Model/ResourceModel/ProductVideo.php
new file mode 100644
index 00000000..bd80414e
--- /dev/null
+++ b/Model/ResourceModel/ProductVideo.php
@@ -0,0 +1,15 @@
+_init(
+ \Cloudinary\Cloudinary\Model\ProductVideo::class,
+ \Cloudinary\Cloudinary\Model\ResourceModel\ProductVideo::class
+ );
+ }
+}
diff --git a/Model/SynchronisationChecker.php b/Model/SynchronisationChecker.php
index ee43f83f..7a380816 100644
--- a/Model/SynchronisationChecker.php
+++ b/Model/SynchronisationChecker.php
@@ -2,39 +2,93 @@
namespace Cloudinary\Cloudinary\Model;
-use Cloudinary\Cloudinary\Core\Image\SynchronizationCheck;
use Cloudinary\Cloudinary\Api\SynchronisationRepositoryInterface;
use Cloudinary\Cloudinary\Core\AutoUploadMapping\AutoUploadConfigurationInterface;
+use Cloudinary\Cloudinary\Core\ConfigurationInterface;
+use Cloudinary\Cloudinary\Core\Image\SynchronizationCheck;
+use Magento\Framework\Registry;
class SynchronisationChecker implements SynchronizationCheck
{
+ /**
+ * @var string
+ */
+ private $imageNameCacheKey;
+
/**
* @var SynchronisationRepositoryInterface
*/
private $synchronisationRepository;
+ /**
+ * @var ConfigurationInterface
+ */
+ private $configuration;
+
/**
* @var Configuration
*/
private $autoUploadConfiguration;
/**
- * @param SynchronisationRepositoryInterface $synchronisationRepository
- * @param AutoUploadConfigurationInterface $autoUploadConfiguration
+ * @var MediaLibraryMapFactory
+ */
+ private $mediaLibraryMapFactory;
+
+ /**
+ * @var Registry
+ */
+ private $coreRegistry;
+
+ /**
+ * @method __construct
+ * @param SynchronisationRepositoryInterface $synchronisationRepository
+ * @param ConfigurationInterface $configuration
+ * @param AutoUploadConfigurationInterface $autoUploadConfiguration
+ * @param MediaLibraryMapFactory $mediaLibraryMapFactory
+ * @param Registry $coreRegistry
*/
public function __construct(
SynchronisationRepositoryInterface $synchronisationRepository,
- AutoUploadConfigurationInterface $autoUploadConfiguration
+ ConfigurationInterface $configuration,
+ AutoUploadConfigurationInterface $autoUploadConfiguration,
+ MediaLibraryMapFactory $mediaLibraryMapFactory,
+ Registry $coreRegistry
) {
$this->synchronisationRepository = $synchronisationRepository;
+ $this->configuration = $configuration;
$this->autoUploadConfiguration = $autoUploadConfiguration;
+ $this->mediaLibraryMapFactory = $mediaLibraryMapFactory;
+ $this->coreRegistry = $coreRegistry;
}
/**
- * @param $imageName
+ * @method cacheResult
+ * @param bool $result
+ * @return mixed
+ */
+ private function cacheResult($result)
+ {
+ $this->coreRegistry->unregister($this->imageNameCacheKey);
+ $this->coreRegistry->register($this->imageNameCacheKey, $result);
+ return $result;
+ }
+
+ /**
+ * @method cacheResult
+ * @return mixed
+ */
+ private function getFromCache()
+ {
+ return $this->coreRegistry->registry($this->imageNameCacheKey);
+ }
+
+ /**
+ * @param string $imageName
+ * @param bool $refresh
* @return bool
*/
- public function isSynchronized($imageName)
+ public function isSynchronized($imageName, $refresh = false)
{
if (!$imageName) {
return false;
@@ -43,7 +97,23 @@ public function isSynchronized($imageName)
if ($this->autoUploadConfiguration->isActive()) {
return true;
}
-
- return $this->synchronisationRepository->getListByImagePath($imageName)->getTotalCount() > 0;
+
+ $this->imageNameCacheKey = 'cldsynccheckcachekey_' . (string) $imageName;
+ if (!$refresh && ($cacheResult = $this->getFromCache()) !== null) {
+ return $cacheResult;
+ }
+
+ if ($this->configuration->isEnabledLocalMapping()) {
+ //Look for a match on the mapping table:
+ preg_match('/(cld_[A-Za-z0-9]{13}_).+$/i', $imageName, $cldUniqid);
+ if ($cldUniqid && isset($cldUniqid[1])) {
+ $mapped = $this->mediaLibraryMapFactory->create()->getCollection()->addFieldToFilter("cld_uniqid", $cldUniqid[1])->setPageSize(1)->getFirstItem();
+ if ($mapped && ($origPublicId = $mapped->getCldPublicId())) {
+ return $this->cacheResult(true);
+ }
+ }
+ }
+
+ return $this->cacheResult($this->synchronisationRepository->isSynchronizedImagePath($imageName));
}
}
diff --git a/Model/SynchronisationRepository.php b/Model/SynchronisationRepository.php
index 9ddc1320..144bb53d 100644
--- a/Model/SynchronisationRepository.php
+++ b/Model/SynchronisationRepository.php
@@ -2,22 +2,20 @@
namespace Cloudinary\Cloudinary\Model;
-use Cloudinary\Cloudinary\Core\SynchroniseAssetsRepositoryInterface;
-
use Cloudinary\Cloudinary\Api\SynchronisationRepositoryInterface;
-use Cloudinary\Cloudinary\Model\SynchronisationFactory;
-use Cloudinary\Cloudinary\Model\ResourceModel\Synchronisation\CollectionFactory;
+
+use Cloudinary\Cloudinary\Core\SynchroniseAssetsRepositoryInterface;
use Cloudinary\Cloudinary\Model\ResourceModel\Synchronisation\Collection as SynchronisationCollection;
+use Cloudinary\Cloudinary\Model\ResourceModel\Synchronisation\CollectionFactory;
-use Magento\Framework\Api\AbstractSimpleObject;
use Magento\Framework\Api\FilterBuilder;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SearchResultsInterface;
use Magento\Framework\Api\SearchResultsInterfaceFactory;
+use Magento\Framework\App\ResourceConnection;
-class SynchronisationRepository
- implements SynchronisationRepositoryInterface, SynchroniseAssetsRepositoryInterface
+class SynchronisationRepository implements SynchronisationRepositoryInterface, SynchroniseAssetsRepositoryInterface
{
/**
* @var CollectionFactory
@@ -49,6 +47,11 @@ class SynchronisationRepository
*/
private $synchronisationFactory;
+ /**
+ * @var ResourceConnection
+ */
+ private $connection;
+
/**
* @param FilterBuilder $filterBuilder
* @param SearchCriteriaBuilder $searchCriteriaBuilder
@@ -63,7 +66,8 @@ public function __construct(
CollectionFactory $collectionFactory,
SearchResultsInterface $searchResult,
SearchResultsInterfaceFactory $searchResultsFactory,
- SynchronisationFactory $synchronisationFactory
+ SynchronisationFactory $synchronisationFactory,
+ ResourceConnection $resourceConnection
) {
$this->filterBuilder = $filterBuilder;
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
@@ -71,6 +75,7 @@ public function __construct(
$this->searchResult = $searchResult;
$this->searchResultsFactory = $searchResultsFactory;
$this->synchronisationFactory = $synchronisationFactory;
+ $this->connection = $resourceConnection->getConnection();
}
/**
@@ -78,7 +83,7 @@ public function __construct(
*
* @api
*
- * @param SearchCriteriaInterface $searchCriteria
+ * @param SearchCriteriaInterface $searchCriteria
* @return SearchResultsInterface
*/
public function getList(SearchCriteriaInterface $searchCriteria)
@@ -99,7 +104,10 @@ public function getList(SearchCriteriaInterface $searchCriteria)
}
/**
- * @param string $imagePath
+ * @deprecated
+ * For checking if image path is synchronized, use isSynchronizedImagePath()
+ *
+ * @param string $imagePath
*
* @return SearchResultsInterface
*/
@@ -111,7 +119,23 @@ public function getListByImagePath($imagePath)
}
/**
- * @param string $imagePath
+ * @param string $imagePath
+ *
+ * @return bool
+ */
+ public function isSynchronizedImagePath($imagePath)
+ {
+ return $this->connection->fetchAll($this->connection->select()
+ ->from(
+ $this->connection->getTableName("cloudinary_synchronisation"),
+ ['cloudinary_synchronisation_id']
+ )
+ ->where('image_path = ?', $imagePath)
+ ->limit(1)) ? true : false;
+ }
+
+ /**
+ * @param string $imagePath
*/
public function saveAsSynchronized($imagePath)
{
@@ -135,7 +159,7 @@ public function removeSynchronised($imagePath)
/**
* Create image name filter
*
- * @param string $imagePath
+ * @param string $imagePath
* @return \Magento\Framework\Api\Filter
*/
private function createImagePathFilter($imagePath)
@@ -148,8 +172,8 @@ private function createImagePathFilter($imagePath)
}
/**
- * @param SearchCriteriaInterface $searchCriteria
- * @param SynchronisationCollection $collection
+ * @param SearchCriteriaInterface $searchCriteria
+ * @param SynchronisationCollection $collection
*/
private function setFilters(SearchCriteriaInterface $searchCriteria, $collection)
{
diff --git a/Model/Template/Filter.php b/Model/Template/Filter.php
index 316c187c..8897e606 100644
--- a/Model/Template/Filter.php
+++ b/Model/Template/Filter.php
@@ -3,104 +3,17 @@
namespace Cloudinary\Cloudinary\Model\Template;
use Magento\Widget\Model\Template\Filter as WidgetFilter;
-use Cloudinary\Cloudinary\Core\Image\ImageFactory;
-use Cloudinary\Cloudinary\Core\UrlGenerator;
class Filter extends WidgetFilter
{
/**
- * @var ImageFactory
- */
- private $imageFactory;
-
- /**
- * @var UrlGenerator
- */
- private $urlGenerator;
-
- /**
- * @param \Magento\Framework\Stdlib\StringUtils $string
- * @param \Psr\Log\LoggerInterface $logger
- * @param \Magento\Framework\Escaper $escaper
- * @param \Magento\Framework\View\Asset\Repository $assetRepo
- * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
- * @param \Magento\Variable\Model\VariableFactory $coreVariableFactory
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Framework\View\LayoutInterface $layout
- * @param \Magento\Framework\View\LayoutFactory $layoutFactory
- * @param \Magento\Framework\App\State $appState
- * @param \Magento\Framework\UrlInterface $urlModel
- * @param \Pelago\Emogrifier $emogrifier
- * @param \Magento\Email\Model\Source\Variables $configVariables
- * @param \Magento\Widget\Model\ResourceModel\Widget $widgetResource
- * @param \Magento\Widget\Model\Widget $widget
- * @param ImageFactory $imageFactory
- * @param UrlGenerator $urlGenerator
- * @SuppressWarnings(PHPMD.ExcessiveParameterList)
- */
- public function __construct(
- \Magento\Framework\Stdlib\StringUtils $string,
- \Psr\Log\LoggerInterface $logger,
- \Magento\Framework\Escaper $escaper,
- \Magento\Framework\View\Asset\Repository $assetRepo,
- \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
- \Magento\Variable\Model\VariableFactory $coreVariableFactory,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Framework\View\LayoutInterface $layout,
- \Magento\Framework\View\LayoutFactory $layoutFactory,
- \Magento\Framework\App\State $appState,
- \Magento\Framework\UrlInterface $urlModel,
- \Pelago\Emogrifier $emogrifier,
- \Magento\Email\Model\Source\Variables $configVariables,
- \Magento\Widget\Model\ResourceModel\Widget $widgetResource,
- \Magento\Widget\Model\Widget $widget,
- ImageFactory $imageFactory,
- UrlGenerator $urlGenerator
- ) {
- $this->imageFactory = $imageFactory;
- $this->urlGenerator = $urlGenerator;
-
- parent::__construct(
- $string,
- $logger,
- $escaper,
- $assetRepo,
- $scopeConfig,
- $coreVariableFactory,
- $storeManager,
- $layout,
- $layoutFactory,
- $appState,
- $urlModel,
- $emogrifier,
- $configVariables,
- $widgetResource,
- $widget
- );
- }
-
- /**
- * Retrieve media file URL directive
+ * Return associative array of parameters *exposing $this->getParameters().
*
- * @param string[] $construction
- * @return string
+ * @param string $value raw parameters
+ * @return array
*/
- public function mediaDirective($construction)
+ public function getParams($value)
{
- $params = $this->getParameters($construction[2]);
- $storeManager = $this->_storeManager;
-
- $image = $this->imageFactory->build(
- $params['url'],
- function() use ($storeManager, $params) {
- return sprintf(
- '%s%s',
- $storeManager->getStore()->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA),
- $params['url']
- );
- }
- );
-
- return $this->urlGenerator->generateFor($image);
+ return $this->getParameters($value);
}
}
diff --git a/Model/Transformation.php b/Model/Transformation.php
index b23aecad..fba1337b 100644
--- a/Model/Transformation.php
+++ b/Model/Transformation.php
@@ -2,10 +2,9 @@
namespace Cloudinary\Cloudinary\Model;
-use Cloudinary\Cloudinary\Model\ResourceModel\Transformation as TransformationResourceModel;
-use Cloudinary\Cloudinary\Model\Configuration;
-use Cloudinary\Cloudinary\Core\Image\Transformation\Freeform;
use Cloudinary\Cloudinary\Core\Image\Transformation as ImageTransformation;
+use Cloudinary\Cloudinary\Core\Image\Transformation\Freeform;
+use Cloudinary\Cloudinary\Model\ResourceModel\Transformation as TransformationResourceModel;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\Model\AbstractModel;
use Magento\Framework\Model\Context;
@@ -14,22 +13,27 @@
class Transformation extends AbstractModel
{
+ /**
+ * @var string
+ */
+ private $imageNameCacheKey;
+
private $configuration;
/**
- * @param Context $context
- * @param Registry $registry
- * @param Configuration $configuration
+ * @param Context $context
+ * @param Registry $registry
+ * @param Configuration $configuration
* @param AbstractResource $resource
- * @param AbstractDb $resourceCollection
- * @param array $data
+ * @param AbstractDb $resourceCollection
+ * @param array $data
*/
public function __construct(
Context $context,
Registry $registry,
Configuration $configuration,
- AbstractResource $resource = null,
- AbstractDb $resourceCollection = null,
+ ?AbstractResource $resource = null,
+ ?AbstractDb $resourceCollection = null,
array $data = []
) {
$this->configuration = $configuration;
@@ -42,7 +46,7 @@ protected function _construct()
}
/**
- * @param string $imageName
+ * @param string $imageName
* @return $this
*/
public function setImageName($imageName)
@@ -59,7 +63,7 @@ public function getImageName()
}
/**
- * @param string $transformation
+ * @param string $transformation
* @return $this
*/
public function setFreeTransformation($transformation)
@@ -76,28 +80,36 @@ public function getFreeTransformation()
}
/**
- * @param string $imageFile
+ * @param string $imageName
* @return ImageTransformation
*/
- public function transformationForImage($imageFile)
+ public function transformationForImage($imageName)
{
return $this->addFreeformTransformationForImage(
$this->configuration->getDefaultTransformation(),
- $imageFile
+ $imageName
);
}
/**
- * @param ImageTransformation $transformation
- * @param string $imageFile
+ * @param ImageTransformation $transformation
+ * @param string $imageName
+ * @param bool $refresh
* @return ImageTransformation
*/
- public function addFreeformTransformationForImage(ImageTransformation $transformation, $imageFile)
+ public function addFreeformTransformationForImage(ImageTransformation $transformation, $imageName, $refresh = false)
{
- $this->load($imageFile);
- if (($this->getImageName() === $imageFile) && $this->hasFreeTransformation()) {
- $transformation->withFreeform(Freeform::fromString($this->getFreeTransformation()));
+ $this->imageNameCacheKey = 'cldfreetransformcachekey_' . (string) $imageName;
+ if (!$refresh && ($cacheResult = $this->getFromCache()) !== null) {
+ $model = $cacheResult;
+ } else {
+ $model = $this->cacheResult($this->load($imageName));
}
+
+ if ($model->getImageName() === $imageName && $model->hasFreeTransformation()) {
+ $transformation->withFreeform(Freeform::fromString($model->getFreeTransformation()));
+ }
+
return $transformation;
}
@@ -108,4 +120,25 @@ private function hasFreeTransformation()
{
return !empty($this->getFreeTransformation());
}
+
+ /**
+ * @method cacheResult
+ * @param bool $result
+ * @return mixed
+ */
+ private function cacheResult($result)
+ {
+ $this->_registry->unregister($this->imageNameCacheKey);
+ $this->_registry->register($this->imageNameCacheKey, $result);
+ return $result;
+ }
+
+ /**
+ * @method cacheResult
+ * @return mixed
+ */
+ private function getFromCache()
+ {
+ return $this->_registry->registry($this->imageNameCacheKey);
+ }
}
diff --git a/Plugin/AttributeSavePlugin.php b/Plugin/AttributeSavePlugin.php
new file mode 100644
index 00000000..6c3cde67
--- /dev/null
+++ b/Plugin/AttributeSavePlugin.php
@@ -0,0 +1,128 @@
+_configuration = $configuration;
+ $this->_cldImageManager = $cldImageManager;
+ $this->_swatchFactory = $swatchFactory;
+ $this->resource = $resource;
+ $this->directoryList = $directoryList;
+
+ }
+
+
+ /**
+ * @param $filepath
+ * @return string
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function getMediaPath($filepath)
+ {
+ return $this->directoryList->getPath(DirectoryList::MEDIA) . DIRECTORY_SEPARATOR . self::SWATCH_MEDIA_PATH . $filepath;
+ }
+
+
+
+
+ /**
+ * After save plugin to update swatch image URLs
+ *
+ * @param AttributeResource $subject
+ * @param Attribute $attribute
+ * @return Attribute
+ */
+ public function afterSave(AttributeResource $subject, Attribute $attribute)
+ {
+ if ($this->_configuration->isEnabled() && $this->_configuration->isLoadSwatchesFromCloudinary()) {
+
+ if ($attribute->getFrontendInput() === 'select' && $attribute->getData('swatch_input_type') === 'visual') {
+ $connection = $this->resource->getConnection();
+ $optionTable = $connection->getTableName('eav_attribute_option');
+
+ // Get all option IDs for the current attribute
+ $optionIds = $connection->fetchCol(
+ $connection->select()
+ ->from($optionTable, 'option_id')
+ ->where('attribute_id = ?', $attribute->getId())
+ );
+
+ if (!empty($optionIds)) {
+ $swatchCollection = $this->_swatchFactory->create()
+ ->addFieldToFilter('option_id', ['in' => $optionIds])
+ ->addFieldToFilter('type', ['eq' => Swatch::SWATCH_TYPE_VISUAL_IMAGE]);
+
+ foreach ($swatchCollection as $swatch) {
+ if ( $swatch->getValue()) {
+ // Visual Image means
+ $imagePath = $this->getMediaPath($swatch->getValue());
+ $image = $this->_configuration->getMediaBaseUrl() . self::SWATCH_MEDIA_PATH . $swatch->getValue();
+ try {
+ $cldImage = $this->_cldImageManager->uploadAndSynchronise(
+ Image::fromPath($imagePath)
+ );
+ } catch (\Exception $e) {
+ throw new LocalizedException($e->getMessage());
+ }
+
+ //$swatch->save()
+ }
+ }
+ }
+ }
+ }
+
+ return $attribute;
+ }
+}
diff --git a/Plugin/Catalog/Block/Category/Image.php b/Plugin/Catalog/Block/Category/Image.php
new file mode 100644
index 00000000..6c121599
--- /dev/null
+++ b/Plugin/Catalog/Block/Category/Image.php
@@ -0,0 +1,165 @@
+configuration = $configuration;
+ $this->configurationBuilder = $configurationBuilder;
+ $this->transformation = $transformation;
+ $this->mediaLibraryMapCollectionFactory = $mediaLibraryMapCollectionFactory;
+ }
+
+ /**
+ * Authorize Cloudinary configuration
+ *
+ * @return void
+ */
+ protected function authorise()
+ {
+ if (!$this->authorised && $this->configuration->isEnabled()) {
+ Configuration::instance($this->configurationBuilder->build());
+ $this->authorised = true;
+ }
+ }
+
+ /**
+ * Plugin after getUrl to return Cloudinary version
+ *
+ * For rendition images (.renditions/*) without local mapping, returns the local image URL.
+ * For regular uploaded images, returns the Cloudinary URL.
+ *
+ * @param CategoryImageViewModel $subject
+ * @param string $result
+ * @param Category $category
+ * @param string $attributeCode
+ * @return string
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterGetUrl(
+ CategoryImageViewModel $subject,
+ string $result,
+ Category $category,
+ string $attributeCode = 'image'
+ ): string {
+ $this->authorise();
+
+ $imagePath = $category->getData($attributeCode);
+ if (!$this->configuration->isEnabled() || !$imagePath) {
+ return $result;
+ }
+
+ try {
+ $imageId = ltrim($imagePath, '/');
+ $isRenditionImage = strpos($imageId, '.renditions/') !== false;
+
+ // Check if this is a rendition image (gallery selection or other rendition)
+ // These images are stored locally and should not use Cloudinary unless there's a mapping
+ if ($isRenditionImage && !$this->configuration->isEnabledLocalMapping()) {
+ // Local mapping not enabled - return local image URL for rendition images
+ return $result;
+ }
+
+ // Try to find mapping if local mapping is enabled
+ if ($this->configuration->isEnabledLocalMapping()) {
+ $mappedPublicId = $this->getMappedPublicId($imageId);
+ if ($mappedPublicId) {
+ // If it's a full URL, return it directly
+ if (preg_match('/https?:\/\//i', $mappedPublicId)) {
+ return $mappedPublicId;
+ }
+ $imageId = $mappedPublicId;
+ } elseif ($isRenditionImage) {
+ // Rendition image without mapping - return local image URL
+ return $result;
+ }
+ }
+
+ // Generate Cloudinary URL using the imageId
+ $imagePath = Media::fromParams($imageId, [
+ 'transformation' => $this->transformation->build(),
+ 'secure' => true,
+ 'sign_url' => $this->configuration->getUseSignedUrls(),
+ 'version' => 1
+ ]) . '?_i=AB';
+
+ $image = CoreImage::fromPath($imagePath, '');
+ return (string) $image;
+
+ } catch (\Exception $e) {
+ return $result; // fallback to original if Cloudinary fails
+ }
+ }
+
+ /**
+ * Get mapped public ID from database
+ *
+ * @param string $imageId
+ * @return string|null
+ */
+ private function getMappedPublicId($imageId)
+ {
+ preg_match('/(cld_[A-Za-z0-9]{13}_).+$/i', $imageId, $cldUniqid);
+ if (!$cldUniqid || !isset($cldUniqid[1])) {
+ return null;
+ }
+
+ $mapped = $this->mediaLibraryMapCollectionFactory->create()
+ ->addFieldToFilter('cld_uniqid', $cldUniqid[1])
+ ->setPageSize(1)
+ ->getFirstItem();
+
+ if ($mapped && ($origPublicId = $mapped->getCldPublicId())) {
+ return $origPublicId;
+ }
+
+ return null;
+ }
+}
diff --git a/Plugin/Catalog/Block/Category/View.php b/Plugin/Catalog/Block/Category/View.php
new file mode 100644
index 00000000..2f19a904
--- /dev/null
+++ b/Plugin/Catalog/Block/Category/View.php
@@ -0,0 +1,23 @@
+process($subject, $html);
+ }
+}
diff --git a/Plugin/Catalog/Block/Product/ImageFactory.php b/Plugin/Catalog/Block/Product/ImageFactory.php
new file mode 100644
index 00000000..657f3642
--- /dev/null
+++ b/Plugin/Catalog/Block/Product/ImageFactory.php
@@ -0,0 +1,245 @@
+objectManager = $objectManager;
+ $this->presentationConfig = $presentationConfig;
+ $this->cloudinaryImageFactory = $cloudinaryImageFactory;
+ $this->urlGenerator = $urlGenerator;
+ $this->configuration = $configuration;
+ $this->transformationModel = $transformationFactory->create();
+ $this->dimensions = null;
+ $this->imageFile = null;
+ $this->keepFrame = true;
+ }
+
+ /**
+ * Retrieve image custom attributes for HTML element
+ *
+ * @param array $attributes
+ * @return string
+ */
+ private function getStringCustomAttributes(array $attributes)
+ {
+ $result = [];
+ foreach ($attributes as $name => $value) {
+ if ($name != 'class') {
+ $result[] = $name . '="' . $value . '"';
+ }
+ }
+ return !empty($result) ? implode(' ', $result) : '';
+ }
+
+ /**
+ * Create image block from product
+ *
+ * @param CatalogImageFactory $catalogImageFactory
+ * @param callable $proceed
+ * @param Product $product
+ * @param string $imageId
+ * @param array|null $attributes
+ * @return ImageBlock
+ */
+ public function aroundCreate(CatalogImageFactory $catalogImageFactory, callable $proceed, $product = null, $imageId = null, $attributes = null)
+ {
+ $imageBlock = call_user_func_array($proceed, array_slice(func_get_args(), 2));
+
+ if (!$this->configuration->isEnabled()) {
+ return $imageBlock;
+ }
+
+ if ($imageBlock->getImageUrl() === 'no_selection') {
+ return $imageBlock;
+ }
+
+ if ($this->configuration->isEnabledLazyload()) {
+ $useOldImageTheme = is_string($imageBlock->getCustomAttributes()) ? 'old_' : '';
+ $imageBlock->setTemplate(
+ \preg_match('/\/image_with_borders.phtml$/', $imageBlock->getTemplate()) ?
+ 'Cloudinary_Cloudinary::product/' . $useOldImageTheme . 'image_with_borders.phtml' : 'Cloudinary_Cloudinary::' . $useOldImageTheme . 'product/image.phtml'
+ );
+ $imageBlock->setLazyloadPlaceholder(Configuration::LAZYLOAD_DATA_PLACEHOLDER);
+ }
+
+ //Skip on Magento versions prior to 2.3
+ if (is_array($product) || !class_exists('\Magento\Catalog\Model\Product\Image\ParamsBuilder')) {
+ return $imageBlock;
+ }
+
+ $this->imageParamsBuilder = $this->objectManager->get('\Magento\Catalog\Model\Product\Image\ParamsBuilder');
+
+ try {
+ if (strpos($imageBlock->getImageUrl(), $this->configuration->getMediaBaseUrl() . 'catalog/product') === 0) {
+ $viewImageConfig = $this->presentationConfig->getViewConfig()->getMediaAttributes(
+ 'Magento_Catalog',
+ CatalogImageHelper::MEDIA_TYPE_CONFIG_NODE,
+ $imageId
+ );
+ $imageMiscParams = $this->imageParamsBuilder->build($viewImageConfig);
+
+ $imagePath = preg_replace('/^' . preg_quote($this->configuration->getMediaBaseUrl(), '/') . '/', '/', $imageBlock->getImageUrl());
+ $imagePath = preg_replace('/\/catalog\/product\/cache\/[a-f0-9]{32}\//', '/', $imagePath);
+
+ $image = $this->cloudinaryImageFactory->build(
+ sprintf('catalog/product%s', $imagePath),
+ function () use ($imageBlock) {
+ return $imageBlock->getImageUrl();
+ }
+ );
+
+ $transformations = $this->createTransformation($imageMiscParams);
+
+ if ($this->configuration->isEnabledProductFreeTransformations()) {
+ $transformations = $this->transformationModel->addFreeformTransformationForImage(
+ $transformations,
+ $imagePath
+ );
+ }
+
+ $generatedImageUrl = $this->urlGenerator->generateFor(
+ $image,
+ $transformations
+ );
+
+ $imageBlock->setOriginalImageUrl($imageBlock->setImageUrl());
+ $imageBlock->setImageUrl($generatedImageUrl);
+
+ if ($this->configuration->isEnabledLazyload()) {
+ $generatedImageUrl = $this->urlGenerator->generateFor(
+ $image,
+ $transformations->withFreeform($this->configuration->getLazyloadPlaceholderFreeform())
+ );
+ $imageBlock->setLazyloadPlaceholder($generatedImageUrl);
+ }
+ }
+ } catch (\Exception $e) {
+ $imageBlock = $proceed($product, $imageId, $attributes);
+ }
+
+ return $imageBlock;
+ }
+
+ /**
+ * @param array $imageMiscParams
+ * @return Transformation
+ */
+ private function createTransformation(array $imageMiscParams)
+ {
+ $dimensions = $this->getDimensions($imageMiscParams);
+ $transform = $this->configuration->getDefaultTransformation(true)->withDimensions($dimensions);
+
+ if (isset($imageMiscParams['keep_frame'])) {
+ $this->keepFrame = (bool) $imageMiscParams['keep_frame'];
+ }
+
+ if ($this->keepFrame) {
+ $transform->withCrop(Crop::lpad())
+ ->withDimensions(Dimensions::squareMissingDimension($dimensions));
+ } else {
+ $transform->withCrop(Crop::limit());
+ }
+
+ return $transform;
+ }
+
+ /**
+ * @param array $imageMiscParams
+ * @return Dimensions
+ */
+ private function getDimensions(array $imageMiscParams)
+ {
+ $imageMiscParams['image_height'] = (isset($imageMiscParams['image_height'])) ? $imageMiscParams['image_height'] : null;
+ $imageMiscParams['image_width'] = (isset($imageMiscParams['image_width'])) ? $imageMiscParams['image_width'] : null;
+ return $this->dimensions ?: Dimensions::fromWidthAndHeight($imageMiscParams['image_width'], $imageMiscParams['image_height']);
+ }
+}
diff --git a/Plugin/Catalog/Block/Product/View/Gallery.php b/Plugin/Catalog/Block/Product/View/Gallery.php
new file mode 100644
index 00000000..fb98f947
--- /dev/null
+++ b/Plugin/Catalog/Block/Product/View/Gallery.php
@@ -0,0 +1,196 @@
+productGalleryHelper = $productGalleryHelper;
+ $this->jsonEncoder = $jsonEncoder;
+ $this->configuration = $configuration;
+ $this->productSpinsetMapFactory = $productSpinsetMapFactory;
+ }
+
+ /**
+ * Override product gallery with the one from Cloudinary
+ *
+ * @param \Magento\Catalog\Block\Product\View\Gallery $productGalleryBlock
+ * @return string
+ */
+ public function beforeToHtml(\Magento\Catalog\Block\Product\View\Gallery $productGalleryBlock)
+ {
+ if (!$this->processed && $this->productGalleryHelper->canDisplayProductGallery()) {
+ $this->processed = true;
+ $this->productGalleryBlock = $productGalleryBlock;
+ $productGalleryBlock->setTemplate('Cloudinary_Cloudinary::product/gallery.phtml');
+ $productGalleryBlock->setCloudinaryPGOptions($this->getCloudinaryPGOptions());
+ $productGalleryBlock->setCldPGid($this->getCldPGid());
+ }
+ }
+ public function getCname()
+ {
+ $config = $this->configuration->getCredentials();
+ return ($config['cname']) ?? '';
+ }
+ public function getHtmlId()
+ {
+ if (!$this->htmlId) {
+ $this->htmlId = hash('sha256', uniqid('', true));
+ }
+ return $this->htmlId;
+ }
+
+ public function getCldPGid()
+ {
+ return 'cldPGid_' . $this->getHtmlId();
+ }
+
+ /**
+ * Retrieve product images in JSON format
+ *
+ * @return string
+ */
+ protected function getGalleryImagesJson()
+ {
+ $imagesItems = [];
+ /** @var DataObject $image */
+ foreach ($this->productGalleryBlock->getGalleryImages() as $image) {
+ $imageItem = new DataObject(
+ [
+ 'file' => $image->getData('file'),
+ 'thumb' => $image->getData('small_image_url'),
+ 'img' => $image->getData('medium_image_url'),
+ 'full' => $image->getData('large_image_url'),
+ 'caption' => ($image->getLabel() ?: $this->productGalleryBlock->getProduct()->getName()),
+ 'position' => $image->getData('position'),
+ 'isMain' => $this->productGalleryBlock->isMainImage($image),
+ 'type' => str_replace('external-', '', $image->getMediaType()),
+ 'videoUrl' => $image->getVideoUrl(),
+ ]
+ );
+ foreach ($this->productGalleryBlock->getGalleryImagesConfig()->getItems() as $imageConfig) {
+ $imageItem->setData(
+ $imageConfig->getData('json_object_key'),
+ $image->getData($imageConfig->getData('data_object_key'))
+ );
+ }
+ $imagesItems[] = $imageItem->toArray();
+ }
+ if (empty($imagesItems)) {
+ return $this->productGalleryBlock->getGalleryImagesJson();
+ }
+ return $this->jsonEncoder->encode($imagesItems);
+ }
+
+ /**
+ * @method getCloudinaryPGOptions
+ * @param bool $refresh Refresh options
+ * @param bool $ignoreDisabled Get te options even if the module or the product gallery are disabled
+ * @return array
+ */
+ protected function getCloudinaryPGOptions($refresh = false, $ignoreDisabled = false)
+ {
+ if (is_null($this->cloudinaryPGoptions) || $refresh) {
+ $this->cloudinaryPGoptions = $this->productGalleryHelper->getCloudinaryPGOptions($refresh, $ignoreDisabled);
+ $this->cloudinaryPGoptions['container'] = '#' . $this->getCldPGid();
+ $galleryAssets = (array) json_decode($this->getGalleryImagesJson(), true);
+ if (count($galleryAssets)>1) {
+ usort($galleryAssets, function ($a, $b) {
+ return $a['position'] - $b['position'];
+ });
+ /*usort($galleryAssets, function ($a, $b) {
+ return $b['isMain'] - $a['isMain'];
+ });*/
+ }
+ $this->cloudinaryPGoptions['mediaAssets'] = [];
+ foreach ($galleryAssets as $key => $value) {
+ $publicId = $url = $transformation = null;
+ if ($value['type'] === 'image' && isset($value['file']) && $value['file'] ) {
+ //Check if image is a spinset:
+ $cldspinset = $this->productSpinsetMapFactory->create()->getCollection()->addFieldToFilter("image_name", $value['file'])->setPageSize(1)->getFirstItem();
+ if ($cldspinset && ($cldspinset = $cldspinset->getCldspinset())) {
+ $this->cloudinaryPGoptions['mediaAssets'][] = (object)[
+ "tag" => $cldspinset,
+ "mediaType" => 'spin'
+ ];
+ continue;
+ }
+ //==================================//
+ $url = $value['full'] ?: $value['img'];
+ } elseif ($value['type'] === 'video') {
+ $url = $value['videoUrl'];
+ }
+ if ((strpos($url, '.cloudinary.com/') !== false) && (strpos($url, '/' . $this->productGalleryHelper->getCloudName() . '/') !== false || strpos($url, '://' . $this->productGalleryHelper->getCloudName()) !== false) || strpos($url, $this->getCname()) !== false) {
+ $parsed = $this->configuration->parseCloudinaryUrl($url);
+ $publicId = ($value['type'] === 'image') ? $parsed['publicId'] . '.' . $parsed['extension'] : $parsed['publicId'];
+ $transformation = \str_replace('/', ',', $parsed['transformations_string']);
+ }
+ if ($publicId) {
+ $this->cloudinaryPGoptions['mediaAssets'][] = (object)[
+ "publicId" => $publicId,
+ "mediaType" => $value['type'],
+ "transformation" => $transformation,
+ ];
+ }
+ }
+ }
+ return $this->jsonEncoder->encode(
+ [
+ 'htmlId' => $this->getHtmlId(),
+ 'cldPGid' => $this->getCldPGid(),
+ 'cloudinaryPGoptions' => $this->cloudinaryPGoptions,
+ ]
+ );
+ }
+}
diff --git a/Plugin/Catalog/Model/Product/Image/UrlBuilder.php b/Plugin/Catalog/Model/Product/Image/UrlBuilder.php
new file mode 100644
index 00000000..60268a0a
--- /dev/null
+++ b/Plugin/Catalog/Model/Product/Image/UrlBuilder.php
@@ -0,0 +1,197 @@
+objectManager = $objectManager;
+ $this->presentationConfig = $presentationConfig;
+ $this->cloudinaryImageFactory = $cloudinaryImageFactory;
+ $this->urlGenerator = $urlGenerator;
+ $this->configuration = $configuration;
+ $this->transformationModel = $transformationFactory->create();
+ $this->dimensions = null;
+ $this->imageFile = null;
+ $this->keepFrame = true;
+ }
+
+ /**
+ * Build image url using base path and params
+ *
+ * @param CatalogUrlBuilder $catalogUrlBuilder
+ * @param callable $proceed
+ * @param string $baseFilePath
+ * @param string $imageDisplayArea
+ * @return string
+ */
+ public function aroundGetUrl(CatalogUrlBuilder $catalogUrlBuilder, callable $proceed, string $baseFilePath, string $imageDisplayArea)
+ {
+ $url = $proceed($baseFilePath, $imageDisplayArea);
+
+ if (!$this->configuration->isEnabled()) {
+ return $url;
+ }
+
+ if ($url === 'no_selection') {
+ return $url;
+ }
+
+ if (class_exists('\Magento\Catalog\Model\Product\Image\ParamsBuilder')) {
+ $this->imageParamsBuilder = $this->objectManager->get('\Magento\Catalog\Model\Product\Image\ParamsBuilder');
+ } else {
+ //Skip on Magento versions prior to 2.3
+ return $url;
+ }
+
+ try {
+ if (strpos($url, $this->configuration->getMediaBaseUrl() . 'catalog/product') === 0) {
+ $imageArguments = $this->presentationConfig->getViewConfig()->getMediaAttributes(
+ 'Magento_Catalog',
+ CatalogImageHelper::MEDIA_TYPE_CONFIG_NODE,
+ $imageDisplayArea
+ );
+ $imageMiscParams = $this->imageParamsBuilder->build($imageArguments);
+
+ $imagePath = preg_replace('/^' . preg_quote($this->configuration->getMediaBaseUrl(), '/') . '/', '/', $url);
+ $imagePath = preg_replace('/\/catalog\/product\/cache\/[a-f0-9]{32}\//', '/', $imagePath);
+
+ $image = $this->cloudinaryImageFactory->build(
+ sprintf('catalog/product%s', $imagePath),
+ function () use ($url) {
+ return $url;
+ }
+ );
+
+ $transformations = $this->createTransformation($imageMiscParams);
+
+ if ($this->configuration->isEnabledProductFreeTransformations()) {
+ $transformations = $this->transformationModel->addFreeformTransformationForImage(
+ $transformations,
+ $imagePath
+ );
+ }
+
+ $generatedImageUrl = $this->urlGenerator->generateFor(
+ $image,
+ $transformations
+ );
+
+ $url = $generatedImageUrl;
+ }
+ } catch (\Exception $e) {
+ $url = $proceed($baseFilePath, $imageDisplayArea);
+ }
+
+ return $url;
+ }
+
+ /**
+ * @param array $imageMiscParams
+ * @return Transformation
+ */
+ private function createTransformation(array $imageMiscParams)
+ {
+ $imageMiscParams['image_height'] = (isset($imageMiscParams['image_height'])) ? $imageMiscParams['image_height'] : null;
+ $imageMiscParams['image_width'] = (isset($imageMiscParams['image_width'])) ? $imageMiscParams['image_width'] : null;
+ $dimensions = $this->dimensions ?: Dimensions::fromWidthAndHeight($imageMiscParams['image_width'], $imageMiscParams['image_height']);
+ $transform = $this->configuration->getDefaultTransformation(true)->withDimensions($dimensions);
+
+ if (isset($imageMiscParams['keep_frame'])) {
+ $this->keepFrame = (bool) $imageMiscParams['keep_frame'];
+ }
+
+ if ($this->keepFrame) {
+ $transform->withCrop(Crop::lpad())
+ ->withDimensions(Dimensions::squareMissingDimension($dimensions));
+ } else {
+ $transform->withCrop(Crop::limit());
+ }
+
+ return $transform;
+ }
+}
diff --git a/Plugin/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php b/Plugin/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php
new file mode 100644
index 00000000..5b1687ec
--- /dev/null
+++ b/Plugin/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php
@@ -0,0 +1,298 @@
+coreRegistry = $coreRegistry;
+ $this->configuration = $configuration;
+ $this->skuProcessor = $skuProcessor;
+ $this->metadataPool = $metadataPool;
+ $this->connection = $resourceConnection->getConnection();
+ $this->resourceFactory = $resourceModelFactory;
+ }
+
+ /**
+ * Save product media gallery.
+ *
+ * @param \Magento\CatalogImportExport\Model\Import\Product\MediaGalleryProcessor $mediaGalleryProcessorModel
+ * @param callable $proceed
+ * @param array $mediaGalleryData
+ * @return void
+ */
+ public function aroundSaveMediaGallery(\Magento\CatalogImportExport\Model\Import\Product\MediaGalleryProcessor $mediaGalleryProcessorModel, callable $proceed, array $mediaGalleryData)
+ {
+ $cloudinaryVideosImportMap = $this->coreRegistry->registry('cloudinary_videos_import_map') ?: [];
+
+ if (!$this->configuration->isEnabled() || !$cloudinaryVideosImportMap) {
+ return $proceed($mediaGalleryData);
+ }
+
+ $this->initMediaGalleryResources();
+ $mediaGalleryDataGlobal = array_replace_recursive(...$mediaGalleryData);
+ $imageNames = [];
+ $multiInsertData = [];
+ $valueToProductId = [];
+ foreach ($mediaGalleryDataGlobal as $productSku => $mediaGalleryRows) {
+ $productId = $this->skuProcessor->getNewSku($productSku)[$this->getProductEntityLinkField()];
+ $insertedGalleryImgs = [];
+ foreach ($mediaGalleryRows as $insertValue) {
+ if (!in_array($insertValue['value'], $insertedGalleryImgs)) {
+ $valueArr = [
+ 'attribute_id' => $insertValue['attribute_id'],
+ 'value' => $insertValue['value'],
+ 'media_type' => (isset($cloudinaryVideosImportMap[$insertValue['value']])) ? 'external-video' : 'image'
+ ];
+ $valueToProductId[$insertValue['value']][] = $productId;
+ $imageNames[] = $insertValue['value'];
+ $multiInsertData[] = $valueArr;
+ $insertedGalleryImgs[] = $insertValue['value'];
+ }
+ }
+ }
+ $oldMediaValues = $this->connection->fetchAssoc(
+ $this->connection->select()->from($this->mediaGalleryTableName, ['value_id', 'value'])
+ ->where('value IN (?)', $imageNames)
+ );
+ $this->connection->insertOnDuplicate($this->mediaGalleryTableName, $multiInsertData);
+ $newMediaSelect = $this->connection->select()->from($this->mediaGalleryTableName, ['value_id', 'value'])
+ ->where('value IN (?)', $imageNames);
+ if (array_keys($oldMediaValues)) {
+ $newMediaSelect->where('value_id NOT IN (?)', array_keys($oldMediaValues));
+ }
+ $newMediaValues = $this->connection->fetchAssoc($newMediaSelect);
+ foreach ($mediaGalleryData as $storeId => $storeMediaGalleryData) {
+ $this->processMediaPerStore((int)$storeId, $storeMediaGalleryData, $newMediaValues, $valueToProductId);
+ }
+ }
+
+ /**
+ * Init media gallery resources.
+ *
+ * @return void
+ */
+ private function initMediaGalleryResources()
+ {
+ if (null == $this->mediaGalleryTableName) {
+ $this->productEntityTableName = $this->getResource()->getTable('catalog_product_entity');
+ $this->mediaGalleryTableName = $this->getResource()->getTable('catalog_product_entity_media_gallery');
+ $this->mediaGalleryValueTableName = $this->getResource()->getTable(
+ 'catalog_product_entity_media_gallery_value'
+ );
+ $this->mediaGalleryValueVideoTableName = $this->getResource()->getTable(
+ 'catalog_product_entity_media_gallery_value_video'
+ );
+ $this->mediaGalleryEntityToValueTableName = $this->getResource()->getTable(
+ 'catalog_product_entity_media_gallery_value_to_entity'
+ );
+ }
+ }
+
+ /**
+ * Save media gallery data per store.
+ *
+ * @param int $storeId
+ * @param array $mediaGalleryData
+ * @param array $newMediaValues
+ * @param array $valueToProductId
+ * @return void
+ */
+ private function processMediaPerStore(
+ int $storeId,
+ array $mediaGalleryData,
+ array $newMediaValues,
+ array $valueToProductId
+ ) {
+ $multiInsertData = [];
+ $multiInsertDataVideos = [];
+ $dataForSkinnyTable = [];
+ $cloudinaryVideosImportMap = $this->coreRegistry->registry('cloudinary_videos_import_map') ?: [];
+ foreach ($mediaGalleryData as $mediaGalleryRows) {
+ foreach ($mediaGalleryRows as $insertValue) {
+ foreach ($newMediaValues as $value_id => $values) {
+ if ($values['value'] == $insertValue['value']) {
+ $insertValue['value_id'] = $value_id;
+ $insertValue[$this->getProductEntityLinkField()]
+ = array_shift($valueToProductId[$values['value']]);
+ unset($newMediaValues[$value_id]);
+ break;
+ }
+ }
+ if (isset($insertValue['value_id'])) {
+ $valueArr = [
+ 'value_id' => $insertValue['value_id'],
+ 'store_id' => $storeId,
+ $this->getProductEntityLinkField() => $insertValue[$this->getProductEntityLinkField()],
+ 'label' => $insertValue['label'],
+ 'position' => $insertValue['position'],
+ 'disabled' => $insertValue['disabled'],
+ ];
+ $multiInsertData[] = $valueArr;
+ $dataForSkinnyTable[] = [
+ 'value_id' => $insertValue['value_id'],
+ $this->getProductEntityLinkField() => $insertValue[$this->getProductEntityLinkField()],
+ ];
+ if (isset($cloudinaryVideosImportMap[$insertValue['value']])) {
+ $multiInsertDataVideos[$insertValue['value_id']] = [
+ 'value_id' => $insertValue['value_id'],
+ 'store_id' => $storeId,
+ 'provider' => 'cloudinary',
+ 'url' => $cloudinaryVideosImportMap[$insertValue['value']]
+ ];
+ }
+ }
+ }
+ }
+ try {
+ $this->connection->insertOnDuplicate(
+ $this->mediaGalleryValueTableName,
+ $multiInsertData,
+ ['value_id', 'store_id', $this->getProductEntityLinkField(), 'label', 'position', 'disabled']
+ );
+ $this->connection->insertOnDuplicate(
+ $this->mediaGalleryEntityToValueTableName,
+ $dataForSkinnyTable,
+ ['value_id']
+ );
+ if ($multiInsertDataVideos) {
+ $this->connection->insertOnDuplicate(
+ $this->mediaGalleryValueVideoTableName,
+ $multiInsertDataVideos,
+ ['value_id', 'store_id']
+ );
+ }
+ $this->coreRegistry->unregister('cloudinary_videos_import_map');
+ } catch (\Exception $e) {
+ $this->connection->delete(
+ $this->mediaGalleryTableName,
+ $this->connection->quoteInto('value_id IN (?)', $newMediaValues)
+ );
+ throw $e;
+ }
+ }
+
+ /**
+ * Get product entity link field.
+ *
+ * @return string
+ */
+ private function getProductEntityLinkField()
+ {
+ if (!$this->productEntityLinkField) {
+ $this->productEntityLinkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
+ }
+
+ return $this->productEntityLinkField;
+ }
+
+ /**
+ * Get resource.
+ *
+ * @return \Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModel
+ */
+ private function getResource()
+ {
+ if (!$this->resourceModel) {
+ $this->resourceModel = $this->resourceFactory->create();
+ }
+
+ return $this->resourceModel;
+ }
+}
diff --git a/Plugin/CatalogImportExport/Model/Import/Uploader.php b/Plugin/CatalogImportExport/Model/Import/Uploader.php
new file mode 100644
index 00000000..722d32ab
--- /dev/null
+++ b/Plugin/CatalogImportExport/Model/Import/Uploader.php
@@ -0,0 +1,162 @@
+mediaConfig = $mediaConfig;
+ $this->fileSystem = $fileSystem;
+ $this->coreRegistry = $coreRegistry;
+ $this->configuration = $configuration;
+ $this->mediaLibraryMapFactory = $mediaLibraryMapFactory;
+ $this->transformationFactory = $transformationFactory;
+ $this->_directory = $fileSystem->getDirectoryWrite(DirectoryList::ROOT);
+ }
+
+ /**
+ * Prepare component configuration
+ *
+ * @param \Magento\CatalogImportExport\Model\Import\Uploader $uploaderModel
+ * @param callable $proceed
+ * @param string $fileName
+ * @param bool $renameFileOff
+ * @return array
+ */
+ public function aroundMove(\Magento\CatalogImportExport\Model\Import\Uploader $uploaderModel, callable $proceed, $fileName, $renameFileOff = false)
+ {
+ //= Before
+ if ($this->configuration->isEnabled()) {
+ $this->remoteFileUrl = $fileName;
+ $this->parsedRemoteFileUrl = $this->configuration->parseCloudinaryUrl($this->remoteFileUrl);
+ if ($this->parsedRemoteFileUrl["scheme"] && \strpos($this->parsedRemoteFileUrl["host"], "cloudinary.com") !== false) {
+ $fileName = $this->parsedRemoteFileUrl['transformationless_url'];
+ if ($this->parsedRemoteFileUrl['type'] === 'video') {
+ $fileName = $this->parsedRemoteFileUrl['thumbnail_url'];
+ }
+ } else {
+ $this->parsedRemoteFileUrl["publicId"] = null;
+ }
+ }
+
+ //===========================================//
+ $result = $proceed($fileName, $renameFileOff);
+ //===========================================//
+
+ //= After
+ if ($this->configuration->isEnabled() && $this->parsedRemoteFileUrl["publicId"]) {
+ if ($this->configuration->isEnabledLocalMapping()) {
+ $this->cldUniqid = $this->configuration->generateCLDuniqid();
+ $catalogMediaPath = $this->fileSystem->getDirectoryRead(DirectoryList::MEDIA)->getAbsolutePath() . $this->mediaConfig->getBaseMediaPath();
+ $result['name'] = $this->configuration->addUniquePrefixToBasename($result['name'], $this->cldUniqid);
+ $_tmpName = $this->configuration->addUniquePrefixToBasename($result['tmp_name'], $this->cldUniqid);
+ $_file = $this->configuration->addUniquePrefixToBasename($result['file'], $this->cldUniqid);
+ $this->_directory->renameFile($result['tmp_name'], $_tmpName);
+ $this->_directory->renameFile($catalogMediaPath . $result['file'], $catalogMediaPath . $_file);
+ $result['tmp_name'] = $_tmpName;
+ $result['file'] = $_file;
+
+ $this->mediaLibraryMapFactory->create()
+ ->setCldUniqid($this->cldUniqid)
+ ->setCldPublicId(($this->parsedRemoteFileUrl["type"] === "video") ? $this->parsedRemoteFileUrl["thumbnail_url"] : $this->parsedRemoteFileUrl["publicId"] . '.' . $this->parsedRemoteFileUrl["extension"])
+ ->setFreeTransformation(\rawurldecode($this->parsedRemoteFileUrl["transformations_string"]))
+ ->save();
+ }
+
+ if ($this->parsedRemoteFileUrl["type"] === "image" && $this->parsedRemoteFileUrl['transformations_string']) {
+ $this->transformationFactory->create()
+ ->setImageName($result['file'])
+ ->setFreeTransformation(\rawurldecode($this->parsedRemoteFileUrl["transformations_string"]))
+ ->save();
+ }
+
+ if ($this->parsedRemoteFileUrl['type'] === 'video') {
+ $cloudinaryVideosImportMap = $this->coreRegistry->registry('cloudinary_videos_import_map') ?: [];
+ $cloudinaryVideosImportMap["{$result['file']}"] = $this->parsedRemoteFileUrl["orig_url"];
+ $this->coreRegistry->unregister('cloudinary_videos_import_map');
+ $this->coreRegistry->register('cloudinary_videos_import_map', $cloudinaryVideosImportMap);
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/Plugin/Cms/Block/Block.php b/Plugin/Cms/Block/Block.php
new file mode 100644
index 00000000..604db392
--- /dev/null
+++ b/Plugin/Cms/Block/Block.php
@@ -0,0 +1,24 @@
+process($subject, $html);
+ }
+}
diff --git a/Plugin/Cms/Block/Widget/Block.php b/Plugin/Cms/Block/Widget/Block.php
new file mode 100644
index 00000000..302a2530
--- /dev/null
+++ b/Plugin/Cms/Block/Widget/Block.php
@@ -0,0 +1,24 @@
+process($subject, $html);
+ }
+}
diff --git a/Plugin/CmsBlockLazyloadAbstract.php b/Plugin/CmsBlockLazyloadAbstract.php
new file mode 100644
index 00000000..7f708961
--- /dev/null
+++ b/Plugin/CmsBlockLazyloadAbstract.php
@@ -0,0 +1,89 @@
+_configuration = $configuration;
+ $this->_urlGenerator = $urlGenerator;
+ $this->_transformationModel = $transformationFactory->create();
+ $this->_coreRegistry = $coreRegistry;
+ }
+
+ protected function process($subject, $html)
+ {
+ if (!$this->_configuration->isEnabled() || !$this->_configuration->isEnabledLazyload() || !$this->_configuration->isLazyloadAutoReplaceCmsBlocks() || in_array($subject->getBlockId(), $this->_configuration->getLazyloadIgnoredCmsBlocksArray())) {
+ return $html;
+ }
+
+ if (stripos($html, " loadHTML($html);
+ libxml_use_internal_errors($useErrors);
+ $dom->preserveWhiteSpace = false;
+ $modified = 0;
+
+ foreach ($dom->getElementsByTagName('img') as $element) {
+ if (strpos($element->getAttribute('class'), "lazyload") === false && strpos($element->getAttribute('class'), "owl-lazy") === false && ($image = $this->_coreRegistry->registry('cloudinary_generated_' . hash('sha256', $element->getAttribute('src')))) !== null) {
+ if (!($placeholderUrl = $this->_urlGenerator->generateFor($image, $this->_configuration->getDefaultTransformation()->withFreeform($this->_configuration->getLazyloadPlaceholderFreeform())))) {
+ continue;
+ }
+ $this->_coreRegistry->unregister('cloudinary_generated_' . hash('sha256', $element->getAttribute('src')));
+ $modified++;
+ $element->setAttribute('class', 'cloudinary-lazyload ' . $element->getAttribute('class'));
+ $element->setAttribute('data-original', $element->getAttribute('src'));
+ $element->setAttribute('src', $placeholderUrl);
+ }
+ }
+
+ if ($modified) {
+ $html = $dom->saveHTML();
+ }
+ }
+
+ return $html;
+ }
+}
diff --git a/Plugin/Config/Block/System/Config/Form/Field/Image.php b/Plugin/Config/Block/System/Config/Form/Field/Image.php
new file mode 100644
index 00000000..9cc4e886
--- /dev/null
+++ b/Plugin/Config/Block/System/Config/Form/Field/Image.php
@@ -0,0 +1,73 @@
+escaper = $escaper;
+ $this->jsonEncoder = $jsonEncoder;
+ $this->mediaLibraryHelper = $mediaLibraryHelper;
+ }
+
+ /**
+ * Get the Html for the element.
+ *
+ * @param \Magento\Config\Block\System\Config\Form\Field\Image $block
+ * @param string $html
+ * @return string
+ */
+ public function afterGetElementHtml(\Magento\Config\Block\System\Config\Form\Field\Image $block, $html)
+ {
+ // TODO: Add JS logics & handlers for after image insert
+ if (($cloudinaryMLoptions = $this->mediaLibraryHelper->getCloudinaryMLOptions(false))) {
+ $html .= 'jsonEncoder->encode(['cloudinaryMLoptions' => $cloudinaryMLoptions, 'cloudinaryMLshowOptions' => $this->mediaLibraryHelper->getCloudinaryMLshowOptions("image")]) . '}\'
+ class="action-secondary add-from-cloudinary-button cloudinary-button-with-logo small-ver sm-top-bottom-margin">
+ ' . $this->escaper->escapeHtml(__('Add from Cloudinary')) . '
+ ';
+ }
+ return $html;
+ }
+}
diff --git a/Plugin/ExcludeFilesFromMinification.php b/Plugin/ExcludeFilesFromMinification.php
new file mode 100644
index 00000000..6a0cb65e
--- /dev/null
+++ b/Plugin/ExcludeFilesFromMinification.php
@@ -0,0 +1,18 @@
+cloudinaryImageManager = $cloudinaryImageManager;
$this->mediaDirectory = $filesystem->getDirectoryRead(DirectoryList::MEDIA);
+ $this->configuration = $configuration;
}
/**
* Delete file (and its thumbnail if exists) from storage
*
- * @param string $target File path to be deleted
+ * @param string $target File path to be deleted
* @return $this
*/
public function beforeDeleteFile(Storage $storage, $target)
{
+ if (!$this->configuration->isEnabled() || !$this->configuration->hasEnvironmentVariable()) {
+ return [$target];
+ }
+
$this->cloudinaryImageManager->removeAndUnSynchronise(
Image::fromPath($target, $this->mediaDirectory->getRelativePath($target))
);
diff --git a/Plugin/FileUploader.php b/Plugin/FileUploader.php
index 4eeec6e7..44e08b8d 100644
--- a/Plugin/FileUploader.php
+++ b/Plugin/FileUploader.php
@@ -2,13 +2,18 @@
namespace Cloudinary\Cloudinary\Plugin;
-use Cloudinary\Cloudinary\Core\Image;
use Cloudinary\Cloudinary\Core\CloudinaryImageManager;
+use Cloudinary\Cloudinary\Core\ConfigurationInterface;
+use Cloudinary\Cloudinary\Core\Image;
+use Cloudinary\Cloudinary\Model\MediaLibraryMapFactory;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\File\Uploader;
+use Magento\Framework\Filesystem;
class FileUploader
{
+ const ALLOWED_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg'];
+
/**
* @var CloudinaryImageManager
*/
@@ -20,39 +25,103 @@ class FileUploader
private $directoryList;
/**
- * @param CloudinaryImageManager $cloudinaryImageManager
- * @param DirectoryList $directoryList
+ * @var ConfigurationInterface
+ */
+ private $configuration;
+
+ /**
+ * @var MediaLibraryMapFactory
+ */
+ private $mediaLibraryMapFactory;
+
+ /**
+ * @var Filesystem
+ */
+ private $filesystem;
+
+ /**
+ * @method __construct
+ * @param CloudinaryImageManager $cloudinaryImageManager
+ * @param DirectoryList $directoryList
+ * @param ConfigurationInterface $configuration
+ * @param MediaLibraryMapFactory $mediaLibraryMapFactory
+ * @param Filesystem $filesystem
*/
public function __construct(
CloudinaryImageManager $cloudinaryImageManager,
- DirectoryList $directoryList
+ DirectoryList $directoryList,
+ ConfigurationInterface $configuration,
+ MediaLibraryMapFactory $mediaLibraryMapFactory,
+ Filesystem $filesystem
) {
$this->cloudinaryImageManager = $cloudinaryImageManager;
$this->directoryList = $directoryList;
+ $this->configuration = $configuration;
+ $this->mediaLibraryMapFactory = $mediaLibraryMapFactory;
+ $this->filesystem = $filesystem;
}
/**
- * @param Uploader $uploader
- * @param array $result
+ * @param Uploader $uploader
+ * @param array $result
* @return array
*/
public function afterSave(Uploader $uploader, $result)
{
+ if (!$this->configuration->isEnabled() || !$this->configuration->hasEnvironmentVariable()) {
+ return $result;
+ }
+
$filepath = $this->absoluteFilePath($result);
- if ($this->isMediaFilePath($filepath) && !$this->isMediaTmpFilePath($filepath)) {
- $this->cloudinaryImageManager->uploadAndSynchronise(
- Image::fromPath($filepath, $this->mediaRelativePath($filepath))
- );
+ if ($this->isAllowedImageExtension($filepath) && $this->isMediaFilePath($filepath)) {
+ try {
+ $relativePath = $this->mediaRelativePath($filepath);
+
+ // Normalize path: remove /tmp/ to match final location
+ $normalizedPath = preg_replace('#/tmp/#', '/', $relativePath);
+
+ $image = Image::fromPath($filepath, $normalizedPath);
+ $uploadResults = $this->cloudinaryImageManager->uploadAndSynchronise($image);
+
+ // fallback to existing files
+ if (isset($uploadResults['file_exists']) && $uploadResults['file_exists']) {
+ $normalizedPath = $uploadResults['file_exists'];
+ }
+
+ // Only save mapping if upload succeeded and settings enabled
+ $cldUniqid = $this->configuration->generateCLDuniqid();
+ // Save public_id without extension (e.g., media/catalog/category/_4_2_3)
+ $publicId = isset($uploadResults['public_id']) ? $uploadResults['public_id'] : preg_replace('/\.[^.]+$/', '', $normalizedPath);
+ $this->mediaLibraryMapFactory->create()
+ ->setCldUniqid($cldUniqid)
+ ->setCldPublicId($publicId)
+ ->setFreeTransformation(null)
+ ->save();
+
+ } catch (\Cloudinary\Cloudinary\Core\Exception\FileExists $e) {
+ // Image already exists in Cloudinary - skip mapping creation
+ } catch (\Exception $e) {
+ // Upload failed - skip mapping creation
+ }
}
return $result;
}
/**
- * @param string $filepath
+ * @param string $filepath
+ * @return string
+ */
+ protected function isAllowedImageExtension($filepath)
+ {
+ return in_array(pathinfo($filepath, PATHINFO_EXTENSION), self::ALLOWED_EXTENSIONS);
+ }
+
+ /**
+ * @param string $filepath
* @return bool
*/
protected function isMediaFilePath($filepath)
@@ -61,16 +130,22 @@ protected function isMediaFilePath($filepath)
}
/**
- * @param string $filepath
+ * @param string $filepath
* @return string
*/
protected function isMediaTmpFilePath($filepath)
{
- return strpos($filepath, sprintf('%s/tmp', $this->directoryList->getPath('media'))) === 0;
+ // Check for standard tmp path
+ $standardTmpCheck = strpos($filepath, sprintf('%s/tmp', $this->directoryList->getPath('media'))) === 0;
+
+ // Also check for catalog/tmp (category images go to catalog/tmp/category)
+ $catalogTmpCheck = strpos($filepath, $this->directoryList->getPath('media') . '/catalog/tmp') === 0;
+
+ return $standardTmpCheck || $catalogTmpCheck;
}
/**
- * @param array $result
+ * @param array $result
* @return string
*/
protected function absoluteFilePath(array $result)
@@ -79,12 +154,12 @@ protected function absoluteFilePath(array $result)
}
/**
- * @param string $filepath
+ * @param string $filepath
* @return string
*/
protected function mediaRelativePath($filepath)
{
- $mediaPath = $this->directoryList->getPath('media') . DIRECTORY_SEPARATOR;
- return (strpos($filepath, $mediaPath) === 0) ? str_replace($mediaPath, '', $filepath) : $filepath;
+ $pubPath = $this->directoryList->getPath(DirectoryList::PUB) . DIRECTORY_SEPARATOR;
+ return (strpos($filepath, $pubPath) === 0) ? str_replace($pubPath, '', $filepath) : $filepath;
}
}
diff --git a/Plugin/ImageHelper.php b/Plugin/ImageHelper.php
index ebfdebb4..83a94440 100644
--- a/Plugin/ImageHelper.php
+++ b/Plugin/ImageHelper.php
@@ -2,16 +2,16 @@
namespace Cloudinary\Cloudinary\Plugin;
-use Cloudinary\Cloudinary\Core\Image\Transformation;
+use Cloudinary\Cloudinary\Core\ConfigurationInterface;
use Cloudinary\Cloudinary\Core\Image\ImageFactory;
-use Cloudinary\Cloudinary\Core\Image\Transformation\Dimensions;
+use Cloudinary\Cloudinary\Core\Image\Transformation;
use Cloudinary\Cloudinary\Core\Image\Transformation\Crop;
+use Cloudinary\Cloudinary\Core\Image\Transformation\Dimensions;
use Cloudinary\Cloudinary\Core\UrlGenerator;
-use Cloudinary\Cloudinary\Core\ConfigurationInterface;
-use Magento\Catalog\Api\Data\ProductInterface;
-use Magento\Catalog\Helper\Image as CatalogImageHelper;
use Cloudinary\Cloudinary\Model\Transformation as TransformationModel;
use Cloudinary\Cloudinary\Model\TransformationFactory;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Helper\Image as CatalogImageHelper;
class ImageHelper
{
@@ -56,8 +56,8 @@ class ImageHelper
private $transformationModel;
/**
- * @param ImageFactory $imageFactory
- * @param UrlGenerator $urlGenerator
+ * @param ImageFactory $imageFactory
+ * @param UrlGenerator $urlGenerator
* @param ConfigurationInterface $configuration
*/
public function __construct(
@@ -75,10 +75,10 @@ public function __construct(
}
/**
- * @param CatalogImageHelper $helper
- * @param ProductInterface $product
- * @param string $imageId
- * @param array $attributes
+ * @param CatalogImageHelper $helper
+ * @param ProductInterface $product
+ * @param string $imageId
+ * @param array $attributes
*
* @return array
*/
@@ -92,8 +92,8 @@ public function beforeInit(CatalogImageHelper $helper, $product, $imageId, $attr
}
/**
- * @param CatalogImageHelper $helper
- * @param string $file
+ * @param CatalogImageHelper $helper
+ * @param string $file
*
* @return string[]
*/
@@ -104,9 +104,9 @@ public function beforeSetImageFile(CatalogImageHelper $helper, $file)
}
/**
- * @param CatalogImageHelper $helper
- * @param int $width
- * @param int $height
+ * @param CatalogImageHelper $helper
+ * @param int $width
+ * @param int $height
*
* @return array
*/
@@ -119,7 +119,7 @@ public function beforeResize(CatalogImageHelper $helper, $width, $height = null)
/**
* @param CatalogImageHelper $helper
- * @param bool $flag
+ * @param bool $flag
*/
public function beforeKeepFrame(CatalogImageHelper $helper, $flag)
{
@@ -127,28 +127,38 @@ public function beforeKeepFrame(CatalogImageHelper $helper, $flag)
}
/**
- * @param CatalogImageHelper $helper
- * @param \Closure $originalMethod
+ * @param CatalogImageHelper $helper
+ * @param \Closure $originalMethod
*
* @return string
*/
public function aroundGetUrl(CatalogImageHelper $helper, \Closure $originalMethod)
{
+ if (!$this->configuration->isEnabled()) {
+ return $originalMethod();
+ }
+
$imagePath = $this->imageFile ?: $this->product->getData($helper->getType());
$image = $this->imageFactory->build(sprintf('catalog/product%s', $imagePath), $originalMethod);
+ $transformations = $this->createTransformation($helper);
+
+ if ($this->configuration->isEnabledProductFreeTransformations()) {
+ $transformations = $this->transformationModel->addFreeformTransformationForImage(
+ $transformations,
+ $imagePath
+ );
+ }
+
return $this->urlGenerator->generateFor(
$image,
- $this->transformationModel->addFreeformTransformationForImage(
- $this->createTransformation($helper),
- $imagePath
- )
+ $transformations
);
}
/**
- * @param CatalogImageHelper $helper
+ * @param CatalogImageHelper $helper
* @return Transformation
*/
private function createTransformation(CatalogImageHelper $helper)
@@ -158,10 +168,10 @@ private function createTransformation(CatalogImageHelper $helper)
$transform = $this->configuration->getDefaultTransformation()->withDimensions($dimensions);
if ($this->keepFrame) {
- $transform->withCrop(Crop::fromString('pad'))
+ $transform->withCrop(Crop::lpad())
->withDimensions(Dimensions::squareMissingDimension($dimensions));
} else {
- $transform->withCrop(Crop::fromString('fit'));
+ $transform->withCrop(Crop::limit());
}
return $transform;
diff --git a/Plugin/MediaConfig.php b/Plugin/MediaConfig.php
index a711bdea..9ef89272 100644
--- a/Plugin/MediaConfig.php
+++ b/Plugin/MediaConfig.php
@@ -29,9 +29,9 @@ public function __construct(ImageFactory $imageFactory, UrlGenerator $urlGenerat
}
/**
- * @param CatalogMediaConfig $mediaConfig
- * @param \Closure $originalMethod
- * @param string $file
+ * @param CatalogMediaConfig $mediaConfig
+ * @param \Closure $originalMethod
+ * @param string $file
*
* @return string
*/
@@ -39,9 +39,11 @@ public function aroundGetMediaUrl(CatalogMediaConfig $mediaConfig, \Closure $ori
{
$image = $this->imageFactory->build(
$mediaConfig->getBaseMediaPath() . $file,
- function() use ($originalMethod, $file) { return $originalMethod($file); }
+ function () use ($originalMethod, $file) {
+ return $originalMethod($file);
+ }
);
- return $this->urlGenerator->generateFor($image);
+ return $this->urlGenerator->generateFor($image);
}
}
diff --git a/Plugin/SwatchImageUrlPlugin.php b/Plugin/SwatchImageUrlPlugin.php
new file mode 100644
index 00000000..69ddc652
--- /dev/null
+++ b/Plugin/SwatchImageUrlPlugin.php
@@ -0,0 +1,158 @@
+storeManager = $storeManager;
+ $this->urlBuilder = $urlBuilder;
+ $this->_configuration = $configuration;
+ $this->configurationBuilder = $configurationBuilder;
+ $this->viewConfig = $configInterface;
+ $this->imageFactory = $imageFactory;
+ $this->mediaDirectory = $filesystem->getDirectoryRead(DirectoryList::PUB);
+ }
+
+ /**
+ * @return array
+ */
+ public function getImageConfig()
+ {
+ if (!$this->imageConfig) {
+ $this->imageConfig = $this->viewConfig->getViewConfig()->getMediaEntities(
+ 'Magento_Catalog',
+ \Magento\Catalog\Helper\Image::MEDIA_TYPE_CONFIG_NODE
+ );
+ }
+
+ return $this->imageConfig;
+ }
+
+ public function getSwatchCachePath($swatchType)
+ {
+ return self::SWATCH_MEDIA_PATH . '/' . $swatchType . '/';
+ }
+
+ protected function getAbsolutePath($swatchType)
+ {
+ return $this->mediaDirectory->getAbsolutePath($this->getSwatchCachePath($swatchType));
+ }
+
+
+ private function authorise()
+ {
+ if (!$this->_authorised && $this->_configuration->isEnabled()) {
+ \Cloudinary\Configuration\Configuration::instance($this->configurationBuilder->build());
+ BaseApiClient::$userPlatform = $this->_configuration->getUserPlatform();
+ $this->_authorised = true;
+ }
+ }
+
+ /**
+ * @param SwatchMediaHelper $subject
+ * @param $result
+ * @param $swatchType
+ * @return mixed|string
+ */
+ public function afterGetSwatchAttributeImage(SwatchMediaHelper $subject, $result, $swatchType)
+ {
+ $this->authorise();
+ if ($this->_authorised && $this->_configuration->isLoadSwatchesFromCloudinary()) {
+ $swatchTypes = ['swatch_image', 'swatch_thumb'];
+ // Check if the file path is valid
+ if (in_array($swatchType, $swatchTypes) && strpos($result, '/') !== false) {
+
+ $parsedUrl = parse_url($result, PHP_URL_PATH);
+ $absolutePath = $this->mediaDirectory->getAbsolutePath() . $parsedUrl;
+ $imageConfig = $this->getImageConfig();
+
+ $image = $this->imageFactory->create($absolutePath);
+ $imageId = pathinfo($result, PATHINFO_FILENAME);
+ $transformations = [
+ 'fetch_format' => $this->_configuration->getFetchFormat(),
+ 'quality' => $this->_configuration->getImageQuality(),
+ 'width' => $image->getOriginalWidth() ?? $imageConfig[$swatchType]['width'],
+ 'height' => $image->getOriginalHeight() ?? $imageConfig[$swatchType]['height']
+ ];
+ try {
+ $image = Media::fromParams(
+ $imageId,
+ [
+ 'transformation' => $transformations,
+ 'secure' => true,
+ 'sign_url' => $this->_configuration->getUseSignedUrls(),
+ 'version' => 1
+ ]
+ ) . '?_i=AB';
+
+
+ return $image;
+ } catch (\Exception $e) {
+ throw new LocalizedException($e->getMessage());
+ }
+
+ }
+ }
+ // return result is fallback to default behavior
+ return $result;
+ }
+}
diff --git a/Plugin/Theme/Block/Html/Header/Logo.php b/Plugin/Theme/Block/Html/Header/Logo.php
new file mode 100644
index 00000000..84731ca6
--- /dev/null
+++ b/Plugin/Theme/Block/Html/Header/Logo.php
@@ -0,0 +1,367 @@
+configuration = $configuration;
+ $this->cloudinaryImageManager = $cloudinaryImageManager;
+ $this->configurationBuilder = $configurationBuilder;
+ $this->urlGenerator = $urlGenerator;
+ $this->filesystem = $filesystem;
+ $this->scopeConfig = $scopeConfig;
+ $this->urlBuilder = $urlBuilder;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Plugin to intercept logo loading and serve from Cloudinary
+ *
+ * @param \Magento\Theme\Block\Html\Header\Logo $subject
+ * @param callable $proceed
+ * @return string
+ */
+ public function aroundGetLogoSrc(
+ \Magento\Theme\Block\Html\Header\Logo $subject,
+ callable $proceed
+ ) {
+ // Check if Cloudinary is enabled
+ if (!$this->configuration->isEnabled()) {
+ return $proceed();
+ }
+
+ try {
+ // Get the original logo URL first
+ $originalLogoUrl = $proceed();
+
+ // Get logo path from configuration
+ $logoPath = $this->scopeConfig->getValue(
+ 'design/header/logo_src',
+ ScopeInterface::SCOPE_STORE
+ );
+
+ if (!$logoPath) {
+ // If no custom logo is set, use the original URL
+ return $this->processLogoFromUrl($originalLogoUrl);
+ }
+
+ // The logo path configuration doesn't include the 'logo/' prefix, so we need to add it
+ $fullLogoPath = 'logo/' . $logoPath;
+
+ // Create a unique public ID for the logo (without logo/ prefix since we add it later)
+ $publicId = pathinfo($logoPath, PATHINFO_FILENAME);
+
+ // Check if logo exists on Cloudinary
+ $cloudinaryUrl = $this->checkAndUploadToCloudinary($fullLogoPath, $publicId, $originalLogoUrl);
+
+ if ($cloudinaryUrl) {
+ return $cloudinaryUrl;
+ }
+ } catch (\Exception $e) {
+ $this->logger->error('Cloudinary Logo Plugin Error: ' . $e->getMessage());
+ }
+
+ // Fallback to original logo URL if anything fails
+ return $proceed();
+ }
+
+ /**
+ * Check if image exists on Cloudinary and upload if necessary
+ *
+ * @param string $logoPath
+ * @param string $publicId
+ * @param string $originalUrl
+ * @return string|null
+ */
+ private function checkAndUploadToCloudinary($logoPath, $publicId, $originalUrl)
+ {
+ try {
+ // Initialize Cloudinary configuration
+ Configuration::instance($this->configurationBuilder->build());
+
+ // Check if the image exists on Cloudinary
+ $existingUrl = $this->checkCloudinaryImageExists($publicId);
+
+ if (!$existingUrl) {
+ // Image doesn't exist on Cloudinary, upload it
+ $mediaDirectory = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA);
+ $logoFullPath = null;
+
+ if ($logoPath && $mediaDirectory->isFile($logoPath)) {
+ $logoFullPath = $mediaDirectory->getAbsolutePath($logoPath);
+ } else {
+ // If the file doesn't exist in media, try to download from original URL
+ $logoFullPath = $this->downloadLogoFromUrl($originalUrl);
+ if (!$logoFullPath) {
+ return null;
+ }
+ }
+
+ // Upload to Cloudinary with specific public_id
+ $uploader = new UploadApi($this->configuration->getCredentials());
+ $uploadOptions = array_merge(
+ $this->configuration->getUploadConfig()->toArray(),
+ [
+ 'public_id' => 'logo/' . $publicId,
+ 'overwrite' => true,
+ 'resource_type' => 'image'
+ ]
+ );
+
+ $uploadResult = $uploader->upload($logoFullPath, $uploadOptions);
+
+ if (isset($uploadResult['secure_url'])) {
+ // Add Magento plugin metadata
+ if (isset($uploadResult['public_id'])) {
+ $metadata = "cld_mag_plugin=1";
+ $uploader->addContext($metadata, [$uploadResult['public_id']]);
+ }
+
+ $this->logger->info('Logo uploaded to Cloudinary with public_id: ' . $publicId);
+
+ // Clean up temp file if it was downloaded
+ if (strpos($logoFullPath, '/tmp/') !== false) {
+ try {
+ unlink($logoFullPath);
+ } catch (\Exception $unlinkException) {
+ $this->logger->debug('Could not delete temporary logo file: ' . $unlinkException->getMessage());
+ }
+ }
+
+ return $uploadResult['secure_url'];
+ }
+ } else {
+ // Image exists on Cloudinary, return its URL
+ $this->logger->info('Logo already exists on Cloudinary with public_id: ' . $publicId);
+ return $existingUrl;
+ }
+ } catch (\Exception $e) {
+ $this->logger->error('Error uploading logo to Cloudinary: ' . $e->getMessage());
+ }
+
+ return null;
+ }
+
+ /**
+ * Check if image exists on Cloudinary
+ *
+ * @param string $publicId
+ * @return string|false
+ */
+ private function checkCloudinaryImageExists($publicId)
+ {
+ try {
+ // Initialize AdminApi to check resource existence
+ $adminApi = new AdminApi($this->configuration->getCredentials());
+
+ // Build the full public_id with folder
+ $fullPublicId = 'logo/' . $publicId;
+
+ try {
+ // Try to get the resource details
+ $result = $adminApi->asset($fullPublicId, ['resource_type' => 'image']);
+
+ if (isset($result['secure_url'])) {
+ $imageUrl = $result['secure_url'];
+
+ // Verify the image is actually accessible by making a HEAD request
+ if ($this->verifyImageAccessibility($imageUrl)) {
+ return $imageUrl;
+ } else {
+ $this->logger->info('Cloudinary image URL not accessible, will re-upload: ' . $imageUrl);
+ return false;
+ }
+ }
+ } catch (\Exception $e) {
+ // Resource doesn't exist, which is expected if not uploaded yet
+ $this->logger->debug('Image not found on Cloudinary: ' . $fullPublicId);
+ }
+ } catch (\Exception $e) {
+ $this->logger->debug('Error checking Cloudinary image existence: ' . $e->getMessage());
+ }
+
+ return false;
+ }
+
+ /**
+ * Verify if an image URL is accessible
+ *
+ * @param string $url
+ * @return bool
+ */
+ private function verifyImageAccessibility($url)
+ {
+ try {
+ $context = stream_context_create([
+ 'http' => [
+ 'method' => 'HEAD',
+ 'timeout' => 5,
+ ],
+ 'ssl' => [
+ 'verify_peer' => false,
+ 'verify_peer_name' => false,
+ ]
+ ]);
+
+ $headers = false;
+ try {
+ $headers = get_headers($url, 1, $context);
+ } catch (\Exception $headerException) {
+ $this->logger->debug('Could not retrieve headers for URL: ' . $headerException->getMessage());
+ }
+
+ if ($headers && isset($headers[0])) {
+ // Check for successful HTTP status codes (200, 201, etc.)
+ return strpos($headers[0], '200') !== false || strpos($headers[0], '201') !== false;
+ }
+ } catch (\Exception $e) {
+ $this->logger->debug('Error verifying image accessibility: ' . $e->getMessage());
+ }
+
+ return false;
+ }
+
+ /**
+ * Process logo from URL when no custom logo is set
+ *
+ * @param string $originalUrl
+ * @return string
+ */
+ private function processLogoFromUrl($originalUrl)
+ {
+ try {
+ // Extract filename from URL
+ $urlParts = parse_url($originalUrl);
+ $path = $urlParts['path'] ?? '';
+ $filename = pathinfo($path, PATHINFO_FILENAME);
+
+ if ($filename) {
+ $publicId = 'logo/' . $filename;
+
+ // Check and upload to Cloudinary
+ $cloudinaryUrl = $this->checkAndUploadToCloudinary('', $publicId, $originalUrl);
+
+ if ($cloudinaryUrl) {
+ return $cloudinaryUrl;
+ }
+ }
+ } catch (\Exception $e) {
+ $this->logger->error('Error processing logo from URL: ' . $e->getMessage());
+ }
+
+ return $originalUrl;
+ }
+
+ /**
+ * Download logo from URL to temporary location
+ *
+ * @param string $url
+ * @return string|null
+ */
+ private function downloadLogoFromUrl($url)
+ {
+ try {
+ $tempDir = $this->filesystem->getDirectoryWrite(DirectoryList::TMP);
+ $extension = pathinfo($url, PATHINFO_EXTENSION) ?: 'svg';
+ $filename = 'logo_' . uniqid() . '.' . $extension;
+ $tempPath = $tempDir->getAbsolutePath($filename);
+
+ // Create context for HTTPS requests to disable SSL verification for local development
+ $context = stream_context_create([
+ 'http' => [
+ 'timeout' => 10,
+ 'method' => 'GET',
+ ],
+ 'ssl' => [
+ 'verify_peer' => false,
+ 'verify_peer_name' => false,
+ ]
+ ]);
+
+ $logoContent = file_get_contents($url, false, $context);
+ if ($logoContent) {
+ file_put_contents($tempPath, $logoContent);
+ return $tempPath;
+ }
+ } catch (\Exception $e) {
+ $this->logger->error('Error downloading logo from URL: ' . $e->getMessage());
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/Plugin/Ui/Component/Form/Element/DataType/Media.php b/Plugin/Ui/Component/Form/Element/DataType/Media.php
new file mode 100644
index 00000000..9e3ce4f4
--- /dev/null
+++ b/Plugin/Ui/Component/Form/Element/DataType/Media.php
@@ -0,0 +1,102 @@
+mediaLibraryHelper = $mediaLibraryHelper;
+ $this->appState = $appState;
+ $this->productMetadata = $productMetadata;
+ }
+
+ protected function isGallerySupported()
+ {
+ if (version_compare($this->productMetadata->getVersion(), self::GALLERY_SUPPORTED_BELOW_VER, '>=')) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Prepare component configuration
+ *
+ * @param \Magento\Ui\Component\Form\Element\DataType\Media $component
+ * @param mixed $result
+ * @return void
+ */
+ public function afterPrepare(\Magento\Ui\Component\Form\Element\DataType\Media $component, $result = null)
+ {
+ if ($this->appState->getAreaCode() === Area::AREA_ADMINHTML
+ && ($cloudinaryMLoptions = $this->mediaLibraryHelper->getCloudinaryMLOptions(false))
+ && ($uploaderConfigUrl = $component->getData('config/uploaderConfig/url'))
+ ) {
+
+ if (strpos($uploaderConfigUrl, '/design_config_fileUploader/') !== false) {
+ $type = 'design_config_fileUploader';
+ } elseif (strpos($uploaderConfigUrl, '/category_image/') !== false) {
+ $type = 'category_image';
+ } elseif (strpos($uploaderConfigUrl, '/pagebuilder/contenttype/') !== false) {
+ $type = 'pagebuilder_contenttype';
+ } else {
+ $type = null;
+ }
+ if ($type) {
+ $config = [
+ 'config' => [
+ 'template' => 'Cloudinary_Cloudinary/form/element/uploader/uploader',
+ 'component' => 'Cloudinary_Cloudinary/js/form/element/file-uploader',
+ 'cloudinaryMLoptions' => [
+ 'imageUploaderUrl' => $component->getContext()->getUrl('cloudinary/ajax/retrieveImage', ['_secure' => true, 'type' => $type]),
+ 'addTmpExtension' => false,
+ 'cloudinaryMLoptions' => $cloudinaryMLoptions,
+ 'cloudinaryMLshowOptions' => $this->mediaLibraryHelper->getCloudinaryMLshowOptions("image")
+ ]
+ ]
+ ];
+
+
+ $component->setData(array_replace_recursive(
+ $component->getData(),
+ $config
+ ));
+ }
+ }
+ return $result;
+ }
+}
diff --git a/Plugin/Ui/Component/Form/Element/DataType/Media/Image.php b/Plugin/Ui/Component/Form/Element/DataType/Media/Image.php
new file mode 100644
index 00000000..7ca7104a
--- /dev/null
+++ b/Plugin/Ui/Component/Form/Element/DataType/Media/Image.php
@@ -0,0 +1,32 @@
+getData('config/cloudinaryMLoptions')) {
+ $component->setData(array_replace_recursive(
+ $component->getData(),
+ [
+ 'config' => [
+ 'template' => 'Cloudinary_Cloudinary/form/element/uploader/image',
+ 'component' => 'Cloudinary_Cloudinary/js/form/element/image-uploader',
+ ]
+ ]
+ ));
+ }
+ return $result;
+ }
+}
diff --git a/Plugin/ValidateProductTransform.php b/Plugin/ValidateProductTransform.php
deleted file mode 100644
index 6afd70e3..00000000
--- a/Plugin/ValidateProductTransform.php
+++ /dev/null
@@ -1,43 +0,0 @@
-helper = $helper;
- }
-
- /**
- * @param Product $product
- * @param mixed $result
- * @return mixed
- */
- public function afterBeforeSave(Product $product, $result)
- {
- $mediaGalleryImages = $this->helper->getMediaGalleryImages($product);
-
- $changedTransforms = $this->helper->filterUpdated(
- $product->getCloudinaryFreeTransform(),
- $product->getCloudinaryFreeTransformChanges()
- );
-
- foreach ($changedTransforms as $id => $transform) {
- $this->helper->validate($this->helper->getImageNameForId($id, $mediaGalleryImages), $transform);
- }
-
- return $result;
- }
-}
diff --git a/Plugin/Widget/Model/Template/Filter.php b/Plugin/Widget/Model/Template/Filter.php
new file mode 100644
index 00000000..4c61bb2d
--- /dev/null
+++ b/Plugin/Widget/Model/Template/Filter.php
@@ -0,0 +1,102 @@
+_imageFactory = $imageFactory;
+ $this->_urlGenerator = $urlGenerator;
+ $this->_configuration = $configuration;
+ $this->_cloudinaryWidgetFilter = $cloudinaryWidgetFilter;
+ $this->_coreRegistry = $coreRegistry;
+ }
+
+ /**
+ * Around retrieve media file URL directive
+ *
+ * @param \Magento\Widget\Model\Template\Filter $widgetFilter
+ * @param callable $proceed
+ * @param string[] $construction
+ * @return string
+ */
+ public function aroundMediaDirective(\Magento\Widget\Model\Template\Filter $widgetFilter, callable $proceed, $construction)
+ {
+ if (!$this->_configuration->isEnabled()) {
+ return $proceed($construction);
+ }
+
+ $params = $this->_cloudinaryWidgetFilter->getParams($construction[2]);
+ if (!isset($params['url'])) {
+ return $proceed($construction);
+ }
+
+ $url = (preg_match('/^".+"$/', $params['url'])) ? preg_replace('/(^")|("$)/', '', $params['url']) : $params['url'];
+
+ $image = $this->_imageFactory->build(
+ $url,
+ function () use ($proceed, $construction) {
+ return $proceed($construction);
+ }
+ );
+
+ $generated = $this->_urlGenerator->generateFor($image);
+
+ if ($this->_configuration->isEnabledLazyload() && $this->_configuration->isLazyloadAutoReplaceCmsBlocks()) {
+ $this->_coreRegistry->register('cloudinary_generated_' . hash('sha256', $generated), $image, true);
+ }
+
+ return $generated;
+ }
+}
diff --git a/README.md b/README.md
index d8cb7fb6..fef8ae6d 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,27 @@
-# Magento 2 [Cloudinary](https://www.cloudinary.com/) Module
-
-Magento 2 module for integration with Cloudinary.
-
----
-
-## ✓ Install via composer (recommended)
-Run the following command under your Magento 2 root dir:
-
+# Cloudinary Magento 2 Extension
+
+The Cloudinary Magento extension links your Magento website to your Cloudinary account, allowing you to serve all your product, category, and content management system (CMS) images directly from Cloudinary.
+
+Before you install the extension, make sure you have a Cloudinary account. You can start by [signing up](https://cloudinary.com/users/register_free?utm_source=magento-2-git-page&utm_medium=affiliate&utm_content=sign-up&utm_campaign=1975) for a free plan. When your requirements grow, you can upgrade to a [plan](https://cloudinary.com/pricing) that best fits your needs.
+
+For more information on using the Cloudinary Magento 2 extension, take a look at our [documentation](https://cloudinary.com/documentation/magento_integration).
+
+## Installation
+
+You can download and install the extension from the [Magento Marketplace](https://marketplace.magento.com/cloudinary-cloudinary.html) or install it via composer by running the following commands under your Magento 2 root dir:
+
```
-composer require cloudinary/magento2-module-cloudinary
+composer require cloudinary/cloudinary-magento2
+php bin/magento maintenance:enable
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy
+php bin/magento maintenance:disable
php bin/magento cache:flush
```
-
----
-
+
https://www.cloudinary.com/
-Copyright © 2018 Cloudinary. All rights reserved.
-
-
+Copyright © 2020 Cloudinary. All rights reserved.
+
+
diff --git a/Setup/InstallSchema.php b/Setup/InstallSchema.php
deleted file mode 100644
index 500da405..00000000
--- a/Setup/InstallSchema.php
+++ /dev/null
@@ -1,43 +0,0 @@
-startSetup();
-
- /**
- * Create table 'cloudinary_synchronisation'
- */
- $table = $setup->getConnection()->newTable(
- $setup->getTable('cloudinary_synchronisation')
- )->addColumn(
- 'cloudinary_synchronisation_id',
- Table::TYPE_INTEGER,
- null,
- ['identity' => true, 'nullable' => false, 'primary' => true, 'unsigned' => true],
- 'Cloudinary Synchronisation ID'
- )->addColumn(
- 'image_path',
- Table::TYPE_TEXT,
- 255,
- ['nullable' => false],
- 'Image Path'
- );
-
- $setup->getConnection()->createTable($table);
-
- $setup->endSetup();
- }
-}
diff --git a/Setup/Patch/Data/DataPatch.php b/Setup/Patch/Data/DataPatch.php
new file mode 100644
index 00000000..eb6d664c
--- /dev/null
+++ b/Setup/Patch/Data/DataPatch.php
@@ -0,0 +1,110 @@
+moduleDataSetup = $moduleDataSetup;
+ $this->resourceConnection = $resourceConnection;
+ $this->output = $output;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function apply()
+ {
+ $this->moduleDataSetup->getConnection()->startSetup();
+
+ if (version_compare($this->getVersion(), '1.6.3') < 0) {
+ if ($this->getVersion()) {
+ $this->output->writeln("Reseting configurations for 'website' & 'store' scopes (only supports 'default' at the moment) ");
+ }
+
+ $this->resourceConnection->getConnection()->delete(
+ $this->resourceConnection->getTableName('core_config_data'),
+ "path LIKE 'cloudinary/%' AND scope != 'default'"
+ );
+ }
+
+ $this->moduleDataSetup->getConnection()->endSetup();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function revert()
+ {
+ $this->moduleDataSetup->getConnection()->startSetup();
+ //Here should go code that will revert all operations from `apply` method
+ //Please note, that some operations, like removing data from column, that is in role of foreign key reference
+ //is dangerous, because it can trigger ON DELETE statement
+ $this->moduleDataSetup->getConnection()->endSetup();
+ }
+
+ public static function getDependencies()
+ {
+ /**
+ * This is dependency to another patch. Dependency should be applied first
+ * One patch can have few dependencies
+ * Patches do not have versions, so if in old approach with Install/Ugrade data scripts you used
+ * versions, right now you need to point from patch with higher version to patch with lower version
+ * But please, note, that some of your patches can be independent and can be installed in any sequence
+ * So use dependencies only if this important for you
+ */
+ return [];
+ }
+
+ public function getAliases()
+ {
+ /**
+ * This internal Magento method, that means that some patches with time can change their names,
+ * but changing name should not affect installation process, that's why if we will change name of the patch
+ * we will add alias here
+ */
+ return [];
+ }
+
+ public static function getVersion()
+ {
+ return '1.18.0';
+ }
+}
diff --git a/Setup/UpgradeSchema.php b/Setup/UpgradeSchema.php
deleted file mode 100644
index d3175e9a..00000000
--- a/Setup/UpgradeSchema.php
+++ /dev/null
@@ -1,50 +0,0 @@
-startSetup();
-
- if (version_compare($context->getVersion(), '1.5.0', '<=')) {
- $this->createTransformationTable($setup);
- }
-
- $setup->endSetup();
- }
-
- /**
- * @param SchemaSetupInterface $setup
- */
- private function createTransformationTable(SchemaSetupInterface $setup)
- {
- $table = $setup->getConnection()->newTable(
- $setup->getTable('cloudinary_transformation')
- )->addColumn(
- 'image_name',
- Table::TYPE_TEXT,
- 255,
- ['nullable' => false, 'primary' => true],
- 'Relative image path'
- )->addColumn(
- 'free_transformation',
- Table::TYPE_TEXT,
- 255,
- [],
- 'Free transformation'
- );
-
- $setup->getConnection()->createTable($table);
- }
-}
diff --git a/Ui/Component/Control/AddFromCloudinary.php b/Ui/Component/Control/AddFromCloudinary.php
new file mode 100644
index 00000000..04789f21
--- /dev/null
+++ b/Ui/Component/Control/AddFromCloudinary.php
@@ -0,0 +1,68 @@
+images = $images;
+ $this->authorization = $authorization;
+ $this->cmsWysiwygImages = $cmsWysiwygImages;
+ }
+ /**
+ * @inheritdoc
+ */
+ public function getButtonData(): array
+ {
+ $cloudinaryMLwidgetOprions = ($this->images->getCloudinaryMediaLibraryWidgetOptions())
+ ? json_decode($this->images->getCloudinaryMediaLibraryWidgetOptions(),true)
+ : null;
+
+ $buttonData = [
+ 'label' => __('Add From Cloudinary'),
+ 'class' => 'action-secondary add-from-cloudinary-button cloudinary-button-with-logo lg-margin-bottom',
+ 'on_click' => 'return false;',
+ 'data_attribute' => [
+ 'mage-init' => ['cloudinaryMediaLibraryModal' => $cloudinaryMLwidgetOprions],
+ 'role' => 'add-from-cloudinary-button',
+
+ ],
+ 'sort_order' => 200,
+ ];
+
+ if (!$this->authorization->isAllowed(self::ACL_UPLOAD_ASSETS)) {
+ $buttonData['disabled'] = 'disabled';
+ }
+
+ return $buttonData;
+ }
+}
diff --git a/Ui/DataProvider/Product/Form/Modifier/Product.php b/Ui/DataProvider/Product/Form/Modifier/Product.php
index 7bfc78c1..71b5943c 100644
--- a/Ui/DataProvider/Product/Form/Modifier/Product.php
+++ b/Ui/DataProvider/Product/Form/Modifier/Product.php
@@ -2,17 +2,17 @@
namespace Cloudinary\Cloudinary\Ui\DataProvider\Product\Form\Modifier;
-use Cloudinary\Cloudinary\Core\Image;
use Cloudinary\Cloudinary\Core\CloudinaryImageProvider;
use Cloudinary\Cloudinary\Core\ConfigurationInterface;
+use Cloudinary\Cloudinary\Core\Image;
use Cloudinary\Cloudinary\Core\Image\Transformation;
use Cloudinary\Cloudinary\Core\Image\Transformation\Freeform;
-use Magento\Catalog\Model\Locator\LocatorInterface;
-use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
-use Magento\Ui\Component\Form;
use Cloudinary\Cloudinary\Model\TransformationFactory;
+use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Catalog\Model\Product\Gallery\Entry;
+use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Framework\UrlInterface;
+use Magento\Ui\Component\Form;
class Product extends AbstractModifier
{
@@ -48,7 +48,7 @@ class Product extends AbstractModifier
protected $cloudinaryImageProvider;
/**
- * @param LocatorInterface $locator
+ * @param LocatorInterface $locator
* @param TransformationFactory $transformationFactory
*/
public function __construct(
@@ -102,11 +102,10 @@ public function modifyMeta(array $meta)
'collapsible' => true,
'opened' => false,
'componentType' => Form\Fieldset::NAME,
- 'sortOrder' =>
- $this->getNextGroupSortOrder(
- $meta,
- static::GROUP_CONTENT,
- static::SORT_ORDER
+ 'sortOrder' => $this->getNextGroupSortOrder(
+ $meta,
+ static::GROUP_CONTENT,
+ static::SORT_ORDER
),
],
],
@@ -154,13 +153,13 @@ private function isCloudinaryModuleActive()
}
/**
- * @param [Entry]
+ * @param [Entry]
* @return [Entry]
*/
private function extractData(array $images)
{
return array_map(
- function($media) {
+ function ($media) {
return $media->getData();
},
$images
@@ -168,21 +167,21 @@ function($media) {
}
/**
- * @param [Entry]
+ * @param [Entry]
* @return [Entry]
*/
private function filterNonImageTypes(array $images)
{
return array_filter(
$images,
- function($image) {
+ function ($image) {
return $image->getMediaType() === 'image';
}
);
}
/**
- * @param [Entry]
+ * @param [Entry]
* @return [Entry]
*/
private function injectFreeTransformations(array $images)
@@ -197,7 +196,7 @@ private function injectFreeTransformations(array $images)
}
/**
- * @param [Entry]
+ * @param [Entry]
* @return [Entry]
*/
private function injectImageUrls(array $images)
@@ -218,12 +217,12 @@ private function injectImageUrls(array $images)
}
/**
- * @param string $freeTransform
+ * @param string $freeTransform
* @return Transformation
*/
private function defaultTransformWithFreeTransform($freeTransform)
{
- $transformation = $this->configuration->getDefaultTransformation();
+ $transformation = $this->configuration->getDefaultTransformation(true);
if ($freeTransform) {
$transformation->withFreeform(Freeform::fromString($freeTransform));
diff --git a/composer.json b/composer.json
index 4d0b24e9..da31abb7 100644
--- a/composer.json
+++ b/composer.json
@@ -1,54 +1,12 @@
{
- "name": "cloudinary/magento2-module-cloudinary",
+ "name": "cloudinary/cloudinary-magento2",
"description": "Cloudinary Magento 2 Integration.",
"type": "magento2-module",
- "version": "1.6.0",
- "minimum-stability": "dev",
- "repositories": {
- "0": {
- "type": "git",
- "url": "git@github.com:cloudinary/cloudinary_magento2.git"
- }
- },
+ "version": "2.1.3",
+ "license": "MIT",
"require": {
- "php": ">=5.5",
- "cloudinary/cloudinary_php": "1.8.0"
- },
- "require-dev": {
- "phpspec/phpspec": "^3.0",
- "behat/behat": "~3.0.15",
- "sensiolabs/behat-page-object-extension": "2.0.*@dev",
- "behat/mink-selenium2-driver": "*",
- "behat/mink-goutte-driver": "^1.0",
- "squizlabs/php_codesniffer": "1.*",
- "phpmd/phpmd": "1.*",
- "sebastian/phpcpd": "2.*",
- "pdepend/pdepend": "1.*",
- "phploc/phploc": "2.*",
- "theseer/phpdox": "0.6.*",
- "theseer/fxsl": "1.0.*@dev",
- "covex-nn/phpcb": "1.0.*@dev",
- "bossa/phpspec2-expect": "^2.0",
- "bex/behat-magento2-init": "dev-master",
- "bex/behat-browser-initialiser": "^1.0",
- "bex/behat-screenshot": "^1.0"
- },
- "config": {
- "bin-dir": "bin",
- "use-include-path": true
- },
- "autoload-dev": {
- "psr-0": {
- "": [
- "features/bootstrap",
- "features/fixtures",
- "/app/app/code/"
- ]
- },
- "psr-4": {
- "Magento\\Framework\\": "/app/vendor/magento/framework/",
- "Magento\\Catalog\\": "/app/vendor/magento/module-catalog"
- }
+ "php": ">=7.3",
+ "cloudinary/cloudinary_php": "^3.0 || >=2.7 <=2.11.0"
},
"autoload": {
"files": [
@@ -57,5 +15,9 @@
"psr-4": {
"Cloudinary\\Cloudinary\\": ""
}
- }
-}
\ No newline at end of file
+ },
+ "repositories": [{
+ "type": "git",
+ "url": "https://github.com/cloudinary/cloudinary_magento2"
+ }]
+}
diff --git a/etc/acl.xml b/etc/acl.xml
new file mode 100644
index 00000000..b73a7eca
--- /dev/null
+++ b/etc/acl.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/etc/adminhtml/di.xml b/etc/adminhtml/di.xml
index 655f6195..5c754716 100644
--- a/etc/adminhtml/di.xml
+++ b/etc/adminhtml/di.xml
@@ -12,5 +12,13 @@
+
+
+
+
diff --git a/etc/adminhtml/events.xml b/etc/adminhtml/events.xml
index e5721cf2..748cd58a 100644
--- a/etc/adminhtml/events.xml
+++ b/etc/adminhtml/events.xml
@@ -1,6 +1,10 @@
+
+
+
+
@@ -10,8 +14,4 @@
-
-
-
-
diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml
index 5175e4a0..b7a83425 100644
--- a/etc/adminhtml/system.xml
+++ b/etc/adminhtml/system.xml
@@ -2,64 +2,501 @@
-
+
Cloudinary
- service
+
+
+ Settings
+ cloudinary
Cloudinary_Cloudinary::config_cloudinary
-
- Enable module
-
- Enable cloudinary
+
+ Enable Extension
+ 1
+
+ Enable Cloudinary
Magento\Config\Model\Config\Source\Yesno
+
+ Module Version
+ Cloudinary\Cloudinary\Block\Adminhtml\System\Config\ModuleVersion
+
-
- Cloudinary Setup
-
+
+ Cloudinary Account
+ 1
+
Cloudinary Account Credentials
- Set the credentials of your Cloudinary account. Copy the "Environment variable" string from the dashboard of Cloudinary's Management Console.
+
+ API environment variable format from the API Keys page. Replace [your_api_key] and [your_api_secret] with your actual values, while your cloud name is already correctly included in the format.]]>
+
Cloudinary\Cloudinary\Model\Config\Backend\Credentials
-
- Cloudinary Configuration
-
+
+ Cloudinary Setup
+ 1
+
Image Delivery Domain Sharding
Enable multiple sub-domains of image delivery URLs for faster page load speed.
Magento\Config\Model\Config\Source\Yesno
-
- Use auto upload mapping to upload images
- When enabled, Cloudinary will fetch images it does not have from your site automatically without requiring the manual migration process.
+
+ Enable auto-upload
+ Automatically upload your existing Magento images to your Cloudinary account when an image is requested by a user. Once enabled, click the button below to map your Magento media directory to your Cloudinary account.
+ Note: Disabling auto upload mapping from Magento will not remove the configuration from Cloudinary. To completely disable your auto upload mapping, navigate to your Cloudinary Upload settings and clear the Auto upload mapping that corresponds to your Magento URLs.
]]>
+
Magento\Config\Model\Config\Source\Yesno
+
+ Cloudinary\Cloudinary\Block\Adminhtml\System\Config\AutoUploadMapping
+
+ 1
+ 1
+
+
-
+
Default Image Transformations
-
+ 0
+
Automatic Image Format Optimisation
Automatically deliver images converted to modern image formats based on viewing device and browser. For example, deliver WebP on Chrome and JPEG-XR on Internet Explorer for better performance and user experience.
Magento\Config\Model\Config\Source\Yesno
-
+
Image Quality
- Adjust quality of generated images to balance between visual quality and file size minimization. The quality is relevant for JPEG and WebP compression levels for example.
+ Adjust the quality of generated images to balance visual quality and file size. The quality is relevant for JPEG and WebP compression levels for example.
Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Quality
-
+
Image Cropping Gravity
- Define the part of the image to focus on when cropping images in order to better match your graphic design.
+ documentation.]]>
Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Gravity
-
+
Image Device Pixel Ratio (DPR)
- Use DPR value higher than 1.0 to generate and deliver hi-res images for better visual result on HiDPI devices, such as Retina Display devices (e.g., 2.0).
+ Set the DPR value for your images. A DPR value of 2.0 will generate and deliver hi-res images for better results on HiDPI devices such as Retina Displays.
Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Dpr
-
- Global custom transform
+
+ Default image
+ The public ID and file extension of the image to use as a placeholder when an asset no longer exists (e.g. sample.jpg).
+
+
+ Global Custom Transformation
+ Custom transformations will be added to the default image transformations settings chosen above. For information about the full range of transforms available see the Cloudinary documentation . You may need to clear or rebuild the Magento block and full page caches to see the changes in the front end.]]>
+ Cloudinary\Cloudinary\Model\Config\Backend\Free
+ cloudinary/transformations/cloudinary_free_transform_global
+
+
+ Products Custom Transformation
+ for products only.
+ *Set the behavior of this field on the dropdown below.]]>
Cloudinary\Cloudinary\Model\Config\Backend\Free
+ cloudinary/transformations/cloudinary_free_transform_global_products
+
+
+ Products Custom Transformation Behavior
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\FreeTransformBehavior
Cloudinary\Cloudinary\Block\Adminhtml\Form\Field\Free
+ cloudinary/transformations/cloudinary_free_transform_global_products_behavior
+
+
+
+ Product Gallery
+ 0
+
+ Enable Cloudinary's Product Gallery
+ Product Gallery documentation for information on the configuration options. *To enable the Product Gallery to access and load your media, ensure that you remove any restrictions on listing resources. To verify this, open the Cloudinary Console , navigate to Settings and select the Security tab. From here, make sure that “Resource list” is unchecked in the “Restricted media types” option group.]]>
+ Magento\Config\Model\Config\Source\Yesno
+ cloudinary/product_gallery/enabled
+
+
+ Theme Colors
+ 1
+
+ 1
+
+
+ Primary
+ cloudinary/product_gallery/themeProps_primary
+ Cloudinary\Cloudinary\Block\Adminhtml\System\Config\Form\Field\ColorPicker
+ validate-length minimum-length-4 maximum-length-7
+
+
+ onPrimary
+ cloudinary/product_gallery/themeProps_onPrimary
+ Cloudinary\Cloudinary\Block\Adminhtml\System\Config\Form\Field\ColorPicker
+ validate-length minimum-length-4 maximum-length-7
+
+
+ Active
+ cloudinary/product_gallery/themeProps_active
+ Cloudinary\Cloudinary\Block\Adminhtml\System\Config\Form\Field\ColorPicker
+ validate-length minimum-length-4 maximum-length-7
+
+
+ onActive
+ cloudinary/product_gallery/themeProps_onActive
+ Cloudinary\Cloudinary\Block\Adminhtml\System\Config\Form\Field\ColorPicker
+ validate-length minimum-length-4 maximum-length-7
+
+
+
+ Main Viewer Parameters
+ 1
+
+ 1
+
+
+ Fade Transition
+ cloudinary/product_gallery/transition
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\Transition
+
+
+ Aspect Ratio
+ cloudinary/product_gallery/aspectRatio
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\AspectRatio
+
+
+ Navigation
+ cloudinary/product_gallery/navigation
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\Navigation
+
+
+ Show Zoom
+ cloudinary/product_gallery/zoom
+ Magento\Config\Model\Config\Source\Yesno
+
+
+ Zoom Type
+ cloudinary/product_gallery/zoomProps_type
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ZoomType
+ NOTICE: Popup type supports only 'click' trigger type.
+
+ 1
+
+
+
+ Zoom Viewer Position
+ cloudinary/product_gallery/zoomProps_viewerPosition
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ZoomViewerPosition
+
+ 1
+ flyout
+
+
+
+ Zoom Trigger
+ cloudinary/product_gallery/zoomProps_trigger
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ZoomTrigger
+
+ 1
+
+
+
+
+ Carousel Parameters
+ 1
+
+ 1
+
+
+ Carousel Location
+ cloudinary/product_gallery/carouselLocation
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\CarouselLocation
+
+
+ Carousel Offset
+ cloudinary/product_gallery/carouselOffset
+ validate-number
+
+
+ Carousel Style
+ cloudinary/product_gallery/carouselStyle
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\CarouselStyle
+
+
+ Thumbnail Width
+ cloudinary/product_gallery/thumbnailProps_width
+ validate-number
+
+ thumbnails
+
+
+
+ Thumbnail Height
+ cloudinary/product_gallery/thumbnailProps_height
+ validate-number
+
+ thumbnails
+
+
+
+ Navigation Button Shape
+ cloudinary/product_gallery/thumbnailProps_navigationShape
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ThumbnailsNavigationShape
+
+ thumbnails
+
+
+
+ Thumbnail Selected Style
+ cloudinary/product_gallery/thumbnailProps_selectedStyle
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ThumbnailsSelectedStyle
+
+ thumbnails
+
+
+
+ Thumbnail Selected Border Position
+ cloudinary/product_gallery/thumbnailProps_selectedBorderPosition
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ThumbnailsSelectedBorderPosition
+
+ thumbnails
+
+
+
+ Thumbnail Selected Border Width
+ cloudinary/product_gallery/thumbnailProps_selectedBorderWidth
+ validate-number
+
+ thumbnails
+
+
+
+ Thumbnail Media Icon Shape
+ cloudinary/product_gallery/thumbnailProps_mediaSymbolShape
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ThumbnailsMediaSymbolShape
+
+ thumbnails
+
+
+
+ Indicators Shape
+ cloudinary/product_gallery/indicatorProps_shape
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\IndicatorsShape
+
+ indicators
+
+
+
+
+ Custom Free Parameters (advanced)
+
+ See Product Gallery reference for full list of parameters. e.g., {"zoom": true, "thumbnailProps": {"borderColor": "#EBF0F4"}}]]>
+
+ cloudinary/product_gallery/custom_free_params
+ Cloudinary\Cloudinary\Model\Config\Backend\ProductGalleryCustomFreeParams
+
+ 1
+
+
+
+
+ Video Player Settings
+ 1
+
+ Enable
+ Magento\Config\Model\Config\Source\Yesno
+
+
+ Show controls
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Video\Controls
+ Note: If the Cloudinary Product Gallery is not enabled, the functionality operates in On/Off mode. Selecting 'All' will display controls, while choosing other options will disable the controls.
+
+ 1
+
+
+
+ Loop video
+ Magento\Config\Model\Config\Source\Yesno
+
+ 1
+
+
+
+ Autoplay
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Video\Autoplay
+ Note: When selecting ‘Always’, the video will autoplay without sound (muted). This behavior is a standard feature across all major browsers. Read more about autoplay ]]>
+
+ 1
+
+
+
+ Video delivery
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Video\StreamMode
+ ABR mode: Adaptive bitrate streaming is a video delivery technique that adjusts the quality of a video stream in real time according to detected bandwidth and CPU capacity. Read more about adaptive bitrate streaming ]]>
+
+ 1
+
+
+
+ Video format
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Video\VideoFormat
+
+ 1
+ optimization
+
+
+
+ Source types
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Video\SourceTypes
+
+ 1
+ none
+ optimization
+
+
+
+ Video quality
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Video\VideoQuality
+
+ 1
+ optimization
+
+
+
+ Streaming protocol
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Video\StreamProtocol
+
+ 1
+ abr
+
+
+
+ Video free parameters (advanced)
+ See Cloudinary Video Player Studio reference to to construct a customized JSON and modify the parameters as required.]]>
+ Cloudinary\Cloudinary\Model\Config\Backend\VideoSettingsFreeParams
+
+ 1
+
+
+
+
+ Lazyload Settings
+ 0
+
+ Enable Lazyload
+ Lazy Load Plugin for jQuery).]]>
+ cloudinary/lazyload/enabled
+ Magento\Config\Model\Config\Source\Yesno
+
+
+ Apply on CMS Blocks
+ Automatically apply lazyload on CMS blocks.
+ cloudinary/lazyload/auto_replace_cms_blocks
+ Magento\Config\Model\Config\Source\Yesno
+
+ 1
+
+
+
+ CMS Blocks to Exclude
+ List of CMS blocks to exclude from Lazyloading. Use this if there are conflicts on some of the blocks.
+ cloudinary/lazyload/ignored_cms_blocks
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\CmsBlocks
+ 1
+
+ 1
+ 1
+
+
+
+ Lazyload Threshold
+ The threshold for how many pixels from the top of the page before lazy loading is applied to your images.
+ cloudinary/lazyload/threshold
+
+ 1
+
+
+
+ Lazyload Effect
+ The effect to use to show the loaded images.
+ cloudinary/lazyload/effect
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Lazyload\Effect
+
+ 1
+
+
+
+ Lazyload Placeholder
+ The transformation effect that will be used on the images as a loading placeholder.
+ cloudinary/lazyload/placeholder
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Lazyload\Placeholder
+
+ 1
+
+
+
+
+ Advanced
+ 0
+
+ Remove version number from URLs
+ Remove version number (e.g., ".../v1/...") from URLs.
+ Magento\Config\Model\Config\Source\Yesno
+
+
+ Use Root Path
+ Remove "/image/upload/" from URLs.
+ Magento\Config\Model\Config\Source\Yesno
+
+
+ Use Signed URLs
+ Use dynamic Cloudinary delivery URLs with a signature that needs to be validated before making it available to view.
+ Magento\Config\Model\Config\Source\Yesno
+
+
+ Use Existing Cloudinary Folder
+ Map existing Cloudinary assets to their original location to prevent duplication in your Cloudinary Media Library. If set to 'No', assets will be synchronized to a new Cloudinary location based on the local Magento path.
+ Magento\Config\Model\Config\Source\Yesno
+
+
+ Process Product Gallery API Requests In The Background (add to queue)
+ When using the rest/V1/cloudinary/products API endpoint, add the requests to queue and process later using a cronjob.
+ Magento\Config\Model\Config\Source\Yesno
+
+
+ Product Gallery API Queue - Limit
+ Maximum queue items to process on each run (0 = no limit).
+
+ 1
+
+
+
+ Product Gallery API Queue - Maximum Tryouts
+ Number of times to try processing the queued item before stopping and adding an error notification on admin (Minimum: 1, Maximum: 20).
+
+ 1
+
+
+
+ Enable Product Free Transformations On Store Front
+ If not in use on your website, you may spare DB queries on the frontend by setting this to 'No'.
+ Magento\Config\Model\Config\Source\Yesno
+
+
+ Enable Product Swatch Image sync with Cloudinary
+ Magento\Config\Model\Config\Source\Yesno
+ Cloudinary\Cloudinary\Model\Config\Backend\SwatchUpload
+ If set to 'No', swatch images will be loaded exclusively from Magento.
+
+
+ Cloudinary cache placeholder Enable
+ Magento\Config\Model\Config\Source\Yesno
+ When enabled, media will be stored only on Cloudinary. Magento will keep a small placeholder file (around 1KB) in place of the full media file. This reduces local storage use and ensures assets are delivered directly from Cloudinary.
+
+
+ Custom Placeholder Image
+ Optional: Upload a small image (recommended ≤10KB) to use as the local placeholder instead of the default Cloudinary one.
+ Magento\Config\Model\Config\Backend\Image
+ cloudinary_placeholder
+ cloudinary_placeholder
+
+ 1
+
diff --git a/etc/config.xml b/etc/config.xml
index 7c0f7bdd..5cc99629 100644
--- a/etc/config.xml
+++ b/etc/config.xml
@@ -6,11 +6,82 @@
1
auto
- 1.0
+ 2.0
+ add
- 1
+ 0
+
+ 1
+ 0
+ 500
+ fadeIn
+ blur
+
+
+ 0
+ 0
+ 1
+ 10
+ 1
+ 20
+ 5
+ 1
+ 0
+
+
+ 0
+ #ffffff
+ #000000
+ #0078ff
+ #ffffff
+ slide
+ square
+ mouseover
+ 1
+ inline
+ right
+ click
+ left
+ 5
+ thumbnails
+ 64
+ 64
+ rectangle
+ all
+ left
+ 2
+ round
+ round
+ {}
+
+
+ 1
+ webm/vp9,mp4/h265,mp4/h264
+
+
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+
+ catalog
+
+ catalog/category
+ cloudinary
+ catalog/custom_folder_name
+
+
+
+
diff --git a/etc/cron_groups.xml b/etc/cron_groups.xml
new file mode 100644
index 00000000..ce8be56c
--- /dev/null
+++ b/etc/cron_groups.xml
@@ -0,0 +1,12 @@
+
+
+
+ 1
+ 4
+ 2
+ 10
+ 60
+ 600
+ 1
+
+
diff --git a/etc/crontab.xml b/etc/crontab.xml
new file mode 100644
index 00000000..4f50aa5f
--- /dev/null
+++ b/etc/crontab.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ * * * * *
+
+
+ * * * * *
+
+
+
diff --git a/etc/csp_whitelist.xml b/etc/csp_whitelist.xml
new file mode 100644
index 00000000..958a3066
--- /dev/null
+++ b/etc/csp_whitelist.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+ cloudinary.com
+ *.cloudinary.com
+ cdnjs.cloudflare.com
+ *.youtube.com
+ *.vimeo.com
+ unpkg.com
+
+
+
+
+ cloudinary.com
+ *.cloudinary.com
+ cdnjs.cloudflare.com
+
+
+
+
+ cloudinary.com
+ *.cloudinary.com
+
+
+
+
+ cloudinary.com
+ *.cloudinary.com
+
+
+
+
+ cloudinary.com
+ *.cloudinary.com
+ data:
+ blob:
+
+
+
+
+ cloudinary.com
+ *.cloudinary.com
+ *.googleapis.com
+ unpkg.com
+
+
+
+
+ cloudinary.com
+ *.cloudinary.com
+
+
+
+
+ *.gstatic.com
+ *.cloudinary.com
+
+
+
+
diff --git a/etc/db_schema.xml b/etc/db_schema.xml
new file mode 100644
index 00000000..54e22eca
--- /dev/null
+++ b/etc/db_schema.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/etc/di.xml b/etc/di.xml
index de25382b..9a90b976 100644
--- a/etc/di.xml
+++ b/etc/di.xml
@@ -4,58 +4,118 @@
+ - Cloudinary\Cloudinary\Command\DownloadImages
- Cloudinary\Cloudinary\Command\UploadImages
- Cloudinary\Cloudinary\Command\StopMigration
- Cloudinary\Cloudinary\Command\ResetAll
+ - Cloudinary\Cloudinary\Command\ProductGalleryApiQueueProcess
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Magento\Framework\Filesystem\Driver\File
+
+
+
+
+ cloudinaryLogger
+
+ - Cloudinary\Cloudinary\Model\Logger\CloudinaryHandler
+
+
+
diff --git a/etc/module.xml b/etc/module.xml
index b3507862..a12e8886 100644
--- a/etc/module.xml
+++ b/etc/module.xml
@@ -1,8 +1,11 @@
-
+
+
+
+
diff --git a/etc/schema.graphqls b/etc/schema.graphqls
new file mode 100644
index 00000000..33bb3732
--- /dev/null
+++ b/etc/schema.graphqls
@@ -0,0 +1,21 @@
+type CustomMediaAttribute {
+ attribute_code: String
+ url: String
+}
+
+type CloudinaryData {
+ image: String
+ small_image: String
+ thumbnail: String
+ media_gallery: [String]
+ gallery_widget_parameters: String @doc(description: "Cloudinary gallery widget parameters (json)")
+ custom_media_attributes(attribute_codes: [String]!): [CustomMediaAttribute]
+ @resolver(class: "\\Cloudinary\\Cloudinary\\Model\\GraphQLResolver\\ProductAttributeCldResolver")
+ @doc(description: "Fetch a custom media attributes for the product based on the provided attribute code.")
+}
+
+interface ProductInterface {
+ cld_data: CloudinaryData
+ @resolver(class: "\\Cloudinary\\Cloudinary\\Model\\GraphQLResolver\\ProductAttributeCldResolver")
+ @doc(description: "Cloudinary urls generated for product images and gallery widget parameters.")
+}
diff --git a/etc/webapi.xml b/etc/webapi.xml
index 66d94106..29c5b701 100644
--- a/etc/webapi.xml
+++ b/etc/webapi.xml
@@ -19,4 +19,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/marketplace.composer.json b/marketplace.composer.json
new file mode 100644
index 00000000..6b955ddd
--- /dev/null
+++ b/marketplace.composer.json
@@ -0,0 +1,18 @@
+{
+ "name": "cloudinary/cloudinary",
+ "description": "Cloudinary Magento 2 Integration.",
+ "type": "magento2-module",
+ "version": "1.18.0",
+ "license": "MIT",
+ "require": {
+ "cloudinary/cloudinary_php": "^1.20.52"
+ },
+ "autoload": {
+ "files": [
+ "registration.php"
+ ],
+ "psr-4": {
+ "Cloudinary\\Cloudinary\\": ""
+ }
+ }
+}
diff --git a/registration.php b/registration.php
index 422034d8..7b3c72b4 100644
--- a/registration.php
+++ b/registration.php
@@ -4,4 +4,4 @@
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Cloudinary_Cloudinary',
__DIR__
-);
+);
\ No newline at end of file
diff --git a/view/adminhtml/js/product_free_transform.js b/view/adminhtml/js/product_free_transform.js
deleted file mode 120000
index 1e82e1a7..00000000
--- a/view/adminhtml/js/product_free_transform.js
+++ /dev/null
@@ -1 +0,0 @@
-/app/modules/cloudinarym2/view/adminhtml/web/js/product_free_transform.js
\ No newline at end of file
diff --git a/view/adminhtml/layout/adminhtml_system_config_edit.xml b/view/adminhtml/layout/adminhtml_system_config_edit.xml
new file mode 100644
index 00000000..685e5f60
--- /dev/null
+++ b/view/adminhtml/layout/adminhtml_system_config_edit.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/view/adminhtml/layout/cms_block_edit.xml b/view/adminhtml/layout/cms_block_edit.xml
new file mode 100644
index 00000000..a00d1014
--- /dev/null
+++ b/view/adminhtml/layout/cms_block_edit.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/view/adminhtml/layout/cms_page_edit.xml b/view/adminhtml/layout/cms_page_edit.xml
new file mode 100644
index 00000000..ec32bb2e
--- /dev/null
+++ b/view/adminhtml/layout/cms_page_edit.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/view/adminhtml/layout/cms_wysiwyg_images_index.xml b/view/adminhtml/layout/cms_wysiwyg_images_index.xml
new file mode 100644
index 00000000..323ba360
--- /dev/null
+++ b/view/adminhtml/layout/cms_wysiwyg_images_index.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+ Magento\Backend\Block\DataProviders\ImageUploadConfig
+
+
+
+
+
diff --git a/view/adminhtml/layout/default.xml b/view/adminhtml/layout/default.xml
new file mode 100644
index 00000000..5ba003dd
--- /dev/null
+++ b/view/adminhtml/layout/default.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/view/adminhtml/requirejs-config.js b/view/adminhtml/requirejs-config.js
index eaca2951..7905b88a 100644
--- a/view/adminhtml/requirejs-config.js
+++ b/view/adminhtml/requirejs-config.js
@@ -3,7 +3,38 @@ var config = {
'*': {
cloudinaryFreeTransform: 'Cloudinary_Cloudinary/js/cloudinary-free',
newVideoDialog: 'Cloudinary_Cloudinary/js/new-video-dialog',
- 'Magento_ProductVideo/js/get-video-information': 'Cloudinary_Cloudinary/js/get-video-information'
+ 'Magento_ProductVideo/js/get-video-information': 'Cloudinary_Cloudinary/js/get-video-information',
+ cloudinaryMediaLibraryModal: 'Cloudinary_Cloudinary/js/cloudinary-media-library-modal',
+ cloudinarySpinsetModal: 'Cloudinary_Cloudinary/js/cloudinary-spinset-modal',
+ cldspinsetDialog: 'Cloudinary_Cloudinary/js/cloudinary-spinset-dialog',
+ productGallery: 'Cloudinary_Cloudinary/js/product-gallery',
+ cloudinaryLazyload: 'Cloudinary_Cloudinary/js/cloudinary-lazyload',
+ updateCmsImages: 'Cloudinary_Cloudinary/js/cms/preview-update',
+ 'Magento_PageBuilder/js/content-type/image/preview': 'Cloudinary_Cloudinary/js/content-type/image/preview',
}
},
-};
\ No newline at end of file
+ paths: {
+ 'jquery.lazyload': "Cloudinary_Cloudinary/js/jquery.lazyload.min",
+ cloudinaryMediaLibraryAll: "//media-library.cloudinary.com/global/all",
+ es6Promise: "//cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min",
+ 'uiComponent': 'Magento_Ui/js/core/app',
+ },
+ shim: {
+ 'jquery.lazyload': {
+ deps: ['jquery']
+ },
+ 'uiComponent': {
+ deps: ['jquery']
+ },
+ 'Cloudinary_Cloudinary/js/cms/preview-update': {
+ deps: ['jquery']
+ }
+ },
+ config: {
+ mixins: {
+ 'Magento_Ui/js/lib/validation/validator': {
+ 'Cloudinary_Cloudinary/js/form/element/validator-rules-mixin': true
+ },
+ }
+ }
+};
diff --git a/view/adminhtml/templates/browser/content.phtml b/view/adminhtml/templates/browser/content.phtml
new file mode 100644
index 00000000..cfdb6455
--- /dev/null
+++ b/view/adminhtml/templates/browser/content.phtml
@@ -0,0 +1,44 @@
+
+getCloudinaryMediaLibraryWidgetOptions();
+?>
+
+
diff --git a/view/adminhtml/templates/catalog/product/helper/gallery.phtml b/view/adminhtml/templates/catalog/product/helper/gallery.phtml
new file mode 100644
index 00000000..891f0097
--- /dev/null
+++ b/view/adminhtml/templates/catalog/product/helper/gallery.phtml
@@ -0,0 +1,390 @@
+getElement()->getName() . '[images]';
+$formName = $block->getFormName();
+$cloudinaryMLwidgetOprions = $block->getCloudinaryMediaLibraryWidgetOptions();
+?>
+
+
+
+getElement();
+$elementToggleCode = $element->getToggleCode() ? $element->getToggleCode() : 'toggleValueElements(this, this.parentNode.parentNode.parentNode)';
+?>
+
+
+
+
+
+
+
+ getElement()->getReadonly()):
+ ?>
+
+ = /* @escapeNotVerified */ $block->getUploaderHtml() ?>
+
+
+ = $escaper->escapeHtml(__('Browse to find or drag image here')) ?>
+
+
+
+ = /* @escapeNotVerified */ $block->getChildHtml('additional_buttons') ?>
+
+ getImageTypes() as $typeData):
+ ?>
+
+
+
+
+
+
+
+
+ = /* @escapeNotVerified */ $block->getFormHtml() ?>
+
+
+
+ = /* @noEscape */ $block->getChildHtml('new-video') ?>
+
+
+
+
+
+
+
diff --git a/view/adminhtml/templates/cms/images.phtml b/view/adminhtml/templates/cms/images.phtml
new file mode 100644
index 00000000..4871eca1
--- /dev/null
+++ b/view/adminhtml/templates/cms/images.phtml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/view/adminhtml/templates/config/auto-upload-mapping-btn.phtml b/view/adminhtml/templates/config/auto-upload-mapping-btn.phtml
new file mode 100644
index 00000000..b3ae44ed
--- /dev/null
+++ b/view/adminhtml/templates/config/auto-upload-mapping-btn.phtml
@@ -0,0 +1,44 @@
+
+= /* @noEscape */ $block->getButtonHtml() ?>
+
+
+isEnabled()): ?>
+
+
+ = $escaper->escapeHtmlAttr( __('This button would be available after enabling the module with a valid environment variable.')) ?>
+
diff --git a/view/adminhtml/templates/config/free.phtml b/view/adminhtml/templates/config/free.phtml
index 7c6b7245..598afe15 100644
--- a/view/adminhtml/templates/config/free.phtml
+++ b/view/adminhtml/templates/config/free.phtml
@@ -12,11 +12,13 @@
title="Preview"
type="button"
class="scalable"
- style="margin-top: 10px;">
+ style="margin-top: 10px;"
+ = /* @noEscape */ (!$this->isCanPreviewTransformations()) ? ' disabled' : '' ?>
+>
Preview custom transformation
", "ajaxKey": "getFormKey(); ?>"}}'>
+ data-mage-init='{"cloudinaryFreeTransform":{"previewButtonId": "#cloudinary_free_transform_preview_button", "transformInputFieldId": "#cloudinary_transformations_cloudinary_free_transform_global", "transformInputProductsFieldId": "#cloudinary_transformations_cloudinary_free_transform_global_products", "transformInputProductsBehaviorFieldId": "#cloudinary_transformations_cloudinary_free_transform_global_products_behavior", "ajaxUrl": "= /* @noEscape */ $this->getUrl('cloudinary/ajax_free/sample') ?>", "ajaxKey": "= /* @noEscape */ $this->getFormKey() ?>"}}'>
diff --git a/view/adminhtml/templates/javascript.phtml b/view/adminhtml/templates/javascript.phtml
new file mode 100644
index 00000000..e8977052
--- /dev/null
+++ b/view/adminhtml/templates/javascript.phtml
@@ -0,0 +1,4 @@
+
+isEnabled() && $cname = $block->getCname()): ?>
+
+
diff --git a/view/adminhtml/templates/lazyload.phtml b/view/adminhtml/templates/lazyload.phtml
new file mode 100644
index 00000000..0df2924e
--- /dev/null
+++ b/view/adminhtml/templates/lazyload.phtml
@@ -0,0 +1,9 @@
+
+isEnabledLazyload()) {
+ return;
+} ?>
+
diff --git a/view/adminhtml/ui_component/media_gallery_listing.xml b/view/adminhtml/ui_component/media_gallery_listing.xml
new file mode 100644
index 00000000..f8f79234
--- /dev/null
+++ b/view/adminhtml/ui_component/media_gallery_listing.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ -
+
-
+ media_gallery_listing.media_gallery_listing_data_source
+
+
+
+
+
+
+
+ media_gallery_columns
+
+ media_gallery_listing.media_gallery_listing_data_source
+
+
+
diff --git a/view/adminhtml/ui_component/pagebuilder_base_form_with_background_video.xml b/view/adminhtml/ui_component/pagebuilder_base_form_with_background_video.xml
new file mode 100644
index 00000000..2b74c1a5
--- /dev/null
+++ b/view/adminhtml/ui_component/pagebuilder_base_form_with_background_video.xml
@@ -0,0 +1,18 @@
+
+
+
diff --git a/view/adminhtml/ui_component/pagebuilder_video_form.xml b/view/adminhtml/ui_component/pagebuilder_video_form.xml
new file mode 100644
index 00000000..81ef3934
--- /dev/null
+++ b/view/adminhtml/ui_component/pagebuilder_video_form.xml
@@ -0,0 +1,19 @@
+
+
+
diff --git a/view/adminhtml/web/css/source/_module.less b/view/adminhtml/web/css/source/_module.less
new file mode 100644
index 00000000..944cd5a0
--- /dev/null
+++ b/view/adminhtml/web/css/source/_module.less
@@ -0,0 +1,215 @@
+.cloudinary-configuration-tab .admin__page-nav-title strong:before {
+ background-image: url("Cloudinary_Cloudinary::images/cloudinary_cloud_glyph_regular.svg");
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center center;
+ display: inline-block;
+ content: "";
+ width: 22px;
+ height: 18px;
+ margin-right: 5px;
+ vertical-align: text-bottom;
+}
+
+.image.image-placeholder.add-from-cloudinary-button .product-image-wrapper:before {
+ background-image: url("Cloudinary_Cloudinary::images/cloudinary_dropzone.svg");
+ background-repeat: no-repeat;
+ background-size: 104px;
+ background-position: center;
+ display: block;
+ content: "";
+ width: 100%;
+ height: 98px;
+ position: absolute;
+ top: 0;
+ left: 0;
+ opacity: 0.8;
+}
+
+.cloudinary-button-with-logo {
+ position: relative;
+ padding: 6px 14px 6px 50px;
+
+ &:before {
+ background-image: url("Cloudinary_Cloudinary::images/cloudinary_cloud_glyph_white.svg");
+ background-repeat: no-repeat;
+ background-size: contain;
+ background-position: center;
+ display: block;
+ vertical-align: middle;
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 14px;
+ width: 30px;
+ height: 30px;
+ }
+
+ &.small-ver {
+ padding: 0 8px 0 44px;
+
+ > span {
+ line-height: 25px;
+ font-size: 1.2rem;
+ }
+
+ &:before {
+ left: 8px;
+ height: 25px;
+ }
+ }
+
+ &.sm-top-bottom-margin {
+ margin: 4px 0;
+ }
+
+ &.lg-margin-bottom {
+ margin: 0 0 20px;
+ }
+}
+
+.adminhtml-system_config-edit .colorpicker_hue div {
+ background-image: url("Cloudinary_Cloudinary::images/colorpicker_indic.gif");
+}
+
+#cldspinset-modal {
+ input[name="new_cldspinset"] {
+ max-width: 500px;
+ }
+
+ .cldspinset-preview-container {
+ .admin__field-control {
+ max-width: 500px;
+ }
+
+ #cldspinset-preview {
+ border: 1px solid #e3e3e3;
+ height: 0;
+ padding-bottom: 100%;
+ position: relative;
+ width: 100%;
+ max-width: 500px;
+
+ #cldspinset-preview-img {
+ transition: all 0.8s ease;
+ margin: 0;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ max-height: 90%;
+ max-width: 90%;
+
+ + #cldspinset-indic {
+ background-image: url("Cloudinary_Cloudinary::images/spinset_indic.svg");
+ background-repeat: no-repeat;
+ background-size: contain;
+ background-position: center;
+ display: block;
+ content: "";
+ z-index: 99;
+ margin: 0;
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ height: 30px;
+ width: 30px;
+ }
+
+ &.placeholder {
+ max-height: 50%;
+ max-width: 50%;
+
+ + #cldspinset-indic {
+ display: none;
+ }
+ }
+ }
+ }
+ }
+}
+
+.product-image-wrapper.cldspinset-overlay {
+ &:after {
+ background-image: url("Cloudinary_Cloudinary::images/spinset_indic.svg");
+ background-repeat: no-repeat;
+ background-size: contain;
+ background-position: center;
+ display: block;
+ content: "";
+ z-index: 99;
+ margin: 0;
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ height: 22px;
+ width: 22px;
+ }
+}
+
+#gallery_top_cloudinary_buttons_container {
+ display: inline-block;
+ position: relative;
+
+ #gallery_top_add_from_cloudinary_dd {
+ display: none;
+ position: absolute;
+ z-index: 9;
+ top: 100%;
+ width: 105%;
+ right: 0;
+ margin-right: -5px;
+ background: transparent;
+ box-sizing: border-box;
+ padding: 3px;
+
+ #gallery_top_add_from_cloudinary_dd_inner {
+ display: block;
+ background: #bebdbc;
+ box-sizing: border-box;
+ width: 100%;
+ border: 1px solid #007bdb;
+ border-radius: 1px;
+ box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5);
+ color: #41362f;
+
+ > button.action-secondary {
+ background-color: #fff;
+ width: 100%;
+ border: none;
+ border-top: 0;
+ text-shadow: none;
+ color: #41362f;
+ font-weight: 400;
+ line-height: 1.36;
+ font-size: 1.4rem;
+
+ &:hover {
+ background-color: #cacaca;
+ box-shadow: none;
+ }
+ }
+ }
+ }
+
+ #gallery_top_add_from_cloudinary_dd_button {
+ &:hover {
+ #gallery_top_add_from_cloudinary_dd {
+ display: block;
+ }
+ }
+ }
+}
+.page-actions button.cloudinary-button-with-logo{
+ margin: 0 0 0 10px !important;
+ span{
+ padding-left: 35px;
+ }
+}
+#row_cloudinary_advanced_custom_placeholder_image img{
+ width: 100%;
+ height: 100%;
+ max-width: 50px;
+ max-height: 50px;
+ border: 1px solid #ccc;
+}
diff --git a/view/adminhtml/web/js/cloudinary-free.js b/view/adminhtml/web/js/cloudinary-free.js
index 8bdf1c2e..e98ed72a 100644
--- a/view/adminhtml/web/js/cloudinary-free.js
+++ b/view/adminhtml/web/js/cloudinary-free.js
@@ -1,80 +1,161 @@
-define(['jquery'], function ($) {
- 'use strict';
+define(
+ [
+ 'jquery'
+ ],
+ function($) {
+ 'use strict';
+
+ $.widget(
+ 'cloudinary.cloudinaryFreeTransform', {
+
+ currentTransform: '',
+ currentTransformProducts: '',
+ currentTransformBehavior: '',
+
+ getTransformText: function() {
+ return $(this.options.transformInputFieldId).val() || '';
+ },
+
+ getTransformProductsText: function() {
+ return $(this.options.transformInputProductsFieldId).val() || '';
+ },
+
+ getTransformBehavior: function() {
+ return $(this.options.transformInputProductsBehaviorFieldId).val();
+ },
+
+ getImageHtml: function(src, header) {
+ if (!src) {
+ return '';
+ }
+ var cls = 'cloudinary_custom_transform_preview_image',
+ style = 'width: auto; height: auto; max-width: 350px; max-height: 350px; min-height: 50px;',
+ header = header || '',
+ footer = 'Image size restricted for viewing purposes
';
+ return header + ' ' + footer;
+ },
+
+ getErrorHtml: function(message) {
+ return '';
+ },
+
+ updatePreviewImage: function(url, url2) {
+ $('#cloudinary_custom_transform_preview').html(
+ this.getImageHtml(url, 'Global Custom Transformation Preview
') +
+ this.getImageHtml(url2, 'Products Custom Transformation Preview
')
+ );
+ },
+
+ updatePreview: function() {
+ var self = this,
+ transformations_string = "";
+
+ if (!self.isPreviewActive()) {
+ return;
+ }
+
+ self.currentTransform = self.getTransformText();
+ self.currentTransformProducts = self.getTransformProductsText();
+ self.currentTransformBehavior = self.getTransformBehavior();
+ self.setPreviewActiveState(false);
+
+ $.ajax({
+ url: this.options.ajaxUrl,
+ data: {
+ free: self.currentTransform,
+ form_key: self.options.ajaxKey
+ },
+ type: 'post',
+ dataType: 'json',
+ showLoader: true
+ }).done(
+ function(response) {
+ if ((transformations_string = self.currentTransformProducts)) {
+ if (self.currentTransformBehavior === 'add') {
+ transformations_string = self.currentTransform + ',' + transformations_string;
+ }
+ var globalResURL = response.url;
+ $.ajax({
+ url: self.options.ajaxUrl,
+ data: {
+ free: transformations_string,
+ form_key: self.options.ajaxKey
+ },
+ type: 'post',
+ dataType: 'json',
+ showLoader: true
+ }).done(
+ function(response) {
+ self.updatePreviewImage(globalResURL, response.url);
+ }
+ ).fail(
+ function(result) {
+ $('#cloudinary_custom_transform_preview').html(self.getErrorHtml(result.responseJSON.error));
+ }
+ );
+ } else {
+ return self.updatePreviewImage(response.url);
+ transformations_string = self.getTransformText();
+ }
+ }
+ ).fail(
+ function(result) {
+ $('#cloudinary_custom_transform_preview').html(self.getErrorHtml(result.responseJSON.error));
+ }
+ );
+ },
+
+ setPreviewActiveState: function(state) {
+ if (
+ state &&
+ (
+ this.currentTransform !== this.getTransformText() ||
+ this.currentTransformProducts !== this.getTransformProductsText() ||
+ (this.getTransformProductsText() && this.currentTransformBehavior !== this.getTransformBehavior())
+ )
+ ) {
+ $(this.options.previewButtonId).removeClass('disabled');
+ } else {
+ $(this.options.previewButtonId).addClass('disabled');
+ }
+ },
+
+ isPreviewActive: function() {
+ return !$(this.options.previewButtonId).hasClass('disabled');
+ },
+
+ _create: function() {
+ var self = this;
+
+ $(this.options.previewButtonId).on(
+ 'click',
+ function() {
+ self.updatePreview();
+ }
+ );
+ $(this.options.transformInputFieldId).on(
+ 'change keydown paste input',
+ function() {
+ self.setPreviewActiveState(true);
+ }
+ );
+ $(this.options.transformInputProductsFieldId).on(
+ 'change keydown paste input',
+ function() {
+ self.setPreviewActiveState(true);
+ }
+ );
+ $(this.options.transformInputProductsBehaviorFieldId).on(
+ 'change',
+ function() {
+ self.setPreviewActiveState(true);
+ }
+ );
+ }
- $.widget('cloudinary.cloudinaryFreeTransform', {
-
- currentTransform: '',
-
- getTransformText: function() {
- return $(this.options.transformInputFieldId).val();
- },
-
- getImageHtml: function(src) {
- var id = 'cloudinary_custom_transform_preview_image',
- style = 'width: auto; height: auto; max-width: 500px; max-height: 500px; min-height: 50px;',
- footer = 'Image size restricted for viewing purposes
';
- return ' ' + footer;
- },
-
- getErrorHtml: function(message) {
- return '';
- },
-
- updatePreviewImage: function(url) {
- var $image = $('#cloudinary_custom_transform_preview_image');
-
- if (!$image.length) {
- $('#cloudinary_custom_transform_preview').html(this.getImageHtml(url));
- } else {
- $image.attr('src', url);
}
- },
-
- updatePreview: function() {
- var self = this;
-
- if (!self.isPreviewActive()) {
- return;
- }
-
- self.currentTransform = self.getTransformText();
- self.setPreviewActiveState(false);
-
- $.ajax({
- url: this.options.ajaxUrl,
- data: {free: self.getTransformText(), form_key: self.options.ajaxKey},
- type: 'post',
- dataType: 'json',
- showLoader: true
- }).done(function(response) {
- self.updatePreviewImage(response.url);
- }).fail(function(result) {
- $('#cloudinary_custom_transform_preview').html(self.getErrorHtml(result.responseJSON.error));
- });
- },
-
- setPreviewActiveState: function(state) {
- if (state && (this.currentTransform !== this.getTransformText())) {
- $(this.options.previewButtonId).removeClass('disabled');
- } else {
- $(this.options.previewButtonId).addClass('disabled');
- }
- },
-
- isPreviewActive: function() {
- return !$(this.options.previewButtonId).hasClass('disabled');
- },
-
- _create: function () {
- var self = this;
-
- $(this.options.previewButtonId).on('click', function() { self.updatePreview(); });
- $(this.options.transformInputFieldId).on('change keydown paste input', function() {
- self.setPreviewActiveState(true);
- });
- }
-
- });
+ );
- return $.cloudinary.cloudinaryFreeTransform;
-});
+ return $.cloudinary.cloudinaryFreeTransform;
+ }
+);
\ No newline at end of file
diff --git a/view/adminhtml/web/js/cloudinary-media-library-modal.js b/view/adminhtml/web/js/cloudinary-media-library-modal.js
new file mode 100644
index 00000000..c04a5f19
--- /dev/null
+++ b/view/adminhtml/web/js/cloudinary-media-library-modal.js
@@ -0,0 +1,251 @@
+define([
+ 'jquery',
+ 'mageUtils',
+ 'uiRegistry',
+ 'productGallery',
+ 'Magento_Ui/js/modal/alert',
+ 'mage/backend/notification',
+ 'mage/translate',
+ 'Magento_MediaGalleryUi/js/image-uploader',
+ 'jquery/ui',
+ 'Magento_Ui/js/modal/modal',
+ 'mage/backend/validation',
+ 'cloudinaryMediaLibraryAll',
+ 'es6Promise',
+
+
+], function($, mageUtils, registry, productGallery, uiAlert, notification, $t, imageUploader) {
+ 'use strict';
+
+ $.widget('mage.cloudinaryMediaLibraryModal', {
+
+ options: {
+ buttonSelector: null,
+ triggerSelector: null, // #media_gallery_content .image.image-placeholder > .uploader' / '.media-gallery-modal'
+ triggerEvent: null, // 'addItem' / 'fileuploaddone'
+ callbackHandler: null,
+ callbackHandlerMethod: null,
+ imageParamName: 'image',
+ cloudinaryMLoptions: {}, // Options for Cloudinary-ML createMediaLibrary()
+ cloudinaryMLshowOptions: {}, // Options for Cloudinary-ML show()
+ cldMLid: 0,
+ useDerived: true,
+ addTmpExtension: false,
+ },
+
+ /**
+ * Bind events
+ * @private
+ */
+ _bind: function() {
+ if ($(this.options.buttonSelector).length) {
+ $(this.options.buttonSelector).on('click', this.openMediaLibrary.bind(this));
+ } else {
+ this.element.on('click', this.openMediaLibrary.bind(this));
+ }
+ },
+
+ /**
+ * @param {Array} messages
+ */
+ notifyError: function(messages) {
+ var data = {
+ content: messages.join('')
+ };
+ if (messages.length > 1) {
+ data.modalClass = '_image-box';
+ }
+ uiAlert(data);
+ return this;
+ },
+
+ /**
+ * @private
+ */
+ _create: function() {
+ this._super();
+ this._bind();
+
+ var widget = this;
+ var uiRegistry = registry;
+
+
+ window.cloudinary_ml = window.cloudinary_ml || [];
+ this.options.cldMLid = this.options.cldMLid || 0;
+
+ if (typeof window.cloudinary_ml[this.options.cldMLid] === "undefined") {
+ this.cloudinary_ml = window.cloudinary_ml[this.options.cldMLid] = cloudinary.createMediaLibrary(
+ this.options.cloudinaryMLoptions, {
+ insertHandler: function(data) {
+ $('body').first().css('overflow', 'initial');
+ if (widget.isMediaBrowser()) {
+ return widget.cloudinaryInsertHandler(data);
+ } else {
+ // new media gallery
+ data['newGalleryMode'] = 1;
+ widget.cloudinaryInsertHandler(data);
+ if (uiRegistry.get('media_gallery_listing.media_gallery_listing_data_source')) {
+ $(window).trigger('reload.MediaGallery');
+ }
+ }
+ }
+ }
+ );
+ } else {
+ this.cloudinary_ml = window.cloudinary_ml[this.options.cldMLid];
+ }
+
+ },
+ getMageMediaBrowserData: function () {
+ return $('.media-gallery-modal').data('mageMediabrowser');
+ },
+
+ isMediaBrowser: function () {
+ return typeof this.getMageMediaBrowserData() !== 'undefined';
+ },
+
+ selectImageInNewMediaGallery: function (record) {
+ this.uploadHandler(record);
+ },
+ /**
+ * Fired on trigger "openMediaLibrary"
+ */
+ openMediaLibrary: function() {
+ this.cloudinary_ml.show(this.options.cloudinaryMLshowOptions);
+ },
+ showLoader: function () {
+ this.loader(true);
+ },
+
+ /**
+ * Hides spinner loader
+ */
+ hideLoader: function () {
+ this.loader(false);
+ },
+ /**
+ * Escape Regex
+ */
+ escapeRegex: function(string) {
+ return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+ },
+
+ /**
+ * Fired on trigger "cloudinaryInsertHandler"
+ */
+ cloudinaryInsertHandler: function(data) {
+ var widget = this;
+ var aggregatedErrorMessages = [];
+ var $i = data.assets.length;
+
+ data.assets.forEach(asset => {
+ //console.log(asset);
+ $i--;
+ if (widget.options.imageUploaderUrl) {
+ asset.asset_url = asset.asset_image_url = asset.secure_url;
+ asset.free_transformation = "";
+ if (asset.derived && asset.derived[0] && asset.derived[0].secure_url) {
+ asset.asset_derived_url = asset.asset_derived_image_url = asset.derived[0].secure_url;
+ asset.free_transformation = (asset.derived[0].hasOwnProperty('raw_transformation')) ?
+ asset.derived[0].raw_transformation :
+ asset.asset_derived_image_url
+ .replace(new RegExp('^.*cloudinary.com/(' + this.options.cloudinaryMLoptions.cloud_name + '/)?' + asset.resource_type + '/' + asset.type + '/'), '')
+ .replace(/\.[^/.]+$/, '')
+ .replace(new RegExp('\/' + widget.escapeRegex(encodeURI(decodeURI(asset.public_id))) + '$'), '')
+ .replace(new RegExp('\/v[0-9]{1,10}$'), '')
+ .replace(new RegExp('\/'), ',');
+ if (widget.options.useDerived) {
+ asset.asset_url = asset.asset_image_url = asset.derived[0].secure_url;
+ }
+ }
+ if (asset.resource_type === "video") {
+ asset.asset_image_url = asset.asset_url
+ .replace(/\.[^/.]+$/, "")
+ .replace(new RegExp('\/v[0-9]{1,10}\/'), '/')
+ .replace(new RegExp('\/(' + widget.escapeRegex(encodeURI(decodeURI(asset.public_id))) + ')$'), '/so_auto/$1.jpg');
+ }
+ $.ajax({
+ url: widget.options.imageUploaderUrl,
+ data: {
+ asset: asset,
+ remote_image: asset.asset_image_url,
+ param_name: widget.options.imageParamName,
+ form_key: window.FORM_KEY
+ },
+ method: 'POST',
+ dataType: 'json',
+ async: false,
+ showLoader: true
+ }).done(
+ function(file) {
+ if (file.file && !file.error) {
+ var context = (asset.context && asset.context.custom) ? asset.context.custom : {};
+ if (asset.resource_type === "video") {
+ file.video_provider = 'cloudinary';
+ file.media_type = "external-video";
+ file.video_url = asset.asset_url;
+ file.video_title = context.caption || context.alt || asset.public_id || "";
+ file.video_description = (context.description || context.alt || context.caption || "").replace(/( |<([^>]+)>)/ig, '');
+ if (file.using_placeholder_fallback) {
+ notification().add({
+ error: false,
+ message: $t("Couldn't automatically generate Cloudinary video thumbnail, using fallback placeholder instead. You can always replace that manually later"),
+ insertMethod: function(constructedMessage) {
+ aggregatedErrorMessages.push(constructedMessage);
+ }
+ });
+ }
+ } else {
+ file.media_type = "image";
+ file.label = asset.label = context.alt || context.caption || asset.public_id || "";
+ if (widget.options.addTmpExtension && !/\.tmp$/.test(file.file)) {
+ file.file = file.file + '.tmp';
+ }
+ }
+ file.free_transformation = asset.free_transformation;
+ file.asset_derived_image_url = asset.asset_derived_image_url;
+ file.image_url = asset.asset_image_url;
+ file.cloudinary_asset = asset;
+
+ if (widget.options.triggerSelector && widget.options.triggerEvent) {
+ $(widget.options.triggerSelector).last().trigger(widget.options.triggerEvent, file);
+ if (asset.resource_type === "video") {
+ $(widget.options.triggerSelector).last().find('img[src="' + file.url + '"]').addClass('video-item');
+ }
+ }
+ if (widget.options.callbackHandler && widget.options.callbackHandlerMethod && typeof widget.options.callbackHandler[widget.options.callbackHandlerMethod] === 'function') {
+ widget.options.callbackHandler[widget.options.callbackHandlerMethod](file);
+ }
+ } else {
+ console.error(file);
+ notification().add({
+ error: true,
+ message: $t('An error occured during ' + asset.resource_type + ' insert (' + asset.public_id + ')!') + '%s%sError: ' + file.error.replace(/File:.*$/, ''),
+ insertMethod: function(constructedMessage) {
+ aggregatedErrorMessages.push(constructedMessage.replace('%s%s', ' '));
+ }
+ });
+ }
+ if (!$i && aggregatedErrorMessages.length) {
+ widget.notifyError(aggregatedErrorMessages);
+ }
+ }
+ ).fail(
+ function(response) {
+ console.error(response);
+ notification().add({
+ error: true,
+ message: $t('An error occured during ' + asset.resource_type + ' insert (' + asset.public_id + ')!')
+ });
+ if (!$i && aggregatedErrorMessages.length) {
+ widget.notifyError(aggregatedErrorMessages);
+ }
+ }
+ );
+ }
+ });
+ }
+ });
+
+ return $.mage.cloudinaryMediaLibraryModal;
+});
diff --git a/view/adminhtml/web/js/cloudinary-spinset-dialog.js b/view/adminhtml/web/js/cloudinary-spinset-dialog.js
new file mode 100644
index 00000000..0afb43c8
--- /dev/null
+++ b/view/adminhtml/web/js/cloudinary-spinset-dialog.js
@@ -0,0 +1,81 @@
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+define(
+ [
+ 'jquery',
+ 'underscore',
+ 'jquery/ui',
+ 'Magento_Ui/js/modal/modal',
+ 'mage/translate',
+ 'mage/backend/tree-suggest',
+ 'mage/backend/validation'
+ ],
+ function($, _) {
+ 'use strict';
+
+ $.widget('mage.newCldSpinsetDialog', {
+ /**
+ * Build widget
+ *
+ * @private
+ */
+ _create: function() {
+ var widget = this;
+
+ this.element.modal({
+ type: 'slide',
+ //appendTo: this._gallery,
+ modalClass: 'cldspinset-dialog form-inline',
+ title: $.mage.__('Add Spinset from Cloudinary'),
+ buttons: [{
+ text: $.mage.__('Save'),
+ class: 'action-primary video-create-button',
+ click: $.proxy(widget._onCreate, widget)
+ },
+ {
+ text: $.mage.__('Cancel'),
+ class: 'video-cancel-button',
+ click: $.proxy(widget._onCancel, widget)
+ }
+ ],
+
+ /**
+ * @returns {null}
+ */
+ opened: function() {
+ console.log('cldspinset opened');
+ },
+
+ /**
+ * Closed
+ */
+ closed: function() {
+ console.log('cldspinset closed');
+ }
+ });
+ },
+
+ /**
+ * Fired when click on create video
+ *
+ * @private
+ */
+ _onCreate: function() {
+ console.log('_onCreate');
+ },
+
+ /**
+ * Fired when click on create video
+ *
+ * @private
+ */
+ _onCancel: function() {
+ console.log('_onCancel');
+ }
+ });
+
+ return $.mage.newCldSpinsetDialog;
+ }
+);
\ No newline at end of file
diff --git a/view/adminhtml/web/js/cloudinary-spinset-modal.js b/view/adminhtml/web/js/cloudinary-spinset-modal.js
new file mode 100644
index 00000000..aeeeb613
--- /dev/null
+++ b/view/adminhtml/web/js/cloudinary-spinset-modal.js
@@ -0,0 +1,312 @@
+define([
+ 'jquery',
+ 'productGallery',
+ 'Magento_Ui/js/modal/alert',
+ 'mage/backend/notification',
+ 'mage/translate',
+ 'jquery/ui',
+ 'Magento_Ui/js/modal/modal',
+ 'mage/backend/tree-suggest',
+ 'mage/backend/validation',
+ 'es6Promise'
+], function($, productGallery, uiAlert, notification, $t) {
+ 'use strict';
+
+ $.widget('mage.cloudinarySpinsetModal', {
+
+ loadedResource: null,
+
+ options: {
+ buttonSelector: '',
+ modalSelector: '#cldspinset-modal',
+ triggerSelector: null, // #media_gallery_content .image.image-placeholder > .uploader' / '.media-gallery-modal'
+ triggerEvent: null, // 'addItem' / 'fileuploaddone'
+ callbackHandler: null,
+ callbackHandlerMethod: null,
+ imageParamName: 'image',
+ cloudinaryMLoptions: {}, // Options for Cloudinary-ML createMediaLibrary()
+ cloudinaryMLshowOptions: {}, // Options for Cloudinary-ML show()
+ cldMLid: 0,
+ useDerived: true,
+ addTmpExtension: false,
+ },
+
+ /**
+ * Bind events
+ * @private
+ */
+ _bind: function() {
+ if ($(this.options.buttonSelector).length) {
+ $(this.options.buttonSelector).on('click', this.openSpinsetModal.bind(this));
+ } else {
+ this.element.on('click', this.openSpinsetModal.bind(this));
+ }
+ },
+
+ /**
+ * @param {Array} messages
+ */
+ notifyError: function(messages) {
+ var data = {
+ content: messages.join('')
+ };
+ if (messages.length > 1) {
+ data.modalClass = '_image-box';
+ }
+ uiAlert(data);
+ return this;
+ },
+
+ /**
+ */
+ noPreviewErrorMessage: function(err) {
+ if (err) {
+ console.error(err);
+ }
+ this.loadedResource = null;
+ $('.new-cldspinset-save-button').prop('disabled', true);
+ $('#cldspinset-preview-img').hide().attr('src', $('#cldspinset-preview-img').attr('data-placeholder')).addClass('placeholder').show();
+ var aggregatedErrorMessages = [];
+ notification().add({
+ error: true,
+ message: $t("No spin set exists for the given tag. Ensure you have uploaded it to Cloudinary correctly, or try again with a different tag name."),
+ insertMethod: function(constructedMessage) {
+ aggregatedErrorMessages.push(constructedMessage);
+ }
+ });
+ if (aggregatedErrorMessages.length) {
+ this.notifyError(aggregatedErrorMessages);
+ }
+ },
+
+
+
+ /**
+ * @private
+ */
+ _create: function() {
+ this._super();
+ this._bind();
+
+ var widget = this;
+
+ widget.cldspinsetDialog = $(widget.options.modalSelector);
+ //this.cldspinsetDialog.mage('newCldSpinsetDialog', this.cldspinsetDialog.data('modalInfo'));
+
+ $(document).on('change', '#cldspinset-modal [name="new_cldspinset"]', function() {
+ var spintetTag = $('#cldspinset-modal [name="new_cldspinset"]').val();
+ if (!spintetTag) {
+ $('.new-cldspinset-save-button').prop('disabled', true);
+ $('#cldspinset-preview-img').hide().attr('src', $('#cldspinset-preview-img').attr('data-placeholder')).addClass('placeholder').show();
+ return;
+ } else {
+ try {
+ $.ajax({
+ method: "GET",
+ url: '/rest/V1/cloudinary/resources/tag',
+ dataType: 'json',
+ data: {
+ id: spintetTag,
+ max_results: 1
+ },
+ showLoader: true,
+ timeout: 15000,
+ success: function(res) {
+ res = JSON.parse(res);
+ if (!res || res.error || !res.data || !res.data[0] || res.data[0].resource_type !== "image") {
+ widget.noPreviewErrorMessage(res);
+ } else {
+ res.data[0].cldspinset = spintetTag;
+ widget.loadedResource = res.data[0];
+ $('.new-cldspinset-save-button').prop('disabled', false);
+ $('#cldspinset-preview-img').hide().attr('src', res.data[0].secure_url).removeClass('placeholder').show();
+ }
+ },
+
+ /**
+ * @private
+ */
+ error: function(err) {
+ widget.noPreviewErrorMessage(err);
+ }
+ });
+ } catch (err) {
+ widget.noPreviewErrorMessage(err);
+ }
+ }
+ });
+ },
+
+ /**
+ * Fired when click on save
+ *
+ * @private
+ */
+ _onModalSave: function() {
+ if (this.loadedResource) {
+ $('.new-cldspinset-save-button').prop('disabled', true);
+ $("body").trigger('processStart');
+ this.cldspinsetInsertHandler(this.loadedResource);
+ }
+ },
+
+ /**
+ * Fired when click on create video
+ *
+ * @private
+ */
+ _onModalCancel: function() {
+ this.cldspinsetDialog.modal('closeModal');
+ },
+
+ /**
+ * Fired on trigger "openSpinsetModal"
+ */
+ openSpinsetModal: function() {
+ var widget = this;
+ this.cldspinsetDialog.modal({
+ type: 'slide',
+ //appendTo: this._gallery,
+ modalClass: 'cldspinset-dialog form-inline',
+ title: $.mage.__('Add Spinset from Cloudinary'),
+ buttons: [{
+ text: $.mage.__('Save'),
+ class: 'action-primary new-cldspinset-save-button',
+ click: $.proxy(widget._onModalSave, widget)
+ },
+ {
+ text: $.mage.__('Cancel'),
+ class: 'new-cldspinset-cancel-button',
+ click: $.proxy(widget._onModalCancel, widget)
+ }
+ ],
+
+ /**
+ * @returns {null}
+ */
+ opened: function() {
+ this.loadedResource = null;
+ $('.new-cldspinset-save-button').prop('disabled', true);
+ $('#cldspinset-modal [name="new_cldspinset"]').val('');
+ $('#cldspinset-preview-img').hide().attr('src', $('#cldspinset-preview-img').attr('data-placeholder')).addClass('placeholder').show();
+ },
+
+ /**
+ * Closed
+ */
+ closed: function() {
+ this.loadedResource = null;
+ $('.new-cldspinset-save-button').prop('disabled', true);
+ $('#cldspinset-modal [name="new_cldspinset"]').val('');
+ $('#cldspinset-preview-img').hide().attr('src', $('#cldspinset-preview-img').attr('data-placeholder')).addClass('placeholder').show();
+ }
+ });
+ this.cldspinsetDialog.modal('openModal');
+ },
+
+ cldspinsetInsertHandler: function(asset) {
+ try {
+ var widget = this;
+ var aggregatedErrorMessages = [];
+
+ if (widget.options.imageUploaderUrl) {
+ asset.asset_url = asset.asset_image_url = asset.secure_url;
+ asset.free_transformation = "";
+ if (asset.derived && asset.derived[0] && asset.derived[0].secure_url) {
+ asset.asset_derived_url = asset.asset_derived_image_url = asset.derived[0].secure_url;
+ asset.free_transformation = (asset.derived[0].hasOwnProperty('raw_transformation')) ?
+ asset.derived[0].raw_transformation :
+ asset.asset_derived_image_url
+ .replace(new RegExp('^.*cloudinary.com/(' + this.options.cloudinaryMLoptions.cloud_name + '/)?' + asset.resource_type + '/' + asset.type + '/'), '')
+ .replace(/\.[^/.]+$/, '')
+ .replace(new RegExp('\/' + asset.public_id + '$'), '')
+ .replace(new RegExp('\/v[0-9]{1,10}$'), '')
+ .replace(new RegExp('\/'), ',');
+ if (widget.options.useDerived) {
+ asset.asset_url = asset.asset_image_url = asset.derived[0].secure_url;
+ }
+ }
+ $.ajax({
+ url: widget.options.imageUploaderUrl,
+ data: {
+ asset: asset,
+ remote_image: asset.asset_image_url,
+ param_name: widget.options.imageParamName,
+ form_key: window.FORM_KEY
+ },
+ method: 'POST',
+ dataType: 'json',
+ async: false,
+ showLoader: true
+ }).done(
+ function(file) {
+ if (file.file && !file.error) {
+ var context = (asset.context && asset.context.custom) ? asset.context.custom : {};
+ file.media_type = "image";
+ file.label = asset.label = context.alt || context.caption || asset.public_id || "";
+ if (widget.options.addTmpExtension && !/\.tmp$/.test(file.file)) {
+ file.file = file.file + '.tmp';
+ }
+ file.cldspinset = asset.cldspinset;
+ file.free_transformation = asset.free_transformation;
+ file.asset_derived_image_url = asset.asset_derived_image_url;
+ file.image_url = asset.asset_image_url;
+ file.cloudinary_asset = asset;
+
+ if (widget.options.triggerSelector && widget.options.triggerEvent) {
+ $(widget.options.triggerSelector).last().trigger(widget.options.triggerEvent, file);
+ }
+ if (widget.options.callbackHandler && widget.options.callbackHandlerMethod && typeof widget.options.callbackHandler[widget.options.callbackHandlerMethod] === 'function') {
+ widget.options.callbackHandler[widget.options.callbackHandlerMethod](file);
+ }
+ $("body").trigger('processStop');
+ widget.cldspinsetDialog.modal('closeModal');
+ } else {
+ $("body").trigger('processStop');
+ $('.new-cldspinset-save-button').prop('disabled', false);
+ console.error(file);
+ notification().add({
+ error: true,
+ message: $t('An error occured during ' + asset.resource_type + ' insert (' + asset.public_id + ')!') + '%s%sError: ' + file.error.replace(/File:.*$/, ''),
+ insertMethod: function(constructedMessage) {
+ aggregatedErrorMessages.push(constructedMessage.replace('%s%s', ' '));
+ }
+ });
+ }
+ if (aggregatedErrorMessages.length) {
+ widget.notifyError(aggregatedErrorMessages);
+ }
+ }
+ ).fail(
+ function(response) {
+ $("body").trigger('processStop');
+ $('.new-cldspinset-save-button').prop('disabled', false);
+ console.error(response);
+ notification().add({
+ error: true,
+ message: $t('An error occured during ' + asset.resource_type + ' insert (' + asset.public_id + ')!')
+ });
+ if (aggregatedErrorMessages.length) {
+ widget.notifyError(aggregatedErrorMessages);
+ }
+ }
+ );
+ }
+ } catch (e) {
+ $("body").trigger('processStop');
+ $('.new-cldspinset-save-button').prop('disabled', false);
+ console.error(e);
+ notification().add({
+ error: true,
+ message: $t('An error occured during ' + asset.resource_type + ' insert! ' + e)
+ });
+ if (aggregatedErrorMessages.length) {
+ widget.notifyError(aggregatedErrorMessages);
+ }
+ }
+
+ }
+ });
+
+ return $.mage.cloudinarySpinsetModal;
+});
\ No newline at end of file
diff --git a/view/adminhtml/web/js/cms/preview-update.js b/view/adminhtml/web/js/cms/preview-update.js
new file mode 100644
index 00000000..d69131ec
--- /dev/null
+++ b/view/adminhtml/web/js/cms/preview-update.js
@@ -0,0 +1,217 @@
+/**
+ * Cloudinary Preview Update Handler
+ * Manages image preview updates with Cloudinary transformed images in Page Builder
+ */
+define(
+ ['jquery', 'Magento_PageBuilder/js/events'],
+ function ($, _PBEvents) {
+ 'use strict';
+
+ /**
+ * @param {Object} config - Configuration object
+ * @param {string} config.ajaxUrl - URL for AJAX requests
+ * @param {string} config.saveButtonSelector - Selector for save button (default: '#save-button')
+ * @param {Element} element - DOM element
+ * @returns {Object} updateHandler instance
+ */
+ return function (config, element) {
+ let updateHandler = {
+ images: {},
+ pendingRequests: {},
+ processedImages: new Set(),
+ saveButtonSelector: config.saveButtonSelector || '#save-button',
+ eventHandlers: [],
+
+ /**
+ * Initialize the handler and set up event listeners
+ */
+ init: function () {
+ let self = this;
+
+ // Store event handler references for cleanup
+ let renderAfterHandler = function (event) {
+ let elem = event.element,
+ key = event.id,
+ image = $(elem).find('img'),
+ imageSrc = image.attr('src');
+
+ // Skip if no image source or already processed
+ if (!imageSrc || self.processedImages.has(imageSrc)) {
+ return;
+ }
+
+ self.images[key] = {
+ key: key,
+ remote_image: imageSrc
+ };
+
+ self.update(key);
+ };
+
+ let saveButtonHandler = function () {
+ self.restoreOriginalImages();
+ };
+
+ // Attach event handlers
+ _PBEvents.on('image:renderAfter', renderAfterHandler);
+ $(self.saveButtonSelector).on('click', saveButtonHandler);
+
+ // Store handlers for cleanup
+ self.eventHandlers.push({
+ event: 'image:renderAfter',
+ handler: renderAfterHandler
+ });
+ self.eventHandlers.push({
+ selector: self.saveButtonSelector,
+ event: 'click',
+ handler: saveButtonHandler
+ });
+
+ return self;
+ },
+
+ /**
+ * Restore original images before save
+ */
+ restoreOriginalImages: function () {
+ let self = this;
+
+ $.each(self.images, function (_, elem) {
+ if (elem.cld_image) {
+ // Use safer selector approach
+ $('img').filter(function () {
+ return $(this).attr('src') === elem.cld_image;
+ }).attr('src', elem.remote_image);
+ }
+ });
+ },
+
+ /**
+ * Validate image URL
+ * @param {string} url - URL to validate
+ * @returns {boolean} Whether URL is valid
+ */
+ isValidImageUrl: function (url) {
+ if (!url || typeof url !== 'string') {
+ return false;
+ }
+
+ // Basic URL validation
+ try {
+ let urlObj = new URL(url, window.location.origin);
+ return /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(urlObj.pathname) ||
+ url.indexOf('media/') !== -1;
+ } catch (e) {
+ return false;
+ }
+ },
+
+ /**
+ * Update image with Cloudinary version
+ * @param {string} key - Image key
+ */
+ update: function (key) {
+ let self = this,
+ imageData = self.images[key];
+
+ // Validation checks
+ if (!imageData || !imageData.remote_image) {
+ return;
+ }
+
+ if (!self.isValidImageUrl(imageData.remote_image)) {
+ console.warn('Invalid image URL:', imageData.remote_image);
+ return;
+ }
+
+ // Check if already processing this image URL
+ let imageUrl = imageData.remote_image;
+ if (self.processedImages.has(imageUrl)) {
+ return;
+ }
+
+ // Abort previous request for this key if still pending
+ if (self.pendingRequests[key]) {
+ self.pendingRequests[key].abort();
+ }
+
+ // Mark as being processed
+ self.processedImages.add(imageUrl);
+
+ // Make AJAX request and store reference
+ self.pendingRequests[key] = $.ajax({
+ url: config.ajaxUrl,
+ type: 'POST',
+ dataType: 'json',
+ data: {
+ remote_image: imageData.remote_image,
+ form_key: window.FORM_KEY || ''
+ },
+ success: function (image) {
+
+ delete self.pendingRequests[key];
+
+ // Validate response
+ if (!image || typeof image !== 'string') {
+ console.warn('Invalid response for image:', key);
+ return;
+ }
+
+ self.images[key].cld_image = image;
+
+ // Update image in DOM using safer selector
+ $('img').filter(function () {
+ return $(this).attr('src') === imageData.remote_image;
+ }).attr('src', image);
+ },
+ error: function (xhr, textStatus, errorThrown) {
+ // Clean up pending request reference
+ delete self.pendingRequests[key];
+
+ // Only log if not aborted
+ if (textStatus !== 'abort') {
+ console.error('Cloudinary image update failed:', {
+ key: key,
+ status: textStatus,
+ error: errorThrown,
+ response: xhr.responseText
+ });
+
+ // Remove from processed set to allow retry
+ self.processedImages.delete(imageUrl);
+ }
+ }
+ });
+ },
+
+ /**
+ * Cleanup event listeners and abort pending requests
+ */
+ destroy: function () {
+ let self = this;
+
+ // Remove event listeners
+ self.eventHandlers.forEach(function (handler) {
+ if (handler.selector) {
+ $(handler.selector).off(handler.event, handler.handler);
+ } else if (handler.event) {
+ _PBEvents.off(handler.event, handler.handler);
+ }
+ });
+
+ // Abort all pending AJAX requests
+ $.each(self.pendingRequests, function (_, request) {
+ request.abort();
+ });
+
+ // Clear data
+ self.images = {};
+ self.pendingRequests = {};
+ self.processedImages.clear();
+ self.eventHandlers = [];
+ }
+ };
+
+ return updateHandler.init();
+ };
+ });
\ No newline at end of file
diff --git a/view/adminhtml/web/js/content-type/image/preview.js b/view/adminhtml/web/js/content-type/image/preview.js
new file mode 100644
index 00000000..eac330e4
--- /dev/null
+++ b/view/adminhtml/web/js/content-type/image/preview.js
@@ -0,0 +1,97 @@
+/*eslint-disable */
+/* jscs:disable */
+function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
+function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
+define(["Magento_PageBuilder/js/events", "Magento_PageBuilder/js/content-type-menu/hide-show-option", "Magento_PageBuilder/js/uploader", "Magento_PageBuilder/js/content-type/preview"], function (_events, _hideShowOption, _uploader, _preview) {
+ /**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+ /**
+ * @api
+ */
+ var Preview = /*#__PURE__*/function (_preview2) {
+ "use strict";
+
+ function Preview() {
+ return _preview2.apply(this, arguments) || this;
+ }
+ _inheritsLoose(Preview, _preview2);
+ var _proto = Preview.prototype;
+ /**
+ * Return an array of options
+ *
+ * @returns {OptionsInterface}
+ */
+ _proto.retrieveOptions = function retrieveOptions() {
+ var options = _preview2.prototype.retrieveOptions.call(this);
+ options.hideShow = new _hideShowOption({
+ preview: this,
+ icon: _hideShowOption.showIcon,
+ title: _hideShowOption.showText,
+ action: this.onOptionVisibilityToggle,
+ classes: ["hide-show-content-type"],
+ sort: 40
+ });
+ return options;
+ }
+
+ /**
+ * Get registry callback reference to uploader UI component
+ *
+ * @returns {Uploader}
+ */;
+ _proto.getUploader = function getUploader() {
+ var initialImageValue = this.contentType.dataStore.get(this.config.additional_data.uploaderConfig.dataScope, "");
+ return new _uploader("imageuploader_" + this.contentType.id, this.config.additional_data.uploaderConfig, this.contentType.id, this.contentType.dataStore, initialImageValue);
+ }
+
+ /**
+ * Get viewport image data
+ * CLOUDINARY MODIFICATION: Removed rand parameter to prevent URL encoding issues
+ */;
+ _proto.getViewportImageData = function getViewportImageData() {
+ var desktopImageData = this.data.desktop_image;
+ var mobileImageData = this.data.mobile_image;
+ var result = this.viewport() === "mobile" && typeof mobileImageData !== "undefined" ? mobileImageData : desktopImageData;
+ // CLOUDINARY: Commented out rand parameter addition to prevent double-encoding issues
+ // if (result && result.attributes() && result.attributes().src.length > 0 && result.attributes().src.indexOf("?rand") === -1) {
+ // result.attributes().src += "?rand=" + Date.now();
+ // }
+ return result;
+ }
+
+ /**
+ * @inheritDoc
+ */;
+ _proto.bindEvents = function bindEvents() {
+ var _this = this;
+ _preview2.prototype.bindEvents.call(this);
+ _events.on("image:mountAfter", function (args) {
+ if (args.id === _this.contentType.id) {
+ _this.isSnapshot.subscribe(function (value) {
+ _this.changeUploaderControlsVisibility();
+ });
+ _this.changeUploaderControlsVisibility();
+ }
+ });
+ _events.on(this.config.name + ":" + this.contentType.id + ":updateAfter", function () {
+ var files = _this.contentType.dataStore.get(_this.config.additional_data.uploaderConfig.dataScope);
+ var imageObject = files ? files[0] : {};
+ _events.trigger("image:" + _this.contentType.id + ":assignAfter", imageObject);
+ });
+ }
+
+ /**
+ * Change uploader controls visibility
+ */;
+ _proto.changeUploaderControlsVisibility = function changeUploaderControlsVisibility() {
+ var _this2 = this;
+ this.getUploader().getUiComponent()(function (uploader) {
+ uploader.visibleControls = !_this2.isSnapshot();
+ });
+ };
+ return Preview;
+ }(_preview);
+ return Preview;
+});
\ No newline at end of file
diff --git a/view/adminhtml/web/js/form/element/validator-rules-mixin.js b/view/adminhtml/web/js/form/element/validator-rules-mixin.js
new file mode 100644
index 00000000..c4bc1514
--- /dev/null
+++ b/view/adminhtml/web/js/form/element/validator-rules-mixin.js
@@ -0,0 +1,64 @@
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+ 'jquery',
+ 'underscore',
+ 'Magento_Ui/js/lib/validation/utils'
+], function($, _, utils) {
+ 'use strict';
+
+ /**
+ * Validate that string is url
+ * @param {String} href
+ * @return {Boolean}
+ */
+ function validateIsUrl(href) {
+ return (/^(http|https|ftp):\/\/(([A-Z0-9]([A-Z0-9_-]*[A-Z0-9]|))(\.[A-Z0-9]([A-Z0-9_-]*[A-Z0-9]|))*)(:(\d+))?(\/[A-Z0-9~](([A-Z0-9_~-]|\.)*[A-Z0-9~]|))*\/?(.*)?$/i).test(href); //eslint-disable-line max-len
+ }
+
+ return function(validator) {
+
+ validator.addRule(
+ 'validate-video-url',
+ function(href) {
+ if (utils.isEmptyNoTrim(href)) {
+ return true;
+ }
+
+ href = (href || '').replace(/^\s+/, '').replace(/\s+$/, '');
+
+ return validateIsUrl(href) && (
+ href.match(/youtube\.com|youtu\.be/) ||
+ href.match(/vimeo\.com/) ||
+ href.match(/cloudinary/) ||
+ href.match(/\.(mp4|ogv|webm)(?!\w)/)
+ );
+ },
+ $.mage.__('Please enter a valid video URL. Valid URLs have a video file extension (.mp4, .webm, .ogv) or links to videos on YouTube, Vimeo or Cloudinary.')//eslint-disable-line max-len
+ );
+
+ validator.addRule(
+ 'validate-video-source',
+ function (href) {
+ if (utils.isEmptyNoTrim(href)) {
+ return true;
+ }
+
+ href = (href || '').replace(/^\s+/, '').replace(/\s+$/, '');
+
+ return validateIsUrl(href) && (
+ href.match(/youtube\.com|youtu\.be/) ||
+ href.match(/vimeo\.com/) ||
+ href.match(/cloudinary/) ||
+ href.match(/\.(mp4|ogv|webm)(?!\w)/)
+ );
+ },
+ $.mage.__('Please enter a valid video URL. Valid URLs have a video file extension (.mp4, .webm, .ogv) or links to videos on YouTube, Vimeo or Cloudinary.')//eslint-disable-line max-len
+ );
+
+ return validator;
+ };
+});
diff --git a/view/adminhtml/web/js/get-video-information.js b/view/adminhtml/web/js/get-video-information.js
index 8bbeb8e4..0825a5ac 100644
--- a/view/adminhtml/web/js/get-video-information.js
+++ b/view/adminhtml/web/js/get-video-information.js
@@ -2,807 +2,850 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-define([
- 'jquery',
- 'Magento_Ui/js/modal/alert',
- 'jquery/ui',
- 'mage/translate'
-], function($, alert) {
- 'use strict';
-
- var videoRegister = {
- _register: {},
-
- /**
- * Checks, if api is already registered
- *
- * @param {String} api
- * @returns {bool}
- */
- isRegistered: function(api) {
- return this._register[api] !== undefined;
- },
-
- /**
- * Checks, if api is loaded
- *
- * @param {String} api
- * @returns {bool}
- */
- isLoaded: function(api) {
- return this._register[api] !== undefined && this._register[api] === true;
- },
-
- /**
- * Register new video api
- * @param {String} api
- * @param {bool} loaded
- */
- register: function(api, loaded) {
- loaded = loaded || false;
- this._register[api] = loaded;
- }
- };
-
- $.widget('mage.productVideoLoader', {
-
- /**
- * @private
- */
- _create: function() {
- switch (this.element.data('type')) {
- case 'youtube':
- this.element.videoYoutube();
- this._player = this.element.data('mageVideoYoutube');
- break;
-
- case 'vimeo':
- this.element.videoVimeo();
- this._player = this.element.data('mageVideoVimeo');
- break;
- case 'cloudinary':
- this.element.videoCloudinary();
- this._player = this.element.data('mageVideoCloudinary');
- break;
- default:
- throw {
- name: $.mage.__('Video Error'),
- message: $.mage.__('Unknown video type'),
+define(
+ [
+ 'jquery',
+ 'Magento_Ui/js/modal/alert',
+ 'jquery/ui',
+ 'mage/translate'
+ ],
+ function($, alert) {
+ 'use strict';
+
+ var videoRegister = {
+ _register: {},
- /**
- * Return string
- */
- toString: function() {
- return this.name + ': ' + this.message;
- }
- };
- }
- },
-
- /**
- * Initializes variables
- * @private
- */
- _initialize: function() {
- this._params = this.element.data('params') || {};
- this._code = this.element.data('code');
- this._width = this.element.data('width');
- this._height = this.element.data('height');
- this._autoplay = !!this.element.data('autoplay');
- this._videoSrc = this.element.data('video-src');
- this._playing = this._autoplay || false;
- this.useYoutubeNocookie = this.element.data('youtubenocookie') || false;
-
- this._responsive = this.element.data('responsive') !== false;
-
- if (this._responsive === true) {
- this.element.addClass('responsive');
- }
+ /**
+ * Checks, if api is already registered
+ *
+ * @param {String} api
+ * @returns {bool}
+ */
+ isRegistered: function(api) {
+ return this._register[api] !== undefined;
+ },
+
+ /**
+ * Checks, if api is loaded
+ *
+ * @param {String} api
+ * @returns {bool}
+ */
+ isLoaded: function(api) {
+ return this._register[api] !== undefined && this._register[api] === true;
+ },
- this._calculateRatio();
- },
-
- /**
- * Abstract play command
- */
- play: function() {
- this._player.play();
- },
-
- /**
- * Abstract pause command
- */
- pause: function() {
- this._player.pause();
- },
-
- /**
- * Abstract stop command
- */
- stop: function() {
- this._player.stop();
- },
-
- /**
- * Abstract playing command
- */
- playing: function() {
- return this._player.playing();
- },
-
- /**
- * Abstract destroying command
- */
- destroy: function() {
- this._player.destroy();
- },
-
- /**
- * Calculates ratio for responsive videos
- * @private
- */
- _calculateRatio: function() {
- if (!this._responsive) {
- return;
+ /**
+ * Register new video api
+ *
+ * @param {String} api
+ * @param {bool} loaded
+ */
+ register: function(api, loaded) {
+ loaded = loaded || false;
+ this._register[api] = loaded;
}
- this.element.css('paddingBottom', this._height / this._width * 100 + '%');
- }
- });
+ };
- $.widget('mage.videoYoutube', $.mage.productVideoLoader, {
+ $.widget(
+ 'mage.productVideoLoader', {
- /**
- * Initialization of the Youtube widget
- * @private
- */
- _create: function() {
- var self = this;
+ /**
+ * @private
+ */
+ _create: function() {
+ switch (this.element.data('type')) {
+ case 'youtube':
+ this.element.videoYoutube();
+ this._player = this.element.data('mageVideoYoutube');
+ break;
- this._initialize();
+ case 'vimeo':
+ this.element.videoVimeo();
+ this._player = this.element.data('mageVideoVimeo');
+ break;
+ case 'cloudinary':
+ this.element.videoCloudinary();
+ this._player = this.element.data('mageVideoCloudinary');
+ break;
+ default:
+ throw {
+ name: $.mage.__('Video Error'),
+ message: $.mage.__('Unknown video type'),
+
+ /**
+ * Return string
+ */
+ toString: function() {
+ return this.name + ': ' + this.message;
+ }
+ };
+ }
+ },
- this.element.append('
');
+ /**
+ * Initializes variables
+ *
+ * @private
+ */
+ _initialize: function() {
+ this._params = this.element.data('params') || {};
+ this._code = this.element.data('code');
+ this._width = this.element.data('width');
+ this._height = this.element.data('height');
+ this._autoplay = !!this.element.data('autoplay');
+ this._videoSrc = this.element.data('video-src');
+ this._playing = this._autoplay || false;
+ this.useYoutubeNocookie = this.element.data('youtubenocookie') || false;
+
+ this._responsive = this.element.data('responsive') !== false;
+
+ if (this._responsive === true) {
+ this.element.addClass('responsive');
+ }
- this._on(window, {
+ this._calculateRatio();
+ },
/**
- * Youtube state check
- * @private
+ * Abstract play command
*/
- 'youtubeapiready': function() {
- var host = 'https://www.youtube.com';
+ play: function () {
+ this._player.play();
+ },
- if (self.useYoutubeNocookie) {
- host = 'https://www.youtube-nocookie.com';
+ /**
+ * Abstract pause command
+ */
+ pause: function () {
+ this._player.pause();
+ },
+
+ /**
+ * Abstract stop command
+ */
+ stop: function () {
+ this._player.stop();
+ },
+
+ /**
+ * Abstract playing command
+ */
+ playing: function () {
+ return this._player.playing();
+ },
+
+ /**
+ * Abstract destroying command
+ */
+ destroy: function () {
+ if (this._player) {
+ this._player.destroy();
}
+ },
- if (self._player !== undefined) {
+ /**
+ * Calculates ratio for responsive videos
+ * @private
+ */
+ _calculateRatio: function () {
+ if (!this._responsive) {
return;
}
+ this.element.css('paddingBottom', this._height / this._width * 100 + '%');
+ }
+ }
+ );
- if (self._autoplay) {
- self._params.autoplay = 1;
- }
- self._params.rel = 0;
+ $.widget(
+ 'mage.videoYoutube', $.mage.productVideoLoader, {
- self._player = new window.YT.Player(self.element.children(':first')[0], {
- height: self._height,
- width: self._width,
- videoId: self._code,
- playerVars: self._params,
- host: host,
- events: {
+ /**
+ * Initialization of the Youtube widget
+ *
+ * @private
+ */
+ _create: function() {
+ var self = this;
+
+ this._initialize();
+
+ this.element.append('
');
+
+ this._on(
+ window, {
/**
+ * Youtube state check
+ *
* @private
*/
- 'onReady': function onPlayerReady() {
- self._player.getDuration();
- },
+ 'youtubeapiready': function() {
+ var host = 'https://www.youtube.com';
- /**
- * State change flag init
- */
- onStateChange: function(data) {
- switch (window.parseInt(data.data, 10)) {
- case 1:
- self._playing = true;
- break;
- default:
- self._playing = false;
- break;
+ if (self.useYoutubeNocookie) {
+ host = 'https://www.youtube-nocookie.com';
+ }
+
+ if (self._player !== undefined) {
+ return;
}
- self._trigger('statechange', {}, data);
+ if (self._autoplay) {
+ self._params.autoplay = 1;
+ }
+ self._params.rel = 0;
+
+ self._player = new window.YT.Player(
+ self.element.children(':first')[0], {
+ height: self._height,
+ width: self._width,
+ videoId: self._code,
+ playerVars: self._params,
+ host: host,
+ events: {
+
+ /**
+ * @private
+ */
+ 'onReady': function onPlayerReady() {
+ self._player.getDuration();
+ },
+
+ /**
+ * State change flag init
+ */
+ onStateChange: function(data) {
+ switch (window.parseInt(data.data, 10)) {
+ case 1:
+ self._playing = true;
+ break;
+ default:
+ self._playing = false;
+ break;
+ }
+
+ self._trigger('statechange', {}, data);
+ }
+ }
+
+ }
+ );
}
}
+ );
- });
- }
- });
-
- this._loadApi();
- },
-
- /**
- * Loads Youtube API and triggers event, when loaded
- * @private
- */
- _loadApi: function() {
- var element,
- scriptTag;
-
- if (videoRegister.isRegistered('youtube')) {
- if (videoRegister.isLoaded('youtube')) {
- $(window).trigger('youtubeapiready');
- }
+ this._loadApi();
+ },
- return;
- }
- videoRegister.register('youtube');
+ /**
+ * Loads Youtube API and triggers event, when loaded
+ *
+ * @private
+ */
+ _loadApi: function() {
+ var element,
+ scriptTag;
- element = document.createElement('script');
- scriptTag = document.getElementsByTagName('script')[0];
+ if (videoRegister.isRegistered('youtube')) {
+ if (videoRegister.isLoaded('youtube')) {
+ $(window).trigger('youtubeapiready');
+ }
- element.async = true;
- element.src = 'https://www.youtube.com/iframe_api';
- scriptTag.parentNode.insertBefore(element, scriptTag);
+ return;
+ }
+ videoRegister.register('youtube');
- /**
- * Trigger youtube api ready event
- */
- window.onYouTubeIframeAPIReady = function() {
- $(window).trigger('youtubeapiready');
- videoRegister.register('youtube', true);
- };
- },
-
- /**
- * Play command for Youtube
- */
- play: function() {
- this._player.playVideo();
- this._playing = true;
- },
-
- /**
- * Pause command for Youtube
- */
- pause: function() {
- this._player.pauseVideo();
- this._playing = false;
- },
-
- /**
- * Stop command for Youtube
- */
- stop: function() {
- this._player.stopVideo();
- this._playing = false;
- },
-
- /**
- * Playing command for Youtube
- */
- playing: function() {
- return this._playing;
- },
-
- /**
- * stops and unloads player
- * @private
- */
- destroy: function() {
- this.stop();
- this._player.destroy();
- }
- });
-
- $.widget('mage.videoVimeo', $.mage.productVideoLoader, {
-
- /**
- * Initialize the Vimeo widget
- * @private
- */
- _create: function() {
- var timestamp,
- src,
- additionalParams;
-
- this._initialize();
- timestamp = new Date().getTime();
-
- if (this._autoplay) {
- additionalParams += '&autoplay=1';
- }
+ element = document.createElement('script');
+ scriptTag = document.getElementsByTagName('script')[0];
- src = 'https://player.vimeo.com/video/' +
- this._code + '?api=1&player_id=vimeo' +
- this._code +
- timestamp +
- additionalParams;
- this.element.append(
- $('')
- .attr('frameborder', 0)
- .attr('id', 'vimeo' + this._code + timestamp)
- .attr('width', this._width)
- .attr('height', this._height)
- .attr('src', src)
- );
-
- }
- });
-
- $.widget('mage.videoCloudinary', $.mage.productVideoLoader, {
-
- /**
- * Initialize the Cloudinary widget
- * @private
- */
- _create: function() {
- this._initialize();
-
- this.element.append(
- $('')
- .attr('frameborder', 0)
- .attr('id', 'cloudinary' + this._code + (new Date().getTime()))
- .attr('class', 'cld-video-player')
- .attr('width', this._width)
- .attr('height', this._height)
- .attr('src', this._videoSrc.replace(/(^\w+:|^)/, ''))
- .on("loadstart", function() {
- $('body').loader('show');
- })
- .on("load", function() {
- $('body').loader('hide');
- })
- );
-
- /*
- var fileExtensionrRegex = /(?:\.([^.]+))?$/;
- var video = $(' ')
- .attr('id', 'cloudinary' + this._code + (new Date().getTime()))
- .attr('class', 'cld-video-player')
- .attr('width', this._width)
- .attr('height', this._height)
- .attr('controls', true)
- .attr('src', this._videoSrc)
- .attr('type', "video/" + fileExtensionrRegex.exec(this._videoSrc)[1])
- .on("loadstart", function() {
- $('body').loader('show');
- })
- .on("loadeddata", function() {
- $('body').loader('hide');
- });
-
- this.element.append(
- video
- );
- */
-
- }
- });
-
- $.widget('mage.videoData', {
- options: {
- youtubeKey: '',
- cloudinaryPlaceholder: '',
- eventSource: '' //where is data going from - focus out or click on button
- },
-
- _REQUEST_VIDEO_INFORMATION_TRIGGER: 'request_video_information',
-
- _UPDATE_VIDEO_INFORMATION_TRIGGER: 'updated_video_information',
-
- _START_UPDATE_INFORMATION_TRIGGER: 'update_video_information',
-
- _ERROR_UPDATE_INFORMATION_TRIGGER: 'error_updated_information',
-
- _FINISH_UPDATE_INFORMATION_TRIGGER: 'finish_update_information',
-
- _VIDEO_URL_VALIDATE_TRIGGER: 'validate_video_url',
-
- _videoInformation: null,
-
- _currentVideoUrl: null,
-
- /**
- * @private
- */
- _init: function() {
- this.element.on(this._START_UPDATE_INFORMATION_TRIGGER, $.proxy(this._onRequestHandler, this));
- this.element.on(this._ERROR_UPDATE_INFORMATION_TRIGGER, $.proxy(this._onVideoInvalid, this));
- this.element.on(this._FINISH_UPDATE_INFORMATION_TRIGGER, $.proxy(
- function() {
- this._currentVideoUrl = null;
- }, this
- ));
- this.element.on(this._VIDEO_URL_VALIDATE_TRIGGER, $.proxy(this._onUrlValidateHandler, this));
- },
-
- /**
- * @private
- */
- _onUrlValidateHandler: function(event, callback, forceVideo) {
- var url = this.element.val(),
- videoInfo;
-
- videoInfo = this._validateURL(url, forceVideo);
-
- if (videoInfo) {
- callback();
- } else {
- this._onRequestError($.mage.__('Invalid video url'));
- }
- },
-
- /**
- * @private
- */
- _onRequestHandler: function() {
- var url = this.element.val(),
- self = this,
- videoInfo,
- type,
- id,
- googleapisUrl;
-
- if (this._currentVideoUrl === url) {
- return;
- }
+ element.async = true;
+ element.src = 'https://www.youtube.com/iframe_api';
+ scriptTag.parentNode.insertBefore(element, scriptTag);
- this._currentVideoUrl = url;
+ /**
+ * Trigger youtube api ready event
+ */
+ window.onYouTubeIframeAPIReady = function() {
+ $(window).trigger('youtubeapiready');
+ videoRegister.register('youtube', true);
+ };
+ },
- this.element.trigger(this._REQUEST_VIDEO_INFORMATION_TRIGGER, {
- url: url
- });
+ /**
+ * Play command for Youtube
+ */
+ play: function() {
+ this._player.playVideo();
+ this._playing = true;
+ },
- if (!url) {
- return;
- }
+ /**
+ * Pause command for Youtube
+ */
+ pause: function() {
+ this._player.pauseVideo();
+ this._playing = false;
+ },
- videoInfo = this._validateURL(url);
+ /**
+ * Stop command for Youtube
+ */
+ stop: function() {
+ this._player.stopVideo();
+ this._playing = false;
+ },
- if (!videoInfo) {
- this._onRequestError($.mage.__('Invalid video url'));
+ /**
+ * Playing command for Youtube
+ */
+ playing: function() {
+ return this._playing;
+ },
- return;
+ /**
+ * stops and unloads player
+ *
+ * @private
+ */
+ destroy: function() {
+ this.stop();
+ this._player.destroy();
+ }
}
+ );
+
+ $.widget('mage.videoVimeo', $.mage.productVideoLoader, {
/**
- *
- * @param {Object} data
+ * Initialize the Vimeo widget
* @private
*/
- function _onYouTubeLoaded(data) {
- var tmp,
- uploadedFormatted,
- respData,
- createErrorMessage;
+ _create: function () {
+ var timestamp,
+ src,
+ additionalParams;
+
+ this._initialize();
+ timestamp = new Date().getTime();
+
+ if (this._autoplay) {
+ additionalParams += '&autoplay=1';
+ }
+
+ src = 'https://player.vimeo.com/video/' +
+ this._code + '?api=1&player_id=vimeo' +
+ this._code +
+ timestamp +
+ additionalParams;
+ this.element.append(
+ $('')
+ .attr('frameborder', 0)
+ .attr('id', 'vimeo' + this._code + timestamp)
+ .attr('width', this._width)
+ .attr('height', this._height)
+ .attr('src', src)
+ );
+
+ }
+ });
+
+ $.widget(
+ 'mage.videoCloudinary', $.mage.productVideoLoader, {
/**
- * Create errors message
+ * Initialize the Cloudinary widget
*
- * @returns {String}
+ * @private
*/
- createErrorMessage = function() {
- var error = data.error,
- errors = error.errors,
- i,
- errLength = errors.length,
- tmpError,
- errReason,
- errorsMessage = [];
-
- for (i = 0; i < errLength; i++) {
- tmpError = errors[i];
- errReason = tmpError.reason;
-
- if (['keyInvalid'].indexOf(errReason) !== -1) {
- errorsMessage.push($.mage.__('Youtube API key is invalid'));
+ _create: function() {
+ this._initialize();
+
+ this.element.append(
+ $('')
+ .attr('frameborder', 0)
+ .attr('id', 'cloudinary' + this._code + (new Date().getTime()))
+ .attr('class', 'cld-video-player')
+ .attr('width', this._width)
+ .attr('height', this._height)
+ .attr('src', this._videoSrc.replace(/(^\w+:|^)/, ''))
+ .on(
+ "loadstart",
+ function() {
+ $('body').loader('show');
+ }
+ )
+ .on(
+ "load",
+ function() {
+ $('body').loader('hide');
+ }
+ )
+ );
+ }
+ }
+ );
- break;
- }
+ $.widget(
+ 'mage.videoData', {
+ options: {
+ youtubeKey: '',
+ cloudinaryPlaceholder: '',
+ eventSource: '' //where is data going from - focus out or click on button
+ },
- errorsMessage.push(tmpError.message);
- }
+ _REQUEST_VIDEO_INFORMATION_TRIGGER: 'request_video_information',
- return $.mage.__('Video cant be shown due to the following reason: ') +
- $.unique(errorsMessage).join(', ');
- };
+ _UPDATE_VIDEO_INFORMATION_TRIGGER: 'updated_video_information',
- if (data.error && [400, 402, 403].indexOf(data.error.code) !== -1) {
- this._onRequestError(createErrorMessage());
+ _START_UPDATE_INFORMATION_TRIGGER: 'update_video_information',
- return;
- }
+ _ERROR_UPDATE_INFORMATION_TRIGGER: 'error_updated_information',
- if (!data.items || data.items.length < 1) {
- this._onRequestError($.mage.__('Video not found'));
+ _FINISH_UPDATE_INFORMATION_TRIGGER: 'finish_update_information',
- return;
- }
+ _VIDEO_URL_VALIDATE_TRIGGER: 'validate_video_url',
- tmp = data.items[0];
- uploadedFormatted = tmp.snippet.publishedAt.replace('T', ' ').replace(/\..+/g, '');
- respData = {
- duration: this._formatYoutubeDuration(tmp.contentDetails.duration),
- channel: tmp.snippet.channelTitle,
- channelId: tmp.snippet.channelId,
- uploaded: uploadedFormatted,
- title: tmp.snippet.localized.title,
- description: tmp.snippet.description,
- thumbnail: tmp.snippet.thumbnails.high.url,
- videoId: videoInfo.id,
- videoProvider: videoInfo.type,
- useYoutubeNocookie: videoInfo.useYoutubeNocookie
- };
- this._videoInformation = respData;
- this.element.trigger(this._UPDATE_VIDEO_INFORMATION_TRIGGER, respData);
- this.element.trigger(this._FINISH_UPDATE_INFORMATION_TRIGGER, true);
- }
+ _videoInformation: null,
- /**
- * @private
- */
- function _onVimeoLoaded(data) {
- var tmp,
- respData;
+ _currentVideoUrl: null,
- if (data.length < 1) {
- this._onRequestError($.mage.__('Video not found'));
+ /**
+ * @private
+ */
+ _init: function() {
+ this.element.on(this._START_UPDATE_INFORMATION_TRIGGER, $.proxy(this._onRequestHandler, this));
+ this.element.on(this._ERROR_UPDATE_INFORMATION_TRIGGER, $.proxy(this._onVideoInvalid, this));
+ this.element.on(
+ this._FINISH_UPDATE_INFORMATION_TRIGGER, $.proxy(
+ function() {
+ this._currentVideoUrl = null;
+ }, this
+ )
+ );
+ this.element.on(this._VIDEO_URL_VALIDATE_TRIGGER, $.proxy(this._onUrlValidateHandler, this));
+ },
- return null;
- }
- tmp = data[0];
- respData = {
- duration: this._formatVimeoDuration(tmp.duration),
- channel: tmp['user_name'],
- channelId: tmp['user_url'],
- uploaded: tmp['upload_date'],
- title: tmp.title,
- description: tmp.description.replace(/( |<([^>]+)>)/ig, ''),
- thumbnail: tmp['thumbnail_large'],
- videoId: videoInfo.id,
- videoProvider: videoInfo.type
- };
- this._videoInformation = respData;
- this.element.trigger(this._UPDATE_VIDEO_INFORMATION_TRIGGER, respData);
- this.element.trigger(this._FINISH_UPDATE_INFORMATION_TRIGGER, true);
- }
+ /**
+ * @private
+ */
+ _onUrlValidateHandler: function(event, callback, forceVideo) {
+ var url = this.element.val(),
+ videoInfo;
- /**
- * @private
- */
- function _onCloudinaryLoaded(data) {
- var tmp,
- respData,
- context,
- thumbnail,
- thumbnail_bytes;
-
- if (data.length < 1) {
- this._onRequestError($.mage.__('Video not found'));
- return null;
- }
- if (data.error) {
- this._onRequestError($.mage.__('Video not found'));
- console.error(data.message);
- return null;
- }
- tmp = data.data;
+ videoInfo = this._validateURL(url, forceVideo);
- context = (tmp.context && tmp.context.custom) ? tmp.context.custom : {};
+ if (videoInfo) {
+ callback();
+ } else {
+ this._onRequestError($.mage.__('Invalid video url'));
+ }
+ },
- tmp.derived = tmp.derived || [];
- thumbnail = this.options.cloudinaryPlaceholder;
- thumbnail_bytes = 0;
- tmp.derived.forEach(function(derivedItem) {
- if (derivedItem.bytes && derivedItem.bytes > thumbnail_bytes) {
- thumbnail_bytes = derivedItem.bytes;
- thumbnail = (videoInfo.videoSrc.indexOf('https') === 0) ? derivedItem.secure_url : derivedItem.url;
+ /**
+ * @private
+ */
+ _onRequestHandler: function() {
+ var url = this.element.val(),
+ self = this,
+ videoInfo,
+ type,
+ id,
+ googleapisUrl;
+
+ if (this._currentVideoUrl === url) {
+ return;
}
- });
-
- respData = {
- duration: 'unknown',
- uploaded: tmp.created_at,
- title: context.caption || context.alt || "",
- description: (context.description || context.alt || context.caption || "").replace(/( |<([^>]+)>)/ig, ''),
- thumbnail: thumbnail,
- videoId: tmp.public_id,
- videoSrc: videoInfo.videoSrc,
- videoProvider: 'cloudinary'
- };
-
- this._videoInformation = respData;
- this.element.trigger(this._UPDATE_VIDEO_INFORMATION_TRIGGER, respData);
- this.element.trigger(this._FINISH_UPDATE_INFORMATION_TRIGGER, true);
- }
- type = videoInfo.type;
- id = videoInfo.id;
- videoInfo.videoSrc = url;
-
- if (type === 'youtube') {
- googleapisUrl = 'https://www.googleapis.com/youtube/v3/videos?id=' +
- id +
- '&part=snippet,contentDetails,statistics,status&key=' +
- this.options.youtubeKey + '&alt=json&callback=?';
- $.getJSON(googleapisUrl, {
- format: 'json'
- },
- $.proxy(_onYouTubeLoaded, self)
- ).fail(
- function() {
- self._onRequestError('Video not found');
+ this._currentVideoUrl = url;
+
+ this.element.trigger(
+ this._REQUEST_VIDEO_INFORMATION_TRIGGER, {
+ url: url
+ }
+ );
+
+ if (!url) {
+ return;
+ }
+
+ videoInfo = this._validateURL(url);
+
+ if (!videoInfo) {
+ this._onRequestError($.mage.__('Invalid video url'));
+
+ return;
}
- );
- } else if (type === 'vimeo') {
- $.ajax({
- url: 'https://www.vimeo.com/api/v2/video/' + id + '.json',
- dataType: 'jsonp',
- data: {
- format: 'json'
- },
- timeout: 5000,
- success: $.proxy(_onVimeoLoaded, self),
/**
+ *
+ * @param {Object} data
* @private
*/
- error: function() {
- self._onRequestError($.mage.__('Video not found'));
+ function _onYouTubeLoaded(data) {
+ var tmp,
+ uploadedFormatted,
+ respData,
+ createErrorMessage;
+
+ /**
+ * Create errors message
+ *
+ * @returns {String}
+ */
+ createErrorMessage = function () {
+ var error = data.error,
+ errors = error.errors,
+ i,
+ errLength = errors.length,
+ tmpError,
+ errReason,
+ errorsMessage = [];
+
+ for (i = 0; i < errLength; i++) {
+ tmpError = errors[i];
+ errReason = tmpError.reason;
+
+ if (['keyInvalid'].indexOf(errReason) !== -1) {
+ errorsMessage.push($.mage.__('Youtube API key is invalid'));
+
+ break;
+ }
+
+ errorsMessage.push(tmpError.message);
+ }
+
+ return $.mage.__('Video cant be shown due to the following reason: ') +
+ $.unique(errorsMessage).join(', ');
+ };
+
+ if (data.error && [400, 402, 403].indexOf(data.error.code) !== -1) {
+ this._onRequestError(createErrorMessage());
+
+ return;
+ }
+
+ if (!data.items || data.items.length < 1) {
+ this._onRequestError($.mage.__('Video not found'));
+
+ return;
+ }
+
+ tmp = data.items[0];
+ uploadedFormatted = tmp.snippet.publishedAt.replace('T', ' ').replace(/\..+/g, '');
+ respData = {
+ duration: this._formatYoutubeDuration(tmp.contentDetails.duration),
+ channel: tmp.snippet.channelTitle,
+ channelId: tmp.snippet.channelId,
+ uploaded: uploadedFormatted,
+ title: tmp.snippet.localized.title,
+ description: tmp.snippet.description,
+ thumbnail: tmp.snippet.thumbnails.high.url,
+ videoId: videoInfo.id,
+ videoProvider: videoInfo.type,
+ useYoutubeNocookie: videoInfo.useYoutubeNocookie
+ };
+ this._videoInformation = respData;
+ this.element.trigger(this._UPDATE_VIDEO_INFORMATION_TRIGGER, respData);
+ this.element.trigger(this._FINISH_UPDATE_INFORMATION_TRIGGER, true);
}
- });
- } else if (type === 'cloudinary') {
- $.ajax({
- method: "GET",
- url: '/rest/V1/cloudinary/resources/video',
- dataType: 'json',
- data: {
- id: videoInfo.id
- },
- timeout: 5000,
- success: $.proxy(_onCloudinaryLoaded, self),
/**
* @private
*/
- error: function() {
- self._onRequestError($.mage.__('Video not found'));
+ function _onVimeoLoaded(data) {
+ var tmp,
+ respData;
+
+ if (!data) {
+ this._onRequestError($.mage.__('Video not found'));
+
+ return null;
+ }
+ tmp = data;
+ respData = {
+ duration: this._formatVimeoDuration(tmp.duration),
+ channel: tmp['author_name'],
+ channelId: tmp['author_url'],
+ uploaded: tmp['upload_date'],
+ title: tmp.title,
+ description: tmp.description.replace(/( |<([^>]+)>)/ig, ''),
+ thumbnail: tmp['thumbnail_url'],
+ videoId: videoInfo.id,
+ videoProvider: videoInfo.type
+ };
+ this._videoInformation = respData;
+ this.element.trigger(this._UPDATE_VIDEO_INFORMATION_TRIGGER, respData);
+ this.element.trigger(this._FINISH_UPDATE_INFORMATION_TRIGGER, true);
}
- });
- }
- },
-
- /**
- * @private
- */
- _onVideoInvalid: function(event, data) {
- this._videoInformation = null;
- this.element.val('');
- alert({
- content: 'Error: "' + data + '"'
- });
- },
-
- /**
- * @private
- */
- _onRequestError: function(error) {
- this.element.trigger(this._ERROR_UPDATE_INFORMATION_TRIGGER, error);
- this.element.trigger(this._FINISH_UPDATE_INFORMATION_TRIGGER, false);
- this._currentVideoUrl = null;
- },
-
- /**
- * @private
- */
- _formatYoutubeDuration: function(duration) {
- var match = duration.match(/PT(\d+H)?(\d+M)?(\d+S)?/),
- hours = parseInt(match[1], 10) || 0,
- minutes = parseInt(match[2], 10) || 0,
- seconds = parseInt(match[3], 10) || 0;
-
- return this._formatVimeoDuration(hours * 3600 + minutes * 60 + seconds);
- },
-
- /**
- * @private
- */
- _formatVimeoDuration: function(seconds) {
- return (new Date(seconds * 1000)).toUTCString().match(/(\d\d:\d\d:\d\d)/)[0];
- },
-
- /**
- * @private
- */
- _parseHref: function(href) {
- var a = document.createElement('a');
-
- a.href = href;
-
- return a;
- },
-
- /**
- * @private
- */
- _baseName: function(str) {
- var base = new String(str).substring(str.lastIndexOf('/') + 1);
- if (base.lastIndexOf(".") != -1)
- base = base.substring(0, base.lastIndexOf("."));
- return base;
- },
-
- /**
- * @private
- */
- _fileExtension: function(str) {
- var re = /(?:\.([^.]+))?$/;
- return re.exec(str)[1];
- },
-
- /**
- * @private
- */
- _validateURL: function(href, forceVideo) {
- var id,
- type,
- ampersandPosition,
- vimeoRegex,
- useYoutubeNocookie = false;
-
- if (typeof href !== 'string') {
- return href;
- }
- href = this._parseHref(href);
- if (href.host.match(/youtube\.com/) && href.search) {
+ /**
+ * @private
+ */
+ function _onCloudinaryLoaded(data) {
+ var tmp,
+ respData,
+ context,
+ thumbnail,
+ thumbnail_bytes;
+ var self = this;
+
+ try {
+ data = JSON.parse(data);
+ } catch (e) {
+ this._onRequestError($.mage.__('Video not found'));
+ return null;
+ }
- id = href.search.split('v=')[1];
+ if (data.length < 1) {
+ this._onRequestError($.mage.__('Video not found'));
+ return null;
+ }
- if (id) {
- ampersandPosition = id.indexOf('&');
- type = 'youtube';
- }
+ if (data.error) {
+ this._onRequestError($.mage.__('Video not found'));
+ console.error(data.message);
+ return null;
+ }
- if (id && ampersandPosition !== -1) {
- id = id.substring(0, ampersandPosition);
- }
+ tmp = data.data;
+
+ context = (tmp.context && tmp.context.custom) ? tmp.context.custom : {};
+
+ tmp.derived = tmp.derived || [];
+ thumbnail = videoInfo.videoSrc
+ .replace(/\.[^/.]+$/, "")
+ .replace(new RegExp('\/v[0-9]{1,10}\/'), '/')
+ .replace(new RegExp('\/(' + this._escapeRegex(encodeURI(decodeURI(tmp.public_id))) + ')$'), '/so_auto/$1.jpg');
+
+ //Fallback for video thumbnail image, use placeholder or store logo
+ $.ajax({
+ type: "GET",
+ url: thumbnail,
+ async: false,
+ error: function(request, status, error) {
+ thumbnail = self.options.cloudinaryPlaceholder;
+ /*alert({
+ content: "Couldn't automatically generate Cloudinary video thumbnail, using fallback placeholder instead. You can always replace that manually later"
+ });*/
+ }
+ });
+
+ respData = {
+ duration: 'unknown',
+ uploaded: tmp.created_at,
+ title: context.caption || context.alt || tmp.public_id || "",
+ description: (context.description || context.alt || context.caption || "").replace(/( |<([^>]+)>)/ig, ''),
+ thumbnail: thumbnail,
+ videoId: tmp.public_id,
+ videoSrc: videoInfo.videoSrc,
+ videoProvider: 'cloudinary'
+ };
+
+ this._videoInformation = respData;
+ this.element.trigger(this._UPDATE_VIDEO_INFORMATION_TRIGGER, respData);
+ this.element.trigger(this._FINISH_UPDATE_INFORMATION_TRIGGER, true);
+ }
- } else if (href.host.match(/youtube\.com|youtu\.be|youtube-nocookie.com/)) {
- id = href.pathname.replace(/^\/(embed\/|v\/)?/, '').replace(/\/.*/, '');
- type = 'youtube';
+ type = videoInfo.type;
+ id = videoInfo.id;
+ videoInfo.videoSrc = url;
+
+ if (type === 'youtube') {
+ googleapisUrl = 'https://www.googleapis.com/youtube/v3/videos?id=' +
+ id +
+ '&part=snippet,contentDetails&key=' +
+ this.options.youtubeKey + '&alt=json&callback=?';
+ $.getJSON(googleapisUrl,
+ {
+ format: 'json'
+ },
+ $.proxy(_onYouTubeLoaded, self)
+ ).fail(
+ function () {
+ self._onRequestError('Video not found');
+ }
+ );
+ } else if (type === 'vimeo') {
+ $.ajax({
+ url: 'https://vimeo.com/api/oembed.json',
+ dataType: 'jsonp',
+ data: {
+ format: 'json',
+ url: 'https://vimeo.com/' + id
+ },
+ timeout: 5000,
+ success: $.proxy(_onVimeoLoaded, self),
- if (href.host.match(/youtube-nocookie.com/)) {
- useYoutubeNocookie = true;
- }
- } else if (href.host.match(/vimeo\.com/)) {
- type = 'vimeo';
- vimeoRegex = new RegExp(['https?:\\/\\/(?:www\\.|player\\.)?vimeo.com\\/(?:channels\\/(?:\\w+\\/)',
- '?|groups\\/([^\\/]*)\\/videos\\/|album\\/(\\d+)\\/video\\/|video\\/|)(\\d+)(?:$|\\/|\\?)'
- ].join(''));
-
- if (href.href.match(vimeoRegex) != null) {
- id = href.href.match(vimeoRegex)[3];
- }
- } else if (href.host.match(/cloudinary\.com/)) {
- type = 'cloudinary';
- id = this._baseName(href.href);
- }
+ /**
+ * @private
+ */
+ error: function () {
+ self._onRequestError($.mage.__('Video not found'));
+ }
+ });
+ } else if (type === 'cloudinary') {
+ $.ajax({
+ method: "GET",
+ url: '/rest/V1/cloudinary/resources/video',
+ dataType: 'json',
+ data: {
+ id: videoInfo.id
+ },
+ timeout: 5000,
+ success: $.proxy(_onCloudinaryLoaded, self),
- if ((!id || !type) && forceVideo) {
- id = href.href;
- type = 'custom';
- }
+ /**
+ * @private
+ */
+ error: function() {
+ self._onRequestError($.mage.__('Video not found'));
+ }
+ });
+ }
+ },
+
+ /**
+ * @private
+ */
+ _onVideoInvalid: function(event, data) {
+ this._videoInformation = null;
+ this.element.val('');
+ alert({
+ content: 'Error: "' + data + '"'
+ });
+ },
+
+ /**
+ * @private
+ */
+ _onRequestError: function(error) {
+ this.element.trigger(this._ERROR_UPDATE_INFORMATION_TRIGGER, error);
+ this.element.trigger(this._FINISH_UPDATE_INFORMATION_TRIGGER, false);
+ this._currentVideoUrl = null;
+ },
+
+ /**
+ * @private
+ */
+ _formatYoutubeDuration: function(duration) {
+ var match = duration.match(/PT(\d+H)?(\d+M)?(\d+S)?/),
+ hours = parseInt(match[1], 10) || 0,
+ minutes = parseInt(match[2], 10) || 0,
+ seconds = parseInt(match[3], 10) || 0;
+
+ return this._formatVimeoDuration(hours * 3600 + minutes * 60 + seconds);
+ },
+
+ /**
+ * @private
+ */
+ _formatVimeoDuration: function(seconds) {
+ return (new Date(seconds * 1000)).toUTCString().match(/(\d\d:\d\d:\d\d)/)[0];
+ },
+
+ /**
+ * @private
+ */
+ _parseHref: function(href) {
+ var a = document.createElement('a');
+
+ a.href = href;
+
+ return a;
+ },
+
+ /**
+ * @private
+ */
+ _baseName: function(str) {
+ var base = new String(str).substring(str.lastIndexOf('/') + 1);
+ if (base.lastIndexOf(".") != -1) {
+ base = base.substring(0, base.lastIndexOf("."));
+ }
+ return base;
+ },
+
+ /**
+ * @private
+ */
+ _fileExtension: function(str) {
+ var re = /(?:\.([^.]+))?$/;
+ return re.exec(str)[1];
+ },
+
+ /**
+ * @private
+ */
+ _escapeRegex: function(string) {
+ return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+ },
+
+ /**
+ * @private
+ */
+ _validateURL: function(href, forceVideo) {
+ var id,
+ type,
+ ampersandPosition,
+ vimeoRegex,
+ useYoutubeNocookie = false,
+ cname;
+
+ cname = $('#cld_domain_cname').data('value');
+
+ if (typeof href !== 'string') {
+ return href;
+ }
+ href = this._parseHref(href);
- return id ? {
- id: id,
- type: type,
- s: href.search.replace(/^\?/, ''),
- useYoutubeNocookie: useYoutubeNocookie
- } : false;
- }
- });
-});
\ No newline at end of file
+ if (href.host.match(/youtube\.com/) && href.search) {
+
+ id = href.search.split('v=')[1];
+
+ if (id) {
+ ampersandPosition = id.indexOf('&');
+ type = 'youtube';
+ }
+
+ if (id && ampersandPosition !== -1) {
+ id = id.substring(0, ampersandPosition);
+ }
+
+ } else if (href.host.match(/youtube\.com|youtu\.be|youtube-nocookie.com/)) {
+ id = href.pathname.replace(/^\/(embed\/|v\/)?/, '').replace(/\/.*/, '');
+ type = 'youtube';
+
+ if (href.host.match(/youtube-nocookie.com/)) {
+ useYoutubeNocookie = true;
+ }
+ } else if (href.host.match(/vimeo\.com/)) {
+ type = 'vimeo';
+ vimeoRegex = new RegExp(['https?:\\/\\/(?:www\\.|player\\.)?vimeo.com\\/(?:channels\\/(?:\\w+\\/)',
+ '?|groups\\/([^\\/]*)\\/videos\\/|album\\/(\\d+)\\/video\\/|video\\/|)(\\d+)(?:$|\\/|\\?)'
+ ].join(''));
+
+ if (href.href.match(vimeoRegex) != null) {
+ id = href.href.match(vimeoRegex)[3];
+ }
+ } else if (href.host.match(/cloudinary/) || href.href.includes(cname)) {
+ type = 'cloudinary';
+ id = href.href.replace(new RegExp('^.*\/video\/(upload\/)?((.*\/)?v[0-9]{1,10}\/)?'), '').replace(/\.[^.\/]+$/, '');
+ }
+
+ if ((!id || !type)) {
+ id = href.href;
+ type = 'custom';
+ }
+
+ return id ? {
+ id: id,
+ type: type,
+ s: href.search.replace(/^\?/, ''),
+ useYoutubeNocookie: useYoutubeNocookie
+ } : false;
+ }
+ }
+ );
+ }
+);
diff --git a/view/adminhtml/web/js/new-video-dialog.js b/view/adminhtml/web/js/new-video-dialog.js
index 8469f11d..fd744ce9 100644
--- a/view/adminhtml/web/js/new-video-dialog.js
+++ b/view/adminhtml/web/js/new-video-dialog.js
@@ -2,1277 +2,1395 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-define([
- 'jquery',
- 'underscore',
- 'jquery/ui',
- 'Magento_Ui/js/modal/modal',
- 'mage/translate',
- 'mage/backend/tree-suggest',
- 'mage/backend/validation',
- 'Cloudinary_Cloudinary/js/get-video-information'
-], function($, _) {
- 'use strict';
-
- $.widget('mage.createVideoPlayer', {
- options: {
- videoId: '',
- videoProvider: '',
- videoSrc: '',
- container: '.video-player-container',
- videoClass: 'product-video',
- reset: false,
- useYoutubeNocookie: false,
- metaData: {
- DOM: {
- title: '.video-information.title span',
- uploaded: '.video-information.uploaded span',
- uploader: '.video-information.uploader span',
- duration: '.video-information.duration span',
- all: '.video-information span',
- wrapper: '.video-information'
+define(
+ [
+ 'jquery',
+ 'underscore',
+ 'jquery/ui',
+ 'Magento_Ui/js/modal/modal',
+ 'mage/translate',
+ 'mage/backend/tree-suggest',
+ 'mage/backend/validation',
+ 'Cloudinary_Cloudinary/js/get-video-information'
+ ],
+ function($, _) {
+ 'use strict';
+
+ $.widget(
+ 'mage.createVideoPlayer', {
+ options: {
+ videoId: '',
+ videoProvider: '',
+ videoSrc: '',
+ container: '.video-player-container',
+ videoClass: 'product-video',
+ reset: false,
+ useYoutubeNocookie: false,
+ metaData: {
+ DOM: {
+ title: '.video-information.title span',
+ uploaded: '.video-information.uploaded span',
+ uploader: '.video-information.uploader span',
+ duration: '.video-information.duration span',
+ all: '.video-information span',
+ wrapper: '.video-information'
+ },
+ data: {
+ title: '',
+ uploaded: '',
+ uploader: '',
+ uploaderUrl: '',
+ duration: ''
+ }
+ }
},
- data: {
- title: '',
- uploaded: '',
- uploader: '',
- uploaderUrl: '',
- duration: ''
- }
- }
- },
- _FINISH_CREATE_VIDEO_TRIGGER: 'finish_create_video',
+ _FINISH_CREATE_VIDEO_TRIGGER: 'finish_create_video',
- _FINISH_UPDATE_VIDEO_TRIGGER: 'finish_update_video',
+ _FINISH_UPDATE_VIDEO_TRIGGER: 'finish_update_video',
- /**
- * @private
- */
- _init: function() {
- if (this.options.reset) {
- this.reset();
- } else {
- this.update();
- }
+ /**
+ * @private
+ */
+ _init: function() {
+ if (this.options.reset) {
+ this.reset();
+ } else {
+ this.update();
+ }
- this.element.on('reset', $.proxy(this.reset, this));
+ this.element.on('reset', $.proxy(this.reset, this));
- },
+ },
- /**
- * @returns {Boolean}
- */
- update: function() {
- var checkVideoID = this.element.find(this.options.container).find(
- '.' + this.options.videoClass
- ).data('code'),
- eventVideoData = {
- oldVideoId: checkVideoID ? checkVideoID.toString() : checkVideoID,
- newVideoId: this.options.videoId ? this.options.videoId.toString() : this.options.videoId
- };
-
- if (checkVideoID && checkVideoID !== this.options.videoId) {
- this._doUpdate();
- this.element.trigger(this._FINISH_UPDATE_VIDEO_TRIGGER, eventVideoData);
- } else if (checkVideoID && checkVideoID === this.options.videoId) {
- return false;
- } else if (!checkVideoID) {
- this._doUpdate();
- this.element.trigger(this._FINISH_CREATE_VIDEO_TRIGGER, eventVideoData);
- }
+ /**
+ * @returns {Boolean}
+ */
+ update: function() {
+ var checkVideoID = this.element.find(this.options.container).find(
+ '.' + this.options.videoClass
+ ).data('code'),
+ eventVideoData = {
+ oldVideoId: checkVideoID ? checkVideoID.toString() : checkVideoID,
+ newVideoId: this.options.videoId ? this.options.videoId.toString() : this.options.videoId
+ };
- },
+ if (checkVideoID && checkVideoID !== this.options.videoId) {
+ this._doUpdate();
+ this.element.trigger(this._FINISH_UPDATE_VIDEO_TRIGGER, eventVideoData);
+ } else if (checkVideoID && checkVideoID === this.options.videoId) {
+ return false;
+ } else if (!checkVideoID) {
+ this._doUpdate();
+ this.element.trigger(this._FINISH_CREATE_VIDEO_TRIGGER, eventVideoData);
+ }
- /**
- * @private
- */
- _doUpdate: function() {
- var uploaderLinkUrl,
- uploaderLink;
-
- this.reset();
- this.element.find(this.options.container).append(
- '
'
- );
- this.element.find(this.options.metaData.DOM.wrapper).show();
- this.element.find(this.options.metaData.DOM.title).text(this.options.metaData.data.title);
- this.element.find(this.options.metaData.DOM.uploaded).text(this.options.metaData.data.uploaded);
- this.element.find(this.options.metaData.DOM.duration).text(this.options.metaData.data.duration);
-
- if (this.options.videoProvider === 'youtube') {
- uploaderLinkUrl = 'https://youtube.com/channel/' + this.options.metaData.data.uploaderUrl;
- } else if (this.options.videoProvider === 'vimeo') {
- uploaderLinkUrl = this.options.metaData.data.uploaderUrl;
- }
- if (uploaderLinkUrl) {
- uploaderLink = document.createElement('a');
- uploaderLink.setAttribute('href', uploaderLinkUrl);
- uploaderLink.setAttribute('target', '_blank');
- uploaderLink.innerText = this.options.metaData.data.uploader;
- this.element.find(this.options.metaData.DOM.uploader)[0].appendChild(uploaderLink);
- }
- this.element.find('.' + this.options.videoClass).productVideoLoader();
+ },
+
+ /**
+ * @private
+ */
+ _doUpdate: function() {
+ var uploaderLinkUrl,
+ uploaderLink;
+
+ this.reset();
+ this.element.find(this.options.container).append(
+ '
'
+ );
+ this.element.find(this.options.metaData.DOM.wrapper).show();
+ this.element.find(this.options.metaData.DOM.title).text(this.options.metaData.data.title);
+ this.element.find(this.options.metaData.DOM.uploaded).text(this.options.metaData.data.uploaded);
+ this.element.find(this.options.metaData.DOM.duration).text(this.options.metaData.data.duration);
+
+ if (this.options.videoProvider === 'youtube') {
+ uploaderLinkUrl = 'https://youtube.com/channel/' + this.options.metaData.data.uploaderUrl;
+ } else if (this.options.videoProvider === 'vimeo') {
+ uploaderLinkUrl = this.options.metaData.data.uploaderUrl;
+ }
+ if (uploaderLinkUrl) {
+ uploaderLink = document.createElement('a');
+ uploaderLink.setAttribute('href', uploaderLinkUrl);
+ uploaderLink.setAttribute('target', '_blank');
+ uploaderLink.innerText = this.options.metaData.data.uploader;
+ this.element.find(this.options.metaData.DOM.uploader)[0].appendChild(uploaderLink);
+ }
+ this.element.find('.' + this.options.videoClass).productVideoLoader();
- },
+ },
- /**
- * Reset
- */
- reset: function() {
- this.element.find(this.options.container).find('.' + this.options.videoClass).remove();
- this.element.find(this.options.metaData.DOM.wrapper).hide();
- this.element.find(this.options.metaData.DOM.all).text('');
-
- }
- });
-
- $.widget('mage.updateInputFields', {
- options: {
- reset: false,
- DOM: {
- urlField: 'input[name="video_url"]',
- titleField: 'input[name="video_title"]',
- fileField: '#file_name',
- descriptionField: 'textarea[name="video_description"]',
- thumbnailLocation: '.field-new_video_screenshot_preview .admin__field-control'
- },
- data: {
- url: '',
- title: '',
- description: '',
- thumbnail: ''
- }
- },
+ /**
+ * Reset
+ */
+ reset: function() {
+ this.element.find(this.options.container).find('.' + this.options.videoClass).remove();
+ this.element.find(this.options.metaData.DOM.wrapper).hide();
+ this.element.find(this.options.metaData.DOM.all).text('');
- /**
- * @private
- */
- _init: function() {
- if (this.options.reset) {
- this.reset();
- } else {
- this.update();
+ }
}
- },
+ );
- /**
- * Update
- */
- update: function() {
- $(this.options.DOM.titleField).val(this.options.data.title);
- $(this.options.DOM.descriptionField).val(this.options.data.description);
- },
+ $.widget(
+ 'mage.updateInputFields', {
+ options: {
+ reset: false,
+ DOM: {
+ urlField: 'input[name="video_url"]',
+ titleField: 'input[name="video_title"]',
+ fileField: '#file_name',
+ descriptionField: 'textarea[name="video_description"]',
+ thumbnailLocation: '.field-new_video_screenshot_preview .admin__field-control'
+ },
+ data: {
+ url: '',
+ title: '',
+ description: '',
+ thumbnail: ''
+ }
+ },
- /**
- * Reset
- */
- reset: function() {
- $(this.options.DOM.fileField).val('');
- $(this.options.DOM.urlField).val('');
- $(this.options.DOM.titleField).val('');
- $(this.options.DOM.descriptionField).val('');
- }
- });
+ /**
+ * @private
+ */
+ _init: function() {
+ if (this.options.reset) {
+ this.reset();
+ } else {
+ this.update();
+ }
+ },
- /**
- */
- $.widget('mage.newVideoDialog', {
+ /**
+ * Update
+ */
+ update: function() {
+ $(this.options.DOM.titleField).val(this.options.data.title);
+ $(this.options.DOM.descriptionField).val(this.options.data.description);
+ },
- _previewImage: null,
+ /**
+ * Reset
+ */
+ reset: function() {
+ $(this.options.DOM.fileField).val('');
+ $(this.options.DOM.urlField).val('');
+ $(this.options.DOM.titleField).val('');
+ $(this.options.DOM.descriptionField).val('');
+ }
+ }
+ );
- clickedElement: '',
+ /**
+ */
+ $.widget(
+ 'mage.newVideoDialog', {
- _images: {},
+ _previewImage: null,
- _imageTypes: [
- '.jpeg',
- '.pjpeg',
- '.jpeg',
- '.jpg',
- '.pjpeg',
- '.png',
- '.gif'
- ],
+ clickedElement: '',
- _imageProductGalleryWrapperSelector: '#image-container',
+ _images: {},
- _videoPreviewInputSelector: '#new_video_screenshot',
+ _imageTypes: [
+ '.jpeg',
+ '.pjpeg',
+ '.jpeg',
+ '.jpg',
+ '.pjpeg',
+ '.png',
+ '.gif'
+ ],
- _videoPreviewRemoteSelector: '',
+ _imageProductGalleryWrapperSelector: '#image-container',
- _videoDisableinputSelector: '#new_video_disabled',
+ _videoPreviewInputSelector: '#new_video_screenshot',
- _videoPreviewImagePointer: '#new_video_screenshot_preview',
+ _videoPreviewRemoteSelector: '',
- _videoFormSelector: '#new_video_form',
+ _videoDisableinputSelector: '#new_video_disabled',
- _itemIdSelector: '#item_id',
+ _videoPreviewImagePointer: '#new_video_screenshot_preview',
- _videoUrlSelector: '[name="video_url"]',
+ _videoFormSelector: '#new_video_form',
- _videoImageFilenameselector: '#file_name',
+ _itemIdSelector: '#item_id',
- _videoUrlWidget: null,
+ _videoUrlSelector: '[name="video_url"]',
- _videoInformationBtnSelector: '[name="new_video_get"]',
+ _videoImageFilenameselector: '#file_name',
- _editVideoBtnSelector: '.image',
+ _videoUrlWidget: null,
- _deleteGalleryVideoSelector: '[data-role=delete-button]',
+ _videoInformationBtnSelector: '[name="new_video_get"]',
- _deleteGalleryVideoSelectorBtn: null,
+ _editVideoBtnSelector: '.image',
- _videoInformationGetBtn: null,
+ _deleteGalleryVideoSelector: '[data-role=delete-button]',
- _videoInformationGetUrlField: null,
+ _deleteGalleryVideoSelectorBtn: null,
- _videoInformationGetEditBtn: null,
+ _videoInformationGetBtn: null,
- _isEditPage: false,
+ _videoInformationGetUrlField: null,
- _onlyVideoPlayer: false,
+ _videoInformationGetEditBtn: null,
- _tempPreviewImageData: null,
+ _isEditPage: false,
- _videoPlayerSelector: '.mage-new-video-dialog',
+ _onlyVideoPlayer: false,
- _videoRequestComplete: null,
+ _tempPreviewImageData: null,
- _gallery: null,
+ _videoPlayerSelector: '.mage-new-video-dialog',
- /**
- * Bind events
- * @private
- */
- _bind: function() {
- var events = {
- 'setImage': '_onSetImage'
- };
-
- this._on(events);
-
- this._videoUrlWidget = this.element.find(this._videoUrlSelector).videoData({
- youtubeKey: this.options.youTubeApiKey,
- cloudinaryPlaceholder: this.options.cloudinaryPlaceholder,
- eventSource: 'focusout'
- });
-
- this._videoInformationGetBtn = this.element.find(this._videoInformationBtnSelector);
- this._videoInformationGetUrlField = this.element.find(this._videoUrlSelector);
- this._videoInformationGetEditBtn = this._gallery.find(this._editVideoBtnSelector);
-
- this._videoInformationGetBtn.on('click', $.proxy(this._onGetVideoInformationClick, this));
- this._videoInformationGetUrlField.on('focusout', $.proxy(this._onGetVideoInformationFocusOut, this));
-
- this._videoUrlWidget.on('updated_video_information', $.proxy(this._onGetVideoInformationSuccess, this));
- this._videoUrlWidget.on('error_updated_information', $.proxy(this._onGetVideoInformationError, this));
- this._videoUrlWidget.on(
- 'request_video_information',
- $.proxy(this._onGetVideoInformationStartRequest, this)
- );
- },
+ _videoRequestComplete: null,
- /**
- * Fired when user click on button "Get video information"
- * @private
- */
- _onGetVideoInformationClick: function() {
- this._onlyVideoPlayer = false;
- this._isEditPage = false;
- this._videoUrlWidget.trigger('update_video_information');
- },
+ _gallery: null,
- /**
- * Fired when user do focus out from url field
- * @private
- */
- _onGetVideoInformationFocusOut: function() {
- this._videoUrlWidget.trigger('update_video_information');
- },
+ /**
+ * Bind events
+ *
+ * @private
+ */
+ _bind: function() {
+ var events = {
+ 'setImage': '_onSetImage'
+ };
+
+ this._on(events);
+
+ this._videoUrlWidget = this.element.find(this._videoUrlSelector).videoData({
+ youtubeKey: this.options.youTubeApiKey,
+ cloudinaryPlaceholder: this.options.cloudinaryPlaceholder,
+ eventSource: 'focusout'
+ });
+
+ this._videoInformationGetBtn = this.element.find(this._videoInformationBtnSelector);
+ this._videoInformationGetUrlField = this.element.find(this._videoUrlSelector);
+ this._videoInformationGetEditBtn = this._gallery.find(this._editVideoBtnSelector);
+
+ this._videoInformationGetBtn.on('click', $.proxy(this._onGetVideoInformationClick, this));
+ this._videoInformationGetUrlField.on('focusout', $.proxy(this._onGetVideoInformationFocusOut, this));
+
+ this._videoUrlWidget.on('updated_video_information', $.proxy(this._onGetVideoInformationSuccess, this));
+ this._videoUrlWidget.on('error_updated_information', $.proxy(this._onGetVideoInformationError, this));
+ this._videoUrlWidget.on(
+ 'request_video_information',
+ $.proxy(this._onGetVideoInformationStartRequest, this)
+ );
+ },
- /**
- * @private
- */
- _onGetVideoInformationStartRequest: function() {
- this._videoRequestComplete = false;
- },
+ /**
+ * Fired when user click on button "Get video information"
+ *
+ * @private
+ */
+ _onGetVideoInformationClick: function() {
+ this._onlyVideoPlayer = false;
+ this._isEditPage = false;
+ this._videoUrlWidget.trigger('update_video_information');
+ },
- /**
- * Fired when user click Edit Video button
- * @private
- */
- _onGetVideoInformationEditClick: function() {
- this._onlyVideoPlayer = true;
- this._isEditPage = true;
- this._videoUrlWidget.trigger('update_video_information');
- },
+ /**
+ * Fired when user do focus out from url field
+ *
+ * @private
+ */
+ _onGetVideoInformationFocusOut: function() {
+ this._videoUrlWidget.trigger('update_video_information');
+ },
- /**
- * Fired when successfully received information about the video.
- * @param {Object} e
- * @param {Object} data
- * @private
- */
- _onGetVideoInformationSuccess: function(e, data) {
- var self = this;
+ /**
+ * @private
+ */
+ _onGetVideoInformationStartRequest: function() {
+ this._videoRequestComplete = false;
+ },
- self.element.on('finish_update_video finish_create_video', $.proxy(function(element, playerData) {
- if (!self._onlyVideoPlayer ||
- !self._isEditPage && playerData.oldVideoId !== playerData.newVideoId ||
- playerData.oldVideoId && playerData.oldVideoId !== playerData.newVideoId
- ) {
- self.element.updateInputFields({
+ /**
+ * Fired when user click Edit Video button
+ *
+ * @private
+ */
+ _onGetVideoInformationEditClick: function() {
+ this._onlyVideoPlayer = true;
+ this._isEditPage = true;
+ this._videoUrlWidget.trigger('update_video_information');
+ },
+
+ /**
+ * Fired when successfully received information about the video.
+ *
+ * @param {Object} e
+ * @param {Object} data
+ * @private
+ */
+ _onGetVideoInformationSuccess: function(e, data) {
+ var self = this;
+
+ self.element.on(
+ 'finish_update_video finish_create_video', $.proxy(
+ function(element, playerData) {
+ if (!self._onlyVideoPlayer ||
+ !self._isEditPage && playerData.oldVideoId !== playerData.newVideoId ||
+ playerData.oldVideoId && playerData.oldVideoId !== playerData.newVideoId
+ ) {
+ self.element.updateInputFields({
+ reset: false,
+ data: {
+ title: data.title,
+ description: data.description
+ }
+ });
+ this._loadRemotePreview(data.thumbnail, data.videoProvider);
+ }
+ self._onlyVideoPlayer = true;
+ }, this
+ )
+ )
+ .createVideoPlayer({
+ videoId: data.videoId,
+ videoProvider: data.videoProvider,
+ videoSrc: data.videoSrc || '',
+ useYoutubeNocookie: data.useYoutubeNocookie,
reset: false,
- data: {
- title: data.title,
- description: data.description
+ metaData: {
+ DOM: {
+ title: '.video-information.title span',
+ uploaded: '.video-information.uploaded span',
+ uploader: '.video-information.uploader span',
+ duration: '.video-information.duration span',
+ all: '.video-information span',
+ wrapper: '.video-information'
+ },
+ data: {
+ title: data.title,
+ uploaded: data.uploaded,
+ uploader: data.channel,
+ duration: data.duration,
+ uploaderUrl: data.channelId
+ }
}
- });
- this._loadRemotePreview(data.thumbnail);
- }
- self._onlyVideoPlayer = true;
- }, this))
- .createVideoPlayer({
- videoId: data.videoId,
- videoProvider: data.videoProvider,
- videoSrc: data.videoSrc || '',
- useYoutubeNocookie: data.useYoutubeNocookie,
- reset: false,
- metaData: {
- DOM: {
- title: '.video-information.title span',
- uploaded: '.video-information.uploaded span',
- uploader: '.video-information.uploader span',
- duration: '.video-information.duration span',
- all: '.video-information span',
- wrapper: '.video-information'
- },
- data: {
- title: data.title,
- uploaded: data.uploaded,
- uploader: data.channel,
- duration: data.duration,
- uploaderUrl: data.channelId
- }
- }
- })
- .off('finish_update_video finish_create_video');
-
- this._videoRequestComplete = true;
- },
+ })
+ .off('finish_update_video finish_create_video');
- /**
- * Load preview from youtube/vimeo
- * @param {String} sourceUrl
- * @private
- */
- _loadRemotePreview: function(sourceUrl) {
- var url = this.options.saveRemoteVideoUrl,
- self = this;
- this._getPreviewImage().attr('src', sourceUrl).hide();
- this._blockActionButtons(true, true);
- $.ajax({
- url: url,
- data: 'remote_image=' + sourceUrl,
- type: 'post',
- success: $.proxy(function(result) {
- this._tempPreviewImageData = result;
- this._getPreviewImage().attr('src', sourceUrl).show();
- this._blockActionButtons(false, true);
- }, self)
- });
- },
+ this._videoRequestComplete = true;
+ },
- /**
- * Fired when receiving information about the video ended with error
- * @private
- */
- _onGetVideoInformationError: function() {},
+ /**
+ * Load preview from youtube/vimeo
+ *
+ * @param {String} sourceUrl
+ * @private
+ */
+ _loadRemotePreview: function(sourceUrl, videoProvider) {
+ var url = this.options.saveRemoteVideoUrl,
+ self = this;
+ this._getPreviewImage().attr('src', sourceUrl).hide();
+ this._blockActionButtons(true, true);
+ $.ajax({
+ url: url,
+ data: 'remote_image=' + (videoProvider === 'cloudinary' ? encodeURI(sourceUrl) : sourceUrl),
+ type: 'post',
+ success: $.proxy(
+ function(result) {
+ this._tempPreviewImageData = result;
+ this._getPreviewImage().attr('src', sourceUrl).show();
+ this._blockActionButtons(false, true);
+ }, self
+ )
+ });
+ },
- /**
- * Remove ".tmp"
- * @param {String} name
- * @returns {*}
- * @private
- */
- __prepareFilename: function(name) {
- var tmppost = '.tmp';
+ /**
+ * Fired when receiving information about the video ended with error
+ *
+ * @private
+ */
+ _onGetVideoInformationError: function() {},
- if (!name) {
- return name;
- }
+ /**
+ * Remove ".tmp"
+ *
+ * @param {String} name
+ * @returns {*}
+ * @private
+ */
+ __prepareFilename: function(name) {
+ var tmppost = '.tmp';
- if (name.endsWith(tmppost)) {
- name = name.slice(0, name.length - tmppost.length);
- }
+ if (!name) {
+ return name;
+ }
- return name;
- },
+ if (name.endsWith(tmppost)) {
+ name = name.slice(0, name.length - tmppost.length);
+ }
- /**
- * Set image data
- * @param {String} file
- * @param {Object} imageData
- * @private
- */
- _setImage: function(file, imageData) {
- file = this.__prepareFilename(file);
- this._images[file] = imageData;
- this._gallery.trigger('addItem', imageData);
- this.element.trigger('setImage', imageData);
- this._addVideoClass(imageData.url);
- },
+ return name;
+ },
- /**
- * Get image data
- *
- * @param {String} file
- * @returns {*}
- * @private
- */
- _getImage: function(file) {
- file = this.__prepareFilename(file);
+ /**
+ * Set image data
+ *
+ * @param {String} file
+ * @param {Object} imageData
+ * @private
+ */
+ _setImage: function(file, imageData) {
+ file = this.__prepareFilename(file);
+ this._images[file] = imageData;
+ this._gallery.trigger('addItem', imageData);
+ this.element.trigger('setImage', imageData);
+ this._addVideoClass(imageData.url);
+ },
- return this._images[file];
- },
+ /**
+ * Get image data
+ *
+ * @param {String} file
+ * @returns {*}
+ * @private
+ */
+ _getImage: function(file) {
+ file = this.__prepareFilename(file);
- /**
- * Replace image (update)
- * @param {String} oldFile
- * @param {String} newFile
- * @param {Object} imageData
- * @private
- */
- _replaceImage: function(oldFile, newFile, imageData) {
- var tmpNewFile = newFile,
- tmpOldImage,
- newImageId,
- oldNewFilePosition,
- fc,
- suff,
- searchsuff,
- key,
- oldValIdElem;
-
- oldFile = this.__prepareFilename(oldFile);
- newFile = this.__prepareFilename(newFile);
- tmpOldImage = this._images[oldFile];
-
- if (newFile === oldFile) {
- this._images[newFile] = imageData;
- this.saveImageRoles(imageData);
- this._updateVisibility(imageData);
- this._updateImageTitle(imageData);
-
- return null;
- }
+ return this._images[file];
+ },
- this._removeImage(oldFile);
- this._setImage(newFile, imageData);
+ /**
+ * Replace image (update)
+ *
+ * @param {String} oldFile
+ * @param {String} newFile
+ * @param {Object} imageData
+ * @private
+ */
+ _replaceImage: function(oldFile, newFile, imageData) {
+ var tmpNewFile = newFile,
+ tmpOldImage,
+ newImageId,
+ oldNewFilePosition,
+ fc,
+ suff,
+ searchsuff,
+ key,
+ oldValIdElem;
+
+ oldFile = this.__prepareFilename(oldFile);
+ newFile = this.__prepareFilename(newFile);
+ tmpOldImage = this._images[oldFile];
+
+ if (newFile === oldFile) {
+ this._images[newFile] = imageData;
+ this.saveImageRoles(imageData);
+ this._updateVisibility(imageData);
+ this._updateImageTitle(imageData);
- if (!oldFile || !imageData.oldFile) {
- return null;
- }
+ return null;
+ }
- newImageId = this.findElementId(tmpNewFile);
- fc = this.element.find(this._itemIdSelector).val();
+ this._removeImage(oldFile);
+ this._setImage(newFile, imageData);
- suff = 'product[media_gallery][images]' + fc;
+ if (!oldFile || !imageData.oldFile) {
+ return null;
+ }
- searchsuff = 'input[name="' + suff + '[value_id]"]';
- key = this._gallery.find(searchsuff).val();
+ newImageId = this.findElementId(tmpNewFile);
+ fc = this.element.find(this._itemIdSelector).val();
- if (!key) {
- return null;
- }
+ suff = 'product[media_gallery][images]' + fc;
- oldValIdElem = document.createElement('input');
- this._gallery.find('form[data-form="edit-product"]').append(oldValIdElem);
- $(oldValIdElem).attr({
- type: 'hidden',
- name: 'product[media_gallery][images][' + newImageId + '][save_data_from]'
- }).val(key);
+ searchsuff = 'input[name="' + suff + '[value_id]"]';
+ key = this._gallery.find(searchsuff).val();
- oldNewFilePosition = parseInt(tmpOldImage.position, 10);
- imageData.position = oldNewFilePosition;
+ if (!key) {
+ return null;
+ }
- this._gallery.trigger('setPosition', {
- imageData: imageData,
- position: oldNewFilePosition
- });
- },
+ oldValIdElem = document.createElement('input');
+ this._gallery.find('form[data-form="edit-product"]').append(oldValIdElem);
+ $(oldValIdElem).attr({
+ type: 'hidden',
+ name: 'product[media_gallery][images][' + newImageId + '][save_data_from]'
+ }).val(key);
- /**
- * Remove image data
- * @param {String} file
- * @private
- */
- _removeImage: function(file) {
- var imageData = this._getImage(file);
+ oldNewFilePosition = parseInt(tmpOldImage.position, 10);
+ imageData.position = oldNewFilePosition;
- if (!imageData) {
- return null;
- }
+ this._gallery.trigger(
+ 'setPosition', {
+ imageData: imageData,
+ position: oldNewFilePosition
+ }
+ );
+ },
- this._gallery.trigger('removeItem', imageData);
- this.element.trigger('removeImage', imageData);
- delete this._images[file];
- },
+ /**
+ * Remove image data
+ *
+ * @param {String} file
+ * @private
+ */
+ _removeImage: function(file) {
+ var imageData = this._getImage(file);
- /**
- * Fired when image setted
- * @param {Event} event
- * @param {Object} imageData
- * @private
- */
- _onSetImage: function(event, imageData) {
- this.saveImageRoles(imageData);
- },
+ if (!imageData) {
+ return null;
+ }
- /**
- *
- * Wrap _uploadFile
- * @param {String} file
- * @param {String} oldFile
- * @param {Function} callback
- * @private
- */
- _uploadImage: function(file, oldFile, callback) {
- var url = this.options.saveVideoUrl,
- data = {
- files: file,
- url: url
- };
+ this._gallery.trigger('removeItem', imageData);
+ this.element.trigger('removeImage', imageData);
+ delete this._images[file];
+ },
- this._blockActionButtons(true, true);
- this._uploadFile(data, $.proxy(function(result) {
- this._onImageLoaded(result, file, oldFile, callback);
- this._blockActionButtons(false);
- }, this));
+ /**
+ * Fired when image setted
+ *
+ * @param {Event} event
+ * @param {Object} imageData
+ * @private
+ */
+ _onSetImage: function(event, imageData) {
+ this.saveImageRoles(imageData);
+ },
- },
+ /**
+ * Wrap _uploadFile
+ *
+ * @param {String} file
+ * @param {String} oldFile
+ * @param {Function} callback
+ * @private
+ */
+ _uploadImage: function(file, oldFile, callback) {
+ var url = this.options.saveVideoUrl,
+ data = {
+ files: file,
+ url: url
+ };
- /**
- * @param {String} result
- * @param {String} file
- * @param {String} oldFile
- * @param {Function} callback
- * @private
- */
- _onImageLoaded: function(result, file, oldFile, callback) {
- var data = JSON.parse(result);
+ this._blockActionButtons(true, true);
+ this._uploadFile(
+ data, $.proxy(
+ function(result) {
+ this._onImageLoaded(result, file, oldFile, callback);
+ this._blockActionButtons(false);
+ }, this
+ )
+ );
- if (this.element.find('#video_url').parent().find('.image-upload-error').length > 0) {
- this.element.find('.image-upload-error').remove();
- }
+ },
- if (data.errorcode || data.error) {
- this.element.find('#video_url').parent().append('' +
- '
' + data.error + ' ');
+ /**
+ * @param {String} result
+ * @param {String} file
+ * @param {String} oldFile
+ * @param {Function} callback
+ * @private
+ */
+ _onImageLoaded: function(result, file, oldFile, callback) {
+ var data = JSON.parse(result);
- return;
- }
- $.each(this.element.find(this._videoFormSelector).serializeArray(), function(i, field) {
- data[field.name] = field.value;
- });
- data.disabled = this.element.find(this._videoDisableinputSelector).attr('checked') ? 1 : 0;
- data['media_type'] = 'external-video';
- data.oldFile = oldFile;
-
- oldFile ?
- this._replaceImage(oldFile, data.file, data) :
- this._setImage(data.file, data);
- callback.call(0, data);
- },
+ if (this.element.find('#video_url').parent().find('.image-upload-error').length > 0) {
+ this.element.find('.image-upload-error').remove();
+ }
- /**
- * File uploader
- * @private
- */
- _uploadFile: function(data, callback) {
- var fu = this.element.find(this._videoPreviewInputSelector),
- tmpInput = document.createElement('input'),
- fileUploader = null;
-
- $(tmpInput).attr({
- 'name': fu.attr('name'),
- 'value': fu.val(),
- 'type': 'file',
- 'data-ui-ud': fu.attr('data-ui-ud')
- }).css('display', 'none');
- fu.parent().append(tmpInput);
- fileUploader = $(tmpInput).fileupload();
- fileUploader.fileupload('send', data).success(function(result, textStatus, jqXHR) {
- tmpInput.remove();
- callback.call(null, result, textStatus, jqXHR);
- });
- },
+ if (data.errorcode || data.error) {
+ this.element.find('#video_url').parent().append(
+ '' +
+ '
' + data.error + ' '
+ );
- /**
- * Update style
- * @param {String} url
- * @private
- */
- _addVideoClass: function(url) {
- var classVideo = 'video-item';
+ return;
+ }
+ $.each(
+ this.element.find(this._videoFormSelector).serializeArray(),
+ function(i, field) {
+ data[field.name] = field.value;
+ }
+ );
+ data.disabled = this.element.find(this._videoDisableinputSelector).attr('checked') ? 1 : 0;
+ data['media_type'] = 'external-video';
+ data.oldFile = oldFile;
+
+ oldFile ?
+ this._replaceImage(oldFile, data.file, data) :
+ this._setImage(data.file, data);
+ callback.call(0, data);
+ },
- this._gallery.find('img[src="' + url + '"]').addClass(classVideo);
- },
+ /**
+ * File uploader
+ *
+ * @private
+ */
+ _uploadFile: function(data, callback) {
+ var fu = this.element.find(this._videoPreviewInputSelector),
+ tmpInput = document.createElement('input'),
+ fileUploader = null;
+
+ $(tmpInput).attr({
+ 'name': fu.attr('name'),
+ 'value': fu.val(),
+ 'type': 'file',
+ 'data-ui-ud': fu.attr('data-ui-ud')
+ }).css('display', 'none');
+ fu.parent().append(tmpInput);
+ fileUploader = $(tmpInput).fileupload();
+ fileUploader.fileupload('send', data).success(
+ function(result, textStatus, jqXHR) {
+ tmpInput.remove();
+ callback.call(null, result, textStatus, jqXHR);
+ }
+ );
+ },
- /**
- * Build widget
- * @private
- */
- _create: function() {
- var imgs = _.values(this.element.closest(this.options.videoSelector).data('images')) || [],
- widget,
- uploader,
- tmp,
- i;
-
- this._gallery = this.element.closest(this.options.videoSelector);
-
- for (i = 0; i < imgs.length; i++) {
- tmp = imgs[i];
- this._images[tmp.file] = tmp;
-
- if (tmp['media_type'] === 'external-video') {
- tmp.subclass = 'video-item';
- this._addVideoClass(tmp.url);
- }
- }
+ /**
+ * Update style
+ *
+ * @param {String} url
+ * @private
+ */
+ _addVideoClass: function(url) {
+ var classVideo = 'video-item';
- this._gallery.on('openDialog', $.proxy(this._onOpenDialog, this));
- this._bind();
- this.createVideoItemIcons();
- widget = this;
- uploader = this.element.find(this._videoPreviewInputSelector);
- uploader.on('change', this._onImageInputChange.bind(this));
- uploader.attr('accept', this._imageTypes.join(','));
-
- this.element.modal({
- type: 'slide',
- //appendTo: this._gallery,
- modalClass: 'mage-new-video-dialog form-inline',
- title: $.mage.__('New Video'),
- buttons: [{
- text: $.mage.__('Save'),
- class: 'action-primary video-create-button',
- click: $.proxy(widget._onCreate, widget)
- },
- {
- text: $.mage.__('Cancel'),
- class: 'video-cancel-button',
- click: $.proxy(widget._onCancel, widget)
- },
- {
- text: $.mage.__('Delete'),
- class: 'video-delete-button',
- click: $.proxy(widget._onDelete, widget)
- },
- {
- text: $.mage.__('Save'),
- class: 'action-primary video-edit',
- click: $.proxy(widget._onUpdate, widget)
- }
- ],
+ this._gallery.find('img[src="' + url + '"]').addClass(classVideo);
+ },
/**
- * @returns {null}
+ * Build widget
+ *
+ * @private
*/
- opened: function() {
- var roles,
- file,
- modalTitleElement,
- imageData,
- modal = widget.element.closest('.mage-new-video-dialog');
-
- widget.element.find('#video_url').focus();
- roles = widget.element.find('.video_image_role');
- roles.prop('disabled', false);
- file = widget.element.find('#file_name').val();
- widget._onGetVideoInformationEditClick();
- modalTitleElement = modal.find('.modal-title');
-
- if (!file) {
- widget._blockActionButtons(true);
-
- modal.find('.video-delete-button').hide();
- modal.find('.video-edit').hide();
- modal.find('.video-create-button').show();
- roles.prop('checked', widget._gallery.find('.image.item:not(.removed)').length < 1);
- modalTitleElement.text($.mage.__('New Video'));
- widget._isEditPage = false;
-
- return null;
+ _create: function() {
+ var imgs = _.values(this.element.closest(this.options.videoSelector).data('images')) || [],
+ widget,
+ uploader,
+ tmp,
+ i;
+
+ this._gallery = this.element.closest(this.options.videoSelector);
+
+ for (i = 0; i < imgs.length; i++) {
+ tmp = imgs[i];
+ this._images[tmp.file] = tmp;
+
+ if (tmp['media_type'] === 'external-video') {
+ tmp.subclass = 'video-item';
+ this._addVideoClass(tmp.url);
+ }
}
- widget._blockActionButtons(false);
- modalTitleElement.text($.mage.__('Edit Video'));
- widget._isEditPage = true;
- imageData = widget._getImage(file);
- if (!imageData) {
- imageData = {
- url: _.find(widget._gallery.find('.product-image'), function(image) {
- return image.src.indexOf(file) > -1;
- }).src
- };
- }
+ this._gallery.on('openDialog', $.proxy(this._onOpenDialog, this));
+ this._bind();
+ this.createVideoItemIcons();
+ widget = this;
+ uploader = this.element.find(this._videoPreviewInputSelector);
+ uploader.on('change', this._onImageInputChange.bind(this));
+ uploader.attr('accept', this._imageTypes.join(','));
+
+ this.element.modal({
+ type: 'slide',
+ //appendTo: this._gallery,
+ modalClass: 'mage-new-video-dialog form-inline',
+ title: $.mage.__('New Video'),
+ buttons: [{
+ text: $.mage.__('Save'),
+ class: 'action-primary video-create-button',
+ click: $.proxy(widget._onCreate, widget)
+ },
+ {
+ text: $.mage.__('Cancel'),
+ class: 'video-cancel-button',
+ click: $.proxy(widget._onCancel, widget)
+ },
+ {
+ text: $.mage.__('Delete'),
+ class: 'video-delete-button',
+ click: $.proxy(widget._onDelete, widget)
+ },
+ {
+ text: $.mage.__('Save'),
+ class: 'action-primary video-edit',
+ click: $.proxy(widget._onUpdate, widget)
+ }
+ ],
+
+ /**
+ * @returns {null}
+ */
+ opened: function() {
+ var roles,
+ file,
+ modalTitleElement,
+ imageData,
+ modal = widget.element.closest('.mage-new-video-dialog');
+
+ widget.element.find('#video_url').focus();
+ roles = widget.element.find('.video_image_role');
+ roles.prop('disabled', false);
+ file = widget.element.find('#file_name').val();
+ widget._onGetVideoInformationEditClick();
+ modalTitleElement = modal.find('.modal-title');
+
+ if (!file) {
+ widget._blockActionButtons(true);
+
+ modal.find('.video-delete-button').hide();
+ modal.find('.video-edit').hide();
+ modal.find('.video-create-button').show();
+ roles.prop('checked', widget._gallery.find('.image.item:not(.removed)').length < 1);
+ modalTitleElement.text($.mage.__('New Video'));
+ widget._isEditPage = false;
+
+ return null;
+ }
+ widget._blockActionButtons(false);
+ modalTitleElement.text($.mage.__('Edit Video'));
+ widget._isEditPage = true;
+ imageData = widget._getImage(file);
+
+ if (!imageData) {
+ imageData = {
+ url: _.find(
+ widget._gallery.find('.product-image'),
+ function(image) {
+ return image.src.indexOf(file) > -1;
+ }
+ ).src
+ };
+ }
+
+ widget._onPreview(null, imageData.url, false);
+ },
- widget._onPreview(null, imageData.url, false);
+ /**
+ * Closed
+ */
+ closed: function() {
+ widget._onClose();
+ widget.createVideoItemIcons();
+ }
+ });
+ this.toggleButtons();
},
/**
- * Closed
+ * @param {String} status
+ * @private
*/
- closed: function() {
- widget._onClose();
- widget.createVideoItemIcons();
- }
- });
- this.toggleButtons();
- },
+ _blockActionButtons: function(status) {
+ this.element
+ .closest('.mage-new-video-dialog')
+ .find('.page-actions-buttons button.video-create-button, .page-actions-buttons button.video-edit')
+ .attr('disabled', status);
+ },
- /**
- * @param {String} status
- * @private
- */
- _blockActionButtons: function(status) {
- this.element
- .closest('.mage-new-video-dialog')
- .find('.page-actions-buttons button.video-create-button, .page-actions-buttons button.video-edit')
- .attr('disabled', status);
- },
+ /**
+ * Check form
+ *
+ * @param {Function} callback
+ */
+ isValid: function(callback) {
+ var videoForm = this.element.find(this._videoFormSelector),
+ videoLoaded = true;
+
+ this._blockActionButtons(true);
+
+ this._videoUrlWidget.trigger(
+ 'validate_video_url', $.proxy(
+ function() {
+
+ videoForm.mage(
+ 'validation', {
+
+ /**
+ * @param {jQuery} error
+ * @param {jQuery} element
+ */
+ errorPlacement: function(error, element) {
+ error.insertAfter(element);
+ }
+ }
+ ).on(
+ 'highlight.validate',
+ function() {
+ $(this).validation('option');
+ }
+ );
+
+ videoForm.validation();
+
+ if (this._videoRequestComplete === false) {
+ videoLoaded = false;
+ }
+
+ callback(videoForm.valid() && videoLoaded);
+ }, this
+ )
+ );
- /**
- * Check form
- * @param {Function} callback
- */
- isValid: function(callback) {
- var videoForm = this.element.find(this._videoFormSelector),
- videoLoaded = true;
+ this._blockActionButtons(false);
+ },
- this._blockActionButtons(true);
+ /**
+ * Create video item icons
+ */
+ createVideoItemIcons: function() {
+ var $imageWidget = this._gallery.find('.product-image.video-item'),
+ $productGalleryWrapper = $(this._imageProductGalleryWrapperSelector).find('.product-image.video-item');
+
+ $imageWidget.parent().addClass('video-item');
+ $productGalleryWrapper.parent().addClass('video-item');
+ $imageWidget.removeClass('video-item');
+ $productGalleryWrapper.removeClass('video-item');
+ $('.video-item .action-delete').attr('title', $.mage.__('Delete video'));
+ $('.video-item .action-delete span').html($.mage.__('Delete video'));
+ },
- this._videoUrlWidget.trigger('validate_video_url', $.proxy(function() {
+ /**
+ * Fired when click on create video
+ *
+ * @private
+ */
+ _onCreate: function() {
+ var nvs = this.element.find(this._videoPreviewInputSelector),
+ file = nvs.get(0),
+ reqClass = 'required-entry _required';
- videoForm.mage('validation', {
+ if (file && file.files && file.files.length) {
+ file = file.files[0];
+ } else {
+ file = null;
+ }
- /**
- * @param {jQuery} error
- * @param {jQuery} element
- */
- errorPlacement: function(error, element) {
- error.insertAfter(element);
+ if (!file && !this._tempPreviewImageData) {
+ nvs.addClass(reqClass);
}
- }).on('highlight.validate', function() {
- $(this).validation('option');
- });
- videoForm.validation();
+ this.isValid(
+ $.proxy(
+ function(videoValidStatus) {
+
+ if (!videoValidStatus) {
+ return;
+ }
+
+ if (this._tempPreviewImageData) {
+ this._onImageLoaded(this._tempPreviewImageData, null, null, $.proxy(this.close, this));
+ } else {
+ this._uploadImage(
+ file, null, $.proxy(
+ function() {
+ this.close();
+ }, this
+ )
+ );
+ }
+
+ nvs.removeClass(reqClass);
+ }, this
+ )
+ );
+ },
- if (this._videoRequestComplete === false) {
- videoLoaded = false;
- }
+ /**
+ * Fired when click on update video
+ *
+ * @private
+ */
+ _onUpdate: function() {
+ var inputFile, itemId, _inputSelector, mediaFields, imageData, flagChecked, fileName, callback;
+
+ this.isValid(
+ $.proxy(
+ function(videoValidStatus) {
+
+ if (!videoValidStatus) {
+ return;
+ }
+
+ imageData = this.imageData || {};
+ inputFile = this.element.find(this._videoPreviewInputSelector);
+ itemId = this.element.find(this._itemIdSelector).val();
+ itemId = itemId.slice(1, itemId.length - 1);
+ _inputSelector = '[name*="[' + itemId + ']"]';
+ mediaFields = this._gallery.find('input' + _inputSelector);
+ $.each(
+ mediaFields,
+ function(i, el) {
+ var elName = el.name,
+ start = elName.indexOf(itemId) + itemId.length + 2,
+ fieldName = elName.substring(start, el.name.length - 1),
+ _field = this.element.find('#' + fieldName),
+ _tmp;
+
+ if (_field.length > 0) {
+ _tmp = _inputSelector.slice(0, _inputSelector.length - 2) + '[' + fieldName + ']"]';
+ this._gallery.find(_tmp).val(_field.val());
+ imageData[fieldName] = _field.val();
+ }
+ }.bind(this)
+ );
+ flagChecked = this.element.find(this._videoDisableinputSelector).attr('checked') ? 1 : 0;
+ this._gallery.find('input[name*="' + itemId + '][disabled]"]').val(flagChecked);
+ this._gallery.find(_inputSelector).siblings('.image-fade').css(
+ 'visibility', flagChecked ? 'visible' : 'hidden'
+ );
+ imageData.disabled = flagChecked;
+
+ if (this._tempPreviewImageData) {
+ this._onImageLoaded(
+ this._tempPreviewImageData,
+ null,
+ imageData.file,
+ $.proxy(this.close, this)
+ );
+
+ return;
+ }
+ fileName = inputFile.get(0).files;
+
+ if (!fileName || !fileName.length) {
+ fileName = null;
+ }
+ inputFile.replaceWith(inputFile);
+
+ callback = $.proxy(
+ function() {
+ this.close();
+ }, this
+ );
+
+ if (fileName) {
+ this._uploadImage(fileName, imageData.file, callback);
+ } else {
+ this._replaceImage(imageData.file, imageData.file, imageData);
+ callback(0, imageData);
+ }
+ }, this
+ )
+ );
+ },
- callback(videoForm.valid() && videoLoaded);
- }, this));
+ /**
+ * Delegates call to producwt gallery to update video visibility.
+ *
+ * @param {Object} imageData
+ */
+ _updateVisibility: function(imageData) {
+ this._gallery.trigger(
+ 'updateVisibility', {
+ disabled: imageData.disabled,
+ imageData: imageData
+ }
+ );
+ },
- this._blockActionButtons(false);
- },
+ /**
+ * Delegates call to product gallery to update video title.
+ *
+ * @param {Object} imageData
+ */
+ _updateImageTitle: function(imageData) {
+ this._gallery.trigger(
+ 'updateImageTitle', {
+ imageData: imageData
+ }
+ );
+ },
- /**
- * Create video item icons
- */
- createVideoItemIcons: function() {
- var $imageWidget = this._gallery.find('.product-image.video-item'),
- $productGalleryWrapper = $(this._imageProductGalleryWrapperSelector).find('.product-image.video-item');
-
- $imageWidget.parent().addClass('video-item');
- $productGalleryWrapper.parent().addClass('video-item');
- $imageWidget.removeClass('video-item');
- $productGalleryWrapper.removeClass('video-item');
- $('.video-item .action-delete').attr('title', $.mage.__('Delete video'));
- $('.video-item .action-delete span').html($.mage.__('Delete video'));
- },
+ /**
+ * Fired when clicked on cancel
+ *
+ * @private
+ */
+ _onCancel: function() {
+ this.close();
+ },
- /**
- * Fired when click on create video
- * @private
- */
- _onCreate: function() {
- var nvs = this.element.find(this._videoPreviewInputSelector),
- file = nvs.get(0),
- reqClass = 'required-entry _required';
-
- if (file && file.files && file.files.length) {
- file = file.files[0];
- } else {
- file = null;
- }
+ /**
+ * Fired when clicked on delete
+ *
+ * @private
+ */
+ _onDelete: function() {
+ var filename = this.element.find(this._videoImageFilenameselector).val();
- if (!file && !this._tempPreviewImageData) {
- nvs.addClass(reqClass);
- }
+ this._removeImage(filename);
+ this.close();
+ },
- this.isValid($.proxy(
- function(videoValidStatus) {
+ /**
+ * @param {String} file
+ * @param {Function} callback
+ * @private
+ */
+ _readPreviewLocal: function(file, callback) {
+ var fr = new FileReader;
- if (!videoValidStatus) {
+ if (!window.FileReader) {
return;
}
- if (this._tempPreviewImageData) {
- this._onImageLoaded(this._tempPreviewImageData, null, null, $.proxy(this.close, this));
- } else {
- this._uploadImage(file, null, $.proxy(function() {
- this.close();
- }, this));
+ /**
+ * On load end
+ */
+ fr.onloadend = function() {
+ callback(fr.result);
+ };
+ fr.readAsDataURL(file);
+ },
+
+ /**
+ * Image file input handler
+ *
+ * @private
+ */
+ _onImageInputChange: function() {
+ var jFile = this.element.find(this._videoPreviewInputSelector),
+ file = jFile[0],
+ val = jFile.val(),
+ prev = this._getPreviewImage(),
+ ext = '.' + val.split('.').pop();
+
+ if (!val) {
+ return;
}
+ ext = ext ? ext.toLowerCase() : '';
- nvs.removeClass(reqClass);
- }, this
- ));
- },
+ if (ext.length < 2 ||
+ this._imageTypes.indexOf(ext.toLowerCase()) === -1 || !file.files || !file.files.length
+ ) {
+ prev.remove();
+ this._previewImage = null;
+ jFile.val('');
- /**
- * Fired when click on update video
- * @private
- */
- _onUpdate: function() {
- var inputFile, itemId, _inputSelector, mediaFields, imageData, flagChecked, fileName, callback;
+ return;
+ } // end if
+ file = file.files[0];
+ this._tempPreviewImageData = null;
+ this._onPreview(null, file, true);
+ },
- this.isValid($.proxy(
- function(videoValidStatus) {
+ /**
+ * Change Preview
+ *
+ * @param {String} error
+ * @param {String} src
+ * @param {Boolean} local
+ * @private
+ */
+ _onPreview: function(error, src, local) {
+ var img, renderImage;
- if (!videoValidStatus) {
- return;
- }
+ img = this._getPreviewImage();
- imageData = this.imageData || {};
- inputFile = this.element.find(this._videoPreviewInputSelector);
- itemId = this.element.find(this._itemIdSelector).val();
- itemId = itemId.slice(1, itemId.length - 1);
- _inputSelector = '[name*="[' + itemId + ']"]';
- mediaFields = this._gallery.find('input' + _inputSelector);
- $.each(mediaFields, function(i, el) {
- var elName = el.name,
- start = elName.indexOf(itemId) + itemId.length + 2,
- fieldName = elName.substring(start, el.name.length - 1),
- _field = this.element.find('#' + fieldName),
- _tmp;
-
- if (_field.length > 0) {
- _tmp = _inputSelector.slice(0, _inputSelector.length - 2) + '[' + fieldName + ']"]';
- this._gallery.find(_tmp).val(_field.val());
- imageData[fieldName] = _field.val();
- }
- }.bind(this));
- flagChecked = this.element.find(this._videoDisableinputSelector).attr('checked') ? 1 : 0;
- this._gallery.find('input[name*="' + itemId + '][disabled]"]').val(flagChecked);
- this._gallery.find(_inputSelector).siblings('.image-fade').css(
- 'visibility', flagChecked ? 'visible' : 'hidden'
- );
- imageData.disabled = flagChecked;
-
- if (this._tempPreviewImageData) {
- this._onImageLoaded(
- this._tempPreviewImageData,
- null,
- imageData.file,
- $.proxy(this.close, this)
- );
+ /**
+ * Callback
+ *
+ * @param {String} source
+ */
+ renderImage = function(source) {
+ img.attr({
+ 'src': source
+ }).show();
+ };
+ if (error) {
return;
}
- fileName = inputFile.get(0).files;
-
- if (!fileName || !fileName.length) {
- fileName = null;
- }
- inputFile.replaceWith(inputFile);
-
- callback = $.proxy(function() {
- this.close();
- }, this);
- if (fileName) {
- this._uploadImage(fileName, imageData.file, callback);
+ if (!local) {
+ renderImage(src);
} else {
- this._replaceImage(imageData.file, imageData.file, imageData);
- callback(0, imageData);
+ this._readPreviewLocal(src, renderImage);
}
- }, this
- ));
- },
-
- /**
- * Delegates call to producwt gallery to update video visibility.
- *
- * @param {Object} imageData
- */
- _updateVisibility: function(imageData) {
- this._gallery.trigger('updateVisibility', {
- disabled: imageData.disabled,
- imageData: imageData
- });
- },
-
- /**
- * Delegates call to product gallery to update video title.
- *
- * @param {Object} imageData
- */
- _updateImageTitle: function(imageData) {
- this._gallery.trigger('updateImageTitle', {
- imageData: imageData
- });
- },
-
- /**
- * Fired when clicked on cancel
- * @private
- */
- _onCancel: function() {
- this.close();
- },
-
- /**
- * Fired when clicked on delete
- * @private
- */
- _onDelete: function() {
- var filename = this.element.find(this._videoImageFilenameselector).val();
-
- this._removeImage(filename);
- this.close();
- },
+ },
- /**
- * @param {String} file
- * @param {Function} callback
- * @private
- */
- _readPreviewLocal: function(file, callback) {
- var fr = new FileReader;
+ /**
+ * Return preview image imstance
+ *
+ * @returns {null}
+ * @private
+ */
+ _getPreviewImage: function() {
- if (!window.FileReader) {
- return;
- }
+ if (!this._previewImage) {
+ this._previewImage = $(document.createElement('img')).css({
+ 'width': '100%',
+ 'display': 'none',
+ 'src': ''
+ });
+ $(this._previewImage).insertAfter(this.element.find(this._videoPreviewImagePointer));
+ $(this._previewImage).attr('data-role', 'video_preview_image');
+ }
- /**
- * On load end
- */
- fr.onloadend = function() {
- callback(fr.result);
- };
- fr.readAsDataURL(file);
- },
+ return this._previewImage;
+ },
- /**
- * Image file input handler
- * @private
- */
- _onImageInputChange: function() {
- var jFile = this.element.find(this._videoPreviewInputSelector),
- file = jFile[0],
- val = jFile.val(),
- prev = this._getPreviewImage(),
- ext = '.' + val.split('.').pop();
-
- if (!val) {
- return;
- }
- ext = ext ? ext.toLowerCase() : '';
-
- if (
- ext.length < 2 ||
- this._imageTypes.indexOf(ext.toLowerCase()) === -1 || !file.files || !file.files.length
- ) {
- prev.remove();
- this._previewImage = null;
- jFile.val('');
-
- return;
- } // end if
- file = file.files[0];
- this._tempPreviewImageData = null;
- this._onPreview(null, file, true);
- },
+ /**
+ * Close slideout dialog
+ */
+ close: function() {
+ this.element.modal('closeModal');
+ },
- /**
- * Change Preview
- * @param {String} error
- * @param {String} src
- * @param {Boolean} local
- * @private
- */
- _onPreview: function(error, src, local) {
- var img, renderImage;
-
- img = this._getPreviewImage();
-
- /**
- * Callback
- * @param {String} source
- */
- renderImage = function(source) {
- img.attr({
- 'src': source
- }).show();
- };
-
- if (error) {
- return;
- }
+ /**
+ * Close dialog wrap
+ *
+ * @private
+ */
+ _onClose: function() {
+ var newVideoForm;
- if (!local) {
- renderImage(src);
- } else {
- this._readPreviewLocal(src, renderImage);
- }
- },
+ this._isEditPage = true;
+ this.imageData = null;
- /**
- *
- * Return preview image imstance
- * @returns {null}
- * @private
- */
- _getPreviewImage: function() {
-
- if (!this._previewImage) {
- this._previewImage = $(document.createElement('img')).css({
- 'width': '100%',
- 'display': 'none',
- 'src': ''
- });
- $(this._previewImage).insertAfter(this.element.find(this._videoPreviewImagePointer));
- $(this._previewImage).attr('data-role', 'video_preview_image');
- }
+ if (this._previewImage) {
+ this._previewImage.remove();
+ this._previewImage = null;
+ }
+ this._tempPreviewImageData = null;
+ this.element.trigger('reset');
+ newVideoForm = this.element.find(this._videoFormSelector);
- return this._previewImage;
- },
+ $(newVideoForm).find('input[type="hidden"][name!="form_key"]').val('');
+ this._gallery.find(
+ 'input[name*="' + this.element.find(
+ this._itemIdSelector
+ ).val() + '"]'
+ ).parent().removeClass('active');
- /**
- * Close slideout dialog
- */
- close: function() {
- this.element.modal('closeModal');
- },
+ try {
+ newVideoForm.validation('clearError');
+ } catch (e) {
- /**
- * Close dialog wrap
- * @private
- */
- _onClose: function() {
- var newVideoForm;
+ }
+ newVideoForm.trigger('reset');
+ },
- this._isEditPage = true;
- this.imageData = null;
+ /**
+ * Find element by fileName
+ *
+ * @param {String} file
+ */
+ findElementId: function(file) {
+ var elem = this._gallery.find('.image.item').find('input[value="' + file + '"]');
- if (this._previewImage) {
- this._previewImage.remove();
- this._previewImage = null;
- }
- this._tempPreviewImageData = null;
- this.element.trigger('reset');
- newVideoForm = this.element.find(this._videoFormSelector);
+ if (!elem.length) {
+ return null;
+ }
- $(newVideoForm).find('input[type="hidden"][name!="form_key"]').val('');
- this._gallery.find('input[name*="' + this.element.find(
- this._itemIdSelector).val() + '"]').parent().removeClass('active');
+ return $(elem).attr('name').replace('product[media_gallery][images][', '').replace('][file]', '');
+ },
- try {
- newVideoForm.validation('clearError');
- } catch (e) {
+ /**
+ * Save image roles
+ *
+ * @param {Object} imageData
+ */
+ saveImageRoles: function(imageData) {
+ var data = imageData.file,
+ self = this,
+ containers;
+
+ if (data && data.length > 0) {
+ containers = this._gallery.find('.image-placeholder').siblings('input');
+ $.each(
+ containers,
+ function(i, el) {
+ var start = el.name.indexOf('[') + 1,
+ end = el.name.indexOf(']'),
+ imageType = el.name.substring(start, end),
+ imageCheckbox = self.element.find(
+ self._videoFormSelector + ' input[value="' + imageType + '"]'
+ );
+
+ self._changeRole(imageType, imageCheckbox.attr('checked'), imageData);
+ }
+ );
+ }
+ },
- }
- newVideoForm.trigger('reset');
- },
+ /**
+ * Change image role
+ *
+ * @param {String} imageType - role name
+ * @param {bool} isEnabled - role active status
+ * @param {Object} imageData - image data object
+ * @private
+ */
+ _changeRole: function(imageType, isEnabled, imageData) {
+ var needCheked = true;
- /**
- * Find element by fileName
- * @param {String} file
- */
- findElementId: function(file) {
- var elem = this._gallery.find('.image.item').find('input[value="' + file + '"]');
+ if (!isEnabled) {
+ needCheked = this._gallery.find('input[name="product[' + imageType + ']"]').val() === imageData.file;
+ }
- if (!elem.length) {
- return null;
- }
+ if (!needCheked) {
+ return null;
+ }
- return $(elem).attr('name').replace('product[media_gallery][images][', '').replace('][file]', '');
- },
+ this._gallery.trigger(
+ 'setImageType', {
+ type: imageType,
+ imageData: isEnabled ? imageData : null
+ }
+ );
+ },
- /**
- * Save image roles
- * @param {Object} imageData
- */
- saveImageRoles: function(imageData) {
- var data = imageData.file,
- self = this,
- containers;
-
- if (data && data.length > 0) {
- containers = this._gallery.find('.image-placeholder').siblings('input');
- $.each(containers, function(i, el) {
- var start = el.name.indexOf('[') + 1,
- end = el.name.indexOf(']'),
- imageType = el.name.substring(start, end),
- imageCheckbox = self.element.find(
- self._videoFormSelector + ' input[value="' + imageType + '"]'
+ /**
+ * On open dialog
+ *
+ * @param {Object} e
+ * @param {Object} imageData
+ * @private
+ */
+ _onOpenDialog: function(e, imageData) {
+ var formFields, flagChecked, file,
+ modal = this.element.closest('.mage-new-video-dialog');
+
+ if (imageData['media_type'] === 'external-video') {
+ this.imageData = imageData;
+ modal.find('.video-create-button').hide();
+ modal.find('.video-delete-button').show();
+ modal.find('.video-edit').show();
+ modal.createVideoPlayer({
+ reset: true
+ }).createVideoPlayer('reset');
+
+ formFields = modal.find(this._videoFormSelector).find('.edited-data');
+
+ $.each(
+ formFields,
+ function(i, field) {
+ $(field).val(imageData[field.name]);
+ }
);
- self._changeRole(imageType, imageCheckbox.attr('checked'), imageData);
- });
- }
- },
-
- /**
- * Change image role
- * @param {String} imageType - role name
- * @param {bool} isEnabled - role active status
- * @param {Object} imageData - image data object
- * @private
- */
- _changeRole: function(imageType, isEnabled, imageData) {
- var needCheked = true;
+ flagChecked = imageData.disabled > 0;
+ modal.find(this._videoDisableinputSelector).prop('checked', flagChecked);
- if (!isEnabled) {
- needCheked = this._gallery.find('input[name="product[' + imageType + ']"]').val() === imageData.file;
- }
+ file = modal.find('#file_name').val(imageData.file);
- if (!needCheked) {
- return null;
- }
-
- this._gallery.trigger('setImageType', {
- type: imageType,
- imageData: isEnabled ? imageData : null
- });
- },
+ $.each(
+ modal.find('.video_image_role'),
+ function() {
+ $(this).prop('checked', false).prop('disabled', false);
+ }
+ );
- /**
- * On open dialog
- * @param {Object} e
- * @param {Object} imageData
- * @private
- */
- _onOpenDialog: function(e, imageData) {
- var formFields, flagChecked, file,
- modal = this.element.closest('.mage-new-video-dialog');
-
- if (imageData['media_type'] === 'external-video') {
- this.imageData = imageData;
- modal.find('.video-create-button').hide();
- modal.find('.video-delete-button').show();
- modal.find('.video-edit').show();
- modal.createVideoPlayer({
- reset: true
- }).createVideoPlayer('reset');
-
- formFields = modal.find(this._videoFormSelector).find('.edited-data');
-
- $.each(formFields, function(i, field) {
- $(field).val(imageData[field.name]);
- });
-
- flagChecked = imageData.disabled > 0;
- modal.find(this._videoDisableinputSelector).prop('checked', flagChecked);
-
- file = modal.find('#file_name').val(imageData.file);
-
- $.each(modal.find('.video_image_role'), function() {
- $(this).prop('checked', false).prop('disabled', false);
- });
-
- $.each(this._gallery.find('.image-placeholder').siblings('input:hidden'), function() {
- var start, end, imageRole;
-
- if ($(this).val() === file.val()) {
- start = this.name.indexOf('[') + 1;
- end = this.name.length - 1;
- imageRole = this.name.substring(start, end);
- modal.find('#new_video_form input[value="' + imageRole + '"]').prop('checked', true);
+ $.each(
+ this._gallery.find('.image-placeholder').siblings('input:hidden'),
+ function() {
+ var start, end, imageRole;
+
+ if ($(this).val() === file.val()) {
+ start = this.name.indexOf('[') + 1;
+ end = this.name.length - 1;
+ imageRole = this.name.substring(start, end);
+ modal.find('#new_video_form input[value="' + imageRole + '"]').prop('checked', true);
+ }
+ }
+ );
}
- });
- }
-
- },
- /**
- * Toggle buttons
- */
- toggleButtons: function() {
- var self = this,
- modal = this.element.closest('.mage-new-video-dialog');
-
- modal.find('.video-placeholder, .add-video-button-container > button').click(function() {
- modal.find('.video-create-button').show();
- modal.find('.video-delete-button').hide();
- modal.find('.video-edit').hide();
- modal.createVideoPlayer({
- reset: true
- }).createVideoPlayer('reset').updateInputFields({
- reset: true
- }).updateInputFields('reset');
- });
- this._gallery.on('click', '.item.video-item', function() {
- modal.find('.video-create-button').hide();
- modal.find('.video-delete-button').show();
- modal.find('.video-edit').show();
- modal.find('.mage-new-video-dialog').createVideoPlayer({
- reset: true
- }).createVideoPlayer('reset');
- });
- this._gallery.on('click', '.item.video-item:not(.removed)', function() {
- var flagChecked,
- file,
- formFields = modal.find('.edited-data'),
- container = $(this);
-
- $.each(formFields, function(i, field) {
- $(field).val(container.find('input[name*="' + field.name + '"]').val());
- });
-
- flagChecked = container.find('input[name*="disabled"]').val() > 0;
- self._gallery.find(self._videoDisableinputSelector).attr('checked', flagChecked);
-
- file = self._gallery.find('#file_name').val(container.find('input[name*="file"]').val());
-
- $.each(self._gallery.find('.video_image_role'), function() {
- $(this).prop('checked', false).prop('disabled', false);
- });
-
- $.each(self._gallery.find('.image-placeholder').siblings('input:hidden'), function() {
- var start, end, imageRole;
-
- if ($(this).val() !== file.val()) {
- return null;
- }
+ },
- start = this.name.indexOf('[') + 1;
- end = this.name.length - 1;
- imageRole = this.name.substring(start, end);
- self._gallery.find('input[value="' + imageRole + '"]').prop('checked', true);
- });
- });
- }
- });
+ /**
+ * Toggle buttons
+ */
+ toggleButtons: function() {
+ var self = this,
+ modal = this.element.closest('.mage-new-video-dialog');
+
+ modal.find('.video-placeholder, .add-video-button-container > button').click(
+ function() {
+ modal.find('.video-create-button').show();
+ modal.find('.video-delete-button').hide();
+ modal.find('.video-edit').hide();
+ modal.createVideoPlayer({
+ reset: true
+ }).createVideoPlayer('reset').updateInputFields({
+ reset: true
+ }).updateInputFields('reset');
+ }
+ );
+ this._gallery.on(
+ 'click', '.item.video-item',
+ function() {
+ modal.find('.video-create-button').hide();
+ modal.find('.video-delete-button').show();
+ modal.find('.video-edit').show();
+ modal.find('.mage-new-video-dialog').createVideoPlayer({
+ reset: true
+ }).createVideoPlayer('reset');
+ }
+ );
+ this._gallery.on(
+ 'click', '.item.video-item:not(.removed)',
+ function() {
+ var flagChecked,
+ file,
+ formFields = modal.find('.edited-data'),
+ container = $(this);
+
+ $.each(
+ formFields,
+ function(i, field) {
+ $(field).val(container.find('input[name*="' + field.name + '"]').val());
+ }
+ );
+
+ flagChecked = container.find('input[name*="disabled"]').val() > 0;
+ self._gallery.find(self._videoDisableinputSelector).attr('checked', flagChecked);
+
+ file = self._gallery.find('#file_name').val(container.find('input[name*="file"]').val());
+
+ $.each(
+ self._gallery.find('.video_image_role'),
+ function() {
+ $(this).prop('checked', false).prop('disabled', false);
+ }
+ );
+
+ $.each(
+ self._gallery.find('.image-placeholder').siblings('input:hidden'),
+ function() {
+ var start, end, imageRole;
+
+ if ($(this).val() !== file.val()) {
+ return null;
+ }
+
+ start = this.name.indexOf('[') + 1;
+ end = this.name.length - 1;
+ imageRole = this.name.substring(start, end);
+ self._gallery.find('input[value="' + imageRole + '"]').prop('checked', true);
+ }
+ );
+ }
+ );
+ }
+ }
+ );
- $('#group-fields-image-management > legend > span').text($.mage.__('Images and Videos'));
+ $('#group-fields-image-management > legend > span').text($.mage.__('Images and Videos'));
- return $.mage.newVideoDialog;
-});
\ No newline at end of file
+ return $.mage.newVideoDialog;
+ }
+);
diff --git a/view/adminhtml/web/js/product-gallery.js b/view/adminhtml/web/js/product-gallery.js
new file mode 100644
index 00000000..220e0611
--- /dev/null
+++ b/view/adminhtml/web/js/product-gallery.js
@@ -0,0 +1,664 @@
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+/**
+ * @api
+ */
+define([
+ 'jquery',
+ 'underscore',
+ 'mage/template',
+ 'uiRegistry',
+ 'jquery/ui',
+ 'baseImage'
+], function($, _, mageTemplate, registry) {
+ 'use strict';
+
+ /**
+ * Formats incoming bytes value to a readable format.
+ *
+ * @param {Number} bytes
+ * @returns {String}
+ */
+ function bytesToSize(bytes) {
+ var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'],
+ i;
+
+ if (bytes === 0) {
+ return '0 Byte';
+ }
+
+ i = window.parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
+
+ return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
+ }
+
+ /**
+ * Product gallery widget
+ */
+ $.widget('mage.productGallery', {
+ options: {
+ imageSelector: '[data-role=image]',
+ imageElementSelector: '[data-role=image-element]',
+ template: '[data-template=image]',
+ imageResolutionLabel: '[data-role=resolution]',
+ imgTitleSelector: '[data-role=img-title]',
+ imageSizeLabel: '[data-role=size]',
+ cldspinset: '[data-role=image-cldspinset]',
+ types: null,
+ initialized: false
+ },
+
+ /**
+ * Gallery creation
+ * @protected
+ */
+ _create: function() {
+ this.options.types = this.options.types || this.element.data('types');
+ this.options.images = this.options.images || this.element.data('images');
+ this.options.parentComponent = this.options.parentComponent || this.element.data('parent-component');
+
+ this.imgTmpl = mageTemplate(this.element.find(this.options.template).html().trim());
+
+ this._bind();
+
+ $.each(this.options.images, $.proxy(function(index, imageData) {
+ this.element.trigger('addItem', imageData);
+ }, this));
+
+ this.options.initialized = true;
+ },
+
+ /**
+ * Bind handler to elements
+ * @protected
+ */
+ _bind: function() {
+ this._on({
+ updateImageTitle: '_updateImageTitle',
+ updateVisibility: '_updateVisibility',
+ openDialog: '_onOpenDialog',
+ addItem: '_addItem',
+ removeItem: '_removeItem',
+ setImageType: '_setImageType',
+ setPosition: '_setPosition',
+ resort: '_resort',
+
+ /**
+ * @param {jQuery.Event} event
+ */
+ 'mouseup [data-role=delete-button]': function(event) {
+ var $imageContainer;
+
+ event.preventDefault();
+ $imageContainer = $(event.currentTarget).closest(this.options.imageSelector);
+ this.element.find('[data-role=dialog]').trigger('close');
+ this.element.trigger('removeItem', $imageContainer.data('imageData'));
+ },
+
+ /**
+ * @param {jQuery.Event} event
+ */
+ 'mouseup [data-role=make-base-button]': function(event) {
+ var $imageContainer,
+ imageData;
+
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ $imageContainer = $(event.currentTarget).closest(this.options.imageSelector);
+ imageData = $imageContainer.data('imageData');
+ this.setBase(imageData);
+ }
+ });
+
+ this.element.sortable({
+ distance: 8,
+ items: this.options.imageSelector,
+ tolerance: 'pointer',
+ cancel: 'input, button, .uploader',
+ update: $.proxy(function() {
+ this.element.trigger('resort');
+ }, this)
+ });
+ },
+
+ /**
+ * Set image as main
+ * @param {Object} imageData
+ * @private
+ */
+ setBase: function(imageData) {
+ var baseImage = this.options.types.image,
+ sameImages = $.grep(
+ $.map(this.options.types, function(el) {
+ return el;
+ }),
+ function(el) {
+ return el.value === baseImage.value;
+ }
+ ),
+ isImageOpened = this.findElement(imageData).hasClass('active');
+
+ $.each(sameImages, $.proxy(function(index, image) {
+ this.element.trigger('setImageType', {
+ type: image.code,
+ imageData: imageData
+ });
+
+ if (isImageOpened) {
+ this.element.find('.item').addClass('selected');
+ this.element.find('[data-role=type-selector]').prop({
+ 'checked': true
+ });
+ }
+ }, this));
+ },
+
+ /**
+ * Find element by fileName
+ * @param {Object} data
+ * @returns {Element}
+ */
+ findElement: function(data) {
+ return this.element.find(this.options.imageSelector).filter(function() {
+ return $(this).data('imageData').file === data.file;
+ }).first();
+ },
+
+ /**
+ * Mark parent fieldset that content was updated
+ */
+ _contentUpdated: function() {
+ if (this.options.initialized && this.options.parentComponent) {
+ registry.async(this.options.parentComponent)(
+ function(parentComponent) {
+ parentComponent.bubble('update', true);
+ }
+ );
+ }
+ },
+
+ /**
+ * Add image
+ * @param {jQuery.Event} event
+ * @param {Object} imageData
+ * @private
+ */
+ _addItem: function(event, imageData) {
+ var count = this.element.find(this.options.imageSelector).length,
+ element,
+ imgElement;
+
+ imageData = $.extend({
+ 'file_id': imageData['value_id'] ? imageData['value_id'] : Math.random().toString(33).substr(2, 18),
+ 'disabled': imageData.disabled ? imageData.disabled : 0,
+ 'position': count + 1,
+ sizeLabel: bytesToSize(imageData.size)
+ }, imageData);
+
+ element = this.imgTmpl({
+ data: imageData
+ });
+
+ element = $(element).data('imageData', imageData);
+
+ if (count === 0) {
+ element.prependTo(this.element);
+ } else {
+ element.insertAfter(this.element.find(this.options.imageSelector + ':last'));
+ }
+
+ if (!this.options.initialized &&
+ this.options.images.length === 0 ||
+ this.options.initialized &&
+ this.element.find(this.options.imageSelector + ':not(.removed)').length === 1
+ ) {
+ this.setBase(imageData);
+ }
+
+ imgElement = element.find(this.options.imageElementSelector);
+
+ imgElement.on('load', this._updateImageDimesions.bind(this, element));
+
+ $.each(this.options.types, $.proxy(function(index, image) {
+ if (imageData.file === image.value) {
+ this.element.trigger('setImageType', {
+ type: image.code,
+ imageData: imageData
+ });
+ }
+ }, this));
+
+ this._updateImagesRoles();
+ this._contentUpdated();
+ },
+
+ /**
+ * Returns a list of current images.
+ *
+ * @returns {jQueryCollection}
+ */
+ _getImages: function() {
+ return this.element.find(this.options.imageSelector);
+ },
+
+ /**
+ * Returns a list of images roles.
+ *
+ * @return {Object}
+ */
+ _getRoles: function() {
+ return _.mapObject(this.options.types, function(data, key) {
+ var elem = this.element.find('.image-' + key);
+
+ return {
+ index: key,
+ value: elem.val(),
+ elem: elem
+ };
+ }, this);
+ },
+
+ /**
+ * Updates labels with roles information for each image.
+ */
+ _updateImagesRoles: function() {
+ var $images = this._getImages().toArray(),
+ roles = this._getRoles();
+
+ $images.forEach(function(img) {
+ var $img = $(img),
+ data = $img.data('imageData');
+
+ $img.find('[data-role=roles-labels] li').each(function(index, elem) {
+ var $elem = $(elem),
+ roleCode = $elem.data('roleCode'),
+ role = roles[roleCode];
+
+ role.value === data.file ?
+ $elem.show() :
+ $elem.hide();
+ });
+
+ });
+ },
+
+ /**
+ * Updates image's dimensions information.
+ *
+ * @param {jQeuryCollection} imgContainer
+ */
+ _updateImageDimesions: function(imgContainer) {
+ var $img = imgContainer.find(this.options.imageElementSelector)[0],
+ $dimens = imgContainer.find('[data-role=image-dimens]');
+
+ $dimens.text($img.naturalWidth + 'x' + $img.naturalHeight + ' px');
+ },
+
+ /**
+ *
+ * @param {jQuery.Event} event
+ * @param {Object} data
+ */
+ _updateImageTitle: function(event, data) {
+ var imageData = data.imageData,
+ $imgContainer = this.findElement(imageData),
+ $title = $imgContainer.find(this.options.imgTitleSelector),
+ value;
+
+ value = imageData['media_type'] === 'external-video' ?
+ imageData['video_title'] :
+ imageData.label;
+
+ $title.text(value);
+
+ this._contentUpdated();
+ },
+
+ /**
+ * Remove Image
+ * @param {jQuery.Event} event
+ * @param {Object} imageData
+ * @private
+ */
+ _removeItem: function(event, imageData) {
+ var $imageContainer = this.findElement(imageData);
+
+ imageData.isRemoved = true;
+ $imageContainer.addClass('removed').hide().find('.is-removed').val(1);
+
+ this._contentUpdated();
+ },
+
+ /**
+ * Set image type
+ * @param {jQuery.Event} event
+ * @param {Obejct} data
+ * @private
+ */
+ _setImageType: function(event, data) {
+ if (data.type === 'image') {
+ this.element.find('.base-image').removeClass('base-image');
+ }
+
+ if (data.imageData) {
+ this.options.types[data.type].value = data.imageData.file;
+
+ if (data.type === 'image') {
+ this.findElement(data.imageData).addClass('base-image');
+ }
+ } else {
+ this.options.types[data.type].value = 'no_selection';
+ }
+ this.element.find('.image-' + data.type).val(this.options.types[data.type].value || 'no_selection');
+ this._updateImagesRoles();
+ this._contentUpdated();
+ },
+
+ /**
+ * Resort images
+ * @private
+ */
+ _resort: function() {
+ this.element.find('.position').each($.proxy(function(index, element) {
+ var value = $(element).val();
+
+ if (value != index) { //eslint-disable-line eqeqeq
+ this.element.trigger('moveElement', {
+ imageData: $(element).closest(this.options.imageSelector).data('imageData'),
+ position: index
+ });
+ $(element).val(index);
+ }
+ }, this));
+
+ this._contentUpdated();
+ },
+
+ /**
+ * Set image position
+ * @param {jQuery.Event} event
+ * @param {Object} data
+ * @private
+ */
+ _setPosition: function(event, data) {
+ var $element = this.findElement(data.imageData),
+ curIndex = this.element.find(this.options.imageSelector).index($element),
+ newPosition = data.position + (curIndex > data.position ? -1 : 0);
+
+ if (data.position != curIndex) { //eslint-disable-line eqeqeq
+ if (data.position === 0) {
+ this.element.prepend($element);
+ } else {
+ $element.insertAfter(
+ this.element.find(this.options.imageSelector).eq(newPosition)
+ );
+ }
+ this.element.trigger('resort');
+ }
+
+ this._contentUpdated();
+ }
+ });
+
+ // Extension for mage.productGallery - Add advanced settings block
+ $.widget('mage.productGallery', $.mage.productGallery, {
+ options: {
+ dialogTemplate: '[data-role=img-dialog-tmpl]',
+ dialogContainerTmpl: '[data-role=img-dialog-container-tmpl]'
+ },
+
+ /** @inheritdoc */
+ _create: function() {
+ var template = this.element.find(this.options.dialogTemplate),
+ containerTmpl = this.element.find(this.options.dialogContainerTmpl);
+
+ this._super();
+ this.modalPopupInit = false;
+
+ if (template.length) {
+ this.dialogTmpl = mageTemplate(template.html().trim());
+ }
+
+ if (containerTmpl.length) {
+ this.dialogContainerTmpl = mageTemplate(containerTmpl.html().trim());
+ } else {
+ this.dialogContainerTmpl = mageTemplate('');
+ }
+
+ this._initDialog();
+ },
+
+ /**
+ * Bind handler to elements
+ * @protected
+ */
+ _bind: function() {
+ var events = {};
+
+ this._super();
+
+ events['click [data-role=close-panel]'] = $.proxy(function() {
+ this.element.find('[data-role=dialog]').trigger('close');
+ }, this);
+
+ /**
+ * @param {jQuery.Event} event
+ */
+ events['click ' + this.options.imageSelector] = function(event) {
+ var imageData, $imageContainer;
+
+ if (!$(event.currentTarget).is('.ui-sortable-helper')) {
+ $(event.currentTarget).addClass('active');
+ imageData = $(event.currentTarget).data('imageData');
+ $imageContainer = this.findElement(imageData);
+
+ if ($imageContainer.is('.removed')) {
+ return;
+ }
+ this.element.trigger('openDialog', [imageData]);
+ }
+ };
+ this._on(events);
+ this.element.on('sortstart', $.proxy(function() {
+ this.element.find('[data-role=dialog]').trigger('close');
+ }, this));
+ },
+
+ /**
+ * Initializes dialog element.
+ */
+ _initDialog: function() {
+ var $dialog = $(this.dialogContainerTmpl());
+
+ $dialog.modal({
+ 'type': 'slide',
+ title: $.mage.__('Image Detail'),
+ buttons: [],
+
+ /** @inheritdoc */
+ opened: function() {
+ $dialog.trigger('open');
+ },
+
+ /** @inheritdoc */
+ closed: function() {
+ $dialog.trigger('close');
+ }
+ });
+
+ $dialog.on('open', this.onDialogOpen.bind(this));
+ $dialog.on('close', function() {
+ var $imageContainer = $dialog.data('imageContainer');
+
+ $imageContainer.removeClass('active');
+ $dialog.find('#hide-from-product-page').remove();
+ });
+
+ $dialog.on('change', '[data-role=type-selector]', function() {
+ var parent = $(this).closest('.item'),
+ selectedClass = 'selected';
+
+ parent.toggleClass(selectedClass, $(this).prop('checked'));
+ });
+
+ $dialog.on('change', '[data-role=type-selector]', $.proxy(this._notifyType, this));
+
+ $dialog.on('change', '[data-role=visibility-trigger]', $.proxy(function(e) {
+ var imageData = $dialog.data('imageData');
+
+ this.element.trigger('updateVisibility', {
+ disabled: $(e.currentTarget).is(':checked'),
+ imageData: imageData
+ });
+ }, this));
+
+ $dialog.on('change', '[data-role="image-description"]', function(e) {
+ var target = $(e.target),
+ targetName = target.attr('name'),
+ desc = target.val(),
+ imageData = $dialog.data('imageData');
+
+ this.element.find('input[type="hidden"][name="' + targetName + '"]').val(desc);
+
+ imageData.label = desc;
+ imageData['label_default'] = desc;
+
+ this.element.trigger('updateImageTitle', {
+ imageData: imageData
+ });
+ }.bind(this));
+
+ $dialog.on('change', '[data-role="image-cldspinset"]', function(e) {
+ var target = $(e.target),
+ targetName = target.attr('name'),
+ cldspinset = target.val(),
+ imageData = $dialog.data('imageData');
+
+ this.element.find('input[type="hidden"][name="' + targetName + '"]').val(cldspinset);
+
+ imageData.cldspinset = cldspinset;
+ }.bind(this));
+
+ this.$dialog = $dialog;
+ },
+
+ /**
+ * @param {Object} imageData
+ * @private
+ */
+ _showDialog: function(imageData) {
+ var $imageContainer = this.findElement(imageData),
+ $template;
+
+ $template = this.dialogTmpl({
+ 'data': imageData
+ });
+
+ this.$dialog
+ .html($template)
+ .data('imageData', imageData)
+ .data('imageContainer', $imageContainer)
+ .modal('openModal');
+ },
+
+ /**
+ * Handles dialog open event.
+ *
+ * @param {EventObject} event
+ */
+ onDialogOpen: function(event) {
+ var imageData = this.$dialog.data('imageData'),
+ imageSizeKb = imageData.sizeLabel,
+ image = document.createElement('img'),
+ sizeSpan = this.$dialog.find(this.options.imageSizeLabel)
+ .find('[data-message]'),
+ resolutionSpan = this.$dialog.find(this.options.imageResolutionLabel)
+ .find('[data-message]'),
+ sizeText = sizeSpan.attr('data-message').replace('{size}', imageSizeKb),
+ resolutionText;
+
+ image.src = imageData.url;
+
+ resolutionText = resolutionSpan
+ .attr('data-message')
+ .replace('{width}^{height}', image.width + 'x' + image.height);
+
+ sizeSpan.text(sizeText);
+ resolutionSpan.text(resolutionText);
+
+ $(event.target)
+ .find('[data-role=type-selector]')
+ .each($.proxy(function(index, checkbox) {
+ var $checkbox = $(checkbox),
+ parent = $checkbox.closest('.item'),
+ selectedClass = 'selected',
+ isChecked = this.options.types[$checkbox.val()].value == imageData.file; //eslint-disable-line
+
+ $checkbox.prop(
+ 'checked',
+ isChecked
+ );
+ parent.toggleClass(selectedClass, isChecked);
+ }, this));
+ },
+
+ /**
+ *
+ * Click by image handler
+ *
+ * @param {jQuery.Event} e
+ * @param {Object} imageData
+ * @private
+ */
+ _onOpenDialog: function(e, imageData) {
+ if (imageData['media_type'] && imageData['media_type'] != 'image') { //eslint-disable-line eqeqeq
+ return;
+ }
+ this._showDialog(imageData);
+ },
+
+ /**
+ * Change visibility
+ *
+ * @param {jQuery.Event} event
+ * * @param {Object} data
+ * @private
+ */
+ _updateVisibility: function(event, data) {
+ var imageData = data.imageData,
+ disabled = +data.disabled,
+ $imageContainer = this.findElement(imageData);
+
+ !!disabled ? //eslint-disable-line no-extra-boolean-cast
+ $imageContainer.addClass('hidden-for-front') :
+ $imageContainer.removeClass('hidden-for-front');
+
+ $imageContainer.find('[name*="disabled"]').val(disabled);
+ imageData.disabled = disabled;
+
+ this._contentUpdated();
+ },
+
+ /**
+ * Set image
+ * @param {jQuery.Event} event
+ * @private
+ */
+ _notifyType: function(event) {
+ var $checkbox = $(event.currentTarget),
+ $imageContainer = $checkbox.closest('[data-role=dialog]').data('imageContainer');
+
+ this.element.trigger('setImageType', {
+ type: $checkbox.val(),
+ imageData: $checkbox.is(':checked') ? $imageContainer.data('imageData') : null
+ });
+
+ this._updateImagesRoles();
+ }
+ });
+
+ return $.mage.productGallery;
+});
\ No newline at end of file
diff --git a/view/adminhtml/web/js/product_free_transform.js b/view/adminhtml/web/js/product_free_transform.js
index 3ff2e04c..af819acf 100644
--- a/view/adminhtml/web/js/product_free_transform.js
+++ b/view/adminhtml/web/js/product_free_transform.js
@@ -1,123 +1,207 @@
-define([
- 'underscore',
- 'uiElement',
- 'uiCollection',
- 'uiRegistry',
- 'jquery'
-], function (_, Element, Collection, registry, $) {
- 'use strict';
-
- var FreeTransformRow = Element.extend({
- defaults: {
- id: 0,
- src: "",
- label: "",
- file: "",
- freeTransformation: "",
- hasChanges: false,
- hasChangesToSave: false,
- error: "",
- hasError: false,
- ajaxUrl: "",
- template: 'Cloudinary_Cloudinary/product/free_transform_row'
- },
-
- initObservable: function () {
- var self = this;
-
- this._super();
-
- this.observe('src freeTransformation hasError error hasChanges hasChangesToSave');
-
- this.on('freeTransformation', function() {
- self.hasChanges(true);
- self.hasChangesToSave(true);
- });
-
- return this;
- },
-
- configure: function(params) {
- this.id = params.id || 0;
- this.label = params.label || "";
- this.file = params.file || "";
- this.ajaxUrl = params.ajaxUrl || "";
- this.src(params.image_url || "");
- this.freeTransformation(params.free_transformation || "");
- this.hasChanges(false);
-
- return this;
- },
-
- inputName: function() {
- return 'product[cloudinary_free_transform][' + this.id + ']';
- },
-
- changesName: function() {
- return 'product[cloudinary_free_transform_changes][' + this.id + ']';
- },
-
- imageSrcForTransform: function(transform) {
- return 'http://res.cloudinary.com/m2501/image/upload/' + transform + '/sample.jpg';
- },
-
- refreshImage: function() {
- var self = this;
-
- self.hasChanges(false);
-
- $.ajax({
- url: self.ajaxUrl,
- data: {
- free: self.freeTransformation(),
- form_key: window.FORM_KEY,
- image: self.file
- },
- type: 'post',
- dataType: 'json',
- showLoader: true
- }).done(function(response) {
- self.src(response.url);
- self.hasError(false);
- }).fail(function(result) {
- self.hasError(true);
- self.error(result.responseJSON.error);
- });
- }
- });
-
- return Collection.extend({
- defaults: {
- ajaxUrl: "",
- template: 'Cloudinary_Cloudinary/product/free_transform'
- },
-
- getTransforms: function() {
- return registry.get('product_form.product_form_data_source').data.product.cloudinary_transforms;
- },
-
- createRow: function(params) {
- return FreeTransformRow().configure(params);
- },
-
- initObservable: function () {
- var self = this;
-
- this._super();
-
- this.getTransforms().each(function(transform) {
- self.insertChild(self.createRow(transform));
- });
-
- return this;
- },
-
- afterRender: function() {
- var self = this;
-
- this.elems.each(function (elem) {
- elem.ajaxUrl = self.ajaxUrl;
- });
- }
- });
-});
+define(
+ [
+ 'underscore',
+ 'uiElement',
+ 'uiCollection',
+ 'uiRegistry',
+ 'jquery'
+ ],
+ function(_, Element, Collection, registry, $) {
+ 'use strict';
+
+ var FreeTransformRow = Element.extend({
+ defaults: {
+ id: 0,
+ src: "",
+ label: "",
+ file: "",
+ origFreeTransformation: "",
+ freeTransformation: "",
+ hasChanges: false,
+ hasChangesToSave: false,
+ error: "",
+ hasError: false,
+ ajaxUrl: "",
+ template: 'Cloudinary_Cloudinary/product/free_transform_row'
+ },
+
+ initObservable: function() {
+ var self = this;
+
+ this._super();
+
+ this.observe('src freeTransformation hasError error hasChanges hasChangesToSave');
+
+ this.on(
+ 'freeTransformation',
+ function() {
+ self.hasChanges(true);
+ self.hasChangesToSave(true);
+ }
+ );
+
+ return this;
+ },
+
+ configure: function(params) {
+ this.id = params.id || 0;
+ this.label = params.label || "";
+ this.file = params.file || "";
+ this.ajaxUrl = params.ajaxUrl || "";
+ this.src(params.image_url || "");
+ this.origFreeTransformation = params.free_transformation || "";
+ this.freeTransformation(params.free_transformation || "");
+ this.hasChanges(false);
+
+ return this;
+ },
+
+ inputName: function() {
+ return 'product[cloudinary_free_transform][' + this.id + ']';
+ },
+
+ changesName: function() {
+ return 'product[cloudinary_free_transform_changes][' + this.id + ']';
+ },
+
+ imageSrcForTransform: function(transform) {
+ return 'http://res.cloudinary.com/m2501/image/upload/' + transform + '/sample.jpg';
+ },
+
+ refreshImage: function() {
+ var self = this;
+
+ self.hasChanges(false);
+
+ if (/\.tmp$/.test(self.file)) {
+ self.src(self.src().replace(new RegExp('\/image\/upload(\/v[0-9]{1,10})?(\/' + this.origFreeTransformation + ')?(\/v[0-9]{1,10})?\/'), '/image/upload/' + self.freeTransformation() + '/'));
+ this.origFreeTransformation = self.freeTransformation();
+ self.hasError(false);
+ return true;
+ }
+
+ $.ajax({
+ url: self.ajaxUrl,
+ data: {
+ free: self.freeTransformation(),
+ form_key: window.FORM_KEY,
+ image: self.file
+ },
+ type: 'post',
+ dataType: 'json',
+ showLoader: true
+ }).done(
+ function(response) {
+ self.src(response.url);
+ self.hasError(false);
+ }
+ ).fail(
+ function(result) {
+ self.hasError(true);
+ self.error(result.responseJSON.error);
+ }
+ );
+ }
+ });
+
+ return Collection.extend({
+ defaults: {
+ ajaxUrl: "",
+ template: 'Cloudinary_Cloudinary/product/free_transform',
+ tableRows: {}
+ },
+
+ /**
+ * Called when another element was added to current component.
+ *
+ * @param {Object} elem - Instance of an element that was added.
+ * @returns {Collection} Chainable.
+ */
+ initElement: function(elem) {
+ elem.initContainer(this);
+ this.ajaxUrl = this.ajaxUrl || this.getAjaxUrl();
+ return this;
+ },
+
+
+ getTransforms: function() {
+ return registry.get('product_form.product_form_data_source').data.product.cloudinary_transforms;
+ },
+
+ getAjaxUrl: function() {
+ return registry.get('product_form.product_form_data_source').data.product.cloudinary_ajax_url;
+ },
+
+ createRow: function(params) {
+ return FreeTransformRow().configure(params);
+ },
+
+ insertChildRow: function(params) {
+ if (!this.tableRows()[params.id]) {
+ params.ajaxUrl = this.ajaxUrl;
+ var elm = this.createRow(params);
+ this.tableRows()[params.id] = elm;
+ this.insertChild(elm);
+ return elm;
+ } else {
+ return this.tableRows()[params.id];
+ }
+ },
+
+ initObservable: function() {
+ var self = this;
+
+ self._super()
+ .observe([
+ 'tableRows'
+ ]);
+
+ if (this.getTransforms()) {
+ $.each(this.getTransforms(), function(i, transform) {
+ self.insertChildRow(transform);
+ });
+ }
+
+ $(document).on('addItem', '#media_gallery_content', function(event, file) {
+ if (file && (file.media_type === 'image') && file.file && (file.image_url || file.url)) {
+ file.image_url = file.image_url || file.url;
+ file.id = file.id || file.file_id || file.value_id || file.fileId;
+ if (!file.id) {
+ file.id = $('input[name^="product[media_gallery][images]"][name$="[file]"][value="' + file.file + '"]:last');
+ if (file.id.length) {
+ file.id = file.file_id = file.id.attr('name')
+ .replace(/^product\[media_gallery\]\[images\]\[/, '')
+ .replace(/\]\[file\]$/, '');
+ }
+ }
+ if (file.id) {
+ file.image_url = file.asset_derived_image_url || file.image_url;
+ self.insertChildRow(file).trigger("freeTransformation");
+ }
+ }
+ });
+
+ $(document).on('removeItem', '#media_gallery_content', function(event, file) {
+ if (file && (file.id || file.file_id || file.value_id || file.fileId)) {
+ file.id = file.id || file.file_id || file.value_id || file.fileId;
+ self.elems.each(function(elem) {
+ if (elem.id == file.id) {
+ self.removeChild(elem);
+ }
+ });
+ }
+ });
+
+ return this;
+ },
+
+ afterRender: function() {
+ var self = this;
+
+ this.elems.each(function(elem) {
+ elem.ajaxUrl = self.ajaxUrl;
+ });
+ }
+ });
+ }
+);
\ No newline at end of file
diff --git a/view/adminhtml/web/template/product/free_transform.html b/view/adminhtml/web/template/product/free_transform.html
index b100ed12..37ee13a6 100644
--- a/view/adminhtml/web/template/product/free_transform.html
+++ b/view/adminhtml/web/template/product/free_transform.html
@@ -1,26 +1,27 @@
-
-
- Image
-
-
- Label
-
-
- File
-
-
- Free transform
-
-
- Action
-
-
+
+
+ Image
+
+
+ Label
+
+
+ File
+
+
+ Cloudinary Transformation
+
+
+ Action
+
+
-
+
+
@@ -32,9 +33,7 @@
For information about the full range of transforms available see the
-
- Cloudinary documentation
- .
+ Cloudinary documentation .
You may need to clear or rebuild the Magento block and full page caches to see the changes in the front end.
diff --git a/view/adminhtml/web/template/product/free_transform_row.html b/view/adminhtml/web/template/product/free_transform_row.html
index 8c9a1ddb..a22cc24a 100644
--- a/view/adminhtml/web/template/product/free_transform_row.html
+++ b/view/adminhtml/web/template/product/free_transform_row.html
@@ -5,8 +5,7 @@
-
+
@@ -18,12 +17,8 @@
-
-
+
+
@@ -34,4 +29,4 @@
}">Preview
-
+
\ No newline at end of file
diff --git a/view/base/web/images/cloudinary_cloud_glyph_blue.png b/view/base/web/images/cloudinary_cloud_glyph_blue.png
new file mode 100644
index 00000000..49af8db9
Binary files /dev/null and b/view/base/web/images/cloudinary_cloud_glyph_blue.png differ
diff --git a/view/base/web/images/cloudinary_cloud_glyph_regular.svg b/view/base/web/images/cloudinary_cloud_glyph_regular.svg
new file mode 100644
index 00000000..a284090d
--- /dev/null
+++ b/view/base/web/images/cloudinary_cloud_glyph_regular.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/view/base/web/images/cloudinary_cloud_glyph_white.svg b/view/base/web/images/cloudinary_cloud_glyph_white.svg
new file mode 100644
index 00000000..5a108827
--- /dev/null
+++ b/view/base/web/images/cloudinary_cloud_glyph_white.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/view/base/web/images/cloudinary_dropzone.svg b/view/base/web/images/cloudinary_dropzone.svg
new file mode 100644
index 00000000..389b079d
--- /dev/null
+++ b/view/base/web/images/cloudinary_dropzone.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/view/base/web/images/cloudinary_logo_for_white_bg.jpg b/view/base/web/images/cloudinary_logo_for_white_bg.jpg
deleted file mode 100644
index b448103c..00000000
Binary files a/view/base/web/images/cloudinary_logo_for_white_bg.jpg and /dev/null differ
diff --git a/view/base/web/images/cloudinary_placeholder.jpg b/view/base/web/images/cloudinary_placeholder.jpg
new file mode 100644
index 00000000..1030f8a8
Binary files /dev/null and b/view/base/web/images/cloudinary_placeholder.jpg differ
diff --git a/view/base/web/images/cloudinary_spinset_placeholder.svg b/view/base/web/images/cloudinary_spinset_placeholder.svg
new file mode 100644
index 00000000..3db93741
--- /dev/null
+++ b/view/base/web/images/cloudinary_spinset_placeholder.svg
@@ -0,0 +1,11 @@
+
+
+ spinset-placeholder
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/view/base/web/images/cloudinary_spinset_placeholder_2.svg b/view/base/web/images/cloudinary_spinset_placeholder_2.svg
new file mode 100644
index 00000000..e370153d
--- /dev/null
+++ b/view/base/web/images/cloudinary_spinset_placeholder_2.svg
@@ -0,0 +1,15 @@
+
+
+ cloudinary=placeholder
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/view/base/web/images/colorpicker_indic.gif b/view/base/web/images/colorpicker_indic.gif
new file mode 100644
index 00000000..7e86962e
Binary files /dev/null and b/view/base/web/images/colorpicker_indic.gif differ
diff --git a/view/base/web/images/spinset_indic.svg b/view/base/web/images/spinset_indic.svg
new file mode 100644
index 00000000..f8fce85f
--- /dev/null
+++ b/view/base/web/images/spinset_indic.svg
@@ -0,0 +1,12 @@
+
+
+ spinset-indication
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/view/base/web/js/cloudinary-lazyload.js b/view/base/web/js/cloudinary-lazyload.js
new file mode 100644
index 00000000..00fc1a58
--- /dev/null
+++ b/view/base/web/js/cloudinary-lazyload.js
@@ -0,0 +1,62 @@
+define([
+ 'jquery',
+ 'jquery.lazyload'
+], function($) {
+ 'use strict';
+
+ $.widget('mage.cloudinaryLazyload', {
+
+ options: {
+ threshold: 500,
+ failure_limit: 0,
+ event: "scroll",
+ effect: "fadeIn",
+ data_attribute: "original",
+ skip_invisible: true,
+ appear: null,
+ load: null,
+ placeholder: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"
+ },
+
+ /**
+ * @private
+ */
+ _create: function() {
+ this._super();
+ this.initialize();
+ },
+
+ initialize: function(options) {
+ var widget = this;
+ options = $.extend({}, widget.options, options || {});
+ this.cldLazyloadInit(options);
+ setInterval(function() {
+ widget.cldLazyloadInit(options);
+ }, 4000);
+ },
+
+ cldLazyloadInit: function(options) {
+ if ($(".cloudinary-lazyload").length) {
+ var widget = this;
+ try {
+ $(".cloudinary-lazyload").lazyload(options || widget.options);
+ $(".cloudinary-lazyload").addClass("cloudinary-lazyload-processed").removeClass("cloudinary-lazyload");
+ } catch (err) {
+ console.warn("Notice: An error occured while initializing Lazyload (" + err + "). Trying to fix automatically...");
+ $(".cloudinary-lazyload").each(function() {
+ if ($(this).is("img") || $(this).is("iframe")) {
+ $(this).attr("src", $(this).attr("data-original"));
+ } else {
+ $(this).css("background-image", "url('" + $(this).attr("data-original") + "')");
+ }
+ $(this).addClass("cloudinary-lazyload-processed").removeClass("cloudinary-lazyload");
+ });
+ }
+ }
+
+ }
+
+ });
+
+ return $.mage.cloudinaryLazyload;
+});
\ No newline at end of file
diff --git a/view/base/web/js/form/element/file-uploader.js b/view/base/web/js/form/element/file-uploader.js
new file mode 100644
index 00000000..77e3fa93
--- /dev/null
+++ b/view/base/web/js/form/element/file-uploader.js
@@ -0,0 +1,25 @@
+/**
+ * @api
+ */
+/* global Base64 */
+define([
+ 'jquery',
+ 'Magento_Ui/js/form/element/file-uploader'
+], function($, Element) {
+ 'use strict';
+
+ return Element.extend({
+
+ /**
+ * {@inheritDoc}
+ */
+ initialize: function() {
+ this._super();
+ this.cloudinaryMLoptions.imageParamName = this.paramName || this.inputName;
+ this.cloudinaryMLoptions.cldMLid = this.cloudinaryMLoptions.imageParamName + '_' + this.uid;
+ this.cloudinaryMLoptions.callbackHandler = this;
+ this.cloudinaryMLoptions.callbackHandlerMethod = 'addFile';
+ },
+
+ });
+});
\ No newline at end of file
diff --git a/view/base/web/js/form/element/image-uploader.js b/view/base/web/js/form/element/image-uploader.js
new file mode 100644
index 00000000..a537b9dc
--- /dev/null
+++ b/view/base/web/js/form/element/image-uploader.js
@@ -0,0 +1,26 @@
+/* global Base64 */
+define([
+ 'jquery',
+ 'Magento_Ui/js/form/element/image-uploader'
+], function($, Element) {
+ 'use strict';
+
+ return Element.extend({
+ /**
+ * {@inheritDoc}
+ */
+ initialize: function() {
+ this._super();
+ this.cloudinaryMLoptions.imageParamName = this.paramName || this.inputName;
+ this.cloudinaryMLoptions.cldMLid = this.cloudinaryMLoptions.imageParamName + '_' + this.uid;
+ this.cloudinaryMLoptions.callbackHandler = this;
+ this.cloudinaryMLoptions.callbackHandlerMethod = 'addFile';
+ },
+ /**
+ * hides the 'Upload from Gallery' button at category page.
+ * */
+ showGalleryUploader: function() {
+ return (this.cloudinaryMLoptions.isGallerySupported)
+ }
+ });
+});
diff --git a/view/base/web/js/jquery.lazyload.min.js b/view/base/web/js/jquery.lazyload.min.js
new file mode 100644
index 00000000..54961065
--- /dev/null
+++ b/view/base/web/js/jquery.lazyload.min.js
@@ -0,0 +1,2 @@
+/*! Lazy Load 1.9.7 - MIT license - Copyright 2010-2015 Mika Tuupola */
+!function(a,b,c,d){var e=a(b);a.fn.lazyload=function(f){function g(){var b=0;i.each(function(){var c=a(this);if(!j.skip_invisible||c.is(":visible"))if(a.abovethetop(this,j)||a.leftofbegin(this,j));else if(a.belowthefold(this,j)||a.rightoffold(this,j)){if(++b>j.failure_limit)return!1}else c.trigger("appear"),b=0})}var h,i=this,j={threshold:0,failure_limit:0,event:"scroll",effect:"show",container:b,data_attribute:"original",skip_invisible:!1,appear:null,load:null,placeholder:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"};return f&&(d!==f.failurelimit&&(f.failure_limit=f.failurelimit,delete f.failurelimit),d!==f.effectspeed&&(f.effect_speed=f.effectspeed,delete f.effectspeed),a.extend(j,f)),h=j.container===d||j.container===b?e:a(j.container),0===j.event.indexOf("scroll")&&h.bind(j.event,function(){return g()}),this.each(function(){var b=this,c=a(b);b.loaded=!1,(c.attr("src")===d||c.attr("src")===!1)&&c.is("img")&&c.attr("src",j.placeholder),c.one("appear",function(){if(!this.loaded){if(j.appear){var d=i.length;j.appear.call(b,d,j)}a(" ").bind("load",function(){var d=c.attr("data-"+j.data_attribute);c.hide(),c.is("img")?c.attr("src",d):c.css("background-image","url('"+d+"')"),c[j.effect](j.effect_speed),b.loaded=!0;var e=a.grep(i,function(a){return!a.loaded});if(i=a(e),j.load){var f=i.length;j.load.call(b,f,j)}}).attr("src",c.attr("data-"+j.data_attribute))}}),0!==j.event.indexOf("scroll")&&c.bind(j.event,function(){b.loaded||c.trigger("appear")})}),e.bind("resize",function(){g()}),/(?:iphone|ipod|ipad).*os 5/gi.test(navigator.appVersion)&&e.bind("pageshow",function(b){b.originalEvent&&b.originalEvent.persisted&&i.each(function(){a(this).trigger("appear")})}),a(c).ready(function(){g()}),this},a.belowthefold=function(c,f){var g;return g=f.container===d||f.container===b?(b.innerHeight?b.innerHeight:e.height())+e.scrollTop():a(f.container).offset().top+a(f.container).height(),g<=a(c).offset().top-f.threshold},a.rightoffold=function(c,f){var g;return g=f.container===d||f.container===b?e.width()+e.scrollLeft():a(f.container).offset().left+a(f.container).width(),g<=a(c).offset().left-f.threshold},a.abovethetop=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollTop():a(f.container).offset().top,g>=a(c).offset().top+f.threshold+a(c).height()},a.leftofbegin=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollLeft():a(f.container).offset().left,g>=a(c).offset().left+f.threshold+a(c).width()},a.inviewport=function(b,c){return!(a.rightoffold(b,c)||a.leftofbegin(b,c)||a.belowthefold(b,c)||a.abovethetop(b,c))},a.extend(a.expr[":"],{"below-the-fold":function(b){return a.belowthefold(b,{threshold:0})},"above-the-top":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-screen":function(b){return a.rightoffold(b,{threshold:0})},"left-of-screen":function(b){return!a.rightoffold(b,{threshold:0})},"in-viewport":function(b){return a.inviewport(b,{threshold:0})},"above-the-fold":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-fold":function(b){return a.rightoffold(b,{threshold:0})},"left-of-fold":function(b){return!a.rightoffold(b,{threshold:0})}})}(jQuery,window,document);
diff --git a/view/base/web/template/form/element/uploader/image.html b/view/base/web/template/form/element/uploader/image.html
new file mode 100644
index 00000000..2e849369
--- /dev/null
+++ b/view/base/web/template/form/element/uploader/image.html
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ : .
+
+
+ : .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/view/base/web/template/form/element/uploader/uploader.html b/view/base/web/template/form/element/uploader/uploader.html
new file mode 100644
index 00000000..2de5fb31
--- /dev/null
+++ b/view/base/web/template/form/element/uploader/uploader.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/view/frontend/layout/catalog_product_view.xml b/view/frontend/layout/catalog_product_view.xml
new file mode 100644
index 00000000..95891c55
--- /dev/null
+++ b/view/frontend/layout/catalog_product_view.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/view/frontend/layout/default.xml b/view/frontend/layout/default.xml
new file mode 100644
index 00000000..5ba003dd
--- /dev/null
+++ b/view/frontend/layout/default.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/view/frontend/requirejs-config.js b/view/frontend/requirejs-config.js
index a888a5dd..e2a9e7bb 100644
--- a/view/frontend/requirejs-config.js
+++ b/view/frontend/requirejs-config.js
@@ -2,7 +2,25 @@ var config = {
map: {
'*': {
loadPlayer: 'Cloudinary_Cloudinary/js/load-player',
- 'Magento_ProductVideo/js/fotorama-add-video-events': 'Cloudinary_Cloudinary/js/fotorama-add-video-events'
+ 'Magento_ProductVideo/js/fotorama-add-video-events': 'Cloudinary_Cloudinary/js/fotorama-add-video-events',
+ cloudinaryProductGallery: 'Cloudinary_Cloudinary/js/cloudinary-product-gallery',
+ cloudinaryLazyload: 'Cloudinary_Cloudinary/js/cloudinary-lazyload'
}
},
-};
\ No newline at end of file
+ paths: {
+ 'jquery.lazyload': "Cloudinary_Cloudinary/js/jquery.lazyload.min",
+ cloudinaryProductGalleryAll: "//product-gallery.cloudinary.com/latest/all"
+ },
+ shim: {
+ 'jquery.lazyload': {
+ deps: ['jquery']
+ },
+ },
+ config: {
+ mixins: {
+ 'Magento_Swatches/js/swatch-renderer': {
+ 'Cloudinary_Cloudinary/js/swatch-renderer-mixin': true
+ }
+ }
+ }
+};
diff --git a/view/frontend/templates/javascript.phtml b/view/frontend/templates/javascript.phtml
new file mode 100644
index 00000000..e8977052
--- /dev/null
+++ b/view/frontend/templates/javascript.phtml
@@ -0,0 +1,4 @@
+
+isEnabled() && $cname = $block->getCname()): ?>
+
+
diff --git a/view/frontend/templates/lazyload.phtml b/view/frontend/templates/lazyload.phtml
new file mode 100644
index 00000000..0df2924e
--- /dev/null
+++ b/view/frontend/templates/lazyload.phtml
@@ -0,0 +1,9 @@
+
+isEnabledLazyload()) {
+ return;
+} ?>
+
diff --git a/view/frontend/templates/product/gallery.phtml b/view/frontend/templates/product/gallery.phtml
new file mode 100644
index 00000000..c5e8326c
--- /dev/null
+++ b/view/frontend/templates/product/gallery.phtml
@@ -0,0 +1,161 @@
+helper('Cloudinary\Cloudinary\Helper\ProductGalleryHelper');
+?>
+
+isHyvaThemeEnabled()): ?>
+
+
+
+
+
+
+
diff --git a/view/frontend/templates/product/image.phtml b/view/frontend/templates/product/image.phtml
new file mode 100644
index 00000000..fd6f2701
--- /dev/null
+++ b/view/frontend/templates/product/image.phtml
@@ -0,0 +1,20 @@
+
+
+
+ getCustomAttributes() as $name => $value): ?>
+ = $escaper->escapeHtmlAttr($name) ?>="= $escaper->escapeHtmlAttr($value) ?>"
+
+ src="= $escaper->escapeHtmlAttr($block->getLazyloadPlaceholder()) ?>"
+ data-original="= $escaper->escapeUrl($block->getImageUrl()) ?>"
+ width="= $escaper->escapeHtmlAttr($block->getWidth()) ?>"
+ height="= $escaper->escapeHtmlAttr($block->getHeight()) ?>"
+ alt="= $escaper->escapeHtmlAttr($block->getLabel()) ?>" />
diff --git a/view/frontend/templates/product/image_with_borders.phtml b/view/frontend/templates/product/image_with_borders.phtml
new file mode 100644
index 00000000..fa0d2c56
--- /dev/null
+++ b/view/frontend/templates/product/image_with_borders.phtml
@@ -0,0 +1,66 @@
+
+getVar('product_image_white_borders', 'Magento_Catalog');
+$enableLazyLoadingWithoutBorders = (bool)$block->getVar(
+ 'enable_lazy_loading_for_images_without_borders',
+ 'Magento_Catalog'
+);
+$width = (int)$block->getWidth();
+$paddingBottom = $block->getRatio() * 100;
+?>
+
+
+ getCustomAttributes() as $name => $value): ?>
+ = $escaper->escapeHtmlAttr($name) ?>="= $escaper->escapeHtmlAttr($value) ?>"
+
+ src="= /* @noEscape */ $block->getLazyloadPlaceholder() ?>"
+ data-original="= $escaper->escapeUrl($block->getImageUrl()) ?>"
+
+ width="= $escaper->escapeHtmlAttr($block->getWidth()) ?>"
+ height="= $escaper->escapeHtmlAttr($block->getHeight()) ?>"
+
+ max-width="= $escaper->escapeHtmlAttr($block->getWidth()) ?>"
+ max-height="= $escaper->escapeHtmlAttr($block->getHeight()) ?>"
+
+ alt="= $escaper->escapeHtmlAttr($block->getLabel()) ?>"/>
+
+getProductId()} {
+ width: {$width}px;
+}
+.product-image-container-{$block->getProductId()} span.product-image-wrapper {
+ padding-bottom: {$paddingBottom}%;
+}
+STYLE;
+//In case a script was using "style" attributes of these elements
+$script = <<
diff --git a/view/frontend/templates/product/video/settings.phtml b/view/frontend/templates/product/video/settings.phtml
new file mode 100644
index 00000000..4195f5ea
--- /dev/null
+++ b/view/frontend/templates/product/video/settings.phtml
@@ -0,0 +1,26 @@
+
+
+
+helper('Cloudinary\Cloudinary\Helper\ProductGalleryHelper');
+?>
+isHyvaThemeEnabled()): ?>
+
+
diff --git a/view/frontend/web/js/cloudinary-product-gallery.js b/view/frontend/web/js/cloudinary-product-gallery.js
new file mode 100644
index 00000000..2974065e
--- /dev/null
+++ b/view/frontend/web/js/cloudinary-product-gallery.js
@@ -0,0 +1,36 @@
+define([
+ 'jquery',
+ 'cloudinaryProductGalleryAll'
+], function($) {
+ 'use strict';
+
+ $.widget('mage.cloudinaryProductGallery', {
+
+ options: {
+ cloudinaryPGoptions: {}, // Options for Cloudinary-PG galleryWidget()
+ cldPGid: 0,
+ },
+
+ /**
+ * @private
+ */
+ _create: function() {
+ this._super();
+
+ var widget = this;
+ window.cloudinary_pg = window.cloudinary_pg || [];
+ this.options.cldPGid = this.options.cldPGid || 0;
+ if (typeof window.cloudinary_pg[this.options.cldPGid] === "undefined") {
+ this.cloudinary_pg = window.cloudinary_pg[this.options.cldPGid] = cloudinary.galleryWidget(this.options.cloudinaryPGoptions);
+ this.cloudinary_pg.render();
+ } else {
+ this.cloudinary_pg = window.cloudinary_pg[this.options.cldPGid];
+ }
+
+
+ },
+
+ });
+
+ return $.mage.cloudinaryProductGallery;
+});
diff --git a/view/frontend/web/js/fotorama-add-video-events.js b/view/frontend/web/js/fotorama-add-video-events.js
index dc5afd4a..def45b26 100644
--- a/view/frontend/web/js/fotorama-add-video-events.js
+++ b/view/frontend/web/js/fotorama-add-video-events.js
@@ -3,839 +3,908 @@
* See COPYING.txt for license details.
*/
-define([
- 'jquery',
- 'jquery/ui',
- 'catalogGallery',
- 'loadPlayer'
-], function($) {
- 'use strict';
-
- /**
- * @private
- */
- var allowBase = true; //global var is needed because fotorama always fully reloads events in case of fullscreen
-
- /**
- * @private
- */
- function parseHref(href) {
- var a = document.createElement('a');
-
- a.href = href;
-
- return a;
- }
-
- /**
- * @private
- */
- function _baseName(str) {
- var base = new String(str).substring(str.lastIndexOf('/') + 1);
- if (base.lastIndexOf(".") != -1)
- base = base.substring(0, base.lastIndexOf("."));
- return base;
- }
-
- /**
- * @private
- */
- function _fileExtension(str) {
- var re = /(?:\.([^.]+))?$/;
- return re.exec(str)[1];
- }
-
- /**
- * @private
- */
- function parseURL(href, forceVideo) {
- var id,
- type,
- ampersandPosition,
- vimeoRegex,
- useYoutubeNocookie = false;
-
- /**
- * Get youtube ID
- * @param {String} srcid
- * @returns {{}}
- */
- function _getYoutubeId(srcid) {
- if (srcid) {
- ampersandPosition = srcid.indexOf('&');
-
- if (ampersandPosition === -1) {
- return srcid;
- }
-
- srcid = srcid.substring(0, ampersandPosition);
- }
-
- return srcid;
- }
-
- if (typeof href !== 'string') {
- return href;
- }
-
- href = parseHref(href);
-
- if (href.host.match(/youtube\.com/) && href.search) {
- id = href.search.split('v=')[1];
-
- if (id) {
- id = _getYoutubeId(id);
- type = 'youtube';
- }
- } else if (href.host.match(/youtube\.com|youtu\.be|youtube-nocookie.com/)) {
- id = href.pathname.replace(/^\/(embed\/|v\/)?/, '').replace(/\/.*/, '');
- type = 'youtube';
-
- if (href.host.match(/youtube-nocookie.com/)) {
- useYoutubeNocookie = true;
- }
- } else if (href.host.match(/vimeo\.com/)) {
- type = 'vimeo';
- vimeoRegex = new RegExp(['https?:\\/\\/(?:www\\.|player\\.)?vimeo.com\\/(?:channels\\/(?:\\w+\\/)',
- '?|groups\\/([^\\/]*)\\/videos\\/|album\\/(\\d+)\\/video\\/|video\\/|)(\\d+)(?:$|\\/|\\?)'
- ].join(''));
- id = href.href.match(vimeoRegex)[3];
- } else if (href.host.match(/cloudinary\.com/)) {
- type = 'cloudinary';
- id = _baseName(href.href);
- }
-
- if ((!id || !type) && forceVideo) {
- id = href.href;
- type = 'custom';
- }
-
- return id ? {
- id: id,
- type: type,
- s: href.search.replace(/^\?/, ''),
- useYoutubeNocookie: useYoutubeNocookie
- } : false;
- }
-
- //create AddFotoramaVideoEvents widget
- $.widget('mage.AddFotoramaVideoEvents', {
- options: {
- videoData: '',
- videoSettings: '',
- optionsVideoData: '',
- dataMergeStrategy: 'replace',
- vimeoJSFrameworkLoaded: false
- },
+define(
+ [
+ 'jquery',
+ 'jquery-ui-modules/widget',
+ 'catalogGallery',
+ 'loadPlayer'
+ ],
+ function ($) {
+ 'use strict';
/**
* @private
*/
- onVimeoJSFramework: function() {},
- defaultVideoData: [],
- PV: 'product-video', // [CONST]
- VU: 'video-unplayed',
- PVLOADED: 'fotorama__product-video--loaded', // [CONST]
- PVLOADING: 'fotorama__product-video--loading', // [CONST]
- VID: 'video', // [CONST]
- VI: 'vimeo', // [CONST]
- CLD: 'cloudinary', // [CONST]
- FTVC: 'fotorama__video-close',
- FTAR: 'fotorama__arr',
- fotoramaSpinner: 'fotorama__spinner',
- fotoramaSpinnerShow: 'fotorama__spinner--show',
- TI: 'video-thumb-icon',
- isFullscreen: false,
- FTCF: '[data-gallery-role="fotorama__fullscreen-icon"]',
- Base: 0, //on check for video is base this setting become true if there is any video with base role
- MobileMaxWidth: 767,
- GP: 'gallery-placeholder', //gallery placeholder class is needed to find and erase