diff --git a/Block/Adminhtml/Cms/Wysiwyg/Images/Content.php b/Block/Adminhtml/Cms/Wysiwyg/Images/Content.php new file mode 100644 index 0000000..d6b5d7e --- /dev/null +++ b/Block/Adminhtml/Cms/Wysiwyg/Images/Content.php @@ -0,0 +1,76 @@ +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; + } + return $this->_jsonEncoder->encode( + [ + 'cldMLid' => 'wysiwyg_media_gallery', + 'imageUploaderUrl' => $this->_urlBuilder->addSessionParam()->getUrl('cloudinary/cms_wysiwyg_images/upload', ['type' => $this->_getMediaType()]), + '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/Product/Helper/Form/Gallery/Content.php b/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php new file mode 100644 index 0000000..d3c6c06 --- /dev/null +++ b/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php @@ -0,0 +1,81 @@ + + * + * @method \Magento\Framework\Data\Form\Element\AbstractElement getElement() + */ +namespace Cloudinary\Cloudinary\Block\Adminhtml\Product\Helper\Form\Gallery; + +use Cloudinary\Cloudinary\Helper\MediaLibraryHelper; +use Magento\Backend\Block\Template\Context; +use Magento\Catalog\Model\Product\Media\Config; +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'; + + /** + * MediaLibraryHelper + * @var array|null + */ + protected $mediaLibraryHelper; + + /** + * @param Context $context + * @param EncoderInterface $jsonEncoder + * @param Config $mediaConfig + * @param MediaLibraryHelper $mediaLibraryHelper + * @param array $data + */ + public function __construct( + Context $context, + EncoderInterface $jsonEncoder, + Config $mediaConfig, + MediaLibraryHelper $mediaLibraryHelper, + array $data = [] + ) { + parent::__construct($context, $jsonEncoder, $mediaConfig, $data); + $this->mediaLibraryHelper = $mediaLibraryHelper; + } + + /** + * 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; + } + return $this->_jsonEncoder->encode( + [ + 'htmlId' => $this->getHtmlId(), + 'cldMLid' => 'product_gallery_' . $this->getHtmlId(), + 'imageUploaderUrl' => $this->_urlBuilder->addSessionParam()->getUrl('cloudinary/ajax/retrieveImage'), + 'triggerSelector' => '#media_gallery_content', + 'triggerEvent' => 'addItem', + 'useDerived' => false, + 'addTmpExtension' => true, + 'cloudinaryMLoptions' => $cloudinaryMLoptions, + 'cloudinaryMLshowOptions' => $this->mediaLibraryHelper->getCloudinaryMLshowOptions(null), + ] + ); + } +} 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 0000000..0928b2c --- /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/Command/DownloadImages.php b/Command/DownloadImages.php new file mode 100644 index 0000000..e973c16 --- /dev/null +++ b/Command/DownloadImages.php @@ -0,0 +1,96 @@ +Are you sure you want to override local files (y/n)[n]?"; + + private $_override = false; + + /** + * @var BatchDownloader + */ + private $batchDownloader; + + /** + * @var OutputLogger + */ + private $outputLogger; + + /** + * @param BatchDownloader $batchDownloader + */ + public function __construct(BatchDownloader $batchDownloader, OutputLogger $outputLogger) + { + parent::__construct('cloudinary:download:all'); + + $this->batchDownloader = $batchDownloader; + $this->outputLogger = $outputLogger; + } + + /** + * 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' + ), + ]); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * + * @return void + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + try { + if ($input->getOption(self::OVERRIDE) && $this->confirmQuestion(self::OVERRIDE_CONFIRM_MESSAGE, $input, $output)) { + $this->_override = 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/StopMigration.php b/Command/StopMigration.php index 6a09c11..e04164e 100644 --- a/Command/StopMigration.php +++ b/Command/StopMigration.php @@ -9,9 +9,9 @@ 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 MigrationTask */ @@ -22,7 +22,7 @@ class StopMigration extends Command */ public function __construct(MigrationTask $migrationTask) { - parent::__construct('cloudinary:upload:stop'); + parent::__construct('cloudinary:migration:stop'); $this->migrationTask = $migrationTask; } @@ -34,7 +34,7 @@ public function __construct(MigrationTask $migrationTask) */ protected function configure() { - $this->setDescription('Stops any currently running upload.'); + $this->setDescription('Stops any currently running upload/download.'); } /** diff --git a/Controller/Adminhtml/Ajax/Free/Image.php b/Controller/Adminhtml/Ajax/Free/Image.php index fcc7493..e210282 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 { diff --git a/Controller/Adminhtml/Ajax/RetrieveImage.php b/Controller/Adminhtml/Ajax/RetrieveImage.php new file mode 100644 index 0000000..b96f0d1 --- /dev/null +++ b/Controller/Adminhtml/Ajax/RetrieveImage.php @@ -0,0 +1,282 @@ +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; + } + + /** + * @return \Magento\Framework\Controller\Result\Raw + */ + public function execute() + { + try { + $remoteFileUrl = $this->getRequest()->getParam('remote_image'); + $this->validateRemoteFile($remoteFileUrl); + $baseTmpMediaPath = $this->getBaseTmpMediaPath(); + $localUniqFilePath = $this->appendNewFileName($baseTmpMediaPath . $this->getLocalTmpFileName($remoteFileUrl)); + $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); + } + } + + /** @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 '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 'design_config_fileUploader': + case 'category_image': + $localTmpFileName = DIRECTORY_SEPARATOR . $localFileName; + break; + default: + $localTmpFileName = Uploader::getDispersionPath($localFileName) . DIRECTORY_SEPARATOR . $localFileName; + break; + } + return $localTmpFileName; + } + + /** + * 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 + */ + 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; + 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)) { + 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; + } +} diff --git a/Controller/Adminhtml/Cms/Wysiwyg/Images/Upload.php b/Controller/Adminhtml/Cms/Wysiwyg/Images/Upload.php new file mode 100644 index 0000000..b7cffd1 --- /dev/null +++ b/Controller/Adminhtml/Cms/Wysiwyg/Images/Upload.php @@ -0,0 +1,240 @@ +_directoryResolver = $directoryResolver; + $this->mediaConfig = $mediaConfig; + $this->fileSystem = $fileSystem; + $this->imageAdapter = $imageAdapterFactory->create(); + $this->curl = $curl; + $this->fileUtility = $fileUtility; + $this->extensionValidator = $extensionValidator; + $this->protocolValidator = $protocolValidator; + } + + /** + * 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->_directoryResolver->validatePath($path, DirectoryList::MEDIA)) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Directory %1 is not under storage root path.', $path) + ); + } + $remoteFileUrl = $this->getRequest()->getParam('remote_image'); + $this->validateRemoteFile($remoteFileUrl); + $localFileName = Uploader::getCorrectFileName(basename($remoteFileUrl)); + $localFilePath = $path . DIRECTORY_SEPARATOR . $localFileName; + $localFileFullPath = $this->appendNewFileName($localFilePath); + $this->validateRemoteFileExtensions($localFileFullPath); + $this->retrieveRemoteImage($remoteFileUrl, $localFileFullPath); + $this->getStorage()->resizeFile($localFileFullPath, true); + $this->imageAdapter->validateUploadFile($localFileFullPath); + $result = $this->appendResultSaveRemoteImage($localFileFullPath); + } 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 + * + * @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); + $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; + } +} diff --git a/Core/CloudinaryImageManager.php b/Core/CloudinaryImageManager.php index 30b14b3..53ddee0 100644 --- a/Core/CloudinaryImageManager.php +++ b/Core/CloudinaryImageManager.php @@ -54,6 +54,11 @@ public function uploadAndSynchronise(Image $image, OutputInterface $output = nul } catch (FileExists $e) { $this->report($output, sprintf(self::MESSAGE_UPLOADED_EXISTS, $image)); } catch (\Exception $e) { + if ($e->getMessage() === FileExists::DEFAULT_MESSAGE) { + $this->report($output, sprintf(self::MESSAGE_UPLOADED_EXISTS, $image)); + return; + } + if ($retryAttempt < self::MAXIMUM_RETRY_ATTEMPTS) { $retryAttempt++; $this->report($output, sprintf(self::MESSAGE_RETRY, $e->getMessage(), $retryAttempt)); diff --git a/Core/CloudinaryImageProvider.php b/Core/CloudinaryImageProvider.php index dd69802..ffd36a8 100644 --- a/Core/CloudinaryImageProvider.php +++ b/Core/CloudinaryImageProvider.php @@ -101,7 +101,7 @@ public function retrieveTransformed(Image $image, Transformation $transformation } if ($this->configuration->getRemoveVersionNumber()) { - $regex = '/\/v[0-9]+\/' . preg_quote(ltrim($image->getId(), '/'), '/') . '$/'; + $regex = '/\/v[0-9]{1,10}\/' . preg_quote(ltrim($image->getId(), '/'), '/') . '$/'; $imagePath = preg_replace($regex, '/' . ltrim($image->getId(), '/'), $imagePath); } diff --git a/Core/Exception/FileExists.php b/Core/Exception/FileExists.php index 2c51553..6bba778 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/Image/Transformation.php b/Core/Image/Transformation.php index 5a588f5..47819e4 100644 --- a/Core/Image/Transformation.php +++ b/Core/Image/Transformation.php @@ -64,9 +64,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; } diff --git a/Helper/MediaLibraryHelper.php b/Helper/MediaLibraryHelper.php new file mode 100644 index 0000000..ced7914 --- /dev/null +++ b/Helper/MediaLibraryHelper.php @@ -0,0 +1,111 @@ +configuration = $configuration; + } + + /** + * @method getCloudinaryMLOptions + * @param bool $multiple Allow multiple + * @param bool $refresh Refresh options + * @return array + */ + public function getCloudinaryMLOptions($multiple = false, $refresh = true) + { + if ((is_null($this->cloudinaryMLoptions) || $refresh) && $this->configuration->isEnabled()) { + $this->cloudinaryMLoptions = []; + $this->timestamp = time(); + $this->credentials = [ + "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']], + ]; + if (($this->credentials["username"] = $this->configuration->getAutomaticLoginUser())) { + $this->cloudinaryMLoptions["timestamp"] = $this->timestamp; + $this->cloudinaryMLoptions["username"] = $this->credentials["username"]; + $this->cloudinaryMLoptions["signature"] = $this->signature = hash('sha256', urldecode(http_build_query([ + 'cloud_name' => $this->credentials['cloud_name'], + 'timestamp' => $this->timestamp, + 'username' => $this->credentials['username'], + ])) . $this->credentials['api_secret']); + } + } + } + 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 10e8c6e..0febc6c 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 { diff --git a/Helper/ProductGalleryHelper.php b/Helper/ProductGalleryHelper.php new file mode 100644 index 0000000..31196f9 --- /dev/null +++ b/Helper/ProductGalleryHelper.php @@ -0,0 +1,110 @@ + '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(self::CASTING[$key])) { + \settype($value, self::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'] = (string)$this->configuration->getCloud(); + } + + return $this->cloudinaryPGoptions; + } + + /** + * @return bool + */ + public function canDisplayProductGallery() + { + return ($this->configuration->isEnabled() && $this->configuration->isEnabledProductGallery()) ? true : false; + } +} diff --git a/Model/BatchDownloader.php b/Model/BatchDownloader.php new file mode 100644 index 0000000..7d2685f --- /dev/null +++ b/Model/BatchDownloader.php @@ -0,0 +1,360 @@ +_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; + if ($this->_configuration->isEnabled()) { + $this->_authorise(); + } + } + + 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) + { + //= 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 a481048..03e0ea6 100644 --- a/Model/BatchUploader.php +++ b/Model/BatchUploader.php @@ -2,10 +2,10 @@ 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 { diff --git a/Model/Config/Backend/Free.php b/Model/Config/Backend/Free.php index 03e1ebe..4298020 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 diff --git a/Model/Config/Backend/ProductGalleryCustomFreeParams.php b/Model/Config/Backend/ProductGalleryCustomFreeParams.php new file mode 100644 index 0000000..04ea774 --- /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/ProductGallery/AspectRatio.php b/Model/Config/Source/Dropdown/ProductGallery/AspectRatio.php new file mode 100644 index 0000000..3f8dbbd --- /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 0000000..5f8fde6 --- /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 0000000..b5da76c --- /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 0000000..0f5e17e --- /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 0000000..c893ef0 --- /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 0000000..707b88c --- /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 0000000..7195579 --- /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 0000000..cec7374 --- /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 0000000..10f7014 --- /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 0000000..7305764 --- /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 0000000..ae902c6 --- /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 0000000..ff5c441 --- /dev/null +++ b/Model/Config/Source/Dropdown/ProductGallery/ZoomType.php @@ -0,0 +1,26 @@ + 'inline', + 'label' => 'Inline', + ], + [ + 'value' => 'flyout', + 'label' => 'Flyout', + ], + [ + 'value' => 'lightbox', + 'label' => 'Lightbox', + ], + ]; + } +} diff --git a/Model/Config/Source/Dropdown/ProductGallery/ZoomViewerPosition.php b/Model/Config/Source/Dropdown/ProductGallery/ZoomViewerPosition.php new file mode 100644 index 0000000..f9eea46 --- /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/Configuration.php b/Model/Configuration.php index 0198d96..3dd612e 100644 --- a/Model/Configuration.php +++ b/Model/Configuration.php @@ -23,25 +23,57 @@ class Configuration implements ConfigurationInterface { + //= 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_PATH_AUTOMATIC_LOGIN_USER = 'cloudinary/setup/cloudinary_automatic_login_user'; + const CONFIG_PATH_CDN_SUBDOMAIN = 'cloudinary/configuration/cloudinary_cdn_subdomain'; + //= Transformations - 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_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_GLOBAL_FREEFORM = 'cloudinary/transformations/cloudinary_free_transform_global'; + //= Advanced const CONFIG_PATH_REMOVE_VERSION_NUMBER = 'cloudinary/advanced/remove_version_number'; const CONFIG_PATH_USE_ROOT_PATH = 'cloudinary/advanced/use_root_path'; + + //= 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 CONFIG_PATH_USE_SIGNED_URLS = 'cloudinary/advanced/use_signed_urls'; + const USER_PLATFORM_TEMPLATE = 'CloudinaryMagento/%s (Magento %s)'; const USE_FILENAME = true; const UNIQUE_FILENAME = false; const OVERWRITE = false; @@ -125,6 +157,14 @@ public function getCredentials() return $this->getEnvironmentVariable()->getCredentials(); } + /** + * @return string + */ + public function getAutomaticLoginUser() + { + return (string) $this->configReader->getValue(self::CONFIG_PATH_AUTOMATIC_LOGIN_USER); + } + /** * @return Transformation */ @@ -143,7 +183,7 @@ 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); } /** @@ -151,7 +191,7 @@ private function getDefaultGlobalFreeform() */ public function getCdnSubdomainStatus() { - return $this->configReader->isSetFlag(self::CONFIG_CDN_SUBDOMAIN); + return $this->configReader->isSetFlag(self::CONFIG_PATH_CDN_SUBDOMAIN); } /** @@ -159,7 +199,7 @@ public function getCdnSubdomainStatus() */ public function getUserPlatform() { - return sprintf(self::USER_PLATFORM_TEMPLATE, '1.7.6', '2.0.0'); + return sprintf(self::USER_PLATFORM_TEMPLATE, '1.9.0', '2.0.0'); } /** @@ -210,7 +250,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); } /** @@ -218,7 +258,7 @@ 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 : ''; } /** @@ -226,7 +266,7 @@ public function getFetchFormat() */ public function getImageQuality() { - return (string) $this->configReader->getValue(self::CONFIG_DEFAULT_QUALITY); + return (string) $this->configReader->getValue(self::CONFIG_PATH_DEFAULT_QUALITY); } /** @@ -234,7 +274,7 @@ public function getImageQuality() */ public function getImageDpr() { - return $this->configReader->getValue(self::CONFIG_DEFAULT_DPR); + return $this->configReader->getValue(self::CONFIG_PATH_DEFAULT_DPR); } /** @@ -265,6 +305,22 @@ public function getEnvironmentVariable() 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); + } + /** * @return bool */ diff --git a/Model/Observer/Configuration.php b/Model/Observer/Configuration.php index 052502e..39b05ed 100644 --- a/Model/Observer/Configuration.php +++ b/Model/Observer/Configuration.php @@ -6,6 +6,7 @@ use Magento\Framework\App\Cache\TypeListInterface; use Magento\Framework\App\Config\ReinitableConfigInterface; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; use Magento\Framework\Message\ManagerInterface; @@ -90,7 +91,7 @@ public function execute(Observer $observer) return $this; } - if (!$this->requestProcessor->handle('media', $this->configuration->getMediaBaseUrl(), true)) { + if (!$this->requestProcessor->handle(DirectoryList::MEDIA, $this->configuration->getMediaBaseUrl(), true)) { $this->messageManager->addErrorMessage(self::AUTO_UPLOAD_SETUP_FAIL_MESSAGE); } } diff --git a/Model/Observer/ProductGalleryChangeTemplate.php b/Model/Observer/ProductGalleryChangeTemplate.php new file mode 100644 index 0000000..3477195 --- /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 4923696..453b642 100644 --- a/Model/Observer/SaveProductTransform.php +++ b/Model/Observer/SaveProductTransform.php @@ -45,6 +45,10 @@ public function execute(Observer $observer) 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/Plugin/Catalog/Block/Product/View/Gallery.php b/Plugin/Catalog/Block/Product/View/Gallery.php new file mode 100644 index 0000000..c07010b --- /dev/null +++ b/Plugin/Catalog/Block/Product/View/Gallery.php @@ -0,0 +1,141 @@ +productGalleryHelper = $productGalleryHelper; + $this->jsonEncoder = $jsonEncoder; + } + + /** + * 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 = md5(uniqid('', true)); + } + return $this->htmlId; + } + + public function getCldPGid() + { + return 'cldPGid_' . $this->getHtmlId(); + } + + /** + * @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->productGalleryBlock->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 = null; + $transformation = null; + switch ($value['type']) { + case 'image': + $publicId = $value['full'] ?: $value['img']; + if (strpos($publicId, '.cloudinary.com/') !== false && strpos($publicId, '/' . DirectoryList::MEDIA . '/') !== false) { + $publicId = preg_replace('/\/v[0-9]{1,10}\//', '/', $publicId); + $publicId = explode('/' . DirectoryList::MEDIA . '/', $publicId); + $prefix = array_shift($publicId); + $publicId = DirectoryList::MEDIA . '/' . implode('/' . DirectoryList::MEDIA . '/', $publicId); + //$publicId = preg_replace('/\.[^.]+$/', '', $publicId); + $transformation = basename($prefix); + if (($prefix = basename(dirname($prefix))) && preg_match('/[a-zA-Z].*_[\w.-].*/', $prefix)) { + $transformation .= ',' . $prefix; + } + } else { + $publicId = null; + } + break; + case 'video': + if (strpos($value['videoUrl'], '.cloudinary.com/') !== false) { + $publicId = @pathinfo($value['videoUrl'], PATHINFO_FILENAME) ?: null; + } + break; + } + 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/Config/Block/System/Config/Form/Field/Image.php b/Plugin/Config/Block/System/Config/Form/Field/Image.php new file mode 100644 index 0000000..9cc4e88 --- /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 .= ''; + } + return $html; + } +} 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 0000000..e627a05 --- /dev/null +++ b/Plugin/Ui/Component/Form/Element/DataType/Media.php @@ -0,0 +1,75 @@ +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'; + } 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 0000000..7ca7104 --- /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 5350a45..0000000 --- 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/composer.json b/composer.json index 2916189..be32cbe 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "cloudinary/cloudinary-magento2", "description": "Cloudinary Magento 2 Integration.", "type": "magento2-module", - "version": "1.7.6", + "version": "1.9.0", "license": "MIT", "require": { "cloudinary/cloudinary_php": "*" diff --git a/etc/adminhtml/di.xml b/etc/adminhtml/di.xml index 655f619..5c75471 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 e5721cf..a9c2ee9 100644 --- a/etc/adminhtml/events.xml +++ b/etc/adminhtml/events.xml @@ -1,6 +1,10 @@ + + + + diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index dd3091e..8427b4f 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -3,14 +3,17 @@ - - + +
cloudinary Cloudinary_Cloudinary::config_cloudinary + 1 Magento\Config\Model\Config\Source\Yesno @@ -18,14 +21,22 @@ + 1 - Format should be: cloudinary://API_Key:API_Secret@Cloud_Name]]> + + Format should be: cloudinary://API_Key:API_Secret@Cloud_Name]]> + Cloudinary\Cloudinary\Model\Config\Backend\Credentials + + + + + 1 Enable multiple sub-domains of image delivery URLs for faster page load speed. @@ -33,12 +44,13 @@ - When enabled, Cloudinary will fetch images it does not have from your site automatically without requiring the manual migration process. + Automatically upload images from your site to your Cloudinary account (if they don’t already exist). Magento\Config\Model\Config\Source\Yesno + 0 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. @@ -46,27 +58,207 @@ - 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 - Define the part of the image to focus on when cropping images in order to better match your graphic design. + Define the part of the image to focus on when cropping images. Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Gravity - 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 - + + Add global custom transformations in addition to those selected above. See the Cloudinary documentation for the full range of transformations. You may need to clear or rebuild the Magento block and page caches to see the changes in the front end. Cloudinary\Cloudinary\Model\Config\Backend\Free Cloudinary\Cloudinary\Block\Adminhtml\Form\Field\Free - + + + 0 + + + Enable Cloudinary's product gallery (override Magento's default) + Magento\Config\Model\Config\Source\Yesno + cloudinary/product_gallery/enabled + + + + 1 + + + cloudinary/product_gallery/themeProps_primary + Cloudinary\Cloudinary\Block\Adminhtml\System\Config\Form\Field\ColorPicker + validate-length minimum-length-4 maximum-length-7 + + + + cloudinary/product_gallery/themeProps_onPrimary + Cloudinary\Cloudinary\Block\Adminhtml\System\Config\Form\Field\ColorPicker + validate-length minimum-length-4 maximum-length-7 + + + + cloudinary/product_gallery/themeProps_active + Cloudinary\Cloudinary\Block\Adminhtml\System\Config\Form\Field\ColorPicker + validate-length minimum-length-4 maximum-length-7 + + + + cloudinary/product_gallery/themeProps_onActive + Cloudinary\Cloudinary\Block\Adminhtml\System\Config\Form\Field\ColorPicker + validate-length minimum-length-4 maximum-length-7 + + + + + 1 + + + cloudinary/product_gallery/transition + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\Transition + + + + cloudinary/product_gallery/aspectRatio + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\AspectRatio + + + + cloudinary/product_gallery/navigation + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\Navigation + + + + cloudinary/product_gallery/zoom + Magento\Config\Model\Config\Source\Yesno + + + + cloudinary/product_gallery/zoomProps_type + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ZoomType + + 1 + + + + + cloudinary/product_gallery/zoomProps_viewerPosition + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ZoomViewerPosition + + 1 + flyout + + + + + cloudinary/product_gallery/zoomProps_trigger + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ZoomTrigger + + 1 + + + + + + 1 + + + cloudinary/product_gallery/carouselLocation + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\CarouselLocation + + + + cloudinary/product_gallery/carouselOffset + validate-number + + + + cloudinary/product_gallery/carouselStyle + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\CarouselStyle + + + + cloudinary/product_gallery/thumbnailProps_width + validate-number + + thumbnails + + + + + cloudinary/product_gallery/thumbnailProps_height + validate-number + + thumbnails + + + + + cloudinary/product_gallery/thumbnailProps_navigationShape + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ThumbnailsNavigationShape + + thumbnails + + + + + cloudinary/product_gallery/thumbnailProps_selectedStyle + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ThumbnailsSelectedStyle + + thumbnails + + + + + cloudinary/product_gallery/thumbnailProps_selectedBorderPosition + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ThumbnailsSelectedBorderPosition + + thumbnails + + + + + cloudinary/product_gallery/thumbnailProps_selectedBorderWidth + validate-number + + thumbnails + + + + + cloudinary/product_gallery/thumbnailProps_mediaSymbolShape + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\ThumbnailsMediaSymbolShape + + thumbnails + + + + + cloudinary/product_gallery/indicatorProps_shape + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\ProductGallery\IndicatorsShape + + indicators + + + + + + + e.g., {"zoom": true, "thumbnailProps": {"borderColor": "#EBF0F4"}}]]> + + cloudinary/product_gallery/custom_free_params + Cloudinary\Cloudinary\Model\Config\Backend\ProductGalleryCustomFreeParams + + + + 0 Remove version number (e.g., ".../v1/...") from URLs @@ -79,7 +271,7 @@ - A signed Cloudinary image delivery URL is a dynamic URL that has its signature validated before making it available for view + 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 diff --git a/etc/config.xml b/etc/config.xml index 251673a..2d4ac67 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -15,6 +15,32 @@ 0 0 + + 0 + #ffffff + #000000 + #0078ff + #ffffff + slide + square + mouseover + 1 + inline + right + click + left + 5 + thumbnails + 64 + 64 + rectangle + all + left + 2 + round + round + {} + diff --git a/etc/di.xml b/etc/di.xml index f5a2aa3..a21fbc0 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -4,6 +4,7 @@ + Cloudinary\Cloudinary\Command\DownloadImages Cloudinary\Cloudinary\Command\UploadImages Cloudinary\Cloudinary\Command\StopMigration Cloudinary\Cloudinary\Command\ResetAll @@ -11,60 +12,60 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + + + + + + + + + \ No newline at end of file diff --git a/etc/module.xml b/etc/module.xml index 134411b..1389dd1 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -1,6 +1,6 @@ - + diff --git a/marketplace.composer.json b/marketplace.composer.json index 7483bb9..8d180a6 100644 --- a/marketplace.composer.json +++ b/marketplace.composer.json @@ -1,7 +1,7 @@ { "name": "cloudinary/cloudinary", "type": "magento2-module", - "version": "1.7.6", + "version": "1.9.0", "description": "Cloudinary Magento 2 Integration.", "license": "MIT", "require": { diff --git a/view/adminhtml/js/product_free_transform.js b/view/adminhtml/js/product_free_transform.js deleted file mode 120000 index 1e82e1a..0000000 --- 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 0000000..685e5f6 --- /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 0000000..53a5bbc --- /dev/null +++ b/view/adminhtml/layout/cms_wysiwyg_images_index.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/view/adminhtml/requirejs-config.js b/view/adminhtml/requirejs-config.js index eaca295..930fbd6 100644 --- a/view/adminhtml/requirejs-config.js +++ b/view/adminhtml/requirejs-config.js @@ -3,7 +3,12 @@ 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' } }, + paths: { + cloudinaryMediaLibraryAll: "//media-library.cloudinary.com/global/all", + es6Promise: "//cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min" + } }; \ 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 0000000..033e9c3 --- /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 0000000..d1dde5d --- /dev/null +++ b/view/adminhtml/templates/catalog/product/helper/gallery.phtml @@ -0,0 +1,355 @@ +getElement()->getName() . '[images]'; +$formName = $block->getFormName(); +$cloudinaryMLwidgetOprions = $block->getCloudinaryMediaLibraryWidgetOptions(); +?> + +
+
+ + + + +
+
+ +getElement(); +$elementToggleCode = $element->getToggleCode() ? $element->getToggleCode() : 'toggleValueElements(this, this.parentNode.parentNode.parentNode)'; +?> + + + diff --git a/view/adminhtml/web/css/source/_module.less b/view/adminhtml/web/css/source/_module.less index 5983d59..f761a36 100644 --- a/view/adminhtml/web/css/source/_module.less +++ b/view/adminhtml/web/css/source/_module.less @@ -9,3 +9,62 @@ 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_icon_for_blue_bg.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: 0px 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 0; + } +} + +.adminhtml-system_config-edit .colorpicker_hue div { + background-image: url('Cloudinary_Cloudinary::images/colorpicker_indic.gif'); +} diff --git a/view/adminhtml/web/js/cloudinary-free.js b/view/adminhtml/web/js/cloudinary-free.js index 96d257e..374c9b8 100644 --- a/view/adminhtml/web/js/cloudinary-free.js +++ b/view/adminhtml/web/js/cloudinary-free.js @@ -1,7 +1,8 @@ define( [ - 'jquery' - ], function ($) { + 'jquery' + ], + function($) { 'use strict'; $.widget( @@ -9,22 +10,22 @@ define( currentTransform: '', - getTransformText: function () { + getTransformText: function() { return $(this.options.transformInputFieldId).val(); }, - getImageHtml: function (src) { + 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

'; + 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) { + getErrorHtml: function(message) { return '
  • ' + message + '
'; }, - updatePreviewImage: function (url) { + updatePreviewImage: function(url) { var $image = $('#cloudinary_custom_transform_preview_image'); if (!$image.length) { @@ -34,7 +35,7 @@ define( } }, - updatePreview: function () { + updatePreview: function() { var self = this; if (!self.isPreviewActive()) { @@ -44,29 +45,27 @@ define( 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) { + $.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) { + function(result) { $('#cloudinary_custom_transform_preview').html(self.getErrorHtml(result.responseJSON.error)); } ); }, - setPreviewActiveState: function (state) { + setPreviewActiveState: function(state) { if (state && (this.currentTransform !== this.getTransformText())) { $(this.options.previewButtonId).removeClass('disabled'); } else { @@ -74,22 +73,22 @@ define( } }, - isPreviewActive: function () { + isPreviewActive: function() { return !$(this.options.previewButtonId).hasClass('disabled'); }, - _create: function () { + _create: function() { var self = this; $(this.options.previewButtonId).on( 'click', - function () { + function() { self.updatePreview(); } ); $(this.options.transformInputFieldId).on( 'change keydown paste input', - function () { + function() { self.setPreviewActiveState(true); } ); 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 0000000..e67e91d --- /dev/null +++ b/view/adminhtml/web/js/cloudinary-media-library-modal.js @@ -0,0 +1,159 @@ +define([ + 'jquery', + 'productGallery', + 'jquery/ui', + 'Magento_Ui/js/modal/modal', + 'mage/translate', + 'mage/backend/tree-suggest', + 'mage/backend/validation', + 'cloudinaryMediaLibraryAll', + 'es6Promise' +], function($, productGallery) { + '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)); + } + }, + + /** + * @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) { + 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); + }, + + /** + * Fired on trigger "cloudinaryInsertHandler" + */ + cloudinaryInsertHandler: function(data) { + var widget = this; + + data.assets.forEach(asset => { + //console.log(asset); + if (widget.options.imageUploaderUrl) { + asset.asset_url = asset.asset_image_url = asset.secure_url; + 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.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; + } + } + if (asset.resource_type === "video") { + asset.asset_image_url = asset.asset_url.replace(/\.[^/.]+$/, "").replace(new RegExp('\/v[0-9]{1,10}\/'), '/').replace(new RegExp('\/(' + 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, ''); + } 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 { + alert($.mage.__('An error occured during ' + asset.resource_type + ' insert!')); + console.error(file); + } + + } + ).fail( + function(response) { + alert($.mage.__('An error occured during ' + asset.resource_type + ' insert!')); + console.error(response); + } + ); + } + }); + } + }); + + return $.mage.cloudinaryMediaLibraryModal; +}); \ No newline at end of file diff --git a/view/adminhtml/web/js/get-video-information.js b/view/adminhtml/web/js/get-video-information.js index 0c17bc2..d02a2c7 100644 --- a/view/adminhtml/web/js/get-video-information.js +++ b/view/adminhtml/web/js/get-video-information.js @@ -637,12 +637,12 @@ define( context = (tmp.context && tmp.context.custom) ? tmp.context.custom : {}; tmp.derived = tmp.derived || []; - thumbnail = videoInfo.videoSrc.replace(/\.[^/.]+$/, "").replace(/\/([^\/]+)$/, '/so_auto/$1.jpg') || this.options.cloudinaryPlaceholder; + thumbnail = videoInfo.videoSrc.replace(/\.[^/.]+$/, "").replace(new RegExp('\/v[0-9]{1,10}\/'), '/').replace(new RegExp('\/(' + tmp.public_id + ')$'), '/so_auto/$1.jpg') || this.options.cloudinaryPlaceholder; respData = { duration: 'unknown', uploaded: tmp.created_at, - title: context.caption || context.alt || "", + title: context.caption || context.alt || tmp.public_id || "", description: (context.description || context.alt || context.caption || "").replace(/( |<([^>]+)>)/ig, ''), thumbnail: thumbnail, videoId: tmp.public_id, diff --git a/view/adminhtml/web/js/new-video-dialog.js b/view/adminhtml/web/js/new-video-dialog.js index c3086df..b23eb6e 100644 --- a/view/adminhtml/web/js/new-video-dialog.js +++ b/view/adminhtml/web/js/new-video-dialog.js @@ -13,7 +13,7 @@ define( 'mage/backend/validation', 'Cloudinary_Cloudinary/js/get-video-information' ], - function ($, _) { + function($, _) { 'use strict'; $.widget( @@ -52,7 +52,7 @@ define( /** * @private */ - _init: function () { + _init: function() { if (this.options.reset) { this.reset(); } else { @@ -66,14 +66,14 @@ define( /** * @returns {Boolean} */ - update: function () { + update: function() { var checkVideoID = this.element.find(this.options.container).find( - '.' + this.options.videoClass - ).data('code'), + '.' + 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(); @@ -90,7 +90,7 @@ define( /** * @private */ - _doUpdate: function () { + _doUpdate: function() { var uploaderLinkUrl, uploaderLink; @@ -132,7 +132,7 @@ define( /** * Reset */ - reset: function () { + 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(''); @@ -163,7 +163,7 @@ define( /** * @private */ - _init: function () { + _init: function() { if (this.options.reset) { this.reset(); } else { @@ -174,7 +174,7 @@ define( /** * Update */ - update: function () { + update: function() { $(this.options.DOM.titleField).val(this.options.data.title); $(this.options.DOM.descriptionField).val(this.options.data.description); }, @@ -182,7 +182,7 @@ define( /** * Reset */ - reset: function () { + reset: function() { $(this.options.DOM.fileField).val(''); $(this.options.DOM.urlField).val(''); $(this.options.DOM.titleField).val(''); @@ -263,20 +263,18 @@ define( * * @private */ - _bind: function () { + _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._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); @@ -298,7 +296,7 @@ define( * * @private */ - _onGetVideoInformationClick: function () { + _onGetVideoInformationClick: function() { this._onlyVideoPlayer = false; this._isEditPage = false; this._videoUrlWidget.trigger('update_video_information'); @@ -309,14 +307,14 @@ define( * * @private */ - _onGetVideoInformationFocusOut: function () { + _onGetVideoInformationFocusOut: function() { this._videoUrlWidget.trigger('update_video_information'); }, /** * @private */ - _onGetVideoInformationStartRequest: function () { + _onGetVideoInformationStartRequest: function() { this._videoRequestComplete = false; }, @@ -325,7 +323,7 @@ define( * * @private */ - _onGetVideoInformationEditClick: function () { + _onGetVideoInformationEditClick: function() { this._onlyVideoPlayer = true; this._isEditPage = true; this._videoUrlWidget.trigger('update_video_information'); @@ -338,57 +336,53 @@ define( * @param {Object} data * @private */ - _onGetVideoInformationSuccess: function (e, data) { + _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( - { + '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); - } + }); + this._loadRemotePreview(data.thumbnail); + } self._onlyVideoPlayer = true; - }, this + }, 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 - } + .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; @@ -400,25 +394,23 @@ define( * @param {String} sourceUrl * @private */ - _loadRemotePreview: function (sourceUrl) { + _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 - ) - } - ); + $.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 + ) + }); }, /** @@ -426,7 +418,7 @@ define( * * @private */ - _onGetVideoInformationError: function () {}, + _onGetVideoInformationError: function() {}, /** * Remove ".tmp" @@ -435,7 +427,7 @@ define( * @returns {*} * @private */ - __prepareFilename: function (name) { + __prepareFilename: function(name) { var tmppost = '.tmp'; if (!name) { @@ -456,7 +448,7 @@ define( * @param {Object} imageData * @private */ - _setImage: function (file, imageData) { + _setImage: function(file, imageData) { file = this.__prepareFilename(file); this._images[file] = imageData; this._gallery.trigger('addItem', imageData); @@ -471,7 +463,7 @@ define( * @returns {*} * @private */ - _getImage: function (file) { + _getImage: function(file) { file = this.__prepareFilename(file); return this._images[file]; @@ -485,7 +477,7 @@ define( * @param {Object} imageData * @private */ - _replaceImage: function (oldFile, newFile, imageData) { + _replaceImage: function(oldFile, newFile, imageData) { var tmpNewFile = newFile, tmpOldImage, newImageId, @@ -530,12 +522,10 @@ define( 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); + $(oldValIdElem).attr({ + type: 'hidden', + name: 'product[media_gallery][images][' + newImageId + '][save_data_from]' + }).val(key); oldNewFilePosition = parseInt(tmpOldImage.position, 10); imageData.position = oldNewFilePosition; @@ -554,7 +544,7 @@ define( * @param {String} file * @private */ - _removeImage: function (file) { + _removeImage: function(file) { var imageData = this._getImage(file); if (!imageData) { @@ -573,7 +563,7 @@ define( * @param {Object} imageData * @private */ - _onSetImage: function (event, imageData) { + _onSetImage: function(event, imageData) { this.saveImageRoles(imageData); }, @@ -585,17 +575,17 @@ define( * @param {Function} callback * @private */ - _uploadImage: function (file, oldFile, callback) { + _uploadImage: function(file, oldFile, callback) { var url = this.options.saveVideoUrl, data = { files: file, url: url - }; + }; this._blockActionButtons(true, true); this._uploadFile( data, $.proxy( - function (result) { + function(result) { this._onImageLoaded(result, file, oldFile, callback); this._blockActionButtons(false); }, this @@ -611,7 +601,7 @@ define( * @param {Function} callback * @private */ - _onImageLoaded: function (result, file, oldFile, callback) { + _onImageLoaded: function(result, file, oldFile, callback) { var data = JSON.parse(result); if (this.element.find('#video_url').parent().find('.image-upload-error').length > 0) { @@ -628,7 +618,7 @@ define( } $.each( this.element.find(this._videoFormSelector).serializeArray(), - function (i, field) { + function(i, field) { data[field.name] = field.value; } ); @@ -647,23 +637,21 @@ define( * * @private */ - _uploadFile: function (data, callback) { + _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'); + $(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) { + function(result, textStatus, jqXHR) { tmpInput.remove(); callback.call(null, result, textStatus, jqXHR); } @@ -676,7 +664,7 @@ define( * @param {String} url * @private */ - _addVideoClass: function (url) { + _addVideoClass: function(url) { var classVideo = 'video-item'; this._gallery.find('img[src="' + url + '"]').addClass(classVideo); @@ -687,7 +675,7 @@ define( * * @private */ - _create: function () { + _create: function() { var imgs = _.values(this.element.closest(this.options.videoSelector).data('images')) || [], widget, uploader, @@ -714,13 +702,12 @@ define( 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: [{ + 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) @@ -740,65 +727,64 @@ define( class: 'action-primary video-edit', click: $.proxy(widget._onUpdate, widget) } - ], + ], - /** - * @returns {null} - */ - opened: function () { - var roles, + /** + * @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'); + 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); + 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; + 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 - }; - } + 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(); - } + /** + * Closed + */ + closed: function() { + widget._onClose(); + widget.createVideoItemIcons(); } - ); + }); this.toggleButtons(); }, @@ -806,7 +792,7 @@ define( * @param {String} status * @private */ - _blockActionButtons: function (status) { + _blockActionButtons: function(status) { this.element .closest('.mage-new-video-dialog') .find('.page-actions-buttons button.video-create-button, .page-actions-buttons button.video-edit') @@ -818,7 +804,7 @@ define( * * @param {Function} callback */ - isValid: function (callback) { + isValid: function(callback) { var videoForm = this.element.find(this._videoFormSelector), videoLoaded = true; @@ -826,7 +812,7 @@ define( this._videoUrlWidget.trigger( 'validate_video_url', $.proxy( - function () { + function() { videoForm.mage( 'validation', { @@ -835,13 +821,13 @@ define( * @param {jQuery} error * @param {jQuery} element */ - errorPlacement: function (error, element) { + errorPlacement: function(error, element) { error.insertAfter(element); } } ).on( 'highlight.validate', - function () { + function() { $(this).validation('option'); } ); @@ -863,7 +849,7 @@ define( /** * Create video item icons */ - createVideoItemIcons: function () { + createVideoItemIcons: function() { var $imageWidget = this._gallery.find('.product-image.video-item'), $productGalleryWrapper = $(this._imageProductGalleryWrapperSelector).find('.product-image.video-item'); @@ -880,7 +866,7 @@ define( * * @private */ - _onCreate: function () { + _onCreate: function() { var nvs = this.element.find(this._videoPreviewInputSelector), file = nvs.get(0), reqClass = 'required-entry _required'; @@ -897,7 +883,7 @@ define( this.isValid( $.proxy( - function (videoValidStatus) { + function(videoValidStatus) { if (!videoValidStatus) { return; @@ -908,7 +894,7 @@ define( } else { this._uploadImage( file, null, $.proxy( - function () { + function() { this.close(); }, this ) @@ -926,12 +912,12 @@ define( * * @private */ - _onUpdate: function () { + _onUpdate: function() { var inputFile, itemId, _inputSelector, mediaFields, imageData, flagChecked, fileName, callback; this.isValid( $.proxy( - function (videoValidStatus) { + function(videoValidStatus) { if (!videoValidStatus) { return; @@ -945,7 +931,7 @@ define( mediaFields = this._gallery.find('input' + _inputSelector); $.each( mediaFields, - function (i, el) { + function(i, el) { var elName = el.name, start = elName.indexOf(itemId) + itemId.length + 2, fieldName = elName.substring(start, el.name.length - 1), @@ -984,7 +970,7 @@ define( inputFile.replaceWith(inputFile); callback = $.proxy( - function () { + function() { this.close(); }, this ); @@ -1005,7 +991,7 @@ define( * * @param {Object} imageData */ - _updateVisibility: function (imageData) { + _updateVisibility: function(imageData) { this._gallery.trigger( 'updateVisibility', { disabled: imageData.disabled, @@ -1019,7 +1005,7 @@ define( * * @param {Object} imageData */ - _updateImageTitle: function (imageData) { + _updateImageTitle: function(imageData) { this._gallery.trigger( 'updateImageTitle', { imageData: imageData @@ -1032,7 +1018,7 @@ define( * * @private */ - _onCancel: function () { + _onCancel: function() { this.close(); }, @@ -1041,7 +1027,7 @@ define( * * @private */ - _onDelete: function () { + _onDelete: function() { var filename = this.element.find(this._videoImageFilenameselector).val(); this._removeImage(filename); @@ -1053,7 +1039,7 @@ define( * @param {Function} callback * @private */ - _readPreviewLocal: function (file, callback) { + _readPreviewLocal: function(file, callback) { var fr = new FileReader; if (!window.FileReader) { @@ -1063,7 +1049,7 @@ define( /** * On load end */ - fr.onloadend = function () { + fr.onloadend = function() { callback(fr.result); }; fr.readAsDataURL(file); @@ -1074,7 +1060,7 @@ define( * * @private */ - _onImageInputChange: function () { + _onImageInputChange: function() { var jFile = this.element.find(this._videoPreviewInputSelector), file = jFile[0], val = jFile.val(), @@ -1086,8 +1072,8 @@ define( } ext = ext ? ext.toLowerCase() : ''; - if (ext.length < 2 - || this._imageTypes.indexOf(ext.toLowerCase()) === -1 || !file.files || !file.files.length + if (ext.length < 2 || + this._imageTypes.indexOf(ext.toLowerCase()) === -1 || !file.files || !file.files.length ) { prev.remove(); this._previewImage = null; @@ -1108,7 +1094,7 @@ define( * @param {Boolean} local * @private */ - _onPreview: function (error, src, local) { + _onPreview: function(error, src, local) { var img, renderImage; img = this._getPreviewImage(); @@ -1118,12 +1104,10 @@ define( * * @param {String} source */ - renderImage = function (source) { - img.attr( - { - 'src': source - } - ).show(); + renderImage = function(source) { + img.attr({ + 'src': source + }).show(); }; if (error) { @@ -1143,16 +1127,14 @@ define( * @returns {null} * @private */ - _getPreviewImage: function () { + _getPreviewImage: function() { if (!this._previewImage) { - this._previewImage = $(document.createElement('img')).css( - { - 'width': '100%', - 'display': 'none', - 'src': '' - } - ); + 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'); } @@ -1163,7 +1145,7 @@ define( /** * Close slideout dialog */ - close: function () { + close: function() { this.element.modal('closeModal'); }, @@ -1172,7 +1154,7 @@ define( * * @private */ - _onClose: function () { + _onClose: function() { var newVideoForm; this._isEditPage = true; @@ -1206,7 +1188,7 @@ define( * * @param {String} file */ - findElementId: function (file) { + findElementId: function(file) { var elem = this._gallery.find('.image.item').find('input[value="' + file + '"]'); if (!elem.length) { @@ -1221,7 +1203,7 @@ define( * * @param {Object} imageData */ - saveImageRoles: function (imageData) { + saveImageRoles: function(imageData) { var data = imageData.file, self = this, containers; @@ -1230,7 +1212,7 @@ define( containers = this._gallery.find('.image-placeholder').siblings('input'); $.each( containers, - function (i, el) { + function(i, el) { var start = el.name.indexOf('[') + 1, end = el.name.indexOf(']'), imageType = el.name.substring(start, end), @@ -1252,7 +1234,7 @@ define( * @param {Object} imageData - image data object * @private */ - _changeRole: function (imageType, isEnabled, imageData) { + _changeRole: function(imageType, isEnabled, imageData) { var needCheked = true; if (!isEnabled) { @@ -1278,7 +1260,7 @@ define( * @param {Object} imageData * @private */ - _onOpenDialog: function (e, imageData) { + _onOpenDialog: function(e, imageData) { var formFields, flagChecked, file, modal = this.element.closest('.mage-new-video-dialog'); @@ -1287,17 +1269,15 @@ define( modal.find('.video-create-button').hide(); modal.find('.video-delete-button').show(); modal.find('.video-edit').show(); - modal.createVideoPlayer( - { - reset: true - } - ).createVideoPlayer('reset'); + modal.createVideoPlayer({ + reset: true + }).createVideoPlayer('reset'); formFields = modal.find(this._videoFormSelector).find('.edited-data'); $.each( formFields, - function (i, field) { + function(i, field) { $(field).val(imageData[field.name]); } ); @@ -1309,14 +1289,14 @@ define( $.each( modal.find('.video_image_role'), - function () { + function() { $(this).prop('checked', false).prop('disabled', false); } ); $.each( this._gallery.find('.image-placeholder').siblings('input:hidden'), - function () { + function() { var start, end, imageRole; if ($(this).val() === file.val()) { @@ -1334,42 +1314,36 @@ define( /** * Toggle buttons */ - toggleButtons: function () { + toggleButtons: function() { var self = this, modal = this.element.closest('.mage-new-video-dialog'); modal.find('.video-placeholder, .add-video-button-container > button').click( - function () { + 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'); + modal.createVideoPlayer({ + reset: true + }).createVideoPlayer('reset').updateInputFields({ + reset: true + }).updateInputFields('reset'); } ); this._gallery.on( 'click', '.item.video-item', - function () { + 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'); + modal.find('.mage-new-video-dialog').createVideoPlayer({ + reset: true + }).createVideoPlayer('reset'); } ); this._gallery.on( 'click', '.item.video-item:not(.removed)', - function () { + function() { var flagChecked, file, formFields = modal.find('.edited-data'), @@ -1377,7 +1351,7 @@ define( $.each( formFields, - function (i, field) { + function(i, field) { $(field).val(container.find('input[name*="' + field.name + '"]').val()); } ); @@ -1389,14 +1363,14 @@ define( $.each( self._gallery.find('.video_image_role'), - function () { + function() { $(this).prop('checked', false).prop('disabled', false); } ); $.each( self._gallery.find('.image-placeholder').siblings('input:hidden'), - function () { + function() { var start, end, imageRole; if ($(this).val() !== file.val()) { diff --git a/view/adminhtml/web/js/product_free_transform.js b/view/adminhtml/web/js/product_free_transform.js index 20b86ee..007047f 100644 --- a/view/adminhtml/web/js/product_free_transform.js +++ b/view/adminhtml/web/js/product_free_transform.js @@ -15,6 +15,7 @@ define( src: "", label: "", file: "", + origFreeTransformation: "", freeTransformation: "", hasChanges: false, hasChangesToSave: false, @@ -48,6 +49,7 @@ define( 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); @@ -71,6 +73,13 @@ define( 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: { @@ -98,7 +107,8 @@ define( return Collection.extend({ defaults: { ajaxUrl: "", - template: 'Cloudinary_Cloudinary/product/free_transform' + template: 'Cloudinary_Cloudinary/product/free_transform', + tableRows: {} }, getTransforms: function() { @@ -109,28 +119,71 @@ define( 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; - this._super(); + self._super() + .observe([ + 'tableRows' + ]); if (this.getTransforms()) { $.each(this.getTransforms(), function(i, transform) { - self.insertChild(self.createRow(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; - } - ); + this.elems.each(function(elem) { + elem.ajaxUrl = self.ajaxUrl; + }); } }); } diff --git a/view/adminhtml/web/template/product/free_transform.html b/view/adminhtml/web/template/product/free_transform.html index b100ed1..10ee595 100644 --- a/view/adminhtml/web/template/product/free_transform.html +++ b/view/adminhtml/web/template/product/free_transform.html @@ -1,23 +1,23 @@
- - - - - - - + + + + + + + @@ -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 8c9a1dd..a22cc24 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 @@ - + \ 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 0000000..389b079 --- /dev/null +++ b/view/base/web/images/cloudinary_dropzone.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/view/base/web/images/cloudinary_icon_for_blue_bg.svg b/view/base/web/images/cloudinary_icon_for_blue_bg.svg new file mode 100644 index 0000000..8a5c6c4 --- /dev/null +++ b/view/base/web/images/cloudinary_icon_for_blue_bg.svg @@ -0,0 +1,69 @@ + + + + + + + + + + diff --git a/view/base/web/images/cloudinary_icon_for_dark_bg.svg b/view/base/web/images/cloudinary_icon_for_dark_bg.svg new file mode 100644 index 0000000..0dae7c9 --- /dev/null +++ b/view/base/web/images/cloudinary_icon_for_dark_bg.svg @@ -0,0 +1,69 @@ + + + + + + + + + + diff --git a/view/base/web/images/cloudinary_logo_for_blue_bg.svg b/view/base/web/images/cloudinary_logo_for_blue_bg.svg new file mode 100644 index 0000000..72b86b0 --- /dev/null +++ b/view/base/web/images/cloudinary_logo_for_blue_bg.svg @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/view/base/web/images/cloudinary_logo_for_dark_bg.svg b/view/base/web/images/cloudinary_logo_for_dark_bg.svg new file mode 100644 index 0000000..58bc88b --- /dev/null +++ b/view/base/web/images/cloudinary_logo_for_dark_bg.svg @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/view/base/web/images/cloudinary_vertical_logo_for_blue_bg.svg b/view/base/web/images/cloudinary_vertical_logo_for_blue_bg.svg new file mode 100644 index 0000000..c29c5e7 --- /dev/null +++ b/view/base/web/images/cloudinary_vertical_logo_for_blue_bg.svg @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/view/base/web/images/cloudinary_vertical_logo_for_dark_bg.svg b/view/base/web/images/cloudinary_vertical_logo_for_dark_bg.svg new file mode 100644 index 0000000..73618ee --- /dev/null +++ b/view/base/web/images/cloudinary_vertical_logo_for_dark_bg.svg @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/view/base/web/images/colorpicker_indic.gif b/view/base/web/images/colorpicker_indic.gif new file mode 100644 index 0000000..7e86962 Binary files /dev/null and b/view/base/web/images/colorpicker_indic.gif differ 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 0000000..77e3fa9 --- /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 0000000..8e11cbb --- /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/template/form/element/uploader/image.html b/view/base/web/template/form/element/uploader/image.html new file mode 100644 index 0000000..7d9be1f --- /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 0000000..d494ae4 --- /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 0000000..40b62a2 --- /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/requirejs-config.js b/view/frontend/requirejs-config.js index a888a5d..8e3fd10 100644 --- a/view/frontend/requirejs-config.js +++ b/view/frontend/requirejs-config.js @@ -2,7 +2,11 @@ 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' } }, + paths: { + cloudinaryProductGalleryAll: "//product-gallery.cloudinary.com/all" + } }; \ No newline at end of file diff --git a/view/frontend/templates/product/gallery.phtml b/view/frontend/templates/product/gallery.phtml new file mode 100644 index 0000000..9378f87 --- /dev/null +++ b/view/frontend/templates/product/gallery.phtml @@ -0,0 +1,9 @@ + + diff --git a/view/frontend/web/js/cloudinary-product-gallery.js b/view/frontend/web/js/cloudinary-product-gallery.js new file mode 100644 index 0000000..8521e0a --- /dev/null +++ b/view/frontend/web/js/cloudinary-product-gallery.js @@ -0,0 +1,34 @@ +define([ + 'jquery', + 'cloudinaryProductGalleryAll' +], function($) { + 'use strict'; + + $.widget('mage.cloudinaryProductGallery', { + + options: { + cloudinaryPGoptions: {}, // Options for Cloudinary-PG galleryWidget() + cldPGid: 0, + }, + + /** + * @private + */ + _create: function() { + this._super(); + + var widget = this; + window.cloudinary_pg = window.cloudinary_pg || []; + this.options.cldPGid = this.options.cldPGid || 0; + if (typeof window.cloudinary_pg[this.options.cldPGid] === "undefined") { + this.cloudinary_pg = window.cloudinary_pg[this.options.cldPGid] = cloudinary.galleryWidget(this.options.cloudinaryPGoptions); + this.cloudinary_pg.render(); + } else { + this.cloudinary_pg = window.cloudinary_pg[this.options.cldPGid]; + } + }, + + }); + + return $.mage.cloudinaryProductGallery; +}); \ No newline at end of file
- Image - - Label - - File - - Free transform - - Action -
+ Image + + Label + + File + + Cloudinary Transformation + + Action +
- - + +
@@ -34,4 +29,4 @@ }">Preview