├── Model ├── Logger.php ├── ProductVideo.php ├── MediaLibraryMap.php ├── ProductSpinsetMap.php ├── ProductGalleryApiQueue.php ├── ResourceModel │ ├── ProductVideo.php │ ├── Synchronisation.php │ ├── Transformation.php │ ├── MediaLibraryMap.php │ ├── ProductSpinsetMap.php │ ├── ProductGalleryApiQueue.php │ ├── ProductVideo │ │ └── Collection.php │ ├── MediaLibraryMap │ │ └── Collection.php │ ├── ProductSpinsetMap │ │ └── Collection.php │ ├── ProductGalleryApiQueue │ │ └── Collection.php │ ├── Transformation │ │ └── Collection.php │ └── Synchronisation │ │ └── Collection.php ├── ProductImageFinder │ ├── ImageFilter.php │ ├── DeletedImageFilter.php │ ├── NewImageFilter.php │ └── ImageCreator.php ├── Logger │ └── CloudinaryHandler.php ├── Template │ └── Filter.php ├── Config │ ├── Source │ │ └── Dropdown │ │ │ ├── Dpr.php │ │ │ ├── Video │ │ │ ├── VideoFormat.php │ │ │ ├── SourceTypes.php │ │ │ ├── Type.php │ │ │ ├── StreamMode.php │ │ │ ├── ABR │ │ │ │ └── Comment.php │ │ │ ├── Controls.php │ │ │ ├── Autoplay.php │ │ │ ├── StreamProtocol.php │ │ │ ├── Autoplay │ │ │ │ └── Comment.php │ │ │ └── VideoQuality.php │ │ │ ├── Lazyload │ │ │ ├── Effect.php │ │ │ └── Placeholder.php │ │ │ ├── FreeTransformBehavior.php │ │ │ ├── ProductGallery │ │ │ ├── ZoomTrigger.php │ │ │ ├── Transition.php │ │ │ ├── ZoomType.php │ │ │ ├── Navigation.php │ │ │ ├── IndicatorsShape.php │ │ │ ├── CarouselStyle.php │ │ │ ├── ThumbnailsSelectedStyle.php │ │ │ ├── CarouselLocation.php │ │ │ ├── ZoomViewerPosition.php │ │ │ ├── ThumbnailsMediaSymbolShape.php │ │ │ ├── ThumbnailsNavigationShape.php │ │ │ ├── ThumbnailsSelectedBorderPosition.php │ │ │ └── AspectRatio.php │ │ │ ├── CmsBlocks.php │ │ │ ├── Gravity.php │ │ │ └── Quality.php │ └── Backend │ │ └── ProductGalleryCustomFreeParams.php ├── Synchronisation.php ├── MigrationTask.php ├── Observer │ ├── ProductGalleryChangeTemplate.php │ ├── DeleteProductImage.php │ ├── UploadProductImage.php │ └── SaveProductTransform.php ├── Framework │ └── File │ │ └── Uploader.php ├── ProductImageFinder.php └── AutoUploadMapping │ └── AutoUploadConfiguration.php ├── view ├── base │ └── web │ │ ├── images │ │ ├── colorpicker_indic.gif │ │ ├── cloudinary_placeholder.jpg │ │ ├── cloudinary_cloud_glyph_blue.png │ │ ├── spinset_indic.svg │ │ ├── cloudinary_cloud_glyph_white.svg │ │ └── cloudinary_cloud_glyph_regular.svg │ │ ├── js │ │ ├── form │ │ │ └── element │ │ │ │ ├── file-uploader.js │ │ │ │ └── image-uploader.js │ │ ├── cloudinary-lazyload.js │ │ └── jquery.lazyload.min.js │ │ └── template │ │ └── form │ │ └── element │ │ └── uploader │ │ └── uploader.html ├── frontend │ ├── templates │ │ ├── product │ │ │ ├── video │ │ │ │ ├── script.phtml │ │ │ │ └── settings.phtml │ │ │ ├── old_image.phtml │ │ │ ├── image.phtml │ │ │ ├── old_image_with_borders.phtml │ │ │ └── image_with_borders.phtml │ │ ├── javascript.phtml │ │ └── lazyload.phtml │ ├── layout │ │ ├── catalog_product_view.xml │ │ └── default.xml │ ├── requirejs-config.js │ └── web │ │ └── js │ │ ├── cloudinary-product-gallery.js │ │ └── swatch-renderer-mixin.js └── adminhtml │ ├── templates │ ├── javascript.phtml │ ├── cms │ │ └── images.phtml │ ├── lazyload.phtml │ ├── config │ │ ├── free.phtml │ │ └── auto-upload-mapping-btn.phtml │ └── browser │ │ └── content.phtml │ ├── layout │ ├── adminhtml_system_config_edit.xml │ ├── cms_block_edit.xml │ ├── cms_page_edit.xml │ ├── default.xml │ └── cms_wysiwyg_images_index.xml │ ├── ui_component │ ├── pagebuilder_base_form_with_background_video.xml │ ├── pagebuilder_video_form.xml │ └── media_gallery_listing.xml │ ├── web │ ├── template │ │ └── product │ │ │ ├── free_transform_row.html │ │ │ └── free_transform.html │ └── js │ │ ├── form │ │ └── element │ │ │ └── validator-rules-mixin.js │ │ └── cloudinary-spinset-dialog.js │ └── requirejs-config.js ├── Core ├── Exception │ ├── InvalidCredentials.php │ ├── ApiError.php │ ├── FileExists.php │ └── MigrationError.php ├── ImageInterface.php ├── Security │ ├── EnvironmentVariable.php │ ├── Key.php │ ├── Secret.php │ ├── ConsoleUrl.php │ ├── ApiSignature.php │ ├── SignedConsoleUrl.php │ └── CloudinaryEnvironmentVariable.php ├── Image │ ├── SynchronizationCheck.php │ ├── Synchronizable.php │ ├── Transformation │ │ ├── Dpr.php │ │ ├── Quality.php │ │ ├── Gravity.php │ │ ├── DefaultImage.php │ │ ├── FetchFormat.php │ │ ├── Freeform.php │ │ ├── Crop.php │ │ └── Dimensions.php │ ├── LocalImage.php │ └── ImageFactory.php ├── Migration │ ├── SynchronizedMediaRepository.php │ ├── Task.php │ ├── Logger.php │ └── Queue.php ├── FolderTranslator.php ├── UploadResponseValidator.php ├── ImageProvider.php ├── SynchroniseAssetsRepositoryInterface.php ├── Cloud.php ├── CredentialValidator.php ├── AutoUploadMapping │ ├── AutoUploadConfigurationInterface.php │ └── RequestProcessor.php ├── Credentials.php ├── ConfigurationInterface.php ├── ConfigurationBuilder.php ├── ValidateRemoteUrlRequest.php ├── UploadConfig.php ├── Image.php └── UrlGenerator.php ├── registration.php ├── .gitignore ├── etc ├── adminhtml │ ├── routes.xml │ ├── events.xml │ └── di.xml ├── module.xml ├── crontab.xml ├── cron_groups.xml ├── acl.xml ├── schema.graphqls └── csp_whitelist.xml ├── marketplace.composer.json ├── Api ├── ResourcesManagementInterface.php ├── SynchronisationRepositoryInterface.php └── ProductGalleryManagementInterface.php ├── Plugin ├── ExcludeFilesFromMinification.php ├── Cms │ └── Block │ │ ├── Block.php │ │ └── Widget │ │ └── Block.php ├── Catalog │ └── Block │ │ └── Category │ │ └── View.php ├── Ui │ └── Component │ │ └── Form │ │ └── Element │ │ └── DataType │ │ └── Media │ │ └── Image.php ├── MediaConfig.php ├── FileRemover.php └── Config │ └── Block │ └── System │ └── Config │ └── Form │ └── Field │ └── Image.php ├── composer.json ├── LICENSE.txt ├── Block ├── Scripts.php ├── Adminhtml │ ├── System │ │ └── Config │ │ │ ├── ModuleVersion.php │ │ │ ├── Form │ │ │ └── Field │ │ │ │ └── ColorPicker.php │ │ │ └── AutoUploadMapping.php │ ├── Form │ │ └── Field │ │ │ └── Free.php │ └── Cms │ │ └── Wysiwyg │ │ └── Images │ │ └── Content.php └── Lazyload.php ├── README.md ├── Controller └── Adminhtml │ ├── PageBuilder │ └── MediaGallery │ │ └── Upload.php │ └── Ajax │ └── Free │ └── Sample.php ├── Command ├── StopMigration.php └── ProductGalleryApiQueueProcess.php ├── Ui └── Component │ └── Control │ └── AddFromCloudinary.php ├── Helper ├── Reset.php └── Product │ └── Free.php └── Cron └── VideoDataImport.php /Model/Logger.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /view/base/web/images/cloudinary_placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudinary/cloudinary_magento2/HEAD/view/base/web/images/cloudinary_placeholder.jpg -------------------------------------------------------------------------------- /view/base/web/images/cloudinary_cloud_glyph_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudinary/cloudinary_magento2/HEAD/view/base/web/images/cloudinary_cloud_glyph_blue.png -------------------------------------------------------------------------------- /Core/Exception/InvalidCredentials.php: -------------------------------------------------------------------------------- 1 | 2 | isEnabled() && $cname = $block->getCname()): ?> 3 | 4 | 5 | -------------------------------------------------------------------------------- /view/frontend/templates/javascript.phtml: -------------------------------------------------------------------------------- 1 | 2 | isEnabled() && $cname = $block->getCname()): ?> 3 | 4 | 5 | -------------------------------------------------------------------------------- /Core/Migration/SynchronizedMediaRepository.php: -------------------------------------------------------------------------------- 1 | _init(\Cloudinary\Cloudinary\Model\ResourceModel\ProductVideo::class); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Model/MediaLibraryMap.php: -------------------------------------------------------------------------------- 1 | _init(\Cloudinary\Cloudinary\Model\ResourceModel\MediaLibraryMap::class); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /view/adminhtml/templates/cms/images.phtml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /view/adminhtml/templates/lazyload.phtml: -------------------------------------------------------------------------------- 1 | 6 | isEnabledLazyload()) { 7 | return; 8 | } ?> 9 | 10 | -------------------------------------------------------------------------------- /view/frontend/templates/lazyload.phtml: -------------------------------------------------------------------------------- 1 | 6 | isEnabledLazyload()) { 7 | return; 8 | } ?> 9 | 10 | -------------------------------------------------------------------------------- /Model/ProductSpinsetMap.php: -------------------------------------------------------------------------------- 1 | _init(\Cloudinary\Cloudinary\Model\ResourceModel\ProductSpinsetMap::class); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Model/ProductGalleryApiQueue.php: -------------------------------------------------------------------------------- 1 | _init(\Cloudinary\Cloudinary\Model\ResourceModel\ProductGalleryApiQueue::class); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Core/Migration/Logger.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Model/ResourceModel/ProductVideo.php: -------------------------------------------------------------------------------- 1 | _init('cloudinary_synchronisation', 'cloudinary_synchronisation_id'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /etc/adminhtml/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Model/ProductImageFinder/ImageFilter.php: -------------------------------------------------------------------------------- 1 | _init('cloudinary_transformation', 'image_name'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Core/FolderTranslator.php: -------------------------------------------------------------------------------- 1 | _init('cloudinary_media_library_map', 'id'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Model/ResourceModel/ProductSpinsetMap.php: -------------------------------------------------------------------------------- 1 | _init('cloudinary_product_spinset_map', 'id'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Core/UploadResponseValidator.php: -------------------------------------------------------------------------------- 1 | key = (string)$key; 12 | } 13 | 14 | public static function fromString($aKey) 15 | { 16 | return new Key($aKey); 17 | } 18 | 19 | public function __toString() 20 | { 21 | return $this->key; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /marketplace.composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudinary/cloudinary", 3 | "description": "Cloudinary Magento 2 Integration.", 4 | "type": "magento2-module", 5 | "version": "1.18.0", 6 | "license": "MIT", 7 | "require": { 8 | "cloudinary/cloudinary_php": "^1.20.52" 9 | }, 10 | "autoload": { 11 | "files": [ 12 | "registration.php" 13 | ], 14 | "psr-4": { 15 | "Cloudinary\\Cloudinary\\": "" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Model/ResourceModel/ProductGalleryApiQueue.php: -------------------------------------------------------------------------------- 1 | _init('cloudinary_product_gallery_api_queue', 'id'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Core/ImageProvider.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Core/Cloud.php: -------------------------------------------------------------------------------- 1 | cloudName = (string)$cloudName; 12 | } 13 | 14 | public static function fromName($aCloudName) 15 | { 16 | return new Cloud($aCloudName); 17 | } 18 | 19 | public function __toString() 20 | { 21 | return $this->cloudName; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Core/Image/Transformation/Dpr.php: -------------------------------------------------------------------------------- 1 | value = $value; 12 | } 13 | 14 | public static function fromString($value) 15 | { 16 | return new Dpr($value); 17 | } 18 | 19 | public function __toString() 20 | { 21 | return $this->value; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Core/Security/Secret.php: -------------------------------------------------------------------------------- 1 | secret = (string)$secret; 12 | } 13 | 14 | public static function fromString($aSecret) 15 | { 16 | return new Secret($aSecret); 17 | } 18 | 19 | public function __toString() 20 | { 21 | return $this->secret; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Model/ProductImageFinder/NewImageFilter.php: -------------------------------------------------------------------------------- 1 | value = $value; 12 | } 13 | 14 | public static function fromString($value) 15 | { 16 | return new Quality($value); 17 | } 18 | 19 | public function __toString() 20 | { 21 | return $this->value; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Model/Template/Filter.php: -------------------------------------------------------------------------------- 1 | getParameters(). 11 | * 12 | * @param string $value raw parameters 13 | * @return array 14 | */ 15 | public function getParams($value) 16 | { 17 | return $this->getParameters($value); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Core/CredentialValidator.php: -------------------------------------------------------------------------------- 1 | validate(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Model/ResourceModel/ProductVideo/Collection.php: -------------------------------------------------------------------------------- 1 | _init( 13 | \Cloudinary\Cloudinary\Model\ProductVideo::class, 14 | \Cloudinary\Cloudinary\Model\ResourceModel\ProductVideo::class 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/Dpr.php: -------------------------------------------------------------------------------- 1 | '1.0', 14 | 'label' => '1.0', 15 | ], 16 | [ 17 | 'value' => '2.0', 18 | 'label' => '2.0', 19 | ], 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Model/ResourceModel/MediaLibraryMap/Collection.php: -------------------------------------------------------------------------------- 1 | _init( 13 | \Cloudinary\Cloudinary\Model\MediaLibraryMap::class, 14 | \Cloudinary\Cloudinary\Model\ResourceModel\MediaLibraryMap::class 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Model/ResourceModel/ProductSpinsetMap/Collection.php: -------------------------------------------------------------------------------- 1 | _init( 13 | \Cloudinary\Cloudinary\Model\ProductSpinsetMap::class, 14 | \Cloudinary\Cloudinary\Model\ResourceModel\ProductSpinsetMap::class 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Api/ResourcesManagementInterface.php: -------------------------------------------------------------------------------- 1 | 'none', 13 | 'label' => 'None', 14 | ], 15 | [ 16 | 'value' => 'auto', 17 | 'label' => 'Auto', 18 | ], 19 | ]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/Lazyload/Effect.php: -------------------------------------------------------------------------------- 1 | 'show', 14 | 'label' => 'Show', 15 | ], 16 | [ 17 | 'value' => 'fadeIn', 18 | 'label' => 'Fade In', 19 | ], 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Core/Image/Transformation/Gravity.php: -------------------------------------------------------------------------------- 1 | value = $value; 12 | } 13 | 14 | public function __toString() 15 | { 16 | return $this->value; 17 | } 18 | 19 | public static function fromString($value) 20 | { 21 | return new Gravity($value); 22 | } 23 | 24 | public static function null() 25 | { 26 | return new Gravity(null); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Model/ResourceModel/ProductGalleryApiQueue/Collection.php: -------------------------------------------------------------------------------- 1 | _init( 13 | \Cloudinary\Cloudinary\Model\ProductGalleryApiQueue::class, 14 | \Cloudinary\Cloudinary\Model\ResourceModel\ProductGalleryApiQueue::class 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Model/ResourceModel/Transformation/Collection.php: -------------------------------------------------------------------------------- 1 | _init(TransformationModel::class, TransformationResourceModel::class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Core/Security/ConsoleUrl.php: -------------------------------------------------------------------------------- 1 | consoleUrl = self::CLOUDINARY_CONSOLE_BASE_URL . $path; 14 | } 15 | 16 | public static function fromPath($path) 17 | { 18 | return new ConsoleUrl($path); 19 | } 20 | 21 | public function __toString() 22 | { 23 | return $this->consoleUrl; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/FreeTransformBehavior.php: -------------------------------------------------------------------------------- 1 | 'add', 14 | 'label' => 'Add', 15 | ], 16 | [ 17 | 'value' => 'override', 18 | 'label' => 'Override', 19 | ], 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Model/ResourceModel/Synchronisation/Collection.php: -------------------------------------------------------------------------------- 1 | _init(SynchronisationModel::class, SynchronisationResourceModel::class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Core/Image/LocalImage.php: -------------------------------------------------------------------------------- 1 | localPathGenerator = $localPathGenerator; 19 | } 20 | 21 | public function __toString() 22 | { 23 | return call_user_func($this->localPathGenerator); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Core/Image/Transformation/DefaultImage.php: -------------------------------------------------------------------------------- 1 | value = $value; 12 | } 13 | 14 | public function __toString() 15 | { 16 | return $this->value; 17 | } 18 | 19 | public static function fromString($value) 20 | { 21 | return new DefaultImage($value); 22 | } 23 | 24 | public static function null() 25 | { 26 | return new DefaultImage(null); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/ProductGallery/ZoomTrigger.php: -------------------------------------------------------------------------------- 1 | 'click', 14 | 'label' => 'Click', 15 | ], 16 | [ 17 | 'value' => 'hover', 18 | 'label' => 'Hover', 19 | ], 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Plugin/ExcludeFilesFromMinification.php: -------------------------------------------------------------------------------- 1 | process($subject, $html); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /view/adminhtml/layout/cms_block_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Plugin/Cms/Block/Widget/Block.php: -------------------------------------------------------------------------------- 1 | process($subject, $html); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Api/SynchronisationRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | apiSignature = Cloudinary::api_sign_request($params, (string) $secret); 14 | } 15 | 16 | public static function fromSecretAndParams(Secret $secret, array $params = []) 17 | { 18 | return new ApiSignature($secret, $params); 19 | } 20 | 21 | public function __toString() 22 | { 23 | return $this->apiSignature; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /etc/crontab.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | * * * * * 6 | 7 | 8 | * * * * * 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudinary/cloudinary-magento2", 3 | "description": "Cloudinary Magento 2 Integration.", 4 | "type": "magento2-module", 5 | "version": "2.1.3", 6 | "license": "MIT", 7 | "require": { 8 | "php": ">=7.3", 9 | "cloudinary/cloudinary_php": "^3.0 || >=2.7 <=2.11.0" 10 | }, 11 | "autoload": { 12 | "files": [ 13 | "registration.php" 14 | ], 15 | "psr-4": { 16 | "Cloudinary\\Cloudinary\\": "" 17 | } 18 | }, 19 | "repositories": [{ 20 | "type": "git", 21 | "url": "https://github.com/cloudinary/cloudinary_magento2" 22 | }] 23 | } 24 | -------------------------------------------------------------------------------- /view/adminhtml/layout/cms_page_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Core/Image/Transformation/FetchFormat.php: -------------------------------------------------------------------------------- 1 | value = $value; 14 | } 15 | 16 | public static function auto() 17 | { 18 | return self::fromString(self::FETCH_FORMAT_AUTO); 19 | } 20 | 21 | public static function fromString($value) 22 | { 23 | return new FetchFormat($value); 24 | } 25 | 26 | public function __toString() 27 | { 28 | return $this->value; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /etc/cron_groups.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 4 6 | 2 7 | 10 8 | 60 9 | 600 10 | 1 11 | 12 | 13 | -------------------------------------------------------------------------------- /Plugin/Catalog/Block/Category/View.php: -------------------------------------------------------------------------------- 1 | process($subject, $html); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /view/frontend/layout/catalog_product_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/ProductGallery/Transition.php: -------------------------------------------------------------------------------- 1 | 'none', 14 | 'label' => 'None', 15 | ], 16 | [ 17 | 'value' => 'fade', 18 | 'label' => 'Fade', 19 | ], 20 | [ 21 | 'value' => 'slide', 22 | 'label' => 'Slide', 23 | ], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/ProductGallery/ZoomType.php: -------------------------------------------------------------------------------- 1 | 'inline', 14 | 'label' => 'Inline', 15 | ], 16 | [ 17 | 'value' => 'flyout', 18 | 'label' => 'Flyout', 19 | ], 20 | [ 21 | 'value' => 'popup', 22 | 'label' => 'Popup', 23 | ], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/Video/SourceTypes.php: -------------------------------------------------------------------------------- 1 | 'webm/vp9', 13 | 'label' => 'WebM/VP9', 14 | ], 15 | [ 16 | 'value' => 'mp4/h265', 17 | 'label' => 'MP4/H.265', 18 | ], 19 | [ 20 | 'value' => 'mp4/h264', 21 | 'label' => 'MP4/H.264', 22 | ] 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/Video/Type.php: -------------------------------------------------------------------------------- 1 | 8 | * Date: 14/01/2024 9 | * Time: 14:22 10 | */ 11 | class Type implements OptionSourceInterface 12 | { 13 | public function toOptionArray() 14 | { 15 | return [ 16 | [ 17 | 'value' => 'native', 18 | 'label' => 'Default player', 19 | ], 20 | [ 21 | 'value' => 'cloudinary', 22 | 'label' => 'Cloudinary player', 23 | ], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/ProductGallery/Navigation.php: -------------------------------------------------------------------------------- 1 | 'none', 14 | 'label' => 'None', 15 | ], 16 | [ 17 | 'value' => 'always', 18 | 'label' => 'Always', 19 | ], 20 | [ 21 | 'value' => 'mouseover', 22 | 'label' => 'Mouseover', 23 | ], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/ProductGallery/IndicatorsShape.php: -------------------------------------------------------------------------------- 1 | 'round', 14 | 'label' => 'Round', 15 | ], 16 | [ 17 | 'value' => 'square', 18 | 'label' => 'Square', 19 | ], 20 | [ 21 | 'value' => 'radius', 22 | 'label' => 'Radius', 23 | ], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/Video/StreamMode.php: -------------------------------------------------------------------------------- 1 | 8 | * Date: 14/01/2024 9 | * Time: 14:22 10 | */ 11 | class StreamMode implements OptionSourceInterface 12 | { 13 | public function toOptionArray() 14 | { 15 | return [ 16 | [ 17 | 'value' => 'optimization', 18 | 'label' => 'Progressive mode (i.e MP4/Webm)', 19 | ], 20 | [ 21 | 'value' => 'abr', 22 | 'label' => 'ABR', 23 | ] 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /view/frontend/layout/default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/ProductGallery/CarouselStyle.php: -------------------------------------------------------------------------------- 1 | 'none', 14 | 'label' => 'None', 15 | ], 16 | [ 17 | 'value' => 'thumbnails', 18 | 'label' => 'Thumbnails', 19 | ], 20 | [ 21 | 'value' => 'indicators', 22 | 'label' => 'Indicators', 23 | ], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /view/adminhtml/layout/default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/ProductGallery/ThumbnailsSelectedStyle.php: -------------------------------------------------------------------------------- 1 | 'border', 14 | 'label' => 'Border', 15 | ], 16 | [ 17 | 'value' => 'gradient', 18 | 'label' => 'Gradient', 19 | ], 20 | [ 21 | 'value' => 'all', 22 | 'label' => 'All', 23 | ], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Core/Credentials.php: -------------------------------------------------------------------------------- 1 | key = $key; 16 | $this->secret = $secret; 17 | } 18 | 19 | public static function fromKeyAndSecret(Key $key, Secret $secret) 20 | { 21 | return new Credentials($key, $secret); 22 | } 23 | 24 | public function getKey() 25 | { 26 | return $this->key; 27 | } 28 | 29 | public function getSecret() 30 | { 31 | return $this->secret; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /view/base/web/js/form/element/file-uploader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @api 3 | */ 4 | /* global Base64 */ 5 | define([ 6 | 'jquery', 7 | 'Magento_Ui/js/form/element/file-uploader' 8 | ], function($, Element) { 9 | 'use strict'; 10 | 11 | return Element.extend({ 12 | 13 | /** 14 | * {@inheritDoc} 15 | */ 16 | initialize: function() { 17 | this._super(); 18 | this.cloudinaryMLoptions.imageParamName = this.paramName || this.inputName; 19 | this.cloudinaryMLoptions.cldMLid = this.cloudinaryMLoptions.imageParamName + '_' + this.uid; 20 | this.cloudinaryMLoptions.callbackHandler = this; 21 | this.cloudinaryMLoptions.callbackHandlerMethod = 'addFile'; 22 | }, 23 | 24 | }); 25 | }); -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/Video/ABR/Comment.php: -------------------------------------------------------------------------------- 1 | urlInterface = $urlInterface; 23 | } 24 | 25 | public function getCommentText($elementValue) 26 | { 27 | 28 | if ($elementValue == 'optimization') { 29 | $comment = ''; 30 | } 31 | return $comment; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Core/Image/Transformation/Freeform.php: -------------------------------------------------------------------------------- 1 | urlParameters = $urlParameters; 20 | } 21 | 22 | /** 23 | * @param string $value 24 | * @return Freeform 25 | */ 26 | public static function fromString($value) 27 | { 28 | return new Freeform($value); 29 | } 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function __toString() 35 | { 36 | return $this->urlParameters; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/Video/Controls.php: -------------------------------------------------------------------------------- 1 | 8 | * Date: 14/01/2024 9 | * Time: 14:22 10 | */ 11 | class Controls implements OptionSourceInterface 12 | { 13 | public function toOptionArray() 14 | { 15 | return [ 16 | [ 17 | 'value' => 'all', 18 | 'label' => 'All', 19 | ], 20 | [ 21 | 'value' => 'play', 22 | 'label' => 'Play buttons', 23 | ], 24 | [ 25 | 'value' => 'none', 26 | 'label' => 'None', 27 | ], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/Video/Autoplay.php: -------------------------------------------------------------------------------- 1 | 8 | * Date: 14/01/2024 9 | * Time: 14:22 10 | */ 11 | class Autoplay implements OptionSourceInterface 12 | { 13 | public function toOptionArray() 14 | { 15 | return [ 16 | [ 17 | 'value' => 'never', 18 | 'label' => 'Off', 19 | ], 20 | [ 21 | 'value' => 'always', 22 | 'label' => 'Always', 23 | ], 24 | [ 25 | 'value' => 'on-scroll', 26 | 'label' => 'On-scroll', 27 | ] 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /view/frontend/templates/product/old_image.phtml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | escapeHtml($block->getCustomAttributes()) ?> 14 | src="getLazyloadPlaceholder() ?>" 15 | data-original="escapeUrl($block->getImageUrl()) ?>" 16 | width="escapeHtmlAttr($block->getWidth()) ?>" 17 | height="escapeHtmlAttr($block->getHeight()) ?>" 18 | alt="stripTags($block->getLabel(), null, true) ?>" /> 19 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/ProductGallery/CarouselLocation.php: -------------------------------------------------------------------------------- 1 | 'top', 14 | 'label' => 'Top', 15 | ], 16 | [ 17 | 'value' => 'right', 18 | 'label' => 'Right', 19 | ], 20 | [ 21 | 'value' => 'left', 22 | 'label' => 'Left', 23 | ], 24 | [ 25 | 'value' => 'bottom', 26 | 'label' => 'Bottom', 27 | ], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/ProductGallery/ZoomViewerPosition.php: -------------------------------------------------------------------------------- 1 | 'top', 14 | 'label' => 'Top', 15 | ], 16 | [ 17 | 'value' => 'right', 18 | 'label' => 'Right', 19 | ], 20 | [ 21 | 'value' => 'left', 22 | 'label' => 'Left', 23 | ], 24 | [ 25 | 'value' => 'bottom', 26 | 'label' => 'Bottom', 27 | ], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/Video/StreamProtocol.php: -------------------------------------------------------------------------------- 1 | 9 | * Date: 10/03/2024 10 | * Time: 17:54 11 | */ 12 | class StreamProtocol implements OptionSourceInterface 13 | { 14 | public function toOptionArray() 15 | { 16 | return [ 17 | [ 18 | 'value' => 'dash', 19 | 'label' => 'Dynamic adaptive streaming over HTTP (MPEG-DASH)', 20 | ], 21 | [ 22 | 'value' => 'hls', 23 | 'label' => 'HTTP live streaming (HLS)', 24 | ], 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/ProductGallery/ThumbnailsMediaSymbolShape.php: -------------------------------------------------------------------------------- 1 | 'none', 14 | 'label' => 'None', 15 | ], 16 | [ 17 | 'value' => 'round', 18 | 'label' => 'Round', 19 | ], 20 | [ 21 | 'value' => 'square', 22 | 'label' => 'Square', 23 | ], 24 | [ 25 | 'value' => 'radius', 26 | 'label' => 'Radius', 27 | ], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /view/adminhtml/ui_component/pagebuilder_base_form_with_background_video.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 |
9 |
10 | 11 | 12 | 13 | Video URLs can be links to videos on YouTube, Vimeo or Cloudinary, or HTTP(S) links to files with valid video extensions (we recommend .mp4) 14 | 15 | 16 | 17 |
18 |
19 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/CmsBlocks.php: -------------------------------------------------------------------------------- 1 | blockFactory = $blockFactory; 19 | } 20 | 21 | public function toOptionArray() 22 | { 23 | $options = []; 24 | foreach ($this->blockFactory->create()->getCollection()->setOrder('title', 'asc') as $block) { 25 | $options[] = [ 26 | 'value' => $block->getIdentifier(), 27 | 'label' => $block->getTitle(), 28 | ]; 29 | } 30 | return $options; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /etc/schema.graphqls: -------------------------------------------------------------------------------- 1 | type CustomMediaAttribute { 2 | attribute_code: String 3 | url: String 4 | } 5 | 6 | type CloudinaryData { 7 | image: String 8 | small_image: String 9 | thumbnail: String 10 | media_gallery: [String] 11 | gallery_widget_parameters: String @doc(description: "Cloudinary gallery widget parameters (json)") 12 | custom_media_attributes(attribute_codes: [String]!): [CustomMediaAttribute] 13 | @resolver(class: "\\Cloudinary\\Cloudinary\\Model\\GraphQLResolver\\ProductAttributeCldResolver") 14 | @doc(description: "Fetch a custom media attributes for the product based on the provided attribute code.") 15 | } 16 | 17 | interface ProductInterface { 18 | cld_data: CloudinaryData 19 | @resolver(class: "\\Cloudinary\\Cloudinary\\Model\\GraphQLResolver\\ProductAttributeCldResolver") 20 | @doc(description: "Cloudinary urls generated for product images and gallery widget parameters.") 21 | } 22 | -------------------------------------------------------------------------------- /view/frontend/templates/product/video/settings.phtml: -------------------------------------------------------------------------------- 1 | 8 | 18 | 21 | helper('Cloudinary\Cloudinary\Helper\ProductGalleryHelper'); 23 | ?> 24 | isHyvaThemeEnabled()): ?> 25 | 26 | 27 | -------------------------------------------------------------------------------- /view/base/web/js/form/element/image-uploader.js: -------------------------------------------------------------------------------- 1 | /* global Base64 */ 2 | define([ 3 | 'jquery', 4 | 'Magento_Ui/js/form/element/image-uploader' 5 | ], function($, Element) { 6 | 'use strict'; 7 | 8 | return Element.extend({ 9 | /** 10 | * {@inheritDoc} 11 | */ 12 | initialize: function() { 13 | this._super(); 14 | this.cloudinaryMLoptions.imageParamName = this.paramName || this.inputName; 15 | this.cloudinaryMLoptions.cldMLid = this.cloudinaryMLoptions.imageParamName + '_' + this.uid; 16 | this.cloudinaryMLoptions.callbackHandler = this; 17 | this.cloudinaryMLoptions.callbackHandlerMethod = 'addFile'; 18 | }, 19 | /** 20 | * hides the 'Upload from Gallery' button at category page. 21 | * */ 22 | showGalleryUploader: function() { 23 | return (this.cloudinaryMLoptions.isGallerySupported) 24 | } 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /etc/adminhtml/events.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Core/Security/SignedConsoleUrl.php: -------------------------------------------------------------------------------- 1 | time(), "mode" => "check"]; 14 | $params["signature"] = (string)ApiSignature::fromSecretAndParams($credentials->getSecret(), $params); 15 | $params["api_key"] = (string)$credentials->getKey(); 16 | $query = http_build_query($params); 17 | 18 | $this->signedConsoleUrl = (string)$url . '?' . $query; 19 | } 20 | 21 | public static function fromConsoleUrlAndCredentials(ConsoleUrl $url, Credentials $credentials) 22 | { 23 | return new SignedConsoleUrl($url, $credentials); 24 | } 25 | 26 | public function __toString() 27 | { 28 | return $this->signedConsoleUrl; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /view/frontend/requirejs-config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | map: { 3 | '*': { 4 | loadPlayer: 'Cloudinary_Cloudinary/js/load-player', 5 | 'Magento_ProductVideo/js/fotorama-add-video-events': 'Cloudinary_Cloudinary/js/fotorama-add-video-events', 6 | cloudinaryProductGallery: 'Cloudinary_Cloudinary/js/cloudinary-product-gallery', 7 | cloudinaryLazyload: 'Cloudinary_Cloudinary/js/cloudinary-lazyload' 8 | } 9 | }, 10 | paths: { 11 | 'jquery.lazyload': "Cloudinary_Cloudinary/js/jquery.lazyload.min", 12 | cloudinaryProductGalleryAll: "//product-gallery.cloudinary.com/latest/all" 13 | }, 14 | shim: { 15 | 'jquery.lazyload': { 16 | deps: ['jquery'] 17 | }, 18 | }, 19 | config: { 20 | mixins: { 21 | 'Magento_Swatches/js/swatch-renderer': { 22 | 'Cloudinary_Cloudinary/js/swatch-renderer-mixin': true 23 | } 24 | } 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/ProductGallery/ThumbnailsNavigationShape.php: -------------------------------------------------------------------------------- 1 | 'none', 14 | 'label' => 'None', 15 | ], 16 | [ 17 | 'value' => 'round', 18 | 'label' => 'Round', 19 | ], 20 | [ 21 | 'value' => 'square', 22 | 'label' => 'Square', 23 | ], 24 | [ 25 | 'value' => 'radius', 26 | 'label' => 'Radius', 27 | ], 28 | [ 29 | 'value' => 'rectangle', 30 | 'label' => 'Rectangle', 31 | ], 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /view/frontend/templates/product/image.phtml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | getCustomAttributes() as $name => $value): ?> 14 | escapeHtmlAttr($name) ?>="escapeHtmlAttr($value) ?>" 15 | 16 | src="escapeHtmlAttr($block->getLazyloadPlaceholder()) ?>" 17 | data-original="escapeUrl($block->getImageUrl()) ?>" 18 | width="escapeHtmlAttr($block->getWidth()) ?>" 19 | height="escapeHtmlAttr($block->getHeight()) ?>" 20 | alt="escapeHtmlAttr($block->getLabel()) ?>" /> 21 | -------------------------------------------------------------------------------- /Core/Image/Transformation/Crop.php: -------------------------------------------------------------------------------- 1 | value = $value; 17 | } 18 | 19 | public static function fromString($value) 20 | { 21 | return new Crop($value); 22 | } 23 | 24 | public static function pad() 25 | { 26 | return new Crop(self::PAD); 27 | } 28 | 29 | public static function lpad() 30 | { 31 | return new Crop(self::LPAD); 32 | } 33 | 34 | public static function fit() 35 | { 36 | return new Crop(self::FIT); 37 | } 38 | 39 | public static function limit() 40 | { 41 | return new Crop(self::LIMIT); 42 | } 43 | 44 | public function __toString() 45 | { 46 | return $this->value; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /view/adminhtml/ui_component/pagebuilder_video_form.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 |
9 |
10 | 11 | 12 | 13 | Video URLs can be links to videos on YouTube, Vimeo or Cloudinary, or HTTP(S) links to files with valid video extensions (we recommend .mp4) 14 | 15 | Video URLs can be links to videos on YouTube, Vimeo or Cloudinary, or HTTP(S) links to files with valid video extensions (we recommend .mp4) 16 | 17 | 18 |
19 |
20 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/Video/Autoplay/Comment.php: -------------------------------------------------------------------------------- 1 | 12 | * Date: 07/03/2024 13 | * Time: 14:04 14 | */ 15 | class Comment implements CommentInterface 16 | { 17 | /** 18 | * @var UrlInterface 19 | */ 20 | protected $urlInterface; 21 | 22 | /** 23 | * @param UrlInterface $urlInterface 24 | */ 25 | public function __construct( 26 | UrlInterface $urlInterface 27 | ) { 28 | $this->urlInterface = $urlInterface; 29 | } 30 | 31 | public function getCommentText($elementValue) 32 | { 33 | $url = 'https://cloudinary.com/glossary/video-autoplay'; 34 | 35 | $comment = ''; 36 | 37 | return $comment; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018 Cloudinary 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /view/adminhtml/layout/cms_wysiwyg_images_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Magento\Backend\Block\DataProviders\ImageUploadConfig 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Model/Synchronisation.php: -------------------------------------------------------------------------------- 1 | _init(SynchronisationResourceModel::class); 14 | } 15 | 16 | public function setImagePath($imagePath) 17 | { 18 | return $this->setData('image_path', $imagePath); 19 | } 20 | 21 | public function getImagePath() 22 | { 23 | return $this->getData('image_path'); 24 | } 25 | 26 | public function getFilename() 27 | { 28 | return basename($this->getImagePath()); 29 | } 30 | 31 | public function getRelativePath() 32 | { 33 | return $this->getImagePath(); 34 | } 35 | 36 | public function tagAsSynchronized() 37 | { 38 | $this->save(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/Video/VideoQuality.php: -------------------------------------------------------------------------------- 1 | 'none', 13 | 'label' => 'Not set', 14 | ], 15 | [ 16 | 'value' => 'q_auto', 17 | 'label' => 'Auto', 18 | ], 19 | [ 20 | 'value' => 'q_auto:best', 21 | 'label' => 'Auto best', 22 | ], 23 | [ 24 | 'value' => 'q_auto:good', 25 | 'label' => 'Auto good', 26 | ], 27 | [ 28 | 'value' => 'q_auto:eco', 29 | 'label' => 'Auto eco', 30 | ], 31 | [ 32 | 'value' => 'q_auto:low', 33 | 'label' => 'Auto low', 34 | ] 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /view/base/web/images/spinset_indic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | spinset-indication 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Plugin/Ui/Component/Form/Element/DataType/Media/Image.php: -------------------------------------------------------------------------------- 1 | getData('config/cloudinaryMLoptions')) { 20 | $component->setData(array_replace_recursive( 21 | $component->getData(), 22 | [ 23 | 'config' => [ 24 | 'template' => 'Cloudinary_Cloudinary/form/element/uploader/image', 25 | 'component' => 'Cloudinary_Cloudinary/js/form/element/image-uploader', 26 | ] 27 | ] 28 | )); 29 | } 30 | return $result; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Core/Exception/MigrationError.php: -------------------------------------------------------------------------------- 1 | message = sprintf('%s%s', $this->message, $suffix); 28 | } 29 | 30 | /** 31 | * @return Image 32 | */ 33 | public function getImage() 34 | { 35 | return $this->image; 36 | } 37 | 38 | /** 39 | * @param Image $image 40 | * @param string $message 41 | * @throws MigrationError 42 | */ 43 | public static function throwWith(Image $image, $message = '') 44 | { 45 | $exception = new static($message ?: static::DEFAULT_MESSAGE); 46 | $exception->image = $image; 47 | throw $exception; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /view/frontend/web/js/cloudinary-product-gallery.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'cloudinaryProductGalleryAll' 4 | ], function($) { 5 | 'use strict'; 6 | 7 | $.widget('mage.cloudinaryProductGallery', { 8 | 9 | options: { 10 | cloudinaryPGoptions: {}, // Options for Cloudinary-PG galleryWidget() 11 | cldPGid: 0, 12 | }, 13 | 14 | /** 15 | * @private 16 | */ 17 | _create: function() { 18 | this._super(); 19 | 20 | var widget = this; 21 | window.cloudinary_pg = window.cloudinary_pg || []; 22 | this.options.cldPGid = this.options.cldPGid || 0; 23 | if (typeof window.cloudinary_pg[this.options.cldPGid] === "undefined") { 24 | this.cloudinary_pg = window.cloudinary_pg[this.options.cldPGid] = cloudinary.galleryWidget(this.options.cloudinaryPGoptions); 25 | this.cloudinary_pg.render(); 26 | } else { 27 | this.cloudinary_pg = window.cloudinary_pg[this.options.cldPGid]; 28 | } 29 | 30 | 31 | }, 32 | 33 | }); 34 | 35 | return $.mage.cloudinaryProductGallery; 36 | }); 37 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/ProductGallery/ThumbnailsSelectedBorderPosition.php: -------------------------------------------------------------------------------- 1 | 'top', 14 | 'label' => 'Top', 15 | ], 16 | [ 17 | 'value' => 'bottom', 18 | 'label' => 'Bottom', 19 | ], 20 | [ 21 | 'value' => 'left', 22 | 'label' => 'Left', 23 | ], 24 | [ 25 | 'value' => 'right', 26 | 'label' => 'Right', 27 | ], 28 | [ 29 | 'value' => 'top-bottom', 30 | 'label' => 'Top-Bottom', 31 | ], 32 | [ 33 | 'value' => 'left-right', 34 | 'label' => 'Left-Right', 35 | ], 36 | [ 37 | 'value' => 'all', 38 | 'label' => 'All', 39 | ], 40 | ]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/Lazyload/Placeholder.php: -------------------------------------------------------------------------------- 1 | 'blur', 14 | 'label' => 'Blur', 15 | ], 16 | [ 17 | 'value' => 'pixelate', 18 | 'label' => 'Pixelate', 19 | ], 20 | [ 21 | 'value' => 'predominant-color', 22 | 'label' => 'Predominant color', 23 | ], 24 | [ 25 | 'value' => 'vectorize', 26 | 'label' => 'Vectorize', 27 | ], 28 | ]; 29 | 30 | /* 31 | export const placeholderImageOptions = { 32 | 'vectorize': {effect: 'vectorize', quality: 1}, 33 | 'pixelate': {effect: 'pixelate', quality: 1, fetch_format: 'auto'}, 34 | 'blur': {effect: 'blur:2000', quality: 1, fetch_format: 'auto'}, 35 | 'predominant-color': predominantColorTransform 36 | }; 37 | */ 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Model/MigrationTask.php: -------------------------------------------------------------------------------- 1 | flagDir = $filesystem->getDirectoryWrite(DirectoryList::VAR_DIR); 26 | } 27 | 28 | public function hasStarted() 29 | { 30 | return $this->flagDir->isExist(self::MIGRATION_RUNNING_FLAG_FILENAME); 31 | } 32 | 33 | public function hasBeenStopped() 34 | { 35 | return !$this->hasStarted(); 36 | } 37 | 38 | public function stop() 39 | { 40 | $this->flagDir->delete(self::MIGRATION_RUNNING_FLAG_FILENAME); 41 | } 42 | 43 | public function start() 44 | { 45 | $this->flagDir->touch(self::MIGRATION_RUNNING_FLAG_FILENAME); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Model/Observer/ProductGalleryChangeTemplate.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 28 | } 29 | 30 | /** 31 | * @param mixed $observer 32 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 33 | * @return void 34 | */ 35 | public function execute(\Magento\Framework\Event\Observer $observer) 36 | { 37 | if (!$this->configuration->isEnabled()) { 38 | return $this; 39 | } 40 | $observer->getBlock()->setTemplate('Cloudinary_Cloudinary::catalog/product/helper/gallery.phtml'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /view/frontend/templates/product/old_image_with_borders.phtml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | 14 | 16 | escapeHtmlAttr($block->getCustomAttributes()) ?> 18 | src="getLazyloadPlaceholder() ?>" 19 | data-original="escapeUrl($block->getImageUrl()) ?>" 20 | width="escapeHtmlAttr($block->getWidth()) ?>" 21 | height="escapeHtmlAttr($block->getHeight()) ?>" 22 | max-width="escapeHtmlAttr($block->getWidth()) ?>" 23 | max-height="escapeHtmlAttr($block->getHeight()) ?>" 24 | alt="stripTags($block->getLabel(), null, true) ?>"/> 25 | 26 | -------------------------------------------------------------------------------- /Core/ConfigurationInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |

6 |
7 | 8 | 9 | 10 |
11 | 12 | 13 |
14 | 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 |
23 | 24 | 25 |
26 | 30 |
31 | 32 | -------------------------------------------------------------------------------- /Core/Image/Transformation/Dimensions.php: -------------------------------------------------------------------------------- 1 | width = is_null($width) ? null : (int) round($width); 14 | $this->height = is_null($height) ? null : (int) round($height); 15 | } 16 | 17 | public function getWidth() 18 | { 19 | return $this->width; 20 | } 21 | 22 | public function getHeight() 23 | { 24 | return $this->height; 25 | } 26 | 27 | public static function square($length) 28 | { 29 | return new Dimensions($length, $length); 30 | } 31 | 32 | public static function squareMissingDimension(Dimensions $dimensions) 33 | { 34 | if (!$dimensions->getWidth()) { 35 | return Dimensions::square($dimensions->getHeight()); 36 | } elseif (!$dimensions->getHeight()) { 37 | return Dimensions::square($dimensions->getWidth()); 38 | } 39 | 40 | return $dimensions; 41 | } 42 | 43 | public static function fromWidthAndHeight($width, $height) 44 | { 45 | return new Dimensions($width, $height); 46 | } 47 | 48 | public static function null() 49 | { 50 | return new Dimensions(null, null); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /view/base/web/images/cloudinary_cloud_glyph_white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /view/base/web/images/cloudinary_cloud_glyph_regular.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Core/ConfigurationBuilder.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 16 | } 17 | 18 | /** 19 | * @return Configuration instance 20 | */ 21 | public function build() 22 | { 23 | 24 | $reg = $this->configuration->getCoreRegistry(); 25 | $credentials = $this->configuration->getCredentials(); 26 | if ($credentials) { 27 | $cloud = [ 28 | 'cloud_name' => $credentials['cloud_name'], 29 | 'api_key' => $credentials['api_key'], 30 | 'api_secret' => $credentials['api_secret'] 31 | ]; 32 | 33 | $url = array_diff($credentials, $cloud); 34 | 35 | $config = array('cloud' => $cloud); 36 | 37 | if ($url && is_array($url)) { 38 | $config['url'] = $url; 39 | } 40 | 41 | if ($this->configuration->getCdnSubdomainStatus()) { 42 | $config['cloud']['cdn_subdomain'] = true; 43 | } 44 | 45 | return $config; 46 | } 47 | return $this; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Core/Migration/Queue.php: -------------------------------------------------------------------------------- 1 | migrationTask = $migrationTask; 26 | $this->synchronizedMediaRepository = $synchronizedMediaRepository; 27 | $this->logger = $logger; 28 | $this->batchUploader = $batchUploader; 29 | } 30 | 31 | public function process() 32 | { 33 | if ($this->migrationTask->hasBeenStopped()) { 34 | return; 35 | } 36 | 37 | $images = $this->synchronizedMediaRepository->findUnsynchronisedImages(); 38 | 39 | if (!$images) { 40 | $this->logger->notice(self::MESSAGE_COMPLETE); 41 | $this->migrationTask->stop(); 42 | } else { 43 | $this->logger->notice(self::MESSAGE_PROCESSING); 44 | $this->batchUploader->uploadImages($images); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Core/Image/ImageFactory.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 29 | $this->synchronizationChecker = $synchronizationChecker; 30 | } 31 | 32 | /** 33 | * @param $imagePath 34 | * @return Image 35 | */ 36 | public function build($imagePath, callable $localPathGenerator) 37 | { 38 | $migratedPath = $this->configuration->getMigratedPath($imagePath); 39 | 40 | if ($this->configuration->isEnabled() && $this->synchronizationChecker->isSynchronized($migratedPath)) { 41 | return Image::fromPath($imagePath, $migratedPath); 42 | } else { 43 | return new LocalImage($localPathGenerator); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Model/Observer/DeleteProductImage.php: -------------------------------------------------------------------------------- 1 | productImageFinder = $productImageFinder; 31 | $this->cloudinaryImageManager = $cloudinaryImageManager; 32 | } 33 | 34 | /** 35 | * @param Observer $observer 36 | */ 37 | public function execute(Observer $observer) 38 | { 39 | $product = $observer->getEvent()->getProduct(); 40 | 41 | foreach ($this->productImageFinder->findDeletedImages($product) as $image) { 42 | $this->cloudinaryImageManager->removeAndUnSynchronise($image); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /etc/adminhtml/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Cloudinary\Cloudinary\Ui\DataProvider\Product\Form\Modifier\Product 9 | 55 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Plugin/MediaConfig.php: -------------------------------------------------------------------------------- 1 | imageFactory = $imageFactory; 28 | $this->urlGenerator = $urlGenerator; 29 | } 30 | 31 | /** 32 | * @param CatalogMediaConfig $mediaConfig 33 | * @param \Closure $originalMethod 34 | * @param string $file 35 | * 36 | * @return string 37 | */ 38 | public function aroundGetMediaUrl(CatalogMediaConfig $mediaConfig, \Closure $originalMethod, $file) 39 | { 40 | $image = $this->imageFactory->build( 41 | $mediaConfig->getBaseMediaPath() . $file, 42 | function () use ($originalMethod, $file) { 43 | return $originalMethod($file); 44 | } 45 | ); 46 | 47 | return $this->urlGenerator->generateFor($image); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Block/Scripts.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 37 | $this->_helper = $mediaHelper; 38 | } 39 | 40 | /** 41 | * @method isEnabledLazyload 42 | * @return boolean 43 | */ 44 | public function isEnabled() 45 | { 46 | return $this->configuration->isEnabled(); 47 | } 48 | 49 | /** 50 | * @return string|null 51 | */ 52 | public function getCname() 53 | { 54 | return $this->_helper->getCname(); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Core/ValidateRemoteUrlRequest.php: -------------------------------------------------------------------------------- 1 | curlHandler = curl_init($url); 12 | $this->setCurlOptions(); 13 | } 14 | 15 | public function validate() 16 | { 17 | $result = $this->execute(); 18 | 19 | if ($result->responseCode == 200 && is_null($result->error)) { 20 | return true; 21 | } 22 | 23 | return false; 24 | } 25 | 26 | private function execute() 27 | { 28 | curl_exec($this->curlHandler); 29 | 30 | $result = new \stdClass(); 31 | $result->responseCode = $this->getResponseCode(); 32 | $result->error = $this->getErrorMessage(); 33 | 34 | curl_close($this->curlHandler); 35 | 36 | return $result; 37 | } 38 | 39 | private function getResponseCode() 40 | { 41 | return curl_getinfo($this->curlHandler, CURLINFO_HTTP_CODE); 42 | } 43 | 44 | private function getErrorMessage() 45 | { 46 | return curl_errno($this->curlHandler) ? curl_error($this->curlHandler) : null; 47 | } 48 | 49 | private function setCurlOptions() 50 | { 51 | curl_setopt($this->curlHandler, CURLOPT_HEADER, 1); 52 | curl_setopt($this->curlHandler, CURLOPT_FAILONERROR, 1); 53 | curl_setopt($this->curlHandler, CURLOPT_RETURNTRANSFER, 1); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/Gravity.php: -------------------------------------------------------------------------------- 1 | '', 14 | 'label' => 'Magento\'s Default', 15 | ], 16 | [ 17 | 'value' => 'north_west', 18 | 'label' => 'North West', 19 | ], 20 | [ 21 | 'value' => 'north', 22 | 'label' => 'North', 23 | ], 24 | [ 25 | 'value' => 'north_east', 26 | 'label' => 'North East', 27 | ], 28 | [ 29 | 'value' => 'east', 30 | 'label' => 'East', 31 | ], 32 | [ 33 | 'value' => 'center', 34 | 'label' => 'Center', 35 | ], 36 | [ 37 | 'value' => 'west', 38 | 'label' => 'West', 39 | ], 40 | [ 41 | 'value' => 'south_west', 42 | 'label' => 'South West', 43 | ], 44 | [ 45 | 'value' => 'south', 46 | 'label' => 'South', 47 | ], 48 | [ 49 | 'value' => 'south_east', 50 | 'label' => 'South East', 51 | ], 52 | ]; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/Quality.php: -------------------------------------------------------------------------------- 1 | '', 14 | 'label' => 'Magento\'s Default', 15 | ], 16 | [ 17 | 'value' => '20', 18 | 'label' => '20%', 19 | ], 20 | [ 21 | 'value' => '30', 22 | 'label' => '30%', 23 | ], 24 | [ 25 | 'value' => '40', 26 | 'label' => '40%', 27 | ], 28 | [ 29 | 'value' => '50', 30 | 'label' => '50%', 31 | ], 32 | [ 33 | 'value' => '60', 34 | 'label' => '60%', 35 | ], 36 | [ 37 | 'value' => '70', 38 | 'label' => '70%', 39 | ], 40 | [ 41 | 'value' => '80', 42 | 'label' => '80%', 43 | ], 44 | [ 45 | 'value' => '90', 46 | 'label' => '90%', 47 | ], 48 | [ 49 | 'value' => '100', 50 | 'label' => '100%', 51 | ], 52 | [ 53 | 'value' => 'auto', 54 | 'label' => 'Auto', 55 | ] 56 | ]; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /view/adminhtml/templates/config/free.phtml: -------------------------------------------------------------------------------- 1 |

2 | 3 | Custom transformations will be added to the default image transformations settings chosen above.
4 | For information about the full range of transforms available see the 5 | Cloudinary documentation. 6 |
7 | You may need to clear or rebuild the Magento block and full page caches to see the changes in the front end. 8 |
9 |

10 | 11 | 20 | 21 |
", "ajaxKey": "getFormKey() ?>"}}'> 24 |
25 | -------------------------------------------------------------------------------- /Model/ProductImageFinder/ImageCreator.php: -------------------------------------------------------------------------------- 1 | mediaDirectory = $filesystem->getDirectoryRead(DirectoryList::MEDIA); 37 | $this->baseMediaPath = $mediaConfig->getBaseMediaPath(); 38 | } 39 | 40 | /** 41 | * @param array $imageData 42 | * 43 | * @return Image 44 | */ 45 | public function __invoke(array $imageData) 46 | { 47 | $fullPath = $this->baseMediaPath . $imageData['file']; 48 | $relativePath = 'media' . DIRECTORY_SEPARATOR . $fullPath; 49 | 50 | return Image::fromPath( 51 | $this->mediaDirectory->getAbsolutePath($fullPath), 52 | $relativePath 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloudinary Magento 2 Extension 2 | ​ 3 | The Cloudinary Magento extension links your Magento website to your Cloudinary account, allowing you to serve all your product, category, and content management system (CMS) images directly from Cloudinary. 4 | ​ 5 | Before you install the extension, make sure you have a Cloudinary account. You can start by [signing up](https://cloudinary.com/users/register_free?utm_source=magento-2-git-page&utm_medium=affiliate&utm_content=sign-up&utm_campaign=1975) for a free plan. When your requirements grow, you can upgrade to a [plan](https://cloudinary.com/pricing) that best fits your needs. 6 | ​ 7 | For more information on using the Cloudinary Magento 2 extension, take a look at our [documentation](https://cloudinary.com/documentation/magento_integration). 8 | ​ 9 | ## Installation 10 | ​ 11 | You can download and install the extension from the [Magento Marketplace](https://marketplace.magento.com/cloudinary-cloudinary.html) or install it via composer by running the following commands under your Magento 2 root dir: 12 | ​ 13 | ``` 14 | composer require cloudinary/cloudinary-magento2 15 | php bin/magento maintenance:enable 16 | php bin/magento setup:upgrade 17 | php bin/magento setup:di:compile 18 | php bin/magento setup:static-content:deploy 19 | php bin/magento maintenance:disable 20 | php bin/magento cache:flush 21 | ``` 22 | ​ 23 | https://www.cloudinary.com/ 24 | 25 | Copyright © 2020 Cloudinary. All rights reserved. 26 | ​ 27 | ![Cloudinary Logo](https://cloudinary-res.cloudinary.com/image/upload/c_scale,w_300/v1/logo/for_white_bg/cloudinary_logo_for_white_bg.svg) 28 | -------------------------------------------------------------------------------- /Block/Adminhtml/System/Config/ModuleVersion.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 29 | parent::__construct($context, $data); 30 | } 31 | 32 | /** 33 | * Remove scope label 34 | * 35 | * @param AbstractElement $element 36 | * @return string 37 | */ 38 | public function render(AbstractElement $element) 39 | { 40 | $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue(); 41 | return parent::render($element); 42 | } 43 | 44 | /** 45 | * Return element html 46 | * 47 | * @param AbstractElement $element 48 | * @return string 49 | */ 50 | protected function _getElementHtml(AbstractElement $element) 51 | { 52 | return "
{$this->configuration->getModuleVersion()}
"; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Core/UploadConfig.php: -------------------------------------------------------------------------------- 1 | useFilename = $useFilename; 31 | $this->uniqueFilename = $uniqueFilename; 32 | $this->overwrite = $overwrite; 33 | } 34 | 35 | public static function fromBooleanValues($useFilename, $uniqueFilename, $overwrite) 36 | { 37 | return new UploadConfig($useFilename, $uniqueFilename, $overwrite); 38 | } 39 | 40 | /** 41 | * @return boolean 42 | */ 43 | public function useFilename() 44 | { 45 | return $this->useFilename; 46 | } 47 | 48 | /** 49 | * @return boolean 50 | */ 51 | public function uniqueFilename() 52 | { 53 | return $this->uniqueFilename; 54 | } 55 | 56 | /** 57 | * @return boolean 58 | */ 59 | public function overwrite() 60 | { 61 | return $this->overwrite; 62 | } 63 | 64 | public function toArray() 65 | { 66 | return [ 67 | "use_filename" => $this->useFilename, 68 | "unique_filename" => $this->uniqueFilename, 69 | "overwrite" => $this->overwrite, 70 | ]; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Core/Image.php: -------------------------------------------------------------------------------- 1 | imagePath = $imagePath; 16 | $this->relativePath = $relativePath; 17 | $this->pathInfo = pathinfo($this->imagePath); 18 | } 19 | 20 | public static function fromPath($imagePath, $relativePath = '') 21 | { 22 | return new Image($imagePath, $relativePath); 23 | } 24 | 25 | public function __toString() 26 | { 27 | return $this->imagePath; 28 | } 29 | 30 | public function getRelativePath() 31 | { 32 | return $this->relativePath; 33 | } 34 | 35 | public function getRelativeFolder() 36 | { 37 | $result = dirname($this->getRelativePath()); 38 | return $result == '.' ? '' : $result; 39 | } 40 | 41 | public function getId() 42 | { 43 | return sprintf( 44 | '%s%s', 45 | $this->relativePath ? ($this->getRelativeFolder() . DIRECTORY_SEPARATOR) : '', 46 | $this->pathInfo['basename'] 47 | ); 48 | } 49 | 50 | public function getIdWithoutExtension() 51 | { 52 | return sprintf( 53 | '%s%s', 54 | $this->relativePath ? ($this->getRelativeFolder() . DIRECTORY_SEPARATOR) : '', 55 | $this->pathInfo['filename'] 56 | ); 57 | } 58 | 59 | public function getExtension() 60 | { 61 | return $this->pathInfo['extension']; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Model/Framework/File/Uploader.php: -------------------------------------------------------------------------------- 1 | 180) { 38 | throw new \InvalidArgumentException('Filename is too long; must be 180 characters or less'); 39 | } 40 | 41 | if (preg_match('/^_+$/', $fileInfo['filename'])) { 42 | $fileName = 'file.' . $fileInfo['extension']; 43 | } 44 | 45 | return $fileName; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Model/Config/Source/Dropdown/ProductGallery/AspectRatio.php: -------------------------------------------------------------------------------- 1 | 'square', 14 | 'label' => 'Square', 15 | ], 16 | [ 17 | 'value' => '1:1', 18 | 'label' => '1:1', 19 | ], 20 | [ 21 | 'value' => '3:4', 22 | 'label' => '3:4', 23 | ], 24 | [ 25 | 'value' => '4:3', 26 | 'label' => '4:3', 27 | ], 28 | [ 29 | 'value' => '4:6', 30 | 'label' => '4:6', 31 | ], 32 | [ 33 | 'value' => '6:4', 34 | 'label' => '6:4', 35 | ], 36 | [ 37 | 'value' => '5:7', 38 | 'label' => '5:7', 39 | ], 40 | [ 41 | 'value' => '7:5', 42 | 'label' => '7:5', 43 | ], 44 | [ 45 | 'value' => '5:8', 46 | 'label' => '5:8', 47 | ], 48 | [ 49 | 'value' => '8:5', 50 | 'label' => '8:5', 51 | ], 52 | [ 53 | 'value' => '9:16', 54 | 'label' => '9:16', 55 | ], 56 | [ 57 | 'value' => '16:9', 58 | 'label' => '16:9', 59 | ], 60 | ]; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Core/UrlGenerator.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 28 | $this->imageProvider = $imageProvider; 29 | } 30 | 31 | /** 32 | * @param ImageInterface $image 33 | * @param Transformation $transformation 34 | * 35 | * @return string 36 | */ 37 | public function generateFor(ImageInterface $image, ?Transformation $transformation = null) 38 | { 39 | if ($image instanceof LocalImage) { 40 | return (string)$image; 41 | } 42 | 43 | return (string)$this->imageProvider->retrieveTransformed( 44 | $image, 45 | $transformation ?: $this->configuration->getDefaultTransformation() 46 | ); 47 | } 48 | 49 | /** 50 | * @param Image $image 51 | * @param Dimensions $dimensions 52 | * 53 | * @return string 54 | */ 55 | public function generateWithDimensions(ImageInterface $image, Dimensions $dimensions) 56 | { 57 | $transformation = $this->configuration->getDefaultTransformation(); 58 | 59 | return $this->generateFor($image, $transformation->withDimensions($dimensions)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Block/Adminhtml/System/Config/Form/Field/ColorPicker.php: -------------------------------------------------------------------------------- 1 | getElementHtml(); 18 | $value = $element->getData('value'); 19 | 20 | $html .= ''; 40 | 41 | return $html; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Model/ProductImageFinder.php: -------------------------------------------------------------------------------- 1 | imageCreator = $imageCreator; 29 | } 30 | 31 | /** 32 | * @param Product $product 33 | * 34 | * @return \Cloudinary\Cloudinary\Core\Image[] 35 | */ 36 | public function findNewImages(Product $product) 37 | { 38 | return $this->find($product, new NewImageFilter()); 39 | } 40 | 41 | /** 42 | * @param Product $product 43 | * 44 | * @return \Cloudinary\Cloudinary\Core\Image[] 45 | */ 46 | public function findDeletedImages(Product $product) 47 | { 48 | return $this->find($product, new DeletedImageFilter()); 49 | } 50 | 51 | /** 52 | * @param Product $product 53 | * @param ImageFilter $filter 54 | * 55 | * @return \Cloudinary\Cloudinary\Core\Image[] 56 | */ 57 | private function find(Product $product, ImageFilter $filter) 58 | { 59 | return array_map( 60 | $this->imageCreator, 61 | array_filter( 62 | $product->getMediaGallery('images') ?: [], 63 | $filter 64 | ) 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /view/adminhtml/requirejs-config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | map: { 3 | '*': { 4 | cloudinaryFreeTransform: 'Cloudinary_Cloudinary/js/cloudinary-free', 5 | newVideoDialog: 'Cloudinary_Cloudinary/js/new-video-dialog', 6 | 'Magento_ProductVideo/js/get-video-information': 'Cloudinary_Cloudinary/js/get-video-information', 7 | cloudinaryMediaLibraryModal: 'Cloudinary_Cloudinary/js/cloudinary-media-library-modal', 8 | cloudinarySpinsetModal: 'Cloudinary_Cloudinary/js/cloudinary-spinset-modal', 9 | cldspinsetDialog: 'Cloudinary_Cloudinary/js/cloudinary-spinset-dialog', 10 | productGallery: 'Cloudinary_Cloudinary/js/product-gallery', 11 | cloudinaryLazyload: 'Cloudinary_Cloudinary/js/cloudinary-lazyload', 12 | updateCmsImages: 'Cloudinary_Cloudinary/js/cms/preview-update', 13 | 'Magento_PageBuilder/js/content-type/image/preview': 'Cloudinary_Cloudinary/js/content-type/image/preview', 14 | } 15 | }, 16 | paths: { 17 | 'jquery.lazyload': "Cloudinary_Cloudinary/js/jquery.lazyload.min", 18 | cloudinaryMediaLibraryAll: "//media-library.cloudinary.com/global/all", 19 | es6Promise: "//cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min", 20 | 'uiComponent': 'Magento_Ui/js/core/app', 21 | }, 22 | shim: { 23 | 'jquery.lazyload': { 24 | deps: ['jquery'] 25 | }, 26 | 'uiComponent': { 27 | deps: ['jquery'] 28 | }, 29 | 'Cloudinary_Cloudinary/js/cms/preview-update': { 30 | deps: ['jquery'] 31 | } 32 | }, 33 | config: { 34 | mixins: { 35 | 'Magento_Ui/js/lib/validation/validator': { 36 | 'Cloudinary_Cloudinary/js/form/element/validator-rules-mixin': true 37 | }, 38 | } 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /Controller/Adminhtml/PageBuilder/MediaGallery/Upload.php: -------------------------------------------------------------------------------- 1 | logger = $logger; 33 | } 34 | 35 | public function execute() 36 | { 37 | $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON); 38 | try{ 39 | $params = $this->getRequest()->getParams(); 40 | 41 | } catch (NoSuchEntityException $e) { 42 | $responseCode = self::HTTP_OK; 43 | $responseContent = []; 44 | } catch (\Exception $e) { 45 | $responseCode = self::HTTP_INTERNAL_ERROR; 46 | $this->logger->critical($e); 47 | $responseContent = [ 48 | 'success' => false, 49 | 'message' => __('An error occurred on attempt to retrieve asset information.'), 50 | ]; 51 | } 52 | 53 | $resultJson->setHttpResponseCode($responseCode); 54 | $resultJson->setData($responseContent); 55 | 56 | return $resultJson; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Command/StopMigration.php: -------------------------------------------------------------------------------- 1 | objectManager = $objectManager; 34 | } 35 | 36 | /** 37 | * Configure the command 38 | * 39 | * @return void 40 | */ 41 | protected function configure() 42 | { 43 | $this->setDescription('Stops any currently running upload/download.'); 44 | } 45 | 46 | /** 47 | * @param InputInterface $input 48 | * @param OutputInterface $output 49 | * 50 | * @return int 51 | */ 52 | protected function execute(InputInterface $input, OutputInterface $output) 53 | { 54 | $this->migrationTask = $this->objectManager 55 | ->get(\Cloudinary\Cloudinary\Model\MigrationTask::class); 56 | 57 | if ($this->migrationTask->hasStarted()) { 58 | $this->migrationTask->stop(); 59 | $output->writeln(self::STOPPED_MESSAGE); 60 | } else { 61 | $output->writeln(self::NOP_MESSAGE); 62 | } 63 | return 1; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /view/adminhtml/web/template/product/free_transform.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 8 | 11 | 14 | 17 | 20 | 21 | 22 | 23 | 24 | 25 |
6 | Image 7 | 9 | Label 10 | 12 | File 13 | 15 | Cloudinary Transformation 16 | 18 | Action 19 |
26 |
27 | 28 |

29 | Product image transformations will be added to the the site-wide default image transformation options 30 | chosen from dropdowns on the Cloudinary config page. Product image transformations will override any 31 | free form transformation options that have been specified in the 'Global custom transform' field 32 | of the Cloudinary config page. 33 |

34 |

35 | For information about the full range of transforms available see the 36 | Cloudinary documentation. 37 |

38 |

39 | You may need to clear or rebuild the Magento block and full page caches to see the changes in the front end. 40 |

41 |

42 | Only images are listed - transforms to videos are not yet possible using the Magento module. 43 |

44 | -------------------------------------------------------------------------------- /Block/Lazyload.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 34 | $this->jsonEncoder = $jsonEncoder; 35 | parent::__construct($context, $data); 36 | } 37 | 38 | /** 39 | * @method isEnabledLazyload 40 | * @return boolean 41 | */ 42 | public function isEnabledLazyload() 43 | { 44 | return $this->configuration->isEnabled() && $this->configuration->isEnabledLazyload(); 45 | } 46 | 47 | /** 48 | * @method getLazyloadOptions 49 | * @param boolean $json 50 | * @return string|array 51 | */ 52 | public function getLazyloadOptions($json = true) 53 | { 54 | $options = [ 55 | 'threshold' => $this->configuration->getLazyloadThreshold(), 56 | 'effect' => $this->configuration->getLazyloadEffect(), 57 | 'placeholder' => $this->configuration->getLazyloadPlaceholder(), 58 | ]; 59 | return $json ? $this->jsonEncoder->encode($options) : $options; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Core/AutoUploadMapping/RequestProcessor.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 26 | $this->apiClient = $apiClient; 27 | } 28 | 29 | /** 30 | * @param string $folder 31 | * @param string $url 32 | * @param bool $force 33 | * @return bool 34 | */ 35 | public function handle($folder, $url, $force = false) 36 | { 37 | if ($this->configuration->isActive() == $this->configuration->getRequestState() && !$force) { 38 | return true; 39 | } 40 | 41 | if ($this->configuration->getRequestState() == AutoUploadConfigurationInterface::ACTIVE) { 42 | return $this->handleActiveRequest($folder, $url); 43 | } 44 | 45 | $this->configuration->setState(AutoUploadConfigurationInterface::INACTIVE); 46 | 47 | return true; 48 | } 49 | 50 | /** 51 | * @param string $folder 52 | * @param string $url 53 | * @return bool 54 | */ 55 | private function handleActiveRequest($folder, $url) 56 | { 57 | $result = $this->apiClient->prepareMapping($folder, $url); 58 | 59 | if ($result) { 60 | $this->configuration->setState(AutoUploadConfigurationInterface::ACTIVE); 61 | } else { 62 | $this->configuration->setRequestState(AutoUploadConfigurationInterface::INACTIVE); 63 | } 64 | 65 | return $result; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Model/Observer/UploadProductImage.php: -------------------------------------------------------------------------------- 1 | productImageFinder = $productImageFinder; 31 | $this->cloudinaryImageManager = $cloudinaryImageManager; 32 | } 33 | 34 | /** 35 | * @param Observer $observer 36 | */ 37 | public function execute(Observer $observer) 38 | { 39 | $product = $observer->getEvent()->getProduct(); 40 | 41 | foreach ($this->productImageFinder->findNewImages($product) as $image) { 42 | if (!$this->isCloudinaryImage($image)) { 43 | $this->cloudinaryImageManager->uploadAndSynchronise($image); 44 | } 45 | } 46 | } 47 | /** 48 | * Check if image sourced from Cloudinary media 49 | * @param $image 50 | * return bool 51 | */ 52 | protected function isCloudinaryImage($image) 53 | { 54 | $file = $image->getRelativePath(); 55 | 56 | // Skip if it's our known placeholder 57 | if (strpos($file, 'cloudinary_placeholder.jpg') !== false) { 58 | return true; 59 | } 60 | 61 | return strpos($file, 'c/l/cld_') !== false; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /view/adminhtml/templates/browser/content.phtml: -------------------------------------------------------------------------------- 1 | 12 | getCloudinaryMediaLibraryWidgetOptions(); 14 | ?> 15 | 16 | 45 | -------------------------------------------------------------------------------- /Api/ProductGalleryManagementInterface.php: -------------------------------------------------------------------------------- 1 | cloudinaryImageManager = $cloudinaryImageManager; 42 | $this->mediaDirectory = $filesystem->getDirectoryRead(DirectoryList::MEDIA); 43 | $this->configuration = $configuration; 44 | } 45 | 46 | /** 47 | * Delete file (and its thumbnail if exists) from storage 48 | * 49 | * @param string $target File path to be deleted 50 | * @return $this 51 | */ 52 | public function beforeDeleteFile(Storage $storage, $target) 53 | { 54 | if (!$this->configuration->isEnabled() || !$this->configuration->hasEnvironmentVariable()) { 55 | return [$target]; 56 | } 57 | 58 | $this->cloudinaryImageManager->removeAndUnSynchronise( 59 | Image::fromPath($target, $this->mediaDirectory->getRelativePath($target)) 60 | ); 61 | 62 | return [$target]; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Command/ProductGalleryApiQueueProcess.php: -------------------------------------------------------------------------------- 1 | objectManager = $objectManager; 40 | $this->appState = $appState; 41 | } 42 | 43 | /** 44 | * Configure the command 45 | * 46 | * @return void 47 | */ 48 | protected function configure() 49 | { 50 | $this->setName('cloudinary:product-gallery-api-queue:process'); 51 | $this->setDescription('Process queued items for product gallery API'); 52 | } 53 | 54 | /** 55 | * @param InputInterface $input 56 | * @param OutputInterface $output 57 | * 58 | * @return void 59 | */ 60 | protected function execute(InputInterface $input, OutputInterface $output) 61 | { 62 | $this->job = $this->objectManager 63 | ->get(\Cloudinary\Cloudinary\Cron\ProductGalleryApiQueue::class); 64 | 65 | $this->appState->setAreaCode(\Magento\Framework\App\Area::AREA_CRONTAB); 66 | return $this->job 67 | ->setOutput($output) 68 | ->execute(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Block/Adminhtml/Form/Field/Free.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 37 | $this->model = $model; 38 | 39 | parent::__construct($context, $data); 40 | } 41 | 42 | /** 43 | * @return $this 44 | */ 45 | protected function _beforeToHtml() 46 | { 47 | $this->setTemplate('Cloudinary_Cloudinary::config/free.phtml'); 48 | return $this; 49 | } 50 | 51 | public function isCanPreviewTransformations() { 52 | return $this->_scopeConfig->getValue(self::XML_PATH_GLOBAL_TRANSFORMATION); 53 | } 54 | 55 | /** 56 | * @param AbstractElement $element 57 | * @return string 58 | */ 59 | protected function _getElementHtml(AbstractElement $element) 60 | { 61 | return sprintf( 62 | '%s%s', 63 | $element->getElementHtml(), 64 | $this->model->hasAccountConfigured() ? $this->toHtml() : '' 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /view/adminhtml/ui_component/media_gallery_listing.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 11 | 12 | 13 | media_gallery_listing.media_gallery_listing_data_source 14 | 15 | 16 | 17 | 18 | 19 | 44 | -------------------------------------------------------------------------------- /Model/AutoUploadMapping/AutoUploadConfiguration.php: -------------------------------------------------------------------------------- 1 | configReader = $configReader; 35 | $this->configWriter = $configWriter; 36 | } 37 | 38 | /** 39 | * @return bool 40 | */ 41 | public function isActive() 42 | { 43 | return $this->configReader->isSetFlag(self::STATE_PATH); 44 | } 45 | 46 | /** 47 | * @param bool $state 48 | */ 49 | public function setState($state) 50 | { 51 | $this->setFlag(self::STATE_PATH, $state); 52 | } 53 | 54 | /** 55 | * @return bool 56 | */ 57 | public function getRequestState() 58 | { 59 | return $this->configReader->isSetFlag(self::REQUEST_PATH); 60 | } 61 | 62 | /** 63 | * @param bool $state 64 | */ 65 | public function setRequestState($state) 66 | { 67 | $this->setFlag(self::REQUEST_PATH, $state); 68 | } 69 | 70 | /** 71 | * @param string $key 72 | * @param bool $state 73 | */ 74 | private function setFlag($key, $state) 75 | { 76 | $this->configWriter->save($key, $state ? self::CONFIG_TRUE : self::CONFIG_FALSE); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /view/adminhtml/templates/config/auto-upload-mapping-btn.phtml: -------------------------------------------------------------------------------- 1 | 7 | getButtonHtml() ?> 8 |
9 | 10 | isEnabled()): ?> 11 | 42 | 43 |

escapeHtmlAttr( __('This button would be available after enabling the module with a valid environment variable.')) ?>

44 | 45 | -------------------------------------------------------------------------------- /Ui/Component/Control/AddFromCloudinary.php: -------------------------------------------------------------------------------- 1 | images = $images; 38 | $this->authorization = $authorization; 39 | $this->cmsWysiwygImages = $cmsWysiwygImages; 40 | } 41 | /** 42 | * @inheritdoc 43 | */ 44 | public function getButtonData(): array 45 | { 46 | $cloudinaryMLwidgetOprions = ($this->images->getCloudinaryMediaLibraryWidgetOptions()) 47 | ? json_decode($this->images->getCloudinaryMediaLibraryWidgetOptions(),true) 48 | : null; 49 | 50 | $buttonData = [ 51 | 'label' => __('Add From Cloudinary'), 52 | 'class' => 'action-secondary add-from-cloudinary-button cloudinary-button-with-logo lg-margin-bottom', 53 | 'on_click' => 'return false;', 54 | 'data_attribute' => [ 55 | 'mage-init' => ['cloudinaryMediaLibraryModal' => $cloudinaryMLwidgetOprions], 56 | 'role' => 'add-from-cloudinary-button', 57 | 58 | ], 59 | 'sort_order' => 200, 60 | ]; 61 | 62 | if (!$this->authorization->isAllowed(self::ACL_UPLOAD_ASSETS)) { 63 | $buttonData['disabled'] = 'disabled'; 64 | } 65 | 66 | return $buttonData; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Helper/Reset.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 37 | $this->synchronisation = $synchronisation; 38 | $this->transformation = $transformation; 39 | } 40 | 41 | public function resetModule() 42 | { 43 | $this->truncate($this->synchronisationTableName()); 44 | $this->truncate($this->transformationTableName()); 45 | $this->removeConfig(); 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | private function synchronisationTableName() 52 | { 53 | return $this->connection->getTableName($this->synchronisation->getMainTable()); 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | private function transformationTableName() 60 | { 61 | return $this->connection->getTableName($this->transformation->getMainTable()); 62 | } 63 | 64 | /** 65 | * @param string $tableName 66 | */ 67 | private function truncate($tableName) 68 | { 69 | $this->connection->getConnection()->query(sprintf('TRUNCATE %s', $tableName)); 70 | } 71 | 72 | /** 73 | * @return string 74 | */ 75 | private function configTableName() 76 | { 77 | return $this->connection->getTableName('core_config_data'); 78 | } 79 | 80 | private function removeConfig() 81 | { 82 | $this->connection->getConnection()->delete( 83 | $this->configTableName(), 84 | "path LIKE 'cloudinary/%'" 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /view/base/web/js/cloudinary-lazyload.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'jquery.lazyload' 4 | ], function($) { 5 | 'use strict'; 6 | 7 | $.widget('mage.cloudinaryLazyload', { 8 | 9 | options: { 10 | threshold: 500, 11 | failure_limit: 0, 12 | event: "scroll", 13 | effect: "fadeIn", 14 | data_attribute: "original", 15 | skip_invisible: true, 16 | appear: null, 17 | load: null, 18 | placeholder: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" 19 | }, 20 | 21 | /** 22 | * @private 23 | */ 24 | _create: function() { 25 | this._super(); 26 | this.initialize(); 27 | }, 28 | 29 | initialize: function(options) { 30 | var widget = this; 31 | options = $.extend({}, widget.options, options || {}); 32 | this.cldLazyloadInit(options); 33 | setInterval(function() { 34 | widget.cldLazyloadInit(options); 35 | }, 4000); 36 | }, 37 | 38 | cldLazyloadInit: function(options) { 39 | if ($(".cloudinary-lazyload").length) { 40 | var widget = this; 41 | try { 42 | $(".cloudinary-lazyload").lazyload(options || widget.options); 43 | $(".cloudinary-lazyload").addClass("cloudinary-lazyload-processed").removeClass("cloudinary-lazyload"); 44 | } catch (err) { 45 | console.warn("Notice: An error occured while initializing Lazyload (" + err + "). Trying to fix automatically..."); 46 | $(".cloudinary-lazyload").each(function() { 47 | if ($(this).is("img") || $(this).is("iframe")) { 48 | $(this).attr("src", $(this).attr("data-original")); 49 | } else { 50 | $(this).css("background-image", "url('" + $(this).attr("data-original") + "')"); 51 | } 52 | $(this).addClass("cloudinary-lazyload-processed").removeClass("cloudinary-lazyload"); 53 | }); 54 | } 55 | } 56 | 57 | } 58 | 59 | }); 60 | 61 | return $.mage.cloudinaryLazyload; 62 | }); -------------------------------------------------------------------------------- /view/adminhtml/web/js/form/element/validator-rules-mixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © Magento, Inc. All rights reserved. 3 | * See COPYING.txt for license details. 4 | */ 5 | 6 | define([ 7 | 'jquery', 8 | 'underscore', 9 | 'Magento_Ui/js/lib/validation/utils' 10 | ], function($, _, utils) { 11 | 'use strict'; 12 | 13 | /** 14 | * Validate that string is url 15 | * @param {String} href 16 | * @return {Boolean} 17 | */ 18 | function validateIsUrl(href) { 19 | return (/^(http|https|ftp):\/\/(([A-Z0-9]([A-Z0-9_-]*[A-Z0-9]|))(\.[A-Z0-9]([A-Z0-9_-]*[A-Z0-9]|))*)(:(\d+))?(\/[A-Z0-9~](([A-Z0-9_~-]|\.)*[A-Z0-9~]|))*\/?(.*)?$/i).test(href); //eslint-disable-line max-len 20 | } 21 | 22 | return function(validator) { 23 | 24 | validator.addRule( 25 | 'validate-video-url', 26 | function(href) { 27 | if (utils.isEmptyNoTrim(href)) { 28 | return true; 29 | } 30 | 31 | href = (href || '').replace(/^\s+/, '').replace(/\s+$/, ''); 32 | 33 | return validateIsUrl(href) && ( 34 | href.match(/youtube\.com|youtu\.be/) || 35 | href.match(/vimeo\.com/) || 36 | href.match(/cloudinary/) || 37 | href.match(/\.(mp4|ogv|webm)(?!\w)/) 38 | ); 39 | }, 40 | $.mage.__('Please enter a valid video URL. Valid URLs have a video file extension (.mp4, .webm, .ogv) or links to videos on YouTube, Vimeo or Cloudinary.')//eslint-disable-line max-len 41 | ); 42 | 43 | validator.addRule( 44 | 'validate-video-source', 45 | function (href) { 46 | if (utils.isEmptyNoTrim(href)) { 47 | return true; 48 | } 49 | 50 | href = (href || '').replace(/^\s+/, '').replace(/\s+$/, ''); 51 | 52 | return validateIsUrl(href) && ( 53 | href.match(/youtube\.com|youtu\.be/) || 54 | href.match(/vimeo\.com/) || 55 | href.match(/cloudinary/) || 56 | href.match(/\.(mp4|ogv|webm)(?!\w)/) 57 | ); 58 | }, 59 | $.mage.__('Please enter a valid video URL. Valid URLs have a video file extension (.mp4, .webm, .ogv) or links to videos on YouTube, Vimeo or Cloudinary.')//eslint-disable-line max-len 60 | ); 61 | 62 | return validator; 63 | }; 64 | }); 65 | -------------------------------------------------------------------------------- /Helper/Product/Free.php: -------------------------------------------------------------------------------- 1 | freeModel = $freeModel; 29 | $this->configuration = $configuration; 30 | } 31 | 32 | /** 33 | * @param string $imageName 34 | * @param string $transform 35 | */ 36 | public function validate($imageName, $transform) 37 | { 38 | $transformation = $this->configuration 39 | ->getDefaultTransformation() 40 | ->withFreeform(Freeform::fromString($transform)); 41 | 42 | $this->freeModel->validate($this->freeModel->namedImageUrl($imageName, $transformation)); 43 | } 44 | 45 | /** 46 | * @param string $id 47 | * @param array $images 48 | * @return string 49 | */ 50 | public function getImageNameForId($id, array $images) 51 | { 52 | return array_key_exists($id, $images) ? $images[$id]['file'] : ''; 53 | } 54 | 55 | /** 56 | * @param Product $product 57 | * @return array 58 | */ 59 | public function getMediaGalleryImages(Product $product) 60 | { 61 | $mediaGallery = $product->getMediaGallery(); 62 | 63 | if (!$mediaGallery || !array_key_exists('images', $mediaGallery)) { 64 | return []; 65 | } 66 | 67 | return $mediaGallery['images']; 68 | } 69 | 70 | /** 71 | * @param array|null $data 72 | * @param array|null $isUpdated 73 | * @return array 74 | */ 75 | public function filterUpdated($data, $isUpdated) 76 | { 77 | if (!is_array($data) || !is_array($isUpdated)) { 78 | return []; 79 | } 80 | 81 | return array_filter( 82 | $data, 83 | function ($id) use ($isUpdated) { 84 | return $isUpdated[$id] === '1'; 85 | }, 86 | ARRAY_FILTER_USE_KEY 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Core/Security/CloudinaryEnvironmentVariable.php: -------------------------------------------------------------------------------- 1 | environmentVariable = (string)$environmentVariable; 28 | try { 29 | //Cloudinary::config_from_url(str_replace('CLOUDINARY_URL=', '', $environmentVariable)); 30 | $this->environmentVariable = str_replace('CLOUDINARY_URL=', '', $environmentVariable); 31 | } catch (\Exception $e) { 32 | throw new \Cloudinary\Cloudinary\Core\Exception\InvalidCredentials('Cloudinary config creation from environment variable failed'); 33 | } 34 | } 35 | 36 | /** 37 | * @param $environmentVariable 38 | * @return CloudinaryEnvironmentVariable 39 | * @throws Cloudinary\Core\Exception\InvalidCredentials 40 | */ 41 | public static function fromString($environmentVariable) 42 | { 43 | return new CloudinaryEnvironmentVariable($environmentVariable); 44 | } 45 | 46 | /** 47 | * @return string 48 | */ 49 | public function getCloud() 50 | { 51 | if (!$this->configuration) { 52 | if (!$this->environmentVariable) { 53 | return false; 54 | } 55 | $this->configuration = new Configuration($this->environmentVariable); 56 | } 57 | return $this->configuration->cloud->cloudName; 58 | } 59 | 60 | /** 61 | * @return Credentials 62 | */ 63 | public function getCredentials() 64 | { 65 | if (!$this->configuration) { 66 | $this->configuration = new Configuration($this->environmentVariable); 67 | } 68 | 69 | return Credentials::fromKeyAndSecret( 70 | Key::fromString($this->configuration->cloud->apiKey), 71 | Secret::fromString($this->configuration->cloud->apiSecret) 72 | ); 73 | } 74 | 75 | /** 76 | * @return array|string|string[] 77 | */ 78 | public function __toString() 79 | { 80 | return $this->environmentVariable; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /view/adminhtml/web/js/cloudinary-spinset-dialog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © Magento, Inc. All rights reserved. 3 | * See COPYING.txt for license details. 4 | */ 5 | define( 6 | [ 7 | 'jquery', 8 | 'underscore', 9 | 'jquery/ui', 10 | 'Magento_Ui/js/modal/modal', 11 | 'mage/translate', 12 | 'mage/backend/tree-suggest', 13 | 'mage/backend/validation' 14 | ], 15 | function($, _) { 16 | 'use strict'; 17 | 18 | $.widget('mage.newCldSpinsetDialog', { 19 | /** 20 | * Build widget 21 | * 22 | * @private 23 | */ 24 | _create: function() { 25 | var widget = this; 26 | 27 | this.element.modal({ 28 | type: 'slide', 29 | //appendTo: this._gallery, 30 | modalClass: 'cldspinset-dialog form-inline', 31 | title: $.mage.__('Add Spinset from Cloudinary'), 32 | buttons: [{ 33 | text: $.mage.__('Save'), 34 | class: 'action-primary video-create-button', 35 | click: $.proxy(widget._onCreate, widget) 36 | }, 37 | { 38 | text: $.mage.__('Cancel'), 39 | class: 'video-cancel-button', 40 | click: $.proxy(widget._onCancel, widget) 41 | } 42 | ], 43 | 44 | /** 45 | * @returns {null} 46 | */ 47 | opened: function() { 48 | console.log('cldspinset opened'); 49 | }, 50 | 51 | /** 52 | * Closed 53 | */ 54 | closed: function() { 55 | console.log('cldspinset closed'); 56 | } 57 | }); 58 | }, 59 | 60 | /** 61 | * Fired when click on create video 62 | * 63 | * @private 64 | */ 65 | _onCreate: function() { 66 | console.log('_onCreate'); 67 | }, 68 | 69 | /** 70 | * Fired when click on create video 71 | * 72 | * @private 73 | */ 74 | _onCancel: function() { 75 | console.log('_onCancel'); 76 | } 77 | }); 78 | 79 | return $.mage.newCldSpinsetDialog; 80 | } 81 | ); -------------------------------------------------------------------------------- /Block/Adminhtml/System/Config/AutoUploadMapping.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 34 | parent::__construct($context, $data); 35 | } 36 | 37 | /** 38 | * Remove scope label 39 | * 40 | * @param AbstractElement $element 41 | * @return string 42 | */ 43 | public function render(AbstractElement $element) 44 | { 45 | $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue(); 46 | return parent::render($element); 47 | } 48 | 49 | /** 50 | * Return element html 51 | * 52 | * @param AbstractElement $element 53 | * @return string 54 | */ 55 | protected function _getElementHtml(AbstractElement $element) 56 | { 57 | return $this->_toHtml(); 58 | } 59 | 60 | /** 61 | * Return ajax url for collect button 62 | * 63 | * @return string 64 | */ 65 | public function getAjaxUrl() 66 | { 67 | return $this->getUrl('cloudinary/ajax_system_config/autoUploadMapping'); 68 | } 69 | 70 | /** 71 | * @return bool 72 | */ 73 | public function isEnabled() 74 | { 75 | return $this->configuration->isEnabled(); 76 | } 77 | 78 | /** 79 | * Generate collect button html 80 | * 81 | * @return string 82 | */ 83 | public function getButtonHtml() 84 | { 85 | $button = $this->getLayout()->createBlock( 86 | 'Magento\Backend\Block\Widget\Button' 87 | )->setData( 88 | [ 89 | 'id' => 'auto-upload-mapping-btn', 90 | 'label' => __('Map media directory'), 91 | 'disabled' => !$this->configuration->isEnabled(), 92 | ] 93 | ); 94 | 95 | return $button->toHtml(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Plugin/Config/Block/System/Config/Form/Field/Image.php: -------------------------------------------------------------------------------- 1 | escaper = $escaper; 47 | $this->jsonEncoder = $jsonEncoder; 48 | $this->mediaLibraryHelper = $mediaLibraryHelper; 49 | } 50 | 51 | /** 52 | * Get the Html for the element. 53 | * 54 | * @param \Magento\Config\Block\System\Config\Form\Field\Image $block 55 | * @param string $html 56 | * @return string 57 | */ 58 | public function afterGetElementHtml(\Magento\Config\Block\System\Config\Form\Field\Image $block, $html) 59 | { 60 | // TODO: Add JS logics & handlers for after image insert 61 | if (($cloudinaryMLoptions = $this->mediaLibraryHelper->getCloudinaryMLOptions(false))) { 62 | $html .= ''; 70 | } 71 | return $html; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /view/base/web/template/form/element/uploader/uploader.html: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | 12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 |
39 | 40 |
41 | 42 | 43 | 44 | 45 | 46 |
47 | 50 |
51 |
52 | 53 |
54 |
55 | -------------------------------------------------------------------------------- /Model/Observer/SaveProductTransform.php: -------------------------------------------------------------------------------- 1 | helper = $helper; 40 | $this->transformationFactory = $transformationFactory; 41 | $this->resourceConnection = $resourceConnection; 42 | } 43 | 44 | /** 45 | * @param Observer $observer 46 | */ 47 | public function execute(Observer $observer) 48 | { 49 | $product = $observer->getProduct(); 50 | $mediaGalleryImages = $this->helper->getMediaGalleryImages($product); 51 | 52 | $changedTransforms = $this->helper->filterUpdated( 53 | $product->getCloudinaryFreeTransform(), 54 | $product->getCloudinaryFreeTransformChanges() 55 | ); 56 | 57 | foreach ($mediaGalleryImages as $gallItemId => $gallItem) { 58 | if (isset($gallItem['cldspinset']) && $gallItem['media_type'] === 'image') { 59 | $this->resourceConnection->getConnection() 60 | ->insertOnDuplicate($this->resourceConnection->getTableName('cloudinary_product_spinset_map'), [ 61 | 'image_name' => $gallItem['file'], 62 | 'cldspinset' => $gallItem['cldspinset'] 63 | ], ['image_name', 'cldspinset']); 64 | } 65 | } 66 | foreach ($changedTransforms as $id => $transform) { 67 | $this->storeFreeTransformation($this->helper->getImageNameForId($id, $mediaGalleryImages), $transform); 68 | } 69 | 70 | foreach ($changedTransforms as $id => $transform) { 71 | $this->helper->validate($this->helper->getImageNameForId($id, $mediaGalleryImages), $transform); 72 | } 73 | } 74 | 75 | /** 76 | * @param string $imageName 77 | * @param string $transform 78 | */ 79 | private function storeFreeTransformation($imageName, $transform) 80 | { 81 | $this->transformationFactory->create() 82 | ->setImageName($imageName) 83 | ->setFreeTransformation($transform) 84 | ->save(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Model/Config/Backend/ProductGalleryCustomFreeParams.php: -------------------------------------------------------------------------------- 1 | 'No error', 20 | JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', 21 | JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)', 22 | JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', 23 | JSON_ERROR_SYNTAX => 'Syntax error', 24 | JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded' 25 | ]; 26 | 27 | /** 28 | * @var ManagerInterface 29 | */ 30 | private $messageManager; 31 | 32 | /** 33 | * Application config 34 | * 35 | * @var ScopeConfigInterface 36 | */ 37 | protected $appConfig; 38 | 39 | public function __construct( 40 | Context $context, 41 | Registry $registry, 42 | ScopeConfigInterface $config, 43 | TypeListInterface $cacheTypeList, 44 | ManagerInterface $messageManager, 45 | ReinitableConfigInterface $appConfig, 46 | ?AbstractResource $resource = null, 47 | ?AbstractDb $resourceCollection = null, 48 | array $data = [] 49 | ) { 50 | $this->messageManager = $messageManager; 51 | $this->appConfig = $appConfig; 52 | 53 | parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data); 54 | } 55 | 56 | public function beforeSave() 57 | { 58 | $rawValue = $this->getValue(); 59 | 60 | parent::beforeSave(); 61 | 62 | $this->cacheTypeList->cleanType(\Magento\Framework\App\Cache\Type\Config::TYPE_IDENTIFIER); 63 | $this->appConfig->reinit(); 64 | 65 | if ($rawValue) { 66 | $data = json_decode($rawValue); 67 | if ($data === null || $data === false) { 68 | $this->setValue('{}'); 69 | try { 70 | if (json_last_error() !== JSON_ERROR_NONE) { 71 | $this->messageManager->addError(self::BAD_JSON_ERROR_MESSAGE . ' (' . self::JSON_ERRORS[json_last_error()] . ')'); 72 | } 73 | } catch (\Exception $e) { 74 | $this->messageManager->addError(self::BAD_JSON_ERROR_MESSAGE); 75 | } 76 | } else { 77 | $this->setValue(json_encode((array)$data)); 78 | } 79 | } else { 80 | $this->setValue('{}'); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /etc/csp_whitelist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | cloudinary.com 7 | *.cloudinary.com 8 | cdnjs.cloudflare.com 9 | *.youtube.com 10 | *.vimeo.com 11 | unpkg.com 12 | 13 | 14 | 15 | 16 | cloudinary.com 17 | *.cloudinary.com 18 | cdnjs.cloudflare.com 19 | 20 | 21 | 22 | 23 | cloudinary.com 24 | *.cloudinary.com 25 | 26 | 27 | 28 | 29 | cloudinary.com 30 | *.cloudinary.com 31 | 32 | 33 | 34 | 35 | cloudinary.com 36 | *.cloudinary.com 37 | data: 38 | blob: 39 | 40 | 41 | 42 | 43 | cloudinary.com 44 | *.cloudinary.com 45 | *.googleapis.com 46 | unpkg.com 47 | 48 | 49 | 50 | 51 | cloudinary.com 52 | *.cloudinary.com 53 | 54 | 55 | 56 | 57 | *.gstatic.com 58 | *.cloudinary.com 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Ajax/Free/Sample.php: -------------------------------------------------------------------------------- 1 | resultJsonFactory = $resultJsonFactory; 46 | $this->model = $model; 47 | $this->configuration = $configuration; 48 | 49 | parent::__construct($context); 50 | } 51 | 52 | public function execute() 53 | { 54 | $result = $this->resultJsonFactory->create(); 55 | 56 | try { 57 | $this->validateAjaxRequest(); 58 | $this->validateAccountConfigured(); 59 | 60 | $url = $this->model->sampleImageUrl( 61 | $this->defaultTransformWithFreeTransform($this->getRequest()->getParam('free')) 62 | ); 63 | 64 | $this->model->validate($url); 65 | 66 | return $result->setData(['url' => $url]); 67 | } catch (\Exception $e) { 68 | return $result->setHttpResponseCode(400)->setData(['error' => $e->getMessage()]); 69 | } 70 | } 71 | 72 | /** 73 | * @param string $freeTransform 74 | * @return Transformation 75 | */ 76 | private function defaultTransformWithFreeTransform($freeTransform) 77 | { 78 | return $this->configuration->getDefaultTransformation() 79 | ->withFreeform(Freeform::fromString($freeTransform), false); 80 | } 81 | 82 | /** 83 | * @throws \Exception 84 | */ 85 | private function validateAjaxRequest() 86 | { 87 | if (!$this->getRequest()->isAjax()) { 88 | throw new \Exception(self::NON_AJAX_REQUEST); 89 | } 90 | } 91 | 92 | /** 93 | * @throws \Exception 94 | */ 95 | private function validateAccountConfigured() 96 | { 97 | if (!$this->model->hasAccountConfigured()) { 98 | throw new \Exception(self::MISSING_ACCOUNT_DETAILS); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /view/frontend/templates/product/image_with_borders.phtml: -------------------------------------------------------------------------------- 1 | 7 | getVar('product_image_white_borders', 'Magento_Catalog'); 17 | $enableLazyLoadingWithoutBorders = (bool)$block->getVar( 18 | 'enable_lazy_loading_for_images_without_borders', 19 | 'Magento_Catalog' 20 | ); 21 | $width = (int)$block->getWidth(); 22 | $paddingBottom = $block->getRatio() * 100; 23 | ?> 24 | 25 | 26 | getCustomAttributes() as $name => $value): ?> 28 | escapeHtmlAttr($name) ?>="escapeHtmlAttr($value) ?>" 29 | 30 | src="getLazyloadPlaceholder() ?>" 31 | data-original="escapeUrl($block->getImageUrl()) ?>" 32 | 33 | width="escapeHtmlAttr($block->getWidth()) ?>" 34 | height="escapeHtmlAttr($block->getHeight()) ?>" 35 | 36 | max-width="escapeHtmlAttr($block->getWidth()) ?>" 37 | max-height="escapeHtmlAttr($block->getHeight()) ?>" 38 | 39 | alt="escapeHtmlAttr($block->getLabel()) ?>"/> 40 | 41 | getProductId()} { 44 | width: {$width}px; 45 | } 46 | .product-image-container-{$block->getProductId()} span.product-image-wrapper { 47 | padding-bottom: {$paddingBottom}%; 48 | } 49 | STYLE; 50 | //In case a script was using "style" attributes of these elements 51 | $script = <<