diff --git a/Api/ProductGalleryManagementInterface.php b/Api/ProductGalleryManagementInterface.php
new file mode 100644
index 00000000..dd20b79a
--- /dev/null
+++ b/Api/ProductGalleryManagementInterface.php
@@ -0,0 +1,63 @@
+mediaLibraryHelper = $mediaLibraryHelper;
+ }
+
+ /**
+ * 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 {
+ //Try to add session param on Magento versions prior to 2.3.5
+ $imageUploadUrl = $this->_urlBuilder->addSessionParam()->getUrl('cloudinary/cms_wysiwyg_images/upload', ['type' => $this->_getMediaType()]);
+ } catch (\Exception $e) {
+ //Catch deprecation error on Magento 2.3.5 and above
+ $imageUploadUrl = $this->_urlBuilder->getUrl('cloudinary/cms_wysiwyg_images/upload', ['type' => $this->_getMediaType()]);
+ }
+
+ 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..240f52b9 100644
--- a/Block/Adminhtml/Form/Field/Free.php
+++ b/Block/Adminhtml/Form/Field/Free.php
@@ -21,10 +21,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,
@@ -48,7 +48,7 @@ protected function _beforeToHtml()
}
/**
- * @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..b31598a7 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;
}
@@ -49,6 +77,7 @@ protected function getCloudinaryConfig()
$this->_cloudinaryConfig['api_url'] = "https://api.cloudinary.com/v1_1/{$this->_cloudinaryConfig['cloud_name']}/";
}
}
+
return $this->_cloudinaryConfig;
}
@@ -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(),
]
);
}
@@ -85,11 +114,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 +138,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/Command/DownloadImages.php b/Command/DownloadImages.php
new file mode 100644
index 00000000..13f2b7c1
--- /dev/null
+++ b/Command/DownloadImages.php
@@ -0,0 +1,140 @@
+Are you sure you want to override local files (y/n)[n]?";
+
+ private $_override = 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');
+ $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
+ ),
+ ]);
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ *
+ * @return void
+ */
+ 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);
+ }
+ $this->outputLogger->setOutput($output);
+ $this->batchDownloader->downloadUnsynchronisedImages($this->outputLogger, $this->_override);
+ } catch (\Exception $e) {
+ $output->writeln($e->getMessage());
+ }
+ }
+
+ /**
+ * @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..bbfc422b
--- /dev/null
+++ b/Command/ProductGalleryApiQueueProcess.php
@@ -0,0 +1,65 @@
+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..a61f0380 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,13 +72,16 @@ protected function configure()
}
/**
- * @param InputInterface $input
- * @param OutputInterface $output
+ * @param InputInterface $input
+ * @param OutputInterface $output
*
* @return void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
+ $this->resetHelper = $this->objectManager
+ ->get(\Cloudinary\Cloudinary\Helper\Reset::class);
+
$this->displayPreActionMessage($output);
$helper = $this->getHelper('question');
@@ -101,7 +116,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 +124,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 +137,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 +150,7 @@ private function readAdminName(InputInterface $input, OutputInterface $output, Q
}
/**
- * @param string $username
+ * @param string $username
* @return User
*/
private function getAdminUser($username)
@@ -144,9 +159,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 +173,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..8e9035ea 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,17 +40,20 @@ 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
*/
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);
diff --git a/Command/UploadImages.php b/Command/UploadImages.php
index 9f7f7bb4..3d1b4946 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,36 +32,77 @@ 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
*/
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);
} catch (\Exception $e) {
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..c1e4b202
--- /dev/null
+++ b/Controller/Adminhtml/Ajax/RetrieveImage.php
@@ -0,0 +1,377 @@
+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;
+ }
+
+ /**
+ * @return \Magento\Framework\Controller\Result\Raw
+ */
+ public function execute()
+ {
+ try {
+ $localUniqFilePath = $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"];
+ $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);
+ $this->retrieveRemoteImage($this->remoteFileUrl, $localUniqFilePath);
+ $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..4512f8a1
--- /dev/null
+++ b/Controller/Adminhtml/Ajax/System/Config/AutoUploadMapping.php
@@ -0,0 +1,130 @@
+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/Cms/Wysiwyg/Images/Upload.php b/Controller/Adminhtml/Cms/Wysiwyg/Images/Upload.php
new file mode 100644
index 00000000..ea5f17e0
--- /dev/null
+++ b/Controller/Adminhtml/Cms/Wysiwyg/Images/Upload.php
@@ -0,0 +1,316 @@
+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;
+ }
+
+ /**
+ * Files upload processing.
+ *
+ * @return \Magento\Framework\Controller\ResultInterface
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ public function execute()
+ {
+ try {
+ $this->_initAction();
+ $path = $this->getStorage()->getSession()->getCurrentPath();
+ 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);
+ 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/Core/AutoUploadMapping/ApiClient.php b/Core/AutoUploadMapping/ApiClient.php
index 082ec6e6..eedf842e 100644
--- a/Core/AutoUploadMapping/ApiClient.php
+++ b/Core/AutoUploadMapping/ApiClient.php
@@ -14,6 +14,11 @@ class ApiClient
const FOLDER_KEY = 'folder';
const URL_KEY = 'template';
+ /**
+ * @var bool
+ */
+ private $_authorised;
+
/**
* @var ConfigurationInterface
*/
@@ -31,8 +36,9 @@ class ApiClient
/**
* ApiClient constructor.
+ *
* @param ConfigurationInterface $configuration
- * @param ConfigurationBuilder $configurationBuilder
+ * @param ConfigurationBuilder $configurationBuilder
*/
public function __construct(
ConfigurationInterface $configuration,
@@ -42,13 +48,10 @@ public function __construct(
$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)
@@ -61,14 +64,14 @@ public static function fromConfiguration(ConfigurationInterface $configuration)
}
/**
- * @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->upload_mappings());
if ($this->hasMapping($existingMappings, $folder)) {
@@ -80,7 +83,6 @@ public function prepareMapping($folder, $url)
}
return true;
-
} catch (\Exception $e) {
$this->errors[] = $e;
return false;
@@ -88,12 +90,13 @@ public function prepareMapping($folder, $url)
}
/**
- * @param Response $response
+ * @param Response $response
* @return array
* @throws \Exception
*/
private function parseFetchMappingsResponse(Response $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 +105,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,9 +130,9 @@ 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)
@@ -137,7 +140,7 @@ private function mappingMatches(array $existingMappings, $folder, $url)
return count(
array_filter(
$this->filterMappings($existingMappings, $folder),
- function(array $mapping) use ($url) {
+ function (array $mapping) use ($url) {
return $mapping[self::URL_KEY] == $url;
}
)
@@ -146,8 +149,11 @@ function(array $mapping) use ($url) {
private function authorise()
{
- Cloudinary::config($this->configurationBuilder->build());
- Cloudinary::$USER_PLATFORM = $this->configuration->getUserPlatform();
+ if (!$this->_authorised && $this->configuration->isEnabled()) {
+ Cloudinary::config($this->configurationBuilder->build());
+ Cloudinary::$USER_PLATFORM = $this->configuration->getUserPlatform();
+ $this->_authorised = true;
+ }
}
/**
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..5502eeb8 100644
--- a/Core/CloudinaryImageManager.php
+++ b/Core/CloudinaryImageManager.php
@@ -7,6 +7,7 @@
/**
* Class CloudinaryImageManager
+ *
* @package Cloudinary\Cloudinary\Core
*/
class CloudinaryImageManager
@@ -26,42 +27,58 @@ 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)
{
+ if (!$this->configuration->isEnabled() || !$this->configuration->hasEnvironmentVariable()) {
+ return;
+ }
+
try {
$this->report($output, sprintf(self::MESSAGE_UPLOADING_IMAGE, $image));
$this->cloudinaryImageProvider->upload($image);
} catch (FileExists $e) {
$this->report($output, sprintf(self::MESSAGE_UPLOADED_EXISTS, $image));
} 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));
+ } 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;
+ }
- throw $e;
+ throw $e;
+ }
}
$this->synchronisationRepository->saveAsSynchronized($image->getRelativePath());
@@ -72,13 +89,17 @@ 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 = '')
{
diff --git a/Core/CloudinaryImageProvider.php b/Core/CloudinaryImageProvider.php
index cbfe3560..f8f6d544 100644
--- a/Core/CloudinaryImageProvider.php
+++ b/Core/CloudinaryImageProvider.php
@@ -3,15 +3,19 @@
namespace Cloudinary\Cloudinary\Core;
use Cloudinary;
-use Cloudinary\Uploader;
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\Uploader;
+use Magento\Catalog\Model\Product\Media\Config as ProductMediaConfig;
class CloudinaryImageProvider implements ImageProvider
{
+ /**
+ * @var bool
+ */
+ private $_authorised;
+
/**
* @var ConfigurationInterface
*/
@@ -28,50 +32,57 @@ class CloudinaryImageProvider implements ImageProvider
private $configurationBuilder;
/**
- * @var CredentialValidator
+ * @var ProductMediaConfig
*/
- private $credentialValidator;
+ private $productMediaConfig;
/**
- * @param ConfigurationInterface $configuration
- * @param ConfigurationBuilder $configurationBuilder
- * @param UploadResponseValidator $uploadResponseValidator
- * @param CredentialValidator $credentialValidator
+ * @var MediaLibraryMapFactory
+ */
+ private $mediaLibraryMapFactory;
+
+ /**
+ * @method __construct
+ * @param ConfigurationInterface $configuration
+ * @param ConfigurationBuilder $configurationBuilder
+ * @param UploadResponseValidator $uploadResponseValidator
+ * @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;
}
@@ -88,33 +99,80 @@ 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 = \cloudinary_url(
+ $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());
}
@@ -125,12 +183,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()) {
+ Cloudinary::config($this->configurationBuilder->build());
+ Cloudinary::$USER_PLATFORM = $this->configuration->getUserPlatform();
+ $this->_authorised = true;
+ }
}
}
diff --git a/Core/ConfigurationBuilder.php b/Core/ConfigurationBuilder.php
index 21e8f50d..d559f1d7 100644
--- a/Core/ConfigurationBuilder.php
+++ b/Core/ConfigurationBuilder.php
@@ -25,6 +25,7 @@ public function build()
if ($this->configuration->getCdnSubdomainStatus()) {
$config['cdn_subdomain'] = true;
}
+
return $config;
}
}
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..d64a26f7 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,20 @@ class Transformation
public function __construct()
{
- $this->crop = 'pad';
+ $this->crop = 'lpad';
$this->flags = [];
}
+ public function withDefaultImage(DefaultImage $defaultImage)
+ {
+ $this->defaultImage = trim((string)$defaultImage);
+ 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 +72,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 +95,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,
'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..8a85685a 100644
--- a/Core/Security/ApiSignature.php
+++ b/Core/Security/ApiSignature.php
@@ -6,7 +6,6 @@
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..405177d3 100644
--- a/Core/Security/CloudinaryEnvironmentVariable.php
+++ b/Core/Security/CloudinaryEnvironmentVariable.php
@@ -8,7 +8,6 @@
class CloudinaryEnvironmentVariable implements EnvironmentVariable
{
-
private $environmentVariable;
private function __construct($environmentVariable)
@@ -16,7 +15,7 @@ private function __construct($environmentVariable)
$this->environmentVariable = (string)$environmentVariable;
try {
Cloudinary::config_from_url(str_replace('CLOUDINARY_URL=', '', $environmentVariable));
- } catch (\Exception $e){
+ } catch (\Exception $e) {
throw new \Cloudinary\Cloudinary\Core\Exception\InvalidCredentials('Cloudinary config creation from environment variable failed');
}
}
@@ -43,5 +42,4 @@ 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..f8eceb59 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)
{
@@ -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..5dbc20bc
--- /dev/null
+++ b/Cron/ProductGalleryApiQueue.php
@@ -0,0 +1,196 @@
+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 $this;
+ }
+
+ /**
+ * @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..e89fed9f
--- /dev/null
+++ b/Helper/MediaLibraryHelper.php
@@ -0,0 +1,107 @@
+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 = [
+ "cloud_name" => (string)$this->configuration->getCloud(),
+ "api_key" => (string)$this->configuration->getCredentials()->getKey(),
+ "api_secret" => (string)$this->configuration->getCredentials()->getSecret()
+ ];
+ 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;
+ }
+}
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..8e3103ca
--- /dev/null
+++ b/Helper/ProductGalleryHelper.php
@@ -0,0 +1,119 @@
+ '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
+ */
+ public function __construct(
+ Context $context,
+ ConfigurationInterface $configuration
+ ) {
+ parent::__construct($context);
+ $this->configuration = $configuration;
+ }
+
+ /**
+ * @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
+ */
+ 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();
+ 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['queryParam'] = 'AB';
+ }
+
+ return $this->cloudinaryPGoptions;
+ }
+
+ /**
+ * @method getCloudName
+ * @return string
+ */
+ public function getCloudName()
+ {
+ return (string)$this->configuration->getCloud();
+ }
+
+ /**
+ * @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..23bdf844
--- /dev/null
+++ b/Model/Api/ProductGalleryManagement.php
@@ -0,0 +1,853 @@
+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;
+ }
+
+ /**
+ * {@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, true);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getProductMediaData($sku)
+ {
+ return $this->_getProductMedia($sku, false);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getProductsMedia($skus)
+ {
+ return $this->_getProductMedia($skus);
+ }
+
+ /**
+ * [_getProductMedia description]
+ * @method _getProductMedia
+ * @param mixed $sku
+ * @return string (json result)
+ */
+ private function _getProductMedia($sku, $onlyUrls = true)
+ {
+ $result = ["data" => []];
+
+ try {
+ $this->checkEnvHeader();
+ $this->checkEnabled();
+ if (is_array($sku) || is_object($sku)) {
+ foreach ($sku as $key => $_sku) {
+ $result['data'][$_sku] = ($onlyUrls ? $this->getProductCldUrlsBySku($_sku) : $this->getProductCldDataBySku($_sku));
+ }
+ } else {
+ $result['data'] = ($onlyUrls ? $this->getProductCldUrlsBySku($sku) : $this->getProductCldDataBySku($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);
+
+ $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);
+ }
+ $localUniqFilePath = $this->appendNewFileName($baseTmpMediaPath . $this->getLocalTmpFileName($localUniqFilePath));
+ $this->validateRemoteFileExtensions($localUniqFilePath);
+ $this->retrieveRemoteImage($remoteFileUrl, $localUniqFilePath);
+ $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;
+ }
+
+ /**
+ * @method getProductCldDataBySku
+ * @param string $sku
+ * @return array
+ */
+ private function getProductCldDataBySku($sku)
+ {
+ $data = [];
+ try {
+ $product = $this->productRepository->get($sku);
+ $attributes = $product->getAttributes();
+ foreach ($product->getMediaGalleryImages() as $gallItem) {
+ $gallItemData = $gallItem->getData();
+ foreach ($attributes as $attribute) {
+ $attrCode = $attribute->getAttributeCode();
+ if($product->getData($attrCode) === $gallItem->getFile()) {
+ $gallItemData['role'] = $attrCode;
+ }
+ }
+ array_push($data, $gallItemData);
+ }
+ } catch (\Exception $e) {
+ $data = [
+ 'error' => 1,
+ 'message' => $e->getMessage()
+ ];
+ }
+ return $data;
+ }
+
+ ///////////////////////////////
+ // 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..5ab7259d 100644
--- a/Model/Api/ResourcesManagement.php
+++ b/Model/Api/ResourcesManagement.php
@@ -6,9 +6,14 @@
use Cloudinary\Api;
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 = [];
@@ -28,63 +33,107 @@ class ResourcesManagement implements \Cloudinary\Cloudinary\Api\ResourcesManagem
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 Api $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()) {
+ Cloudinary::config($this->_configurationBuilder->build());
+ Cloudinary::$USER_PLATFORM = $this->_configuration->getUserPlatform();
+ }
}
+ return $this;
+ }
+
+ public function setId($id)
+ {
+ $this->id = $id;
+ return $this;
+ }
+
+ public function getId()
+ {
+ return $this->id;
}
- private function _authorise()
+ public function setMaxResults($maxResults)
{
- Cloudinary::config($this->_configurationBuilder->build());
- Cloudinary::$USER_PLATFORM = $this->_configuration->getUserPlatform();
+ $this->maxResults = $maxResults;
+ return $this;
}
- public function _sendJsonResponse($response)
+ public function getMaxResults()
{
- header('Content-Type: application/json');
- echo json_encode($response);
- die;
+ 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"), [
+ $this->initialize();
+ $this->_resourceData = $this->_api->resource(
+ $this->getId(),
+ [
"resource_type" => $this->_resourceType
- ]);
- $this->_sendJsonResponse([
+ ]
+ );
+ return $this->_jsonEncoder->encode(
+ [
"error" => 0,
"data" => $this->_resourceData
- ]);
+ ]
+ );
} catch (\Exception $e) {
- $this->_sendJsonResponse([
+ return $this->_jsonEncoder->encode(
+ [
"error" => 1,
"message" => $e->getMessage()
- ]);
+ ]
+ );
}
}
@@ -94,7 +143,7 @@ protected function _getResourceData()
public function getImage()
{
$this->_resourceType = "image";
- $this->_getResourceData();
+ return $this->_getResourceData();
}
/**
@@ -103,6 +152,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->resources_by_tag(
+ $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..fe81cd2a
--- /dev/null
+++ b/Model/BatchDownloader.php
@@ -0,0 +1,366 @@
+_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()
+ {
+ Cloudinary::config($this->_configurationBuilder->build());
+ Cloudinary::$USER_PLATFORM = $this->_configuration->getUserPlatform();
+ }
+
+ /**
+ * 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)
+ {
+ 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);
+
+ //= 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 ($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($nextCursor = null)
+ {
+ $response = $this->_api->resources(
+ [
+ "resource_type" => 'image',
+ "type" => "upload",
+ "prefix" => DirectoryList::MEDIA . DIRECTORY_SEPARATOR,
+ "max_results" => self::API_REQUEST_MAX_RESULTS,
+ "next_cursor" => $nextCursor,
+ ]
+ );
+ $this->_rateLimitResetAt = $response->rate_limit_reset_at;
+ $this->_rateLimitAllowed = $response->rate_limit_allowed;
+ $this->_rateLimitRemaining = $response->rate_limit_remaining;
+ $response->resources = array_values($response['resources']);
+ return new DataObject((array)$response);
+ }
+
+ /**
+ * @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..9f16235b 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)
{
+ 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..ff07e39f 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;
+use Cloudinary\Api;
+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,40 @@ class Credentials extends Encrypted
const CLOUDINARY_ENABLED_PATH = 'groups/cloud/fields/cloudinary_enabled/value';
/**
- * @var CredentialValidator
+ * @var ConfigurationInterface
+ */
+ private $configuration;
+
+ /**
+ * @var ConfigurationBuilder
*/
- private $credentialValidator;
+ private $configurationBuilder;
/**
- * @var ConfigurationInterface
+ * @var Cloudinary\Api
*/
- private $configuration;
+ private $api;
/**
- * @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
+ * Application config
+ *
+ * @var ScopeConfigInterface
+ */
+ protected $appConfig;
+
+ /**
+ * @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 Api $api
+ * @param ReinitableConfigInterface $appConfig
+ * @param array $data
*/
public function __construct(
Context $context,
@@ -52,14 +67,18 @@ public function __construct(
ScopeConfigInterface $config,
TypeListInterface $cacheTypeList,
EncryptorInterface $encryptor,
- CredentialValidator $credentialValidator,
ConfigurationInterface $configuration,
AbstractResource $resource = null,
AbstractDb $resourceCollection = null,
+ ConfigurationBuilder $configurationBuilder,
+ Api $api,
+ ReinitableConfigInterface $appConfig,
array $data = []
) {
- $this->credentialValidator = $credentialValidator;
$this->configuration = $configuration;
+ $this->configurationBuilder = $configurationBuilder;
+ $this->api = $api;
+ $this->appConfig = $appConfig;
parent::__construct(
$context,
@@ -79,50 +98,67 @@ public function beforeSave()
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();
- 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()) {
+ $this->validate($this->getCredentialsFromEnvironmentVariable($rawValue));
+ } else {
+ $this->validate($this->getCredentialsFromConfig());
+ }
}
}
/**
- * @param CredentialsValue $credentials
+ * @param array $credentials
* @throws ValidatorException
*/
- private function validate(CredentialsValue $credentials)
+ private function validate(array $credentials)
{
- if (!$this->credentialValidator->validate($credentials)) {
+ $this->_authorise($credentials);
+ $pingValidation = $this->api->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));
+ $credentials = [
+ "cloud_name" => Cloudinary::config_get('cloud_name'),
+ "api_key" => Cloudinary::config_get('api_key'),
+ "api_secret" => Cloudinary::config_get('api_secret')
+ ];
+ if (Cloudinary::config_get('private_cdn')) {
+ $credentials["private_cdn"] = Cloudinary::config_get('private_cdn');
+ }
+
+ 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->configuration->getEnvironmentVariable()->__toString());
} catch (InvalidCredentials $e) {
throw new ValidatorException(__(self::CREDENTIALS_CHECK_FAILED));
}
@@ -135,4 +171,13 @@ private function isModuleActiveInFormData()
{
return $this->getDataByPath(self::CLOUDINARY_ENABLED_PATH) === '1';
}
+
+ /**
+ * @param array $credentials
+ */
+ private function _authorise(array $credentials)
+ {
+ Cloudinary::config($credentials);
+ Cloudinary::$USER_PLATFORM = $this->configuration->getUserPlatform();
+ }
}
diff --git a/Model/Config/Backend/Free.php b/Model/Config/Backend/Free.php
index 1c00b162..4298020d 100644
--- a/Model/Config/Backend/Free.php
+++ b/Model/Config/Backend/Free.php
@@ -16,7 +16,6 @@
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\Phrase;
use Magento\Framework\Registry;
-use Magento\Framework\UrlInterface;
use Zend_Http_Response;
class Free extends \Magento\Framework\App\Config\Value
@@ -40,16 +39,16 @@ class Free extends \Magento\Framework\App\Config\Value
private $zendClient;
/**
- * @param Context $context
- * @param Registry $registry
- * @param ScopeConfigInterface $config
- * @param TypeListInterface $cacheTypeList
- * @param ConfigurationInterface $configuration,
+ * @param Context $context
+ * @param Registry $registry
+ * @param ScopeConfigInterface $config
+ * @param TypeListInterface $cacheTypeList
+ * @param ConfigurationInterface $configuration,
* @param CloudinaryImageProvider $cloudinaryImageProvider
- * @param ZendClient $zendClient
- * @param AbstractResource $resource
- * @param AbstractDb $resourceCollection
- * @param array $data
+ * @param ZendClient $zendClient
+ * @param AbstractResource $resource
+ * @param AbstractDb $resourceCollection
+ * @param array $data
*/
public function __construct(
Context $context,
@@ -73,20 +72,18 @@ public function __construct(
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)
@@ -105,7 +102,7 @@ public function validate($url)
}
/**
- * @param Zend_Http_Response $response
+ * @param Zend_Http_Response $response
* @return Phrase
*/
public function formatError(Zend_Http_Response $response)
@@ -117,7 +114,7 @@ public function formatError(Zend_Http_Response $response)
}
/**
- * @param string $url
+ * @param string $url
* @return Zend_Http_Response
*/
public function httpRequest($url)
@@ -134,7 +131,7 @@ public function hasAccountConfigured()
}
/**
- * @param Transformation $transformation
+ * @param Transformation $transformation
* @return string
*/
public function sampleImageUrl(Transformation $transformation)
@@ -146,8 +143,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..959a5c95
--- /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,
+ AbstractResource $resource = null,
+ AbstractDb $resourceCollection = null,
+ ManagerInterface $messageManager,
+ ReinitableConfigInterface $appConfig,
+ 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/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/Configuration.php b/Model/Configuration.php
index fad16c2c..b0654441 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,97 @@
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\Module\ModuleListInterface;
+use Magento\Framework\Registry;
+use Magento\Framework\UrlInterface;
+use Magento\Store\Model\StoreManagerInterface;
+use Psr\Log\LoggerInterface;
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';
+
+ //= 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';
+
+ //= 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
@@ -63,23 +134,86 @@ class Configuration implements ConfigurationInterface
private $autoUploadConfiguration;
/**
- * @param ScopeConfigInterface $configReader
- * @param WriterInterface $configWriter
- * @param EncryptorInterface $decryptor
- * @param AutoUploadConfigurationInterface $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;
+
+ /**
+ * @method __construct
+ * @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 CloudinaryLogger $cloudinaryLogger
+ * @param Registry $coreRegistry
*/
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
) {
$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;
+ }
+
+ /**
+ * @return StoreManagerInterface
+ */
+ public function getStoreManager()
+ {
+ return $this->storeManager;
+ }
+
+ /**
+ * @return Registry
+ */
+ public function getCoreRegistry()
+ {
+ return $this->coreRegistry;
}
/**
@@ -99,16 +233,26 @@ public function getCredentials()
}
/**
+ * @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 +260,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 +284,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 +292,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());
}
/**
@@ -146,9 +306,9 @@ public function getUploadConfig()
/**
* @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 +330,12 @@ public function getFormatsToPreserve()
}
/**
- * @param string $file
+ * @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 +343,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 +351,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 +367,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 +375,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,17 +383,18 @@ 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
*/
- private function getEnvironmentVariable()
+ public function getEnvironmentVariable()
{
if (is_null($this->environmentVariable)) {
try {
$this->environmentVariable = CloudinaryEnvironmentVariable::fromString(
+ $this->coreRegistry->registry(self::CONFIG_PATH_ENVIRONMENT_VARIABLE) ?:
$this->decryptor->decrypt(
$this->configReader->getValue(self::CONFIG_PATH_ENVIRONMENT_VARIABLE)
)
@@ -234,6 +403,341 @@ private function getEnvironmentVariable()
$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 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);
+ }
+
+ public function getLazyloadIgnoredCmsBlocksArray()
+ {
+ return (array) explode(',', $this->configReader->getValue(self::XML_PATH_LAZYLOAD_IGNORED_CMS_BLOCKS));
+ }
+
+ 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();
+ }
+
+ /**
+ * 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($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($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";
+
+ $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($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..64e6817c
--- /dev/null
+++ b/Model/Framework/File/Uploader.php
@@ -0,0 +1,45 @@
+ 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/ProductAttributeCldDataResolver.php b/Model/GraphQLResolver/ProductAttributeCldDataResolver.php
new file mode 100644
index 00000000..23eaf471
--- /dev/null
+++ b/Model/GraphQLResolver/ProductAttributeCldDataResolver.php
@@ -0,0 +1,42 @@
+productGalleryManagement = $productGalleryManagement;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
+ {
+ $productId = $value['sku'];
+ $productMediaStr = $this->productGalleryManagement->getProductMediaData($productId);
+ $jsonDecoder = new \Magento\Framework\Serialize\Serializer\Json();
+ $productMedia = $jsonDecoder->unserialize($productMediaStr);
+ return $productMedia['data'];
+ }
+ }
\ No newline at end of file
diff --git a/Model/GraphQLResolver/ProductAttributeCldResolver.php b/Model/GraphQLResolver/ProductAttributeCldResolver.php
new file mode 100644
index 00000000..5520e6f1
--- /dev/null
+++ b/Model/GraphQLResolver/ProductAttributeCldResolver.php
@@ -0,0 +1,42 @@
+productGalleryManagement = $productGalleryManagement;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
+ {
+ $productId = $value['sku'];
+ $productMediaStr = $this->productGalleryManagement->getProductMedia($productId);
+ $jsonDecoder = new \Magento\Framework\Serialize\Serializer\Json();
+ $productMedia = $jsonDecoder->unserialize($productMediaStr);
+ return $productMedia['data'];
+ }
+ }
\ No newline at end of file
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 @@
+_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..d0e9faf8 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,7 +32,7 @@ public function __construct(
}
/**
- * @param Observer $observer
+ * @param Observer $observer
*/
public function execute(Observer $observer)
{
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..116e9692 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,15 +13,20 @@
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,
@@ -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/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..626d4f9a
--- /dev/null
+++ b/Plugin/Catalog/Block/Product/View/Gallery.php
@@ -0,0 +1,192 @@
+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 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') {
+ //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)) {
+ $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..db6a44ad 100644
--- a/Plugin/FileUploader.php
+++ b/Plugin/FileUploader.php
@@ -2,13 +2,16 @@
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 Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\File\Uploader;
class FileUploader
{
+ const ALLOWED_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg'];
+
/**
* @var CloudinaryImageManager
*/
@@ -20,39 +23,59 @@ class FileUploader
private $directoryList;
/**
- * @param CloudinaryImageManager $cloudinaryImageManager
- * @param DirectoryList $directoryList
+ * @var ConfigurationInterface
+ */
+ private $configuration;
+
+ /**
+ * @method __construct
+ * @param CloudinaryImageManager $cloudinaryImageManager
+ * @param DirectoryList $directoryList
+ * @param ConfigurationInterface $configuration
*/
public function __construct(
CloudinaryImageManager $cloudinaryImageManager,
- DirectoryList $directoryList
+ DirectoryList $directoryList,
+ ConfigurationInterface $configuration
) {
$this->cloudinaryImageManager = $cloudinaryImageManager;
$this->directoryList = $directoryList;
+ $this->configuration = $configuration;
}
/**
- * @param Uploader $uploader
- * @param array $result
+ * @param Uploader $uploader
+ * @param array $result
* @return array
*/
public function afterSave(Uploader $uploader, $result)
{
- $filepath = $this->absoluteFilePath($result);
+ if (!$this->configuration->isEnabled() || !$this->configuration->hasEnvironmentVariable()) {
+ return $result;
+ }
- if ($this->isMediaFilePath($filepath) && !$this->isMediaTmpFilePath($filepath)) {
+ $filepath = $this->absoluteFilePath($result);
+ if ($this->isAllowedImageExtension($filepath) && $this->isMediaFilePath($filepath) && !$this->isMediaTmpFilePath($filepath)) {
$this->cloudinaryImageManager->uploadAndSynchronise(
Image::fromPath($filepath, $this->mediaRelativePath($filepath))
);
-
}
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,7 +84,7 @@ protected function isMediaFilePath($filepath)
}
/**
- * @param string $filepath
+ * @param string $filepath
* @return string
*/
protected function isMediaTmpFilePath($filepath)
@@ -70,7 +93,7 @@ protected function isMediaTmpFilePath($filepath)
}
/**
- * @param array $result
+ * @param array $result
* @return string
*/
protected function absoluteFilePath(array $result)
@@ -79,12 +102,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/Ui/Component/Form/Element/DataType/Media.php b/Plugin/Ui/Component/Form/Element/DataType/Media.php
new file mode 100644
index 00000000..d1de81c6
--- /dev/null
+++ b/Plugin/Ui/Component/Form/Element/DataType/Media.php
@@ -0,0 +1,77 @@
+mediaLibraryHelper = $mediaLibraryHelper;
+ $this->appState = $appState;
+ }
+
+ /**
+ * 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) {
+ $component->setData(array_replace_recursive(
+ $component->getData(),
+ [
+ '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"),
+ ]
+ ]
+ ]
+ ));
+ }
+ }
+ 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..78707bbb 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/signup) 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
index 500da405..03482ef7 100644
--- a/Setup/InstallSchema.php
+++ b/Setup/InstallSchema.php
@@ -2,15 +2,16 @@
namespace Cloudinary\Cloudinary\Setup;
+use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
-use Magento\Framework\DB\Ddl\Table;
class InstallSchema implements InstallSchemaInterface
{
/**
* {@inheritdoc}
+ *
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
diff --git a/Setup/UpgradeData.php b/Setup/UpgradeData.php
new file mode 100644
index 00000000..063a5181
--- /dev/null
+++ b/Setup/UpgradeData.php
@@ -0,0 +1,63 @@
+resourceConnection = $resourceConnection;
+ $this->output = $output;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
+ {
+ $setup->startSetup();
+
+ if (version_compare($context->getVersion(), '1.6.3') < 0) {
+ if ($context->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'"
+ );
+ }
+
+ $setup->endSetup();
+ }
+}
diff --git a/Setup/UpgradeSchema.php b/Setup/UpgradeSchema.php
index d3175e9a..e335e041 100644
--- a/Setup/UpgradeSchema.php
+++ b/Setup/UpgradeSchema.php
@@ -2,25 +2,45 @@
namespace Cloudinary\Cloudinary\Setup;
-use Magento\Framework\Setup\UpgradeSchemaInterface;
+use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
-use Magento\Framework\DB\Ddl\Table;
+use Magento\Framework\Setup\UpgradeSchemaInterface;
class UpgradeSchema implements UpgradeSchemaInterface
{
/**
- * @param SchemaSetupInterface $setup
+ * @param SchemaSetupInterface $setup
* @param ModuleContextInterface $context
*/
public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$setup->startSetup();
- if (version_compare($context->getVersion(), '1.5.0', '<=')) {
+ if (version_compare($context->getVersion(), '1.5.0', '<')) {
$this->createTransformationTable($setup);
}
+ if (version_compare($context->getVersion(), '1.10.3', '<')) {
+ $this->createMediaLibraryMapTable($setup);
+ }
+
+ if (version_compare($context->getVersion(), '1.12.0', '<')) {
+ $this->createProductGalleryApiQueueTable($setup);
+ }
+
+ if (version_compare($context->getVersion(), '1.13.0', '<')) {
+ $this->createProductSpinsetMapTable($setup);
+ }
+
+ if (version_compare($context->getVersion(), '1.14.9', '<')) {
+ $setup->getConnection()->addIndex(
+ $setup->getTable('cloudinary_synchronisation'),
+ $setup->getIdxName('cloudinary_synchronisation', ['image_path']),
+ ['image_path']
+ );
+ }
+
$setup->endSetup();
}
@@ -47,4 +67,171 @@ private function createTransformationTable(SchemaSetupInterface $setup)
$setup->getConnection()->createTable($table);
}
+
+ /**
+ * @param SchemaSetupInterface $setup
+ */
+ private function createMediaLibraryMapTable(SchemaSetupInterface $setup)
+ {
+ $table = $setup->getConnection()->newTable(
+ $setup->getTable('cloudinary_media_library_map')
+ )->addColumn(
+ 'id',
+ \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
+ null,
+ ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
+ 'ID'
+ )->addColumn(
+ 'cld_uniqid',
+ Table::TYPE_TEXT,
+ 255,
+ ['nullable' => false],
+ 'Relative image path'
+ )->addColumn(
+ 'cld_public_id',
+ Table::TYPE_TEXT,
+ 255,
+ ['nullable' => false],
+ 'Cloudinary Public ID'
+ )->addColumn(
+ 'free_transformation',
+ Table::TYPE_TEXT,
+ 255,
+ [],
+ 'Free transformation'
+ )->addIndex(
+ $setup->getIdxName(
+ 'cloudinary_media_library_map',
+ ['cld_uniqid'],
+ \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE
+ ),
+ ['cld_uniqid'],
+ ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE]
+ );
+
+ $setup->getConnection()->createTable($table);
+ }
+
+ /**
+ *
+ * @param SchemaSetupInterface $setup
+ */
+ private function createProductGalleryApiQueueTable(SchemaSetupInterface $setup)
+ {
+ $table = $setup->getConnection()->newTable(
+ $setup->getTable('cloudinary_product_gallery_api_queue')
+ )->addColumn(
+ 'id',
+ \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
+ null,
+ ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
+ 'ID'
+ )->addColumn(
+ 'sku',
+ Table::TYPE_TEXT,
+ 255,
+ ['nullable' => false],
+ 'Product SKU'
+ )->addColumn(
+ 'full_item_data',
+ \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
+ 3000,
+ ['nullable' => true],
+ 'Prepared Schema'
+ )
+ ->addColumn(
+ 'created_at',
+ \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
+ null,
+ ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT],
+ 'Created At'
+ )
+ ->addColumn(
+ 'updated_at',
+ \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
+ null,
+ ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE],
+ 'Created At'
+ )
+ ->addColumn(
+ 'success',
+ \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
+ 1,
+ ['unsigned' => true, 'nullable' => false, 'default' => '0'],
+ 'Success'
+ )
+ ->addColumn(
+ 'success_at',
+ \Magento\Framework\DB\Ddl\Table::TYPE_DATETIME,
+ null,
+ ['nullable' => true],
+ 'Success At'
+ )
+ ->addColumn(
+ 'message',
+ \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
+ 3000,
+ ['nullable' => true],
+ 'Message'
+ )
+ ->addColumn(
+ 'has_errors',
+ \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
+ 1,
+ ['unsigned' => true, 'nullable' => true, 'default' => '0'],
+ 'Has Errors'
+ )
+ ->addColumn(
+ 'tryouts',
+ \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
+ 11,
+ ['unsigned' => true, 'nullable' => true, 'default' => '0'],
+ 'Tryouts'
+ );
+ $setup->getConnection()->createTable($table);
+ }
+
+ /**
+ * @param SchemaSetupInterface $setup
+ */
+ private function createProductSpinsetMapTable(SchemaSetupInterface $setup)
+ {
+ $table = $setup->getConnection()->newTable(
+ $setup->getTable('cloudinary_product_spinset_map')
+ )->addColumn(
+ 'id',
+ \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
+ null,
+ ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
+ 'ID'
+ )->addColumn(
+ 'store_id',
+ \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
+ null,
+ ['unsigned' => true, 'nullable' => false, 'default' => '0'],
+ 'Store ID'
+ )->addColumn(
+ 'image_name',
+ Table::TYPE_TEXT,
+ 255,
+ ['nullable' => false],
+ 'Relative image path'
+ )->addColumn(
+ 'cldspinset',
+ Table::TYPE_TEXT,
+ 255,
+ [],
+ 'Cloudinary Spinset Tag'
+ )->addIndex(
+ $setup->getIdxName(
+ 'cloudinary_product_spinset_map',
+ ['store_id', 'image_name'],
+ \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE
+ ),
+ ['store_id', 'image_name'],
+ ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE]
+ );
+
+ $setup->getConnection()->createTable($table);
+ }
}
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..4b1b87b6 100644
--- a/composer.json
+++ b/composer.json
@@ -1,54 +1,11 @@
{
- "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": "1.17.0",
+ "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"
- }
+ "cloudinary/cloudinary_php": "^1.20.0"
},
"autoload": {
"files": [
@@ -57,5 +14,9 @@
"psr-4": {
"Cloudinary\\Cloudinary\\": ""
}
- }
+ },
+ "repositories": [{
+ "type": "git",
+ "url": "https://github.com/cloudinary/cloudinary_magento2"
+ }]
}
\ No newline at end of file
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..7ff03b4d 100644
--- a/etc/adminhtml/system.xml
+++ b/etc/adminhtml/system.xml
@@ -2,64 +2,398 @@
-
+
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.
+
+ Cloudinary's Management Console. Format should be: cloudinary://API_Key:API_Secret@Cloud_Name]]>
+
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
+
+ 1
+
+
+
+ Zoom Trigger
+ cloudinary/product_gallery/zoomProps_viewerPosition
+ Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ZoomViewerPosition
+
+ 1
+ flyout
+
+
+
+ Zoom Viewer Position
+ 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
+
+
+
+
+ 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
+
+
+
+ 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
diff --git a/etc/config.xml b/etc/config.xml
index 7c0f7bdd..14c5437b 100644
--- a/etc/config.xml
+++ b/etc/config.xml
@@ -6,11 +6,65 @@
1
auto
- 1.0
+ 2.0
+ add
1
+
+ 1
+ 0
+ 500
+ fadeIn
+ blur
+
+
+ 0
+ 0
+ 1
+ 10
+ 1
+ 20
+ 5
+ 1
+
+
+ 0
+ #ffffff
+ #000000
+ #0078ff
+ #ffffff
+ slide
+ square
+ mouseover
+ 1
+ inline
+ right
+ click
+ left
+ 5
+ thumbnails
+ 64
+ 64
+ rectangle
+ all
+ left
+ 2
+ round
+ round
+ {}
+
+
+
+
+ 0
+
+
+ 0
+
+
+
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..3eeed4f8
--- /dev/null
+++ b/etc/csp_whitelist.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+ cloudinary.com
+ *.cloudinary.com
+ cdnjs.cloudflare.com
+
+
+
+
+ cloudinary.com
+ *.cloudinary.com
+ cdnjs.cloudflare.com
+
+
+
+
+ cloudinary.com
+ *.cloudinary.com
+
+
+
+
+ cloudinary.com
+ *.cloudinary.com
+
+
+
+
+ cloudinary.com
+ *.cloudinary.com
+ data:
+
+
+
+
+ cloudinary.com
+ *.cloudinary.com
+
+
+
+
diff --git a/etc/di.xml b/etc/di.xml
index de25382b..98fc0e28 100644
--- a/etc/di.xml
+++ b/etc/di.xml
@@ -4,58 +4,108 @@
+ - 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..a4b59f0d 100644
--- a/etc/module.xml
+++ b/etc/module.xml
@@ -1,8 +1,9 @@
-
+
+
diff --git a/etc/schema.graphqls b/etc/schema.graphqls
new file mode 100644
index 00000000..c68abcf3
--- /dev/null
+++ b/etc/schema.graphqls
@@ -0,0 +1,42 @@
+type CloudinaryData {
+ image: String
+ small_image: String
+ thumbnail: String
+ media_gallery: [String]
+}
+
+type CloudinaryImage {
+ role: String
+ disabled: String
+ disabled_default: String
+ entity_id: String
+ file: String
+ id: String
+ label: String
+ label_default: String
+ media_type: String
+ path: String
+ position: String
+ position_default: String
+ url: String
+ value_id: String
+ video_description: String
+ video_description_default: String
+ video_metadata: String
+ video_metadata_default: String
+ video_provider: String
+ video_provider_default: String
+ video_title: String
+ video_title_default: String
+ video_url: String
+ video_url_default: String
+}
+
+interface ProductInterface {
+ cld_data: CloudinaryData
+ @resolver(class: "\\Cloudinary\\Cloudinary\\Model\\GraphQLResolver\\ProductAttributeCldResolver")
+ @doc(description: "Cloudinary urls generated for product images")
+ cloudinary_images: [CloudinaryImage]
+ @resolver(class: "\\Cloudinary\\Cloudinary\\Model\\GraphQLResolver\\ProductAttributeCldDataResolver")
+ @doc(description: "Cloudinary urls generated for product images")
+}
\ No newline at end of file
diff --git a/etc/webapi.xml b/etc/webapi.xml
index 66d94106..afc1e3be 100644
--- a/etc/webapi.xml
+++ b/etc/webapi.xml
@@ -19,4 +19,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/marketplace.composer.json b/marketplace.composer.json
new file mode 100644
index 00000000..d1f80080
--- /dev/null
+++ b/marketplace.composer.json
@@ -0,0 +1,18 @@
+{
+ "name": "cloudinary/cloudinary",
+ "description": "Cloudinary Magento 2 Integration.",
+ "type": "magento2-module",
+ "version": "1.17.0",
+ "license": "MIT",
+ "require": {
+ "cloudinary/cloudinary_php": "^1.20.0"
+ },
+ "autoload": {
+ "files": [
+ "registration.php"
+ ],
+ "psr-4": {
+ "Cloudinary\\Cloudinary\\": ""
+ }
+ }
+}
\ 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_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..92d372c1
--- /dev/null
+++ b/view/adminhtml/layout/default.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/view/adminhtml/requirejs-config.js b/view/adminhtml/requirejs-config.js
index eaca2951..79f46998 100644
--- a/view/adminhtml/requirejs-config.js
+++ b/view/adminhtml/requirejs-config.js
@@ -3,7 +3,29 @@ 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'
}
},
+ 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"
+ },
+ shim: {
+ 'jquery.lazyload': {
+ deps: ['jquery']
+ },
+ },
+ config: {
+ mixins: {
+ 'Magento_Ui/js/lib/validation/validator': {
+ 'Cloudinary_Cloudinary/js/form/element/validator-rules-mixin': true
+ },
+ }
+ }
};
\ No newline at end of file
diff --git a/view/adminhtml/templates/browser/content.phtml b/view/adminhtml/templates/browser/content.phtml
new file mode 100644
index 00000000..033e9c3a
--- /dev/null
+++ b/view/adminhtml/templates/browser/content.phtml
@@ -0,0 +1,41 @@
+
+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..16f13cfb
--- /dev/null
+++ b/view/adminhtml/templates/catalog/product/helper/gallery.phtml
@@ -0,0 +1,389 @@
+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() ?>
+
+
+ = $block->escapeHtml(__('Browse to find or drag image here')) ?>
+
+
+
+ = /* @escapeNotVerified */ $block->getChildHtml('additional_buttons') ?>
+
+ getImageTypes() as $typeData):
+ ?>
+
+
+
+
+
+
+
+
+ = /* @escapeNotVerified */ $block->getFormHtml() ?>
+
+
+
+ = $block->getChildHtml('new-video') ?>
+
+
+
+
+
+
+
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..b7a8551a
--- /dev/null
+++ b/view/adminhtml/templates/config/auto-upload-mapping-btn.phtml
@@ -0,0 +1,43 @@
+
+getButtonHtml() ?>
+
+
+isEnabled()): ?>
+
+
+ = __('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..82b49cc6 100644
--- a/view/adminhtml/templates/config/free.phtml
+++ b/view/adminhtml/templates/config/free.phtml
@@ -18,5 +18,5 @@
", "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": "= $this->getUrl('cloudinary/ajax_free/sample') ?>", "ajaxKey": "= $this->getFormKey() ?>"}}'>
diff --git a/view/adminhtml/templates/lazyload.phtml b/view/adminhtml/templates/lazyload.phtml
new file mode 100644
index 00000000..8ec159ea
--- /dev/null
+++ b/view/adminhtml/templates/lazyload.phtml
@@ -0,0 +1,9 @@
+
+isEnabledLazyload()) {
+ return;
+} ?>
+
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..b3e98a91
--- /dev/null
+++ b/view/adminhtml/web/css/source/_module.less
@@ -0,0 +1,202 @@
+.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;
+ }
+ }
+ }
+}
\ No newline at end of file
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..fde8b08e
--- /dev/null
+++ b/view/adminhtml/web/js/cloudinary-media-library-modal.js
@@ -0,0 +1,215 @@
+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',
+ 'cloudinaryMediaLibraryAll',
+ 'es6Promise'
+], function($, productGallery, uiAlert, notification, $t) {
+ '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;
+ 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');
+ return widget.cloudinaryInsertHandler(data);
+ }
+ }
+ );
+ } else {
+ this.cloudinary_ml = window.cloudinary_ml[this.options.cldMLid];
+ }
+
+ },
+
+ /**
+ * Fired on trigger "openMediaLibrary"
+ */
+ openMediaLibrary: function() {
+ this.cloudinary_ml.show(this.options.cloudinaryMLshowOptions);
+ },
+
+ /**
+ * 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/form/element/validator-rules-mixin.js b/view/adminhtml/web/js/form/element/validator-rules-mixin.js
new file mode 100644
index 00000000..b33b2233
--- /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\.com/) ||
+ 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\.com/) ||
+ 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..db001c90 100644
--- a/view/adminhtml/web/js/get-video-information.js
+++ b/view/adminhtml/web/js/get-video-information.js
@@ -2,807 +2,847 @@
* 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;
+
+ 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\.com/)) {
+ type = 'cloudinary';
+ id = href.href.replace(new RegExp('^.*\/video\/(upload\/)?((.*\/)?v[0-9]{1,10}\/)?'), '').replace(/\.[^.\/]+$/, '');
+ }
+
+ if ((!id || !type) && forceVideo) {
+ 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..10ee595f 100644
--- a/view/adminhtml/web/template/product/free_transform.html
+++ b/view/adminhtml/web/template/product/free_transform.html
@@ -1,23 +1,23 @@
-
-
- Image
-
-
- Label
-
-
- File
-
-
- Free transform
-
-
- Action
-
-
+
+
+ Image
+
+
+ Label
+
+
+ File
+
+
+ Cloudinary Transformation
+
+
+ Action
+
+
@@ -32,13 +32,11 @@
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.
Only images are listed - transforms to videos are not yet possible using the Magento module.
-
+
\ No newline at end of file
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_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..8e11cbbd
--- /dev/null
+++ b/view/base/web/js/form/element/image-uploader.js
@@ -0,0 +1,21 @@
+/* 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';
+ },
+
+ });
+});
\ No newline at end of file
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..7d9be1fc
--- /dev/null
+++ b/view/base/web/template/form/element/uploader/image.html
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ : .
+
+
+ : .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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..d494ae4f
--- /dev/null
+++ b/view/base/web/template/form/element/uploader/uploader.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/view/frontend/layout/catalog_product_view.xml b/view/frontend/layout/catalog_product_view.xml
new file mode 100644
index 00000000..40b62a2a
--- /dev/null
+++ b/view/frontend/layout/catalog_product_view.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/view/frontend/layout/default.xml b/view/frontend/layout/default.xml
new file mode 100644
index 00000000..92d372c1
--- /dev/null
+++ b/view/frontend/layout/default.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/view/frontend/requirejs-config.js b/view/frontend/requirejs-config.js
index a888a5dd..f612cf91 100644
--- a/view/frontend/requirejs-config.js
+++ b/view/frontend/requirejs-config.js
@@ -2,7 +2,18 @@ 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'
}
},
+ paths: {
+ 'jquery.lazyload': "Cloudinary_Cloudinary/js/jquery.lazyload.min",
+ cloudinaryProductGalleryAll: "//product-gallery.cloudinary.com/all"
+ },
+ shim: {
+ 'jquery.lazyload': {
+ deps: ['jquery']
+ },
+ }
};
\ No newline at end of file
diff --git a/view/frontend/templates/lazyload.phtml b/view/frontend/templates/lazyload.phtml
new file mode 100644
index 00000000..8ec159ea
--- /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..9378f87a
--- /dev/null
+++ b/view/frontend/templates/product/gallery.phtml
@@ -0,0 +1,9 @@
+
+
diff --git a/view/frontend/templates/product/image.phtml b/view/frontend/templates/product/image.phtml
new file mode 100644
index 00000000..170909b0
--- /dev/null
+++ b/view/frontend/templates/product/image.phtml
@@ -0,0 +1,20 @@
+
+
+
+ getCustomAttributes() as $name => $value): ?>
+ = $escaper->escapeHtmlAttr($name) ?>="= $escaper->escapeHtmlAttr($value) ?>"
+
+ src="= $block->getLazyloadPlaceholder() ?>"
+ data-original="= $block->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..342956b1
--- /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="= $block->getLazyloadPlaceholder() ?>"
+ data-original="= $block->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 = <<