Cloudinary_Cloudinary - Version 1.2.0

Version Notes

v1.2.0

Release Highlights:

* Foldered migration

Download this release

Release Info

Developer Cloudinary
Extension Cloudinary_Cloudinary
Version 1.2.0
Comparing to
See all releases


Code changes from version 1.1.0 to 1.2.0

Files changed (40) hide show
  1. app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/Manage.php +40 -8
  2. app/code/community/Cloudinary/Cloudinary/Helper/Configuration.php +42 -3
  3. app/code/community/Cloudinary/Cloudinary/Helper/Image.php +11 -3
  4. app/code/community/Cloudinary/Cloudinary/Helper/Util/ArrayUtils.php +22 -0
  5. app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Image.php +10 -8
  6. app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Media.php +6 -2
  7. app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Media/Config.php +3 -2
  8. app/code/community/Cloudinary/Cloudinary/Model/Cms/Synchronisation.php +8 -4
  9. app/code/community/Cloudinary/Cloudinary/Model/{MediaCollectionCounter.php → CollectionCounter.php} +5 -7
  10. app/code/community/Cloudinary/Cloudinary/Model/Cron.php +4 -0
  11. app/code/community/Cloudinary/Cloudinary/Model/Image.php +10 -3
  12. app/code/community/Cloudinary/Cloudinary/Model/Logger.php +29 -0
  13. app/code/community/Cloudinary/Cloudinary/Model/MagentoFolderTranslator.php +81 -0
  14. app/code/community/Cloudinary/Cloudinary/Model/MigrationError.php +26 -0
  15. app/code/community/Cloudinary/Cloudinary/Model/PreConditionsValidator.php +7 -2
  16. app/code/community/Cloudinary/Cloudinary/Model/Resource/Cms/Synchronisation/Collection.php +40 -7
  17. app/code/community/Cloudinary/Cloudinary/Model/Resource/Media/Collection.php +9 -1
  18. app/code/community/Cloudinary/Cloudinary/Model/Resource/MigrationError.php +10 -0
  19. app/code/community/Cloudinary/Cloudinary/Model/Resource/MigrationError/Collection.php +10 -0
  20. app/code/community/Cloudinary/Cloudinary/Model/Resource/Synchronisation/Collection.php +25 -7
  21. app/code/community/Cloudinary/Cloudinary/Model/Synchronisation.php +16 -5
  22. app/code/community/Cloudinary/Cloudinary/Model/SynchronisedMediaUnifier.php +1 -1
  23. app/code/community/Cloudinary/Cloudinary/controllers/Adminhtml/CloudinaryController.php +12 -1
  24. app/code/community/Cloudinary/Cloudinary/data/cloudinary_setup/data-upgrade-1.1.2-1.1.3.php +3 -0
  25. app/code/community/Cloudinary/Cloudinary/etc/config.xml +5 -2
  26. app/code/community/Cloudinary/Cloudinary/etc/system.xml +11 -1
  27. app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.3-1.1.4.php +18 -0
  28. app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.4-1.1.5.php +20 -0
  29. app/design/adminhtml/default/default/template/cloudinary/manage.phtml +46 -2
  30. lib/Cloudinary/Api.php +0 -1
  31. lib/Cloudinary/Cloudinary.php +24 -22
  32. lib/Cloudinary/Uploader.php +22 -12
  33. lib/CloudinaryExtension/CloudinaryImageProvider.php +21 -2
  34. lib/CloudinaryExtension/Exception/MigrationError.php +50 -0
  35. lib/CloudinaryExtension/FolderTranslator.php +16 -0
  36. lib/CloudinaryExtension/Image.php +26 -8
  37. lib/CloudinaryExtension/Image/Synchronizable.php +2 -2
  38. lib/CloudinaryExtension/Migration/BatchUploader.php +41 -7
  39. lib/CloudinaryExtension/Migration/Logger.php +2 -0
  40. package.xml +8 -7
app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/Manage.php CHANGED
@@ -3,7 +3,6 @@
3
  class Cloudinary_Cloudinary_Block_Adminhtml_Manage extends Mage_Adminhtml_Block_Widget_Grid_Container
4
  {
5
  private $_migrationTask;
6
-
7
  private $_cloudinaryConfig;
8
 
9
  public function __construct()
@@ -23,9 +22,20 @@ class Cloudinary_Cloudinary_Block_Adminhtml_Manage extends Mage_Adminhtml_Block_
23
  parent::__construct();
24
  }
25
 
 
 
 
 
 
26
  public function getPercentComplete()
27
  {
28
- return $this->getSynchronizedImageCount() * 100 / $this->getTotalImageCount();
 
 
 
 
 
 
29
  }
30
 
31
  public function getSynchronizedImageCount()
@@ -35,11 +45,16 @@ class Cloudinary_Cloudinary_Block_Adminhtml_Manage extends Mage_Adminhtml_Block_
35
 
36
  public function getTotalImageCount()
37
  {
38
- $mediaCounter = Mage::getModel('cloudinary_cloudinary/mediaCollectionCounter')
39
- ->addCollection(Mage::getResourceModel('cloudinary_cloudinary/media_collection'))
40
- ->addCollection(Mage::getResourceModel('cloudinary_cloudinary/cms_synchronisation_collection'));
 
 
 
 
41
 
42
- return $mediaCounter->count();
 
43
  }
44
 
45
  public function isExtensionEnabled()
@@ -49,7 +64,11 @@ class Cloudinary_Cloudinary_Block_Adminhtml_Manage extends Mage_Adminhtml_Block_
49
 
50
  public function allImagesSynced()
51
  {
52
- return $this->getSynchronizedImageCount() === $this->getTotalImageCount();
 
 
 
 
53
  }
54
 
55
  public function getEnableButton()
@@ -78,6 +97,12 @@ class Cloudinary_Cloudinary_Block_Adminhtml_Manage extends Mage_Adminhtml_Block_
78
  return $this->_makeButton($startLabel, $startAction, $this->allImagesSynced());
79
  }
80
 
 
 
 
 
 
 
81
  private function _makeButton($label, $action, $disabled = false)
82
  {
83
  $button = $this->getLayout()->createBlock('adminhtml/widget_button')
@@ -90,4 +115,11 @@ class Cloudinary_Cloudinary_Block_Adminhtml_Manage extends Mage_Adminhtml_Block_
90
 
91
  return $button->toHtml();
92
  }
93
- }
 
 
 
 
 
 
 
3
  class Cloudinary_Cloudinary_Block_Adminhtml_Manage extends Mage_Adminhtml_Block_Widget_Grid_Container
4
  {
5
  private $_migrationTask;
 
6
  private $_cloudinaryConfig;
7
 
8
  public function __construct()
22
  parent::__construct();
23
  }
24
 
25
+ public function isFolderedMigration()
26
+ {
27
+ return $this->_cloudinaryConfig->isFolderedMigration();
28
+ }
29
+
30
  public function getPercentComplete()
31
  {
32
+ try {
33
+ if ($this->getTotalImageCount() != 0) {
34
+ return $this->getSynchronizedImageCount() * 100 / $this->getTotalImageCount();
35
+ }
36
+ } catch (Exception $e) {
37
+ return 'Unknown';
38
+ }
39
  }
40
 
41
  public function getSynchronizedImageCount()
45
 
46
  public function getTotalImageCount()
47
  {
48
+ try {
49
+ $collectionCounter = Mage::getModel('cloudinary_cloudinary/collectionCounter')
50
+ ->addCollection(Mage::getResourceModel('cloudinary_cloudinary/cms_synchronisation_collection'));
51
+ $result = $collectionCounter->count();
52
+ } catch (Exception $e) {
53
+ return 'Unknown';
54
+ }
55
 
56
+ $result += Mage::getResourceModel('cloudinary_cloudinary/media_collection')->uniqueImageCount();
57
+ return $result;
58
  }
59
 
60
  public function isExtensionEnabled()
64
 
65
  public function allImagesSynced()
66
  {
67
+ try {
68
+ return $this->getSynchronizedImageCount() === $this->getTotalImageCount();
69
+ } catch (Exception $e) {
70
+ return false;
71
+ }
72
  }
73
 
74
  public function getEnableButton()
97
  return $this->_makeButton($startLabel, $startAction, $this->allImagesSynced());
98
  }
99
 
100
+ public function getClearErrorsButton()
101
+ {
102
+ $areThereErrors = $this->getErrors();
103
+ return $this->_makeButton($areThereErrors ? 'Clear errors' : 'No errors to clear', 'clearErrors', !$areThereErrors);
104
+ }
105
+
106
  private function _makeButton($label, $action, $disabled = false)
107
  {
108
  $button = $this->getLayout()->createBlock('adminhtml/widget_button')
115
 
116
  return $button->toHtml();
117
  }
118
+
119
+ public function getErrors()
120
+ {
121
+ $coll = Mage::getModel('cloudinary_cloudinary/migrationError')->getCollection();
122
+ $coll->addOrder('timestamp');
123
+ return $coll->getItems();
124
+ }
125
+ }
app/code/community/Cloudinary/Cloudinary/Helper/Configuration.php CHANGED
@@ -24,12 +24,21 @@ class Cloudinary_Cloudinary_Helper_Configuration extends Mage_Core_Helper_Abstra
24
 
25
  const CONFIG_CDN_SUBDOMAIN = 'cloudinary/configuration/cloudinary_cdn_subdomain';
26
 
 
 
27
  const STATUS_ENABLED = 1;
28
 
29
  const STATUS_DISABLED = 0;
30
 
31
  const USER_PLATFORM_TEMPLATE = 'CloudinaryMagento/%s (Magento %s)';
32
 
 
 
 
 
 
 
 
33
  public function buildCredentials()
34
  {
35
  $environmentVariable = CloudinaryEnvironmentVariable::fromString($this->getEnvironmentVariable());
@@ -66,6 +75,29 @@ class Cloudinary_Cloudinary_Helper_Configuration extends Mage_Core_Helper_Abstra
66
  return (boolean)Mage::getStoreConfig(self::CONFIG_CDN_SUBDOMAIN);
67
  }
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  public function isEnabled()
70
  {
71
  return (boolean)Mage::getStoreConfig(self::CONFIG_PATH_ENABLED);
@@ -98,7 +130,7 @@ class Cloudinary_Cloudinary_Helper_Configuration extends Mage_Core_Helper_Abstra
98
 
99
  $config->setUserPlatform($this->getUserPlatform());
100
 
101
- if($this->getCdnSubdomainFlag()) {
102
  $config->enableCdnSubdomain();
103
  }
104
 
@@ -106,8 +138,7 @@ class Cloudinary_Cloudinary_Helper_Configuration extends Mage_Core_Helper_Abstra
106
  ->withGravity(Gravity::fromString($this->getDefaultGravity()))
107
  ->withFetchFormat(FetchFormat::fromString($this->getFetchFormat()))
108
  ->withQuality(Quality::fromString($this->getImageQuality()))
109
- ->withDpr(Dpr::fromString($this->getImageDpr()))
110
- ;
111
 
112
  return $config;
113
  }
@@ -118,4 +149,12 @@ class Cloudinary_Cloudinary_Helper_Configuration extends Mage_Core_Helper_Abstra
118
  $config->saveConfig($configPath, $value)->reinit();
119
  }
120
 
 
 
 
 
 
 
 
 
121
  }
24
 
25
  const CONFIG_CDN_SUBDOMAIN = 'cloudinary/configuration/cloudinary_cdn_subdomain';
26
 
27
+ const CONFIG_FOLDERED_MIGRATION = 'cloudinary/configuration/cloudinary_foldered_migration';
28
+
29
  const STATUS_ENABLED = 1;
30
 
31
  const STATUS_DISABLED = 0;
32
 
33
  const USER_PLATFORM_TEMPLATE = 'CloudinaryMagento/%s (Magento %s)';
34
 
35
+ private $folderTranslator;
36
+
37
+ public function __construct()
38
+ {
39
+ $this->folderTranslator = Mage::getModel('cloudinary_cloudinary/magentoFolderTranslator');
40
+ }
41
+
42
  public function buildCredentials()
43
  {
44
  $environmentVariable = CloudinaryEnvironmentVariable::fromString($this->getEnvironmentVariable());
75
  return (boolean)Mage::getStoreConfig(self::CONFIG_CDN_SUBDOMAIN);
76
  }
77
 
78
+ public function isFolderedMigration()
79
+ {
80
+ return Mage::getStoreConfigFlag(self::CONFIG_FOLDERED_MIGRATION);
81
+ }
82
+
83
+ public function getMigratedPath($file)
84
+ {
85
+ if ($this->isFolderedMigration()) {
86
+ $result = $this->folderTranslator->translate($file);
87
+ } else {
88
+ $result = basename($file);
89
+ }
90
+ return $result;
91
+ }
92
+
93
+ public function reverseMigratedPathIfNeeded($migratedPath)
94
+ {
95
+ if ($this->isFolderedMigration()) {
96
+ return $this->folderTranslator->reverse($migratedPath);
97
+ }
98
+ return $migratedPath;
99
+ }
100
+
101
  public function isEnabled()
102
  {
103
  return (boolean)Mage::getStoreConfig(self::CONFIG_PATH_ENABLED);
130
 
131
  $config->setUserPlatform($this->getUserPlatform());
132
 
133
+ if ($this->getCdnSubdomainFlag()) {
134
  $config->enableCdnSubdomain();
135
  }
136
 
138
  ->withGravity(Gravity::fromString($this->getDefaultGravity()))
139
  ->withFetchFormat(FetchFormat::fromString($this->getFetchFormat()))
140
  ->withQuality(Quality::fromString($this->getImageQuality()))
141
+ ->withDpr(Dpr::fromString($this->getImageDpr()));
 
142
 
143
  return $config;
144
  }
149
  $config->saveConfig($configPath, $value)->reinit();
150
  }
151
 
152
+ /**
153
+ * @return Cloudinary_Cloudinary_Helper_Configuration
154
+ */
155
+ public static function getInstance()
156
+ {
157
+ return Mage::helper('cloudinary_cloudinary/configuration');
158
+ }
159
+
160
  }
app/code/community/Cloudinary/Cloudinary/Helper/Image.php CHANGED
@@ -48,17 +48,25 @@ class Cloudinary_Cloudinary_Helper_Image extends Mage_Catalog_Helper_Image
48
 
49
  public function __toString()
50
  {
 
51
  $imageFile = $this->_getRequestedImageFile();
52
 
53
  if ($this->_imageShouldComeFromCloudinary($imageFile)) {
54
- $image = Image::fromPath($imageFile);
55
 
56
  $transformation = $this->_configuration->getDefaultTransformation()
57
  ->withDimensions($this->_dimensions);
58
 
59
- return (string)$this->_imageProvider->transformImage($image, $transformation);
 
 
60
  }
 
 
61
 
62
- return parent::__toString();
 
 
63
  }
 
64
  }
48
 
49
  public function __toString()
50
  {
51
+ $result = null;
52
  $imageFile = $this->_getRequestedImageFile();
53
 
54
  if ($this->_imageShouldComeFromCloudinary($imageFile)) {
55
+ $image = Cloudinary_Cloudinary_Helper_Image::newApiImage($imageFile);
56
 
57
  $transformation = $this->_configuration->getDefaultTransformation()
58
  ->withDimensions($this->_dimensions);
59
 
60
+ $result = (string)$this->_imageProvider->transformImage($image, $transformation);
61
+ } else {
62
+ $result = parent::__toString();
63
  }
64
+ return $result;
65
+ }
66
 
67
+ public static function newApiImage($path){
68
+ $migratedPath = Cloudinary_Cloudinary_Helper_Configuration::getInstance()->getMigratedPath($path);
69
+ return Image::fromPath($path, $migratedPath);
70
  }
71
+
72
  }
app/code/community/Cloudinary/Cloudinary/Helper/Util/ArrayUtils.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudinary_Cloudinary_Helper_Util_ArrayUtils
4
+ {
5
+
6
+ /**
7
+ * Results with a subset of an associative array, preserving only the values that have a key that is present in $keys
8
+ *
9
+ * @param $array the original array we want to select from
10
+ * @param $keys the keys to preserve in the input array
11
+ * @return array
12
+ */
13
+ public static function arraySelect($array, $keys)
14
+ {
15
+ $result = [];
16
+ foreach ($keys as $key) {
17
+ $result[$key] = $array[$key];
18
+ }
19
+ return $result;
20
+ }
21
+ }
22
+
app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Image.php CHANGED
@@ -9,13 +9,15 @@ class Cloudinary_Cloudinary_Model_Catalog_Product_Image extends Mage_Catalog_Mod
9
 
10
  public function getUrl()
11
  {
12
- if ($this->_imageShouldComeFromCloudinary($this->_newFile)) {
13
-
14
- $imageProvider = CloudinaryImageProvider::fromConfiguration($this->_getConfigHelper()->buildConfiguration());
15
-
16
- return (string)$imageProvider->transformImage(Image::fromPath($this->_newFile));
 
 
17
  }
18
-
19
- return parent::getUrl();
20
  }
21
- }
9
 
10
  public function getUrl()
11
  {
12
+ $config = $this->_getConfigHelper();
13
+ $file = $this->_newFile;
14
+ if ($this->_imageShouldComeFromCloudinary($file)) {
15
+ $imageProvider = CloudinaryImageProvider::fromConfiguration($config->buildConfiguration());
16
+ $result = (string)$imageProvider->transformImage(Cloudinary_Cloudinary_Helper_Image::newApiImage($file));
17
+ } else {
18
+ $result = parent::getUrl();
19
  }
20
+ Cloudinary_Cloudinary_Model_Logger::getInstance()->debugLog($result);
21
+ return $result;
22
  }
23
+ }
app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Media.php CHANGED
@@ -1,5 +1,5 @@
1
  <?php
2
-
3
  class Cloudinary_Cloudinary_Model_Catalog_Product_Media extends Mage_Core_Model_Abstract
4
  {
5
 
@@ -41,7 +41,11 @@ class Cloudinary_Cloudinary_Model_Catalog_Product_Media extends Mage_Core_Model_
41
 
42
  private function _getRemovedImages(array $mediaGallery)
43
  {
44
- return array_filter(json_decode($mediaGallery['images'], true), array($this, '_isImageRemoved'));
 
 
 
 
45
  }
46
 
47
  private function _isImageRemoved($toFilter)
1
  <?php
2
+
3
  class Cloudinary_Cloudinary_Model_Catalog_Product_Media extends Mage_Core_Model_Abstract
4
  {
5
 
41
 
42
  private function _getRemovedImages(array $mediaGallery)
43
  {
44
+ if (!is_array($mediaGallery['images'])) {
45
+ $mediaGallery['images'] = json_decode($mediaGallery['images'], true);
46
+ }
47
+
48
+ return array_filter($mediaGallery['images'], array($this, '_isImageRemoved'));
49
  }
50
 
51
  private function _isImageRemoved($toFilter)
app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Media/Config.php CHANGED
@@ -28,8 +28,9 @@ class Cloudinary_Cloudinary_Model_Catalog_Product_Media_Config extends Mage_Cata
28
 
29
  private function _getUrlForImage($file)
30
  {
31
- $imageProvider = CloudinaryImageProvider::fromConfiguration($this->_getConfigHelper()->buildConfiguration());
 
32
 
33
- return (string)$imageProvider->transformImage(Image::fromPath($file));
34
  }
35
  }
28
 
29
  private function _getUrlForImage($file)
30
  {
31
+ $config = Cloudinary_Cloudinary_Helper_Configuration::getInstance();
32
+ $imageProvider = CloudinaryImageProvider::fromConfiguration($config->buildConfiguration());
33
 
34
+ return (string)$imageProvider->transformImage(Cloudinary_Cloudinary_Helper_Image::newApiImage($file));
35
  }
36
  }
app/code/community/Cloudinary/Cloudinary/Model/Cms/Synchronisation.php CHANGED
@@ -18,17 +18,21 @@ class Cloudinary_Cloudinary_Model_Cms_Synchronisation extends Mage_Core_Model_Ab
18
  public function setValue($fileName)
19
  {
20
  $this->setData('basename', basename($fileName));
21
-
22
  return $this;
23
  }
24
 
 
 
 
 
 
25
  public function tagAsSynchronized()
26
  {
27
- $this->setData('image_name', $this->getData('basename'));
28
  $this->setData('media_gallery_id', null);
29
  $this->setData('cloudinary_synchronisation_id', null);
30
-
 
31
  $this->save();
32
  }
33
 
34
- }
18
  public function setValue($fileName)
19
  {
20
  $this->setData('basename', basename($fileName));
 
21
  return $this;
22
  }
23
 
24
+ public function getRelativePath(){
25
+ $helperConfig = Mage::helper('cloudinary_cloudinary/configuration');
26
+ return $helperConfig->getMigratedPath($this->getFilename());
27
+ }
28
+
29
  public function tagAsSynchronized()
30
  {
 
31
  $this->setData('media_gallery_id', null);
32
  $this->setData('cloudinary_synchronisation_id', null);
33
+ $this->setData('image_name', $this->getRelativePath());
34
+ Cloudinary_Cloudinary_Model_Logger::getInstance()->debugLog( json_encode($this->toArray(), JSON_PRETTY_PRINT));
35
  $this->save();
36
  }
37
 
38
+ }
app/code/community/Cloudinary/Cloudinary/Model/{MediaCollectionCounter.php → CollectionCounter.php} RENAMED
@@ -1,9 +1,8 @@
1
  <?php
2
 
3
- class Cloudinary_Cloudinary_Model_MediaCollectionCounter implements Countable
4
  {
5
-
6
- private $_collections = array();
7
 
8
  public function addCollection(Varien_Data_Collection $collection)
9
  {
@@ -14,11 +13,10 @@ class Cloudinary_Cloudinary_Model_MediaCollectionCounter implements Countable
14
 
15
  public function count()
16
  {
17
- $mediaCount = 0;
18
  foreach ($this->_collections as $collection) {
19
- $mediaCount += $collection->getSize();
20
  }
21
- return $mediaCount;
22
  }
23
-
24
  }
1
  <?php
2
 
3
+ class Cloudinary_Cloudinary_Model_CollectionCounter implements Countable
4
  {
5
+ private $_collections = [];
 
6
 
7
  public function addCollection(Varien_Data_Collection $collection)
8
  {
13
 
14
  public function count()
15
  {
16
+ $count = 0;
17
  foreach ($this->_collections as $collection) {
18
+ $count += $collection->getSize();
19
  }
20
+ return $count;
21
  }
 
22
  }
app/code/community/Cloudinary/Cloudinary/Model/Cron.php CHANGED
@@ -38,6 +38,10 @@ class Cloudinary_Cloudinary_Model_Cron extends Mage_Core_Model_Abstract
38
 
39
  $migrationQueue->process();
40
 
 
 
 
 
41
  return $this;
42
  }
43
  }
38
 
39
  $migrationQueue->process();
40
 
41
+ foreach ($batchUploader->getMigrationErrors() as $error) {
42
+ Cloudinary_Cloudinary_Model_MigrationError::saveFromException($error);
43
+ }
44
+
45
  return $this;
46
  }
47
  }
app/code/community/Cloudinary/Cloudinary/Model/Image.php CHANGED
@@ -8,10 +8,16 @@ class Cloudinary_Cloudinary_Model_Image extends Mage_Core_Model_Abstract
8
  {
9
  use Cloudinary_Cloudinary_Model_PreConditionsValidator;
10
 
 
 
11
  public function upload(array $imageDetails)
12
  {
 
 
 
 
13
  $imageManager = $this->_getImageProvider();
14
- $imageManager->upload(Image::fromPath($this->_imageFullPathFromImageDetails($imageDetails)));
15
 
16
  Mage::getModel('cloudinary_cloudinary/synchronisation')
17
  ->setValueId($imageDetails['value_id'])
@@ -39,13 +45,14 @@ class Cloudinary_Cloudinary_Model_Image extends Mage_Core_Model_Abstract
39
 
40
  public function deleteImage($imageName)
41
  {
42
- $this->_getImageProvider()->deleteImage(Image::fromPath($imageName));
43
  }
44
 
45
  public function getUrl($imagePath)
46
  {
47
  $imageProvider = $this->_getImageProvider();
48
- return (string)$imageProvider->transformImage(Image::fromPath($imagePath));
 
49
  }
50
 
51
  private function _getImageProvider()
8
  {
9
  use Cloudinary_Cloudinary_Model_PreConditionsValidator;
10
 
11
+ private $_folder;
12
+
13
  public function upload(array $imageDetails)
14
  {
15
+ if ($this->_getConfigHelper()->isFolderedMigration()) {
16
+ $this->_folder = $this->_getConfigHelper()->getMigratedPath($imageDetails['file']);
17
+ }
18
+
19
  $imageManager = $this->_getImageProvider();
20
+ $imageManager->upload(Image::fromPath($this->_imageFullPathFromImageDetails($imageDetails), $this->_folder));
21
 
22
  Mage::getModel('cloudinary_cloudinary/synchronisation')
23
  ->setValueId($imageDetails['value_id'])
45
 
46
  public function deleteImage($imageName)
47
  {
48
+ $this->_getImageProvider()->deleteImage(Cloudinary_Cloudinary_Helper_Image::newApiImage($imageName));
49
  }
50
 
51
  public function getUrl($imagePath)
52
  {
53
  $imageProvider = $this->_getImageProvider();
54
+
55
+ return (string)$imageProvider->transformImage(Cloudinary_Cloudinary_Helper_Image::newApiImage($imagePath));
56
  }
57
 
58
  private function _getImageProvider()
app/code/community/Cloudinary/Cloudinary/Model/Logger.php CHANGED
@@ -2,6 +2,8 @@
2
 
3
  class Cloudinary_Cloudinary_Model_Logger extends Mage_Core_Model_Abstract implements \CloudinaryExtension\Migration\Logger
4
  {
 
 
5
  public function warning($message, array $context = array())
6
  {
7
  Mage::log($message, Zend_Log::WARN);
@@ -16,4 +18,31 @@ class Cloudinary_Cloudinary_Model_Logger extends Mage_Core_Model_Abstract implem
16
  {
17
  Mage::log($message, Zend_Log::ERR);
18
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  }
2
 
3
  class Cloudinary_Cloudinary_Model_Logger extends Mage_Core_Model_Abstract implements \CloudinaryExtension\Migration\Logger
4
  {
5
+ const SIGNATURE_TEMPLATE = "%s::%s ";
6
+
7
  public function warning($message, array $context = array())
8
  {
9
  Mage::log($message, Zend_Log::WARN);
18
  {
19
  Mage::log($message, Zend_Log::ERR);
20
  }
21
+
22
+ public function debugLog($message)
23
+ {
24
+ if (Mage::getIsDeveloperMode()){
25
+ Mage::log($this->getSignature() . $message . "\n", 1, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['class']);
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Add extra information to a log entry: class and funcion name from which the log is called
31
+ * @param $message
32
+ * @return string
33
+ */
34
+ public static function getSignature()
35
+ {
36
+ $parentTrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2];
37
+ $logSignature = sprintf(self::SIGNATURE_TEMPLATE, $parentTrace['class'], $parentTrace['function']);
38
+ return $logSignature;
39
+ }
40
+
41
+ /**
42
+ * @return Cloudinary_Cloudinary_Model_Logger
43
+ */
44
+ public static function getInstance()
45
+ {
46
+ return Mage::getModel('cloudinary_cloudinary/logger');
47
+ }
48
  }
app/code/community/Cloudinary/Cloudinary/Model/MagentoFolderTranslator.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudinary_Cloudinary_Model_MagentoFolderTranslator implements \CloudinaryExtension\FolderTranslator
4
+ {
5
+ private $absolutePathRegex;
6
+ private $wysiwygPathRegex;
7
+ private $productPathRegex;
8
+ private $mediaDir;
9
+
10
+ public function __construct()
11
+ {
12
+ $baseDir = Mage::getBaseDir();
13
+ $mediaDir = $this->mediaDir = $this->removeMagentoBaseDir(Mage::getBaseDir('media'));
14
+ $this->absolutePathRegex = "#^$baseDir/.*#"; //anything starting with the magento base dir
15
+ $this->mediaPathRegex = "#^$mediaDir/.*#"; //anything starting with the media folder
16
+ $this->wysiwygPathRegex = "#^/wysiwyg/.*#"; // anything starting with the /wysiwyg folder
17
+ $this->productPathRegex = "#(^/cache/.*|^/(\\w/){2})#"; // any path that has '/cache/' prefix, or '/l/l/' structure, where l is a single letter
18
+ }
19
+
20
+ /**
21
+ * The method tries to find out the absolute path of the input, and remove the magento base directory from it (this method is typically called via a product image path, a wysiwyg path, or an absolute path)
22
+ *
23
+ * @param $path
24
+ * @return mixed the input, truncated from the magento base directory
25
+ */
26
+ public function translate($path)
27
+ {
28
+ $baseName = basename($path);
29
+ $result = $this->unifiedDirName($path);
30
+ $debug = $result;
31
+
32
+ $baseDir = Mage::getBaseDir();
33
+
34
+ if (preg_match($this->absolutePathRegex, $result)) {
35
+ // the input is absolute, we truncate the magento base dir to get the relative path
36
+ $result = preg_replace("#^$baseDir#", '', $result);
37
+
38
+ } else if (preg_match($this->productPathRegex, $result)) {
39
+ /* the input appears to be a product image, we insert the path to product images relative to magento base dir
40
+ * (by default /media/catalog/product/ ) */
41
+ $catalogMediapath = Mage::getSingleton('catalog/product_media_config')->getBaseMediaPath();
42
+ $result = $this->removeMagentoBaseDir($catalogMediapath) . $result;
43
+
44
+ } else if (preg_match($this->mediaPathRegex, $result)) {
45
+ // the input appears to be relative to the magento base dir
46
+ // NOP, the result should be the input
47
+
48
+ } else {
49
+ // we just assume the input is relative to the media library, in which case the relative path is "/media/$path" (in defualt case)
50
+ $result = $this->mediaDir . $result;
51
+ }
52
+ $result .= $baseName;
53
+ Cloudinary_Cloudinary_Model_Logger::getInstance()->debugLog("$path => $debug => $result");
54
+ return $result;
55
+ }
56
+
57
+ public function reverse($path)
58
+ {
59
+ return str_replace(DS . DS, DS, Mage::getBaseDir() . DS . $path);
60
+ }
61
+
62
+ /**
63
+ * Appends DS the the start of the path, and removes duplicate DS-es
64
+ *
65
+ * @param $path
66
+ * @return mixed
67
+ */
68
+ private static function unifiedDirName($path)
69
+ {
70
+ return str_replace(DS . DS, DS, DS . dirname($path) . DS);
71
+ }
72
+
73
+ /**
74
+ * @param $path an absolute path pointing somewhere inside magento folder structure
75
+ * @return mixed the path relative to the magento base directory
76
+ */
77
+ private static function removeMagentoBaseDir($path)
78
+ {
79
+ return str_replace(Mage::getBaseDir(), '', $path);
80
+ }
81
+ }
app/code/community/Cloudinary/Cloudinary/Model/MigrationError.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+
4
+ class Cloudinary_Cloudinary_Model_MigrationError extends Mage_Core_Model_Abstract
5
+ {
6
+ public function __construct()
7
+ {
8
+ $this->_init('cloudinary_cloudinary/migrationError');
9
+ }
10
+
11
+ public static function saveFromException(\CloudinaryExtension\Exception\MigrationError $e)
12
+ {
13
+ $image = $e->getImage();
14
+ $filePath = (string)$image;
15
+
16
+ $entry = Mage::getModel('cloudinary_cloudinary/migrationError');
17
+ $entry->setFilePath($filePath);
18
+
19
+ $entry->setRelativePath($image->getRelativePath());
20
+ $entry->setMessage($e->getMessage());
21
+ $entry->setCode($e->getCode());
22
+ $entry->setTimestamp(time());
23
+
24
+ $entry->save();
25
+ }
26
+ }
app/code/community/Cloudinary/Cloudinary/Model/PreConditionsValidator.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  trait Cloudinary_Cloudinary_Model_PreConditionsValidator
3
  {
4
  private function _isEnabled()
@@ -11,6 +12,9 @@ trait Cloudinary_Cloudinary_Model_PreConditionsValidator
11
  return Mage::getModel('cloudinary_cloudinary/synchronisation')->isImageInCloudinary($imageName);
12
  }
13
 
 
 
 
14
  private function _getConfigHelper()
15
  {
16
  return Mage::helper('cloudinary_cloudinary/configuration');
@@ -18,7 +22,8 @@ trait Cloudinary_Cloudinary_Model_PreConditionsValidator
18
 
19
  private function _imageShouldComeFromCloudinary($file)
20
  {
21
- return $this->_isEnabled() && $this->_isImageInCloudinary(basename($file));
 
 
22
  }
23
  }
24
-
1
  <?php
2
+
3
  trait Cloudinary_Cloudinary_Model_PreConditionsValidator
4
  {
5
  private function _isEnabled()
12
  return Mage::getModel('cloudinary_cloudinary/synchronisation')->isImageInCloudinary($imageName);
13
  }
14
 
15
+ /**
16
+ * @return Cloudinary_Cloudinary_Helper_Configuration
17
+ */
18
  private function _getConfigHelper()
19
  {
20
  return Mage::helper('cloudinary_cloudinary/configuration');
22
 
23
  private function _imageShouldComeFromCloudinary($file)
24
  {
25
+ $relativePath = $this->_getConfigHelper()->getMigratedPath($file);
26
+ $result = $this->_isEnabled() && $this->_isImageInCloudinary($relativePath);
27
+ return $result;
28
  }
29
  }
 
app/code/community/Cloudinary/Cloudinary/Model/Resource/Cms/Synchronisation/Collection.php CHANGED
@@ -6,37 +6,70 @@ class Cloudinary_Cloudinary_Model_Resource_Cms_Synchronisation_Collection
6
  extends Mage_Cms_Model_Wysiwyg_Images_Storage_Collection
7
  implements SynchronizedMediaRepository
8
  {
 
 
 
 
 
 
9
 
10
  public function __construct()
11
  {
12
  $this->addTargetDir(Mage::helper('cms/wysiwyg_images')->getStorageRoot());
13
  $this->setItemObjectClass('cloudinary_cloudinary/cms_synchronisation');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  }
15
 
16
  public function findUnsynchronisedImages()
17
  {
18
- $this->addFieldToFilter('basename', array('nin' => $this->_getSynchronisedImageNames()));
 
 
 
 
 
19
 
 
20
  return $this->getItems();
21
  }
22
 
23
  private function _getSynchronisedImageNames()
24
  {
25
- return array_map(
26
- function ($itemData) {
27
- return $itemData['image_name'];
 
 
28
  },
29
  $this->_getSynchronisedImageData()
30
  );
 
 
31
  }
32
 
33
  private function _getSynchronisedImageData()
34
  {
35
- return Mage::getResourceModel('cloudinary_cloudinary/synchronisation_collection')
36
  ->addFieldToSelect('image_name')
37
  ->addFieldToFilter('media_gallery_id', array('null' => true))
38
- ->distinct(true)
39
  ->getData();
 
40
  }
41
 
42
- }
6
  extends Mage_Cms_Model_Wysiwyg_Images_Storage_Collection
7
  implements SynchronizedMediaRepository
8
  {
9
+ /**
10
+ * @var string[]
11
+ * @link http://cloudinary.com/documentation/image_transformations#format_conversion
12
+ * @link http://cloudinary.com/documentation/upload_images
13
+ */
14
+ private $allowedImgExtensions = ['JPG', 'PNG', 'GIF', 'BMP', 'TIFF', 'EPS', 'PSD', 'SVG', 'WebP'];
15
 
16
  public function __construct()
17
  {
18
  $this->addTargetDir(Mage::helper('cms/wysiwyg_images')->getStorageRoot());
19
  $this->setItemObjectClass('cloudinary_cloudinary/cms_synchronisation');
20
+ $this->setFilesFilter(
21
+ sprintf('#^[a-z0-9\.\-\_]+\.(?:%s)$#i', implode('|', $this->allowedImgExtensions))
22
+ );
23
+ }
24
+
25
+ public function addTargetDir($value)
26
+ {
27
+ try {
28
+ parent::addTargetDir($value);
29
+ } catch (Exception $e) {
30
+ Mage::logException($e);
31
+ if (!Mage::registry('error_' . $value)) {
32
+ Mage::getSingleton('core/session')->addError("Couldn't find path " . $value);
33
+ Mage::register('error_' . $value, true);
34
+ }
35
+ throw $e;
36
+ }
37
  }
38
 
39
  public function findUnsynchronisedImages()
40
  {
41
+ $helperConfig = Mage::helper('cloudinary_cloudinary/configuration');
42
+ if ($helperConfig->isFolderedMigration()){
43
+ $this->addFieldToFilter('filename', array('nin' => $this->_getSynchronisedImageNames()));
44
+ } else {
45
+ $this->addFieldToFilter('basename', array('nin' => $this->_getSynchronisedImageNames()));
46
+ }
47
 
48
+ Cloudinary_Cloudinary_Model_Logger::getInstance()->debugLog(json_encode($this->toArray(), JSON_PRETTY_PRINT));
49
  return $this->getItems();
50
  }
51
 
52
  private function _getSynchronisedImageNames()
53
  {
54
+ $helperConfig = Cloudinary_Cloudinary_Helper_Configuration::getInstance();
55
+ $result = array_map(
56
+ function ($itemData) use ($helperConfig) {
57
+ $imageName = $itemData['image_name'];
58
+ return $helperConfig->reverseMigratedPathIfNeeded($imageName);
59
  },
60
  $this->_getSynchronisedImageData()
61
  );
62
+ Cloudinary_Cloudinary_Model_Logger::getInstance()->debugLog(print_r($result, true));
63
+ return $result;
64
  }
65
 
66
  private function _getSynchronisedImageData()
67
  {
68
+ $result = Mage::getResourceModel('cloudinary_cloudinary/synchronisation_collection')
69
  ->addFieldToSelect('image_name')
70
  ->addFieldToFilter('media_gallery_id', array('null' => true))
 
71
  ->getData();
72
+ return $result;
73
  }
74
 
75
+ }
app/code/community/Cloudinary/Cloudinary/Model/Resource/Media/Collection.php CHANGED
@@ -7,4 +7,12 @@ class Cloudinary_Cloudinary_Model_Resource_Media_Collection extends Mage_Core_Mo
7
  {
8
  $this->_init('catalog/product_attribute_backend_media');
9
  }
10
- }
 
 
 
 
 
 
 
 
7
  {
8
  $this->_init('catalog/product_attribute_backend_media');
9
  }
10
+
11
+ public function uniqueImageCount()
12
+ {
13
+ $table = $this->getMainTable();
14
+ $query = "select count(distinct value) from $table";
15
+
16
+ return $this->getConnection()->query($query)->fetchColumn();
17
+ }
18
+ }
app/code/community/Cloudinary/Cloudinary/Model/Resource/MigrationError.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudinary_Cloudinary_Model_Resource_MigrationError extends Mage_Core_Model_Resource_Db_Abstract
4
+ {
5
+ protected function _construct()
6
+ {
7
+ $this->_init('cloudinary_cloudinary/migrationError', 'file_path');
8
+ $this->_isPkAutoIncrement = false;
9
+ }
10
+ }
app/code/community/Cloudinary/Cloudinary/Model/Resource/MigrationError/Collection.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudinary_Cloudinary_Model_Resource_MigrationError_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract
4
+ {
5
+
6
+ protected function _construct()
7
+ {
8
+ $this->_init('cloudinary_cloudinary/migrationError');
9
+ }
10
+ }
app/code/community/Cloudinary/Cloudinary/Model/Resource/Synchronisation/Collection.php CHANGED
@@ -31,16 +31,34 @@ class Cloudinary_Cloudinary_Model_Resource_Synchronisation_Collection
31
  return $resource->getMainTable();
32
  }
33
 
34
- public function findUnsynchronisedImages($limit=200)
35
  {
36
  $tableName = Mage::getSingleton('core/resource')->getTableName('cloudinary_cloudinary/catalog_media_gallery');
 
37
 
38
- $this->getSelect()
39
- ->joinRight($tableName, 'value_id=media_gallery_id', '*')
40
- ->where('cloudinary_synchronisation_id is null')
41
- ->limit($limit)
42
- ;
43
 
 
 
 
 
 
 
 
 
44
  return $this->getItems();
45
  }
46
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  return $resource->getMainTable();
32
  }
33
 
34
+ public function findUnsynchronisedImages($limit = 100)
35
  {
36
  $tableName = Mage::getSingleton('core/resource')->getTableName('cloudinary_cloudinary/catalog_media_gallery');
37
+ $syncedImagesQuery = $this->getQueryForSyncedImageNames();
38
 
39
+ $select = $this->getSelect();
 
 
 
 
40
 
41
+ $select
42
+ ->joinRight($tableName, 'value_id=media_gallery_id', '*')
43
+ ->group('value')
44
+ ->order('value')
45
+ ->where("cloudinary_synchronisation_id is null and value not in ($syncedImagesQuery)")
46
+ ->limit($limit);
47
+
48
+ Cloudinary_Cloudinary_Model_Logger::getInstance()->debugLog(print_r($this->toArray(), true));
49
  return $this->getItems();
50
  }
51
+
52
+ /**
53
+ * basically returns with all product image's media_gallery stored name which has been synced
54
+ *
55
+ * @return Varien_Db_Select
56
+ */
57
+ private function getQueryForSyncedImageNames()
58
+ {
59
+ $select = clone $this->getSelect();
60
+ $select->reset(Zend_Db_Select::COLUMNS);
61
+ $select->where('media_gallery_value is not null');
62
+ return $select->columns('media_gallery_value');
63
+ }
64
+ }
app/code/community/Cloudinary/Cloudinary/Model/Synchronisation.php CHANGED
@@ -12,17 +12,21 @@ class Cloudinary_Cloudinary_Model_Synchronisation extends Mage_Core_Model_Abstra
12
 
13
  public function tagAsSynchronized()
14
  {
15
- $this->setData('image_name', basename($this['value']));
16
  $this->setData('media_gallery_id', $this['value_id']);
 
17
  $this->unsetData('value_id');
18
-
19
  $this->save();
20
  }
21
 
22
  public function isImageInCloudinary($imageName)
23
  {
24
- $this->load($imageName, 'image_name');
25
- return !is_null($this->getId());
 
 
 
26
  }
27
 
28
  public function getFilename()
@@ -33,8 +37,15 @@ class Cloudinary_Cloudinary_Model_Synchronisation extends Mage_Core_Model_Abstra
33
  return $this->_baseMediaPath() . $this->getValue();
34
  }
35
 
 
 
 
 
 
 
 
36
  private function _baseMediaPath()
37
  {
38
  return Mage::getModel('catalog/product_media_config')->getBaseMediaPath();
39
  }
40
- }
12
 
13
  public function tagAsSynchronized()
14
  {
15
+ $this->setData('image_name', $this->getRelativePath());
16
  $this->setData('media_gallery_id', $this['value_id']);
17
+ $this->setData('media_gallery_value', $this['value']);
18
  $this->unsetData('value_id');
19
+ Cloudinary_Cloudinary_Model_Logger::getInstance()->debugLog( json_encode($this->toArray(), JSON_PRETTY_PRINT));
20
  $this->save();
21
  }
22
 
23
  public function isImageInCloudinary($imageName)
24
  {
25
+ $coll = $this->getCollection();
26
+ $table = $coll->getMainTable();
27
+ // case sensitive check
28
+ $query = "select count(*) from $table where binary image_name = '$imageName' limit 1";
29
+ return $coll->getConnection()->query($query)->fetchColumn() > 0;
30
  }
31
 
32
  public function getFilename()
37
  return $this->_baseMediaPath() . $this->getValue();
38
  }
39
 
40
+
41
+ public function getRelativePath()
42
+ {
43
+ $helperConfig = Mage::helper('cloudinary_cloudinary/configuration');
44
+ return $helperConfig->getMigratedPath($this->getFilename());
45
+ }
46
+
47
  private function _baseMediaPath()
48
  {
49
  return Mage::getModel('catalog/product_media_config')->getBaseMediaPath();
50
  }
51
+ }
app/code/community/Cloudinary/Cloudinary/Model/SynchronisedMediaUnifier.php CHANGED
@@ -24,4 +24,4 @@ class Cloudinary_Cloudinary_Model_SynchronisedMediaUnifier implements Synchroniz
24
  return array_slice($this->_unsychronisedImages, 0, $limit);
25
  }
26
 
27
- }
24
  return array_slice($this->_unsychronisedImages, 0, $limit);
25
  }
26
 
27
+ }
app/code/community/Cloudinary/Cloudinary/controllers/Adminhtml/CloudinaryController.php CHANGED
@@ -53,6 +53,17 @@ class Cloudinary_Cloudinary_Adminhtml_CloudinaryController extends Mage_Adminhtm
53
  $this->_redirectToManageCloudinary();
54
  }
55
 
 
 
 
 
 
 
 
 
 
 
 
56
  private function _redirectToManageCloudinary()
57
  {
58
  return $this->_redirect('*/cloudinary');
@@ -63,4 +74,4 @@ class Cloudinary_Cloudinary_Adminhtml_CloudinaryController extends Mage_Adminhtm
63
  return $this->getLayout()->createBlock('core/text')->setText('<meta http-equiv="refresh" content="5">');
64
  }
65
 
66
- }
53
  $this->_redirectToManageCloudinary();
54
  }
55
 
56
+ public function clearErrorsAction()
57
+ {
58
+ $items = Mage::getModel('cloudinary_cloudinary/migrationError')->getCollection()->getItems();
59
+
60
+ foreach ($items as $error){
61
+ $error->delete();
62
+ }
63
+
64
+ $this->_redirectToManageCloudinary();
65
+ }
66
+
67
  private function _redirectToManageCloudinary()
68
  {
69
  return $this->_redirect('*/cloudinary');
74
  return $this->getLayout()->createBlock('core/text')->setText('<meta http-equiv="refresh" content="5">');
75
  }
76
 
77
+ }
app/code/community/Cloudinary/Cloudinary/data/cloudinary_setup/data-upgrade-1.1.2-1.1.3.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php
2
+ $config = new Mage_Core_Model_Config();
3
+ $config->saveConfig('cloudinary/cloud/cloudinary_foldered_migration', '0', 'default', 0);
app/code/community/Cloudinary/Cloudinary/etc/config.xml CHANGED
@@ -2,7 +2,7 @@
2
  <config>
3
  <modules>
4
  <Cloudinary_Cloudinary>
5
- <version>1.1.0</version>
6
  </Cloudinary_Cloudinary>
7
  </modules>
8
  <global>
@@ -44,6 +44,9 @@
44
  <catalog_media_gallery>
45
  <table>catalog_product_entity_media_gallery</table>
46
  </catalog_media_gallery>
 
 
 
47
  </entities>
48
  </cloudinary_resource>
49
  </models>
@@ -152,7 +155,7 @@
152
  <jobs>
153
  <cloudinary_migrate>
154
  <schedule>
155
- <cron_expr>* * * * *</cron_expr>
156
  </schedule>
157
  <run>
158
  <model>cloudinary_cloudinary/cron::migrateImages</model>
2
  <config>
3
  <modules>
4
  <Cloudinary_Cloudinary>
5
+ <version>1.1.5</version>
6
  </Cloudinary_Cloudinary>
7
  </modules>
8
  <global>
44
  <catalog_media_gallery>
45
  <table>catalog_product_entity_media_gallery</table>
46
  </catalog_media_gallery>
47
+ <migrationError>
48
+ <table>cloudinary_migration_error</table>
49
+ </migrationError>
50
  </entities>
51
  </cloudinary_resource>
52
  </models>
155
  <jobs>
156
  <cloudinary_migrate>
157
  <schedule>
158
+ <cron_expr>*/3 * * * *</cron_expr>
159
  </schedule>
160
  <run>
161
  <model>cloudinary_cloudinary/cron::migrateImages</model>
app/code/community/Cloudinary/Cloudinary/etc/system.xml CHANGED
@@ -54,6 +54,16 @@
54
  <show_in_store>1</show_in_store>
55
  <source_model>adminhtml/system_config_source_yesno</source_model>
56
  </cloudinary_cdn_subdomain>
 
 
 
 
 
 
 
 
 
 
57
  </fields>
58
  </configuration>
59
  <transformations translate="label">
@@ -108,4 +118,4 @@
108
  </groups>
109
  </cloudinary>
110
  </sections>
111
- </config>
54
  <show_in_store>1</show_in_store>
55
  <source_model>adminhtml/system_config_source_yesno</source_model>
56
  </cloudinary_cdn_subdomain>
57
+ <cloudinary_foldered_migration>
58
+ <label>Migrate images to folders relative to the magento root folder</label>
59
+ <comment>When enabled, cloudinary migration will clone the folder structure of the images relative to the magento root folder</comment>
60
+ <frontend_type>select</frontend_type>
61
+ <sort_order>5</sort_order>
62
+ <show_in_default>1</show_in_default>
63
+ <show_in_website>1</show_in_website>
64
+ <show_in_store>1</show_in_store>
65
+ <source_model>adminhtml/system_config_source_yesno</source_model>
66
+ </cloudinary_foldered_migration>
67
  </fields>
68
  </configuration>
69
  <transformations translate="label">
118
  </groups>
119
  </cloudinary>
120
  </sections>
121
+ </config>
app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.3-1.1.4.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /* @var $installer Mage_Core_Model_Resource_Setup */
4
+ $installer = $this;
5
+ $installer->startSetup();
6
+
7
+ $conn = $installer->getConnection();
8
+ $synchronizationTable = $installer->getTable('cloudinary_cloudinary/synchronisation');
9
+
10
+ $options = [
11
+ 'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
12
+ 'length' => 255,
13
+ 'comment' => 'The name with which the image can be found in the product related media gallery table'
14
+ ];
15
+
16
+ $result = $conn->addColumn($synchronizationTable, 'media_gallery_value', $options);
17
+
18
+ $installer->endSetup();
app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.4-1.1.5.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /* @var $installer Mage_Core_Model_Resource_Setup */
4
+ $installer = $this;
5
+ $installer->startSetup();
6
+
7
+ $table = $installer->getConnection()
8
+ ->newTable($installer->getTable('cloudinary_cloudinary/migrationError'))
9
+ ->addColumn('file_path', Varien_Db_Ddl_Table::TYPE_BINARY, 255, array(
10
+ 'primary' => true,
11
+ 'nullable' => false
12
+ ), 'File path')
13
+ ->addColumn('message', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255)
14
+ ->addColumn('code', Varien_Db_Ddl_Table::TYPE_INTEGER, null)
15
+ ->addColumn('relative_path', Varien_Db_Ddl_Table::TYPE_BINARY, 255)
16
+ ->addColumn('timestamp', Varien_Db_Ddl_Table::TYPE_DATETIME, null);
17
+
18
+ $installer->getConnection()->createTable($table);
19
+
20
+ $installer->endSetup();
app/design/adminhtml/default/default/template/cloudinary/manage.phtml CHANGED
@@ -14,12 +14,56 @@
14
  </div>
15
 
16
  <p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  <?php if ($this->allImagesSynced()): ?>
18
  <div id="messages"><ul class="messages"><li class="notice-msg"><ul><li><span>All images have been successfully migrated to Cloudinary</span></li></ul></li></ul></div>
19
  <?php else: ?>
20
  <p>Image migration progress: <?php echo floor($this->getPercentComplete()) ?>%</p>
21
  <p><?php echo $this->getSynchronizedImageCount() ?> of <?php echo $this->getTotalImageCount() ?> images migrated to Cloudinary.</p>
22
  <?php endif; ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  </p>
24
 
25
  <p>
@@ -31,11 +75,11 @@
31
 
32
 
33
  <p>
34
- <strong>Looking for more information?</strong>
35
  <a href="http://support.cloudinary.com/hc/en-us/sections/200956331-Magento" target="_blank">Visit our Knowledge Base</a>.
36
  </p>
37
 
38
  <p>
39
- <strong>Need assistance or got feedback?</strong>
40
  <a href="http://support.cloudinary.com/hc/en-us/requests/new" target="_blank">Contact our support</a>.
41
  </p>
14
  </div>
15
 
16
  <p>
17
+ <p>
18
+ <p>
19
+
20
+ Foldered migration: <strong><?php echo $this->isFolderedMigration() ? "Enabled" : "Disabled" ?> </strong><br>
21
+
22
+ Foldered migration now available! (don't start migration if you want foldered migration before doing the below tasks) <br> <br>
23
+ To set up foldered migration you need to: <br> <br>
24
+
25
+ - <a target="_blank" href="https://cloudinary.com/console/settings/upload">https://cloudinary.com/console/settings/upload</a>
26
+ and set 'Auto-create folders' - Enabled <br>
27
+
28
+ - <a target="_blank" href="https://cloudinary.com/console/settings/upload">https://cloudinary.com/console/settings/upload</a>
29
+ and set 'Use file name in Media Library' - Yes <br>
30
+
31
+ - <a href="<?php echo $this->getUrl('adminhtml/system_config/edit/section/cloudinary'); ?>"><?php echo $this->__('Go to the Cloudinary configuration section'); ?></a>
32
+ and set 'Migrate images to folders relative to the magento root folder' - Yes <br>
33
+ <br>
34
+
35
+ If you already use simple migration, you will have deprecated information which you can fold: <br>
36
+ - `cloudinary_synchronisation` table entries (you can wipe the table clean before starting foldered migration) <br>
37
+ - all previously migrated images to cloudinary are not touched and will remain in the unfoldered structure
38
+ (they all are in the cloudinary root folder and can be erased safely, since all images migrated with folders
39
+ will take place in the /media/... folder) <br> <br> <br>
40
+ </p>
41
+
42
+ </p>
43
  <?php if ($this->allImagesSynced()): ?>
44
  <div id="messages"><ul class="messages"><li class="notice-msg"><ul><li><span>All images have been successfully migrated to Cloudinary</span></li></ul></li></ul></div>
45
  <?php else: ?>
46
  <p>Image migration progress: <?php echo floor($this->getPercentComplete()) ?>%</p>
47
  <p><?php echo $this->getSynchronizedImageCount() ?> of <?php echo $this->getTotalImageCount() ?> images migrated to Cloudinary.</p>
48
  <?php endif; ?>
49
+
50
+ <div class="migration_errors">
51
+
52
+ <?php
53
+
54
+ $errors = $this->getErrors();
55
+ $count = sizeof($errors);
56
+ echo "<div><strong>ERRORS ($count):</strong> (re-occuring errors refresh their timestamp)</div>";
57
+ echo $this->getClearErrorsButton();
58
+
59
+ foreach ($errors as $error){
60
+ $message = $error->getMessage();
61
+ $filePath = $error->getFilePath();
62
+ $timestamp = $error->getTimestamp();
63
+ echo "<div class=\"migration_error\">$filePath: $message ($timestamp)</div>";
64
+ }
65
+ ?>
66
+ </div>
67
  </p>
68
 
69
  <p>
75
 
76
 
77
  <p>
78
+ <strong>Looking for more information?</strong>
79
  <a href="http://support.cloudinary.com/hc/en-us/sections/200956331-Magento" target="_blank">Visit our Knowledge Base</a>.
80
  </p>
81
 
82
  <p>
83
+ <strong>Need assistance or got feedback?</strong>
84
  <a href="http://support.cloudinary.com/hc/en-us/requests/new" target="_blank">Contact our support</a>.
85
  </p>
lib/Cloudinary/Api.php CHANGED
@@ -246,7 +246,6 @@ class Api {
246
 
247
  # Based on http://snipplr.com/view/17242/
248
  protected function execute($ch) {
249
- \Mage::log(\Cloudinary::userAgent());
250
  $string = curl_exec($ch);
251
  $headers = array();
252
  $content = '';
246
 
247
  # Based on http://snipplr.com/view/17242/
248
  protected function execute($ch) {
 
249
  $string = curl_exec($ch);
250
  $headers = array();
251
  $content = '';
lib/Cloudinary/Cloudinary.php CHANGED
@@ -10,9 +10,9 @@ class Cloudinary {
10
  const RANGE_VALUE_RE = '/^(?P<value>(\d+\.)?\d+)(?P<modifier>[%pP])?$/';
11
  const RANGE_RE = '/^(\d+\.)?\d+[%pP]?\.\.(\d+\.)?\d+[%pP]?$/';
12
 
13
- const VERSION = "1.1.1";
14
  /** @internal Do not change this value */
15
- const USER_AGENT = "CloudinaryPHP/1.1.1";
16
 
17
  /**
18
  * Additional information to be passed with the USER_AGENT, e.g. "CloudinaryMagento/1.0.1". This value is set in platform-specific
@@ -236,26 +236,28 @@ class Cloudinary {
236
  "w" => $width);
237
 
238
  $simple_params = array(
239
- "ac" => "audio_codec",
240
- "af" => "audio_frequency",
241
- "br" => "bit_rate",
242
- "cs" => "color_space",
243
- "d" => "default_image",
244
- "dl" => "delay",
245
- "dn" => "density",
246
- "f" => "fetch_format",
247
- "g" => "gravity",
248
- "l" => "overlay",
249
- "o" => "opacity",
250
- "p" => "prefix",
251
- "pg" => "page",
252
- "q" => "quality",
253
- "r" => "radius",
254
- "u" => "underlay",
255
- "vs" => "video_sampling",
256
- "x" => "x",
257
- "y" => "y",
258
- "z" => "zoom");
 
 
259
 
260
  foreach ($simple_params as $param=>$option) {
261
  $params[$param] = Cloudinary::option_consume($options, $option);
10
  const RANGE_VALUE_RE = '/^(?P<value>(\d+\.)?\d+)(?P<modifier>[%pP])?$/';
11
  const RANGE_RE = '/^(\d+\.)?\d+[%pP]?\.\.(\d+\.)?\d+[%pP]?$/';
12
 
13
+ const VERSION = "1.1.4";
14
  /** @internal Do not change this value */
15
+ const USER_AGENT = "CloudinaryPHP/1.1.4";
16
 
17
  /**
18
  * Additional information to be passed with the USER_AGENT, e.g. "CloudinaryMagento/1.0.1". This value is set in platform-specific
236
  "w" => $width);
237
 
238
  $simple_params = array(
239
+ "ac" => "audio_codec",
240
+ "af" => "audio_frequency",
241
+ "ar" => "aspect_ratio",
242
+ "br" => "bit_rate",
243
+ "cs" => "color_space",
244
+ "d" => "default_image",
245
+ "dl" => "delay",
246
+ "dn" => "density",
247
+ "f" => "fetch_format",
248
+ "g" => "gravity",
249
+ "l" => "overlay",
250
+ "o" => "opacity",
251
+ "p" => "prefix",
252
+ "pg" => "page",
253
+ "q" => "quality",
254
+ "r" => "radius",
255
+ "u" => "underlay",
256
+ "vs" => "video_sampling",
257
+ "x" => "x",
258
+ "y" => "y",
259
+ "z" => "zoom"
260
+ );
261
 
262
  foreach ($simple_params as $param=>$option) {
263
  $params[$param] = Cloudinary::option_consume($options, $option);
lib/Cloudinary/Uploader.php CHANGED
@@ -138,17 +138,19 @@ namespace Cloudinary {
138
  public static function explicit($public_id, $options = array())
139
  {
140
  $params = array(
141
- "timestamp" => time(),
142
- "public_id" => $public_id,
143
- "type" => \Cloudinary::option_get($options, "type"),
144
  "callback" => \Cloudinary::option_get($options, "callback"),
 
 
145
  "eager" => Uploader::build_eager(\Cloudinary::option_get($options, "eager")),
146
  "eager_async" => \Cloudinary::option_get($options, "eager_async"),
147
  "eager_notification_url" => \Cloudinary::option_get($options, "eager_notification_url"),
 
148
  "headers" => Uploader::build_custom_headers(\Cloudinary::option_get($options, "headers")),
 
 
149
  "tags" => \Cloudinary::encode_array(\Cloudinary::option_get($options, "tags")),
150
- "face_coordinates" => \Cloudinary::encode_double_array(\Cloudinary::option_get($options, "face_coordinates")),
151
- "custom_coordinates" => \Cloudinary::encode_double_array(\Cloudinary::option_get($options, "custom_coordinates"))
152
  );
153
  return Uploader::call_api("explicit", $params, $options);
154
  }
@@ -245,12 +247,20 @@ namespace Cloudinary {
245
 
246
  $api_url = \Cloudinary::cloudinary_api_url($action, $options);
247
 
248
- # Serialize params
249
- $api_url .= "?" . preg_replace("/%5B\d+%5D/", "%5B%5D", http_build_query($params));
250
-
251
  $ch = curl_init($api_url);
252
 
253
  $post_params = array();
 
 
 
 
 
 
 
 
 
 
 
254
  if ($file) {
255
  if (!preg_match('/^@|^ftp:|^https?:|^s3:|^data:[^;]*;base64,([a-zA-Z0-9\/+\n=]+)$/', $file)) {
256
  if (function_exists("curl_file_create")) {
@@ -266,10 +276,10 @@ namespace Cloudinary {
266
 
267
  curl_setopt($ch, CURLOPT_POST, true);
268
  $timeout = \Cloudinary::option_get($options, "timeout", \Cloudinary::config_get("timeout", 60));
269
- curl_setopt($ch, CURLOPT_TIMEOUT_MS, $timeout * 1000);
270
  curl_setopt($ch, CURLOPT_POSTFIELDS, $post_params);
271
  curl_setopt($ch, CURLOPT_CAINFO,realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR."cacert.pem");
272
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
273
  curl_setopt($ch, CURLOPT_USERAGENT, \Cloudinary::userAgent());
274
  curl_setopt($ch, CURLOPT_PROXY, \Cloudinary::option_get($options, "api_proxy", \Cloudinary::config_get("api_proxy")));
275
 
@@ -277,7 +287,7 @@ namespace Cloudinary {
277
  if ($range != NULL){
278
  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Range: '.$range));
279
  }
280
- \Mage::log(\Cloudinary::userAgent());
281
  $response = curl_exec($ch);
282
  $curl_error = NULL;
283
  if(curl_errno($ch))
@@ -312,7 +322,7 @@ namespace Cloudinary {
312
  $eager = array();
313
  foreach (\Cloudinary::build_array($transformations) as $trans) {
314
  $transformation = $trans;
315
- $format = \Cloudinary::option_consume($tranformation, "format");
316
  $single_eager = implode("/", array_filter(array(\Cloudinary::generate_transformation_string($transformation), $format)));
317
  array_push($eager, $single_eager);
318
  }
138
  public static function explicit($public_id, $options = array())
139
  {
140
  $params = array(
 
 
 
141
  "callback" => \Cloudinary::option_get($options, "callback"),
142
+ "context" => \Cloudinary::encode_assoc_array(\Cloudinary::option_get($options, "context")),
143
+ "custom_coordinates" => \Cloudinary::encode_double_array(\Cloudinary::option_get($options, "custom_coordinates")),
144
  "eager" => Uploader::build_eager(\Cloudinary::option_get($options, "eager")),
145
  "eager_async" => \Cloudinary::option_get($options, "eager_async"),
146
  "eager_notification_url" => \Cloudinary::option_get($options, "eager_notification_url"),
147
+ "face_coordinates" => \Cloudinary::encode_double_array(\Cloudinary::option_get($options, "face_coordinates")),
148
  "headers" => Uploader::build_custom_headers(\Cloudinary::option_get($options, "headers")),
149
+ "invalidate" => \Cloudinary::option_get($options, "invalidate"),
150
+ "public_id" => $public_id,
151
  "tags" => \Cloudinary::encode_array(\Cloudinary::option_get($options, "tags")),
152
+ "timestamp" => time(),
153
+ "type" => \Cloudinary::option_get($options, "type")
154
  );
155
  return Uploader::call_api("explicit", $params, $options);
156
  }
247
 
248
  $api_url = \Cloudinary::cloudinary_api_url($action, $options);
249
 
 
 
 
250
  $ch = curl_init($api_url);
251
 
252
  $post_params = array();
253
+ foreach ($params as $key => $value) {
254
+ if (is_array($value)) {
255
+ $i = 0;
256
+ foreach ($value as $item) {
257
+ $post_params[$key . "[$i]"] = $item;
258
+ $i++;
259
+ }
260
+ } else {
261
+ $post_params[$key] = $value;
262
+ }
263
+ }
264
  if ($file) {
265
  if (!preg_match('/^@|^ftp:|^https?:|^s3:|^data:[^;]*;base64,([a-zA-Z0-9\/+\n=]+)$/', $file)) {
266
  if (function_exists("curl_file_create")) {
276
 
277
  curl_setopt($ch, CURLOPT_POST, true);
278
  $timeout = \Cloudinary::option_get($options, "timeout", \Cloudinary::config_get("timeout", 60));
279
+ curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
280
  curl_setopt($ch, CURLOPT_POSTFIELDS, $post_params);
281
  curl_setopt($ch, CURLOPT_CAINFO,realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR."cacert.pem");
282
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); # no effect since PHP 5.1.3
283
  curl_setopt($ch, CURLOPT_USERAGENT, \Cloudinary::userAgent());
284
  curl_setopt($ch, CURLOPT_PROXY, \Cloudinary::option_get($options, "api_proxy", \Cloudinary::config_get("api_proxy")));
285
 
287
  if ($range != NULL){
288
  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Range: '.$range));
289
  }
290
+
291
  $response = curl_exec($ch);
292
  $curl_error = NULL;
293
  if(curl_errno($ch))
322
  $eager = array();
323
  foreach (\Cloudinary::build_array($transformations) as $trans) {
324
  $transformation = $trans;
325
+ $format = \Cloudinary::option_consume($transformation, "format");
326
  $single_eager = implode("/", array_filter(array(\Cloudinary::generate_transformation_string($transformation), $format)));
327
  array_push($eager, $single_eager);
328
  }
lib/CloudinaryExtension/CloudinaryImageProvider.php CHANGED
@@ -5,6 +5,8 @@ namespace CloudinaryExtension;
5
 
6
  use Cloudinary;
7
  use Cloudinary\Uploader;
 
 
8
  use CloudinaryExtension\Image\Transformation;
9
  use CloudinaryExtension\Security;
10
 
@@ -12,6 +14,12 @@ class CloudinaryImageProvider implements ImageProvider
12
  {
13
  private $configuration;
14
 
 
 
 
 
 
 
15
  private function __construct(Configuration $configuration)
16
  {
17
  $this->configuration = $configuration;
@@ -25,7 +33,18 @@ class CloudinaryImageProvider implements ImageProvider
25
 
26
  public function upload(Image $image)
27
  {
28
- Uploader::upload((string)$image, array("public_id" => $image->getId()));
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
 
31
  public function transformImage(Image $image, Transformation $transformation = null)
@@ -33,7 +52,7 @@ class CloudinaryImageProvider implements ImageProvider
33
  if ($transformation === null) {
34
  $transformation = $this->configuration->getDefaultTransformation();
35
  }
36
- return Image::fromPath(\cloudinary_url($image->getId(), $transformation->build()));
37
  }
38
 
39
  public function validateCredentials()
5
 
6
  use Cloudinary;
7
  use Cloudinary\Uploader;
8
+ use CloudinaryExtension\Exception\FileAlreadyExists;
9
+ use CloudinaryExtension\Exception\MigrationError;
10
  use CloudinaryExtension\Image\Transformation;
11
  use CloudinaryExtension\Security;
12
 
14
  {
15
  private $configuration;
16
 
17
+ private $uploadConfig = array(
18
+ "use_filename" => true,
19
+ "unique_filename" => false,
20
+ "overwrite" => false
21
+ );
22
+
23
  private function __construct(Configuration $configuration)
24
  {
25
  $this->configuration = $configuration;
33
 
34
  public function upload(Image $image)
35
  {
36
+ try{
37
+ $imagePath = (string)$image;
38
+ $uploadOptionsAndFolder = $this->uploadConfig + ["folder" => $image->getRelativeFolder()];
39
+ $uploadResult = Uploader::upload($imagePath, $uploadOptionsAndFolder);
40
+
41
+ if ($uploadResult['existing'] == 1) {
42
+ MigrationError::throwWith($image, MigrationError::CODE_FILE_ALREADY_EXISTS);
43
+ }
44
+ return $uploadResult;
45
+ } catch (\Exception $e) {
46
+ MigrationError::throwWith($image, MigrationError::CODE_API_ERROR, $e->getMessage());
47
+ }
48
  }
49
 
50
  public function transformImage(Image $image, Transformation $transformation = null)
52
  if ($transformation === null) {
53
  $transformation = $this->configuration->getDefaultTransformation();
54
  }
55
+ return Image::fromPath(\cloudinary_url($image->getId(), $transformation->build()), $image->getRelativePath());
56
  }
57
 
58
  public function validateCredentials()
lib/CloudinaryExtension/Exception/MigrationError.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace CloudinaryExtension\Exception;
4
+
5
+ use CloudinaryExtension\Image;
6
+ use Exception;
7
+
8
+ /**
9
+ * Class MigrationError
10
+ * @package CloudinaryExtension\Exception
11
+ */
12
+ class MigrationError extends Exception
13
+ {
14
+ const CODE_FILE_ALREADY_EXISTS = 0;
15
+ const CODE_API_ERROR = 1;
16
+
17
+ private static $messages = [
18
+ self::CODE_FILE_ALREADY_EXISTS => 'File already exists (cloudinary is case insensitive!!).',
19
+ self::CODE_API_ERROR => 'Internal API error'
20
+ ];
21
+
22
+ private $image;
23
+
24
+ /**
25
+ * @return Image
26
+ */
27
+ public function getImage()
28
+ {
29
+ return $this->image;
30
+ }
31
+
32
+ /**
33
+ * @param Image $image
34
+ * @param $code
35
+ * @param $message overrides the default message attached to the code
36
+ * @return MigrationError
37
+ */
38
+ private static function build(Image $image, $code, $message = '')
39
+ {
40
+ $result = new MigrationError($message ?: self::$messages[$code], $code);
41
+ $result->image = $image;
42
+ return $result;
43
+ }
44
+
45
+ public static function throwWith(Image $image, $code, $message = '')
46
+ {
47
+ throw self::build($image, $code, $message);
48
+ }
49
+
50
+ }
lib/CloudinaryExtension/FolderTranslator.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace CloudinaryExtension;
4
+
5
+ /**
6
+ * Interface FolderTranslator
7
+ *
8
+ * Supposed to contain the logic of which folder should a file be uploaded in cloudinary.
9
+ *
10
+ * @package CloudinaryExtension\Migration
11
+ */
12
+ interface FolderTranslator
13
+ {
14
+ public function translate($path);
15
+ public function reverse($folder);
16
+ }
lib/CloudinaryExtension/Image.php CHANGED
@@ -5,18 +5,21 @@ namespace CloudinaryExtension;
5
  class Image
6
  {
7
  private $imagePath;
 
 
8
 
9
- private $pathParts;
10
-
11
- private function __construct($imagePath)
12
  {
 
 
13
  $this->imagePath = $imagePath;
14
- $this->pathParts = pathinfo(basename($this->imagePath));
 
15
  }
16
 
17
- public static function fromPath($anImagePath)
18
  {
19
- return new Image($anImagePath);
20
  }
21
 
22
  public function __toString()
@@ -24,13 +27,28 @@ class Image
24
  return $this->imagePath;
25
  }
26
 
 
 
 
 
 
 
 
 
 
 
 
27
  public function getId()
28
  {
29
- return $this->pathParts['filename'];
 
 
 
 
30
  }
31
 
32
  public function getExtension()
33
  {
34
- return $this->pathParts['extension'];
35
  }
36
  }
5
  class Image
6
  {
7
  private $imagePath;
8
+ private $relativePath;
9
+ private $pathInfo;
10
 
11
+ private function __construct($imagePath, $relativePath = '')
 
 
12
  {
13
+ $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2];
14
+ \Cloudinary_Cloudinary_Model_Logger::getInstance()->debugLog("$imagePath, $relativePath, {$caller['class']}::{$caller['function']}");
15
  $this->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()
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
+ if ($this->relativePath) {
44
+ return $this->getRelativeFolder() . DS . $this->pathInfo['filename'];
45
+ } else {
46
+ return $this->pathInfo['filename'];
47
+ }
48
  }
49
 
50
  public function getExtension()
51
  {
52
+ return $this->pathInfo['extension'];
53
  }
54
  }
lib/CloudinaryExtension/Image/Synchronizable.php CHANGED
@@ -5,6 +5,6 @@ namespace CloudinaryExtension\Image;
5
  interface Synchronizable
6
  {
7
  public function getFilename();
8
-
9
  public function tagAsSynchronized();
10
- }
5
  interface Synchronizable
6
  {
7
  public function getFilename();
8
+ public function getRelativePath();
9
  public function tagAsSynchronized();
10
+ }
lib/CloudinaryExtension/Migration/BatchUploader.php CHANGED
@@ -2,13 +2,15 @@
2
 
3
  namespace CloudinaryExtension\Migration;
4
 
 
5
  use CloudinaryExtension\Image;
6
  use CloudinaryExtension\Image\Synchronizable;
7
  use CloudinaryExtension\ImageProvider;
 
8
 
9
  class BatchUploader
10
  {
11
- const MESSAGE_STATUS = 'Cloudinary migration: %s images migrated';
12
 
13
  const MESSAGE_UPLOADED = 'Cloudinary migration: uploaded %s';
14
 
@@ -23,6 +25,9 @@ class BatchUploader
23
  private $migrationTask;
24
 
25
  private $countMigrated = 0;
 
 
 
26
 
27
  public function __construct(ImageProvider $imageProvider, Task $migrationTask, Logger $logger, $baseMediaPath)
28
  {
@@ -35,7 +40,6 @@ class BatchUploader
35
  public function uploadImages(array $images)
36
  {
37
  $this->countMigrated = 0;
38
-
39
  foreach ($images as $image) {
40
 
41
  if ($this->migrationTask->hasBeenStopped()) {
@@ -43,8 +47,7 @@ class BatchUploader
43
  }
44
  $this->uploadImage($image);
45
  }
46
-
47
- $this->logger->notice(sprintf(self::MESSAGE_STATUS, $this->countMigrated));
48
  }
49
 
50
  private function getAbsolutePath(Synchronizable $image)
@@ -54,14 +57,45 @@ class BatchUploader
54
 
55
  private function uploadImage(Synchronizable $image)
56
  {
 
 
 
 
57
  try {
58
- $this->imageProvider->upload(Image::fromPath($this->getAbsolutePath($image)));
59
  $image->tagAsSynchronized();
60
  $this->countMigrated++;
61
- $this->logger->notice(sprintf(self::MESSAGE_UPLOADED, $image->getFilename()));
 
62
  } catch (\Exception $e) {
63
- $this->logger->error(sprintf(self::MESSAGE_UPLOAD_ERROR, $e->getMessage(), $image->getFilename()));
 
 
64
  }
65
  }
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  }
2
 
3
  namespace CloudinaryExtension\Migration;
4
 
5
+ use CloudinaryExtension\Exception\MigrationError;
6
  use CloudinaryExtension\Image;
7
  use CloudinaryExtension\Image\Synchronizable;
8
  use CloudinaryExtension\ImageProvider;
9
+ use \Cloudinary_Cloudinary_Helper_Util_ArrayUtils as ArrayUtils;
10
 
11
  class BatchUploader
12
  {
13
+ const MESSAGE_STATUS = 'Cloudinary migration: %s images migrated, %s failed';
14
 
15
  const MESSAGE_UPLOADED = 'Cloudinary migration: uploaded %s';
16
 
25
  private $migrationTask;
26
 
27
  private $countMigrated = 0;
28
+ private $countFailed = 0;
29
+
30
+ private $errors = [];
31
 
32
  public function __construct(ImageProvider $imageProvider, Task $migrationTask, Logger $logger, $baseMediaPath)
33
  {
40
  public function uploadImages(array $images)
41
  {
42
  $this->countMigrated = 0;
 
43
  foreach ($images as $image) {
44
 
45
  if ($this->migrationTask->hasBeenStopped()) {
47
  }
48
  $this->uploadImage($image);
49
  }
50
+ $this->logger->notice(sprintf(self::MESSAGE_STATUS, $this->countMigrated, $this->countFailed));
 
51
  }
52
 
53
  private function getAbsolutePath(Synchronizable $image)
57
 
58
  private function uploadImage(Synchronizable $image)
59
  {
60
+ $absolutePath = $this->getAbsolutePath($image);
61
+ $relativePath = $image->getRelativePath();
62
+ $apiImage = Image::fromPath($absolutePath, $relativePath);
63
+
64
  try {
65
+ $uploadResult = $this->imageProvider->upload($apiImage);
66
  $image->tagAsSynchronized();
67
  $this->countMigrated++;
68
+ $this->_debugLogResult($uploadResult);
69
+ $this->logger->notice(sprintf(self::MESSAGE_UPLOADED, $absolutePath . ' - ' . $relativePath));
70
  } catch (\Exception $e) {
71
+ $this->errors[] = $e;
72
+ $this->countFailed++;
73
+ $this->logger->error(sprintf(self::MESSAGE_UPLOAD_ERROR, $e->getMessage(), $absolutePath . ' - ' . $relativePath));
74
  }
75
  }
76
 
77
+ /**
78
+ * @param $uploadResult
79
+ */
80
+ private function _debugLogResult($uploadResult)
81
+ {
82
+ $extractedResult = ArrayUtils::arraySelect($uploadResult, ['url', 'public_id']);
83
+ $this->logger->debugLog(json_encode($extractedResult, JSON_PRETTY_PRINT) . "\n");
84
+ }
85
+
86
+ /**
87
+ * @return array
88
+ */
89
+ public function getErrors()
90
+ {
91
+ return $this->errors;
92
+ }
93
+
94
+ public function getMigrationErrors()
95
+ {
96
+ return array_filter($this->errors, function ($val) {
97
+ return $val instanceof MigrationError;
98
+ });
99
+ }
100
+
101
  }
lib/CloudinaryExtension/Migration/Logger.php CHANGED
@@ -9,4 +9,6 @@ interface Logger
9
  public function notice($message, array $context = array());
10
 
11
  public function error($message, array $context = array());
 
 
12
  }
9
  public function notice($message, array $context = array());
10
 
11
  public function error($message, array $context = array());
12
+
13
+ public function debugLog($message);
14
  }
package.xml CHANGED
@@ -1,21 +1,22 @@
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Cloudinary_Cloudinary</name>
4
- <version>1.1.0</version>
5
  <stability>stable</stability>
6
  <license>MIT License (MITL)</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>Cloudinary - Image Management In The Cloud</summary>
10
  <description>Cloudinary supercharges your images! Upload images to the cloud, deliver optimized via a fast CDN, perform smart resizing and apply effects.</description>
11
- <notes>v1.1.0&#xD;
12
  &#xD;
13
- * Fix bug when image sizes contain a decimal point&#xD;
14
- * Update to latest Cloudinary PHP Library</notes>
 
15
  <authors><author><name>Cloudinary</name><user>cloudinary</user><email>accounts+magento@cloudinary.com</email></author></authors>
16
- <date>2015-06-08</date>
17
- <time>13:05:01</time>
18
- <contents><target name="magecommunity"><dir name="Cloudinary"><dir name="Cloudinary"><dir name="Block"><dir name="Adminhtml"><dir name="Manage"><file name="Grid.php" hash="b6a05f6ba08c5ba0d08846a7b0a06776"/></dir><file name="Manage.php" hash="0e5969a1fb7ce270776c3c80fb07a072"/><dir name="Page"><file name="Menu.php" hash="891d6a4c075ba03c9a20658076c86ad0"/></dir><dir name="System"><dir name="Config"><file name="Signup.php" hash="235c27f236e45900eb94dea0181027cc"/></dir></dir></dir></dir><dir name="controllers"><dir name="Adminhtml"><file name="CloudinaryController.php" hash="e14ba0c37dafefca8347783b3616403c"/></dir></dir><dir name="data"><dir name="cloudinary_setup"><file name="data-upgrade-0.1.0-0.1.1.php" hash="4c6ce6cd9ab0d94654afb4a398fb3d6c"/></dir></dir><dir name="etc"><file name="adminhtml.xml" hash="46e365e2f4b1d543aad248dfcfb99c50"/><file name="config.xml" hash="76e9a03863c9599d13adcb2d2a834778"/><file name="system.xml" hash="ea40bfc4a7ef533b1800d49c70bc3706"/></dir><dir name="Helper"><file name="Autoloader.php" hash="393b3e2fc25e63ca28157152d2542b18"/><dir name="Configuration"><file name="Validation.php" hash="6d17d39ba39f67888701fadf0fe3de62"/></dir><file name="Configuration.php" hash="a5c02c7b02a0c263d9f6b8f58b08e746"/><file name="Console.php" hash="e4ca7f9bf450b05383def130b2819ce0"/><file name="Data.php" hash="42c9d44f1bbe530e30cf5379846dea65"/><file name="Image.php" hash="018d99d47230d570c40e4c4dabcdfa2f"/></dir><dir name="Model"><dir name="Catalog"><dir name="Product"><file name="Image.php" hash="3634cce83bfa42768f7dbbc2958bbe9a"/><dir name="Media"><file name="Config.php" hash="678422f87913a2ea70ec717aaa2169b1"/></dir><file name="Media.php" hash="4804b00885bfe279ee43a1b290d801da"/></dir></dir><dir name="Cms"><dir name="Adminhtml"><dir name="Template"><file name="Filter.php" hash="4ef453061d790fff6b772286e90439f2"/></dir></dir><file name="Synchronisation.php" hash="c62e3ed78d7463267951157d6ff8bdc9"/><dir name="Template"><file name="Filter.php" hash="5ec9589ef22b1e9c88b20c3272d01f8c"/></dir><file name="Uploader.php" hash="bcaca2303cf806d41bac82a009ba9eb6"/><dir name="Wysiwyg"><dir name="Images"><file name="Storage.php" hash="b3eae9a3a4810d9de5ab6a91243d047d"/></dir></dir></dir><file name="Cron.php" hash="a02414ee2fca1b1e65cb9c5fa59ee657"/><dir name="Exception"><file name="BadFilePathException.php" hash="68135da8dfe2f0589a531b4bd36e3330"/></dir><file name="Image.php" hash="4a2ee22c0ca6510b7633ef2ef3541587"/><file name="Logger.php" hash="c497bae551881533afbb08882f73e91d"/><file name="MediaCollectionCounter.php" hash="a7772d711b6d5fcdde2fa042fc1f7b1d"/><file name="Migration.php" hash="e923053b36d2ab469362b3590935ecfe"/><file name="Observer.php" hash="22a8e380ac895894f218e7239560b2e2"/><file name="PreConditionsValidator.php" hash="8f5f33e3b8c7755d80ae5036848dd5ff"/><dir name="Resource"><dir name="Cms"><dir name="Synchronisation"><file name="Collection.php" hash="94108abee4e5e4a0a7534bfbb7f7e85b"/></dir></dir><dir name="Media"><file name="Collection.php" hash="1805b96c4ad84aa72862b605054ecdfb"/></dir><file name="Migration.php" hash="69a545d0627016afc03ea097641aa749"/><dir name="Synchronisation"><file name="Collection.php" hash="04fe388bb68b38248922884f80126e80"/></dir><file name="Synchronisation.php" hash="5b721d854d8f89bc3310e46081be7153"/></dir><file name="Synchronisation.php" hash="53de366c8e304c5c0a823df1095a276c"/><file name="SynchronisedMediaUnifier.php" hash="7cd30f02d4877caff089230809699b0f"/><dir name="System"><dir name="Config"><dir name="Source"><dir name="Dropdown"><file name="Dpr.php" hash="2b9bfd5f836dbdb5d7224d298264f540"/><file name="Gravity.php" hash="c241498e2093640892170673cd7550cd"/><file name="Quality.php" hash="e0c5902f5c36c96fb8a8ba7cc741ce1f"/></dir></dir></dir></dir></dir><dir name="sql"><dir name="cloudinary_setup"><file name="install-0.1.0.php" hash="55d93b3dab573c2a932edbb5a2fa4865"/><file name="upgrade-0.1.0-0.1.1.php" hash="6c8d430fbf7b9714586b67db3d455008"/></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Cloudinary_Cloudinary.xml" hash="9337962a4ccf8a43164d5d71dfd2d756"/></dir></target><target name="magelib"><dir name="CloudinaryExtension"><file name="CloudinaryImageProvider.php" hash="4142e17a23c4a36380a8efeae504cf0c"/><file name="Cloud.php" hash="59b0debf9ae297e4e824e39ba819b1d1"/><file name="Configuration.php" hash="b4ece3c87e2aa2af83b8362311ad7850"/><file name="Credentials.php" hash="ccd2da6450df39f3218cfd64ac47103f"/><dir name="Exception"><file name="InvalidCredentials.php" hash="abecc635a25f6c9896c605ad16e1f7d7"/></dir><dir name="Image"><file name="Synchronizable.php" hash="dffcdc40ed5a5cde556ef200d3c544ba"/><dir name="Transformation"><file name="Dimensions.php" hash="8b9a09da6980aa2ff42e8f00d79a1b83"/><file name="Dpr.php" hash="f78cd1bfabaf3088ca8d4af972bfd453"/><file name="FetchFormat.php" hash="b81c62dd756dee4ad085ee6f0a83356a"/><file name="Format.php" hash="ab8ea9b6a8c813a24f23b079ea6236da"/><file name="Gravity.php" hash="c1c2adf4dbbeaa6b06d67d2014300559"/><file name="Quality.php" hash="23a857f3910aecf6e45645194ff7f54e"/></dir><file name="Transformation.php" hash="fc443a6abad4a478e94df5661e4aef12"/></dir><file name="Image.php" hash="ea59732b244899cb5b69b3e3e86cffee"/><file name="ImageProvider.php" hash="f4eb49d5e1e4c1728a5dde29b6b5a3fa"/><dir name="Migration"><file name="BatchUploader.php" hash="7cd66596475070673e2467506a9b86ed"/><file name="Logger.php" hash="69f5416f6a3176907b40fce72c2d6e23"/><file name="Queue.php" hash="add92864192b0950c29c91ffe5e5a3ee"/><file name="SynchronizedMediaRepository.php" hash="9e7e1dae66b40ce991b0e86ecdff4c24"/><file name="Task.php" hash="ac11d06c531d48b38cf88f6e8f2bdc19"/></dir><dir name="Security"><file name="ApiSignature.php" hash="049c7db2684ec2a6cf5bb4efcd064951"/><file name="CloudinaryEnvironmentVariable.php" hash="2b5c28381b52350586ea1589854ca498"/><file name="ConsoleUrl.php" hash="4e748cfe0f5a0aeab2307c623179c6f9"/><file name="EnvironmentVariable.php" hash="297fa60b819ffc028b9a32dae6eef63d"/><file name="Key.php" hash="ac3a50b59f2a7db1edcf30386759c7ec"/><file name="Secret.php" hash="b1010679976575d57752dbb07f1b94ed"/><file name="SignedConsoleUrl.php" hash="791e1f1080be23423c2ad87f431f6221"/></dir><file name="ValidateRemoteUrlRequest.php" hash="c2e2eb712e5293ad508a23610dfbbd6d"/></dir><dir name="Cloudinary"><file name="Api.php" hash="972fbd112fdf3745b5c1efa457e71a7f"/><file name="cacert.pem" hash="c4290b9deb70d0bef2f88b67fc68c8ec"/><file name="CloudinaryField.php" hash="411714580d21b58115ab07737367173a"/><file name="Cloudinary.php" hash="1e3709faea6e006fa09c0d1fc2052836"/><file name="Helpers.php" hash="63035ebeaa237bd69dcad2d756a00b44"/><file name="PreloadedFile.php" hash="73cc9e276f96553814f05eae592d11ee"/><file name="Uploader.php" hash="0d62ad1f0816fbb5d1dddd30bdf3f082"/></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><dir name="cloudinary"><file name="cloudinary.xml" hash="8cf333ec4b49c684ea6a209061f5128b"/></dir></dir><dir name="template"><dir name="cloudinary"><file name="manage.phtml" hash="c5a74ed0528f36b409039d5fbdeb8cb9"/><dir name="system"><dir name="config"><file name="signup.phtml" hash="2a0e06990eb542f22531ac2ebb5996f5"/></dir></dir></dir></dir></dir></dir></dir></target></contents>
19
  <compatible/>
20
  <dependencies><required><php><min>5.4.0</min><max>7.0.0</max></php></required></dependencies>
21
  </package>
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Cloudinary_Cloudinary</name>
4
+ <version>1.2.0</version>
5
  <stability>stable</stability>
6
  <license>MIT License (MITL)</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>Cloudinary - Image Management In The Cloud</summary>
10
  <description>Cloudinary supercharges your images! Upload images to the cloud, deliver optimized via a fast CDN, perform smart resizing and apply effects.</description>
11
+ <notes>v1.2.0&#xD;
12
  &#xD;
13
+ Release Highlights:&#xD;
14
+ &#xD;
15
+ * Foldered migration</notes>
16
  <authors><author><name>Cloudinary</name><user>cloudinary</user><email>accounts+magento@cloudinary.com</email></author></authors>
17
+ <date>2016-02-29</date>
18
+ <time>16:32:17</time>
19
+ <contents><target name="magecommunity"><dir name="Cloudinary"><dir name="Cloudinary"><dir name="Block"><dir name="Adminhtml"><dir name="Manage"><file name="Grid.php" hash="b6a05f6ba08c5ba0d08846a7b0a06776"/></dir><file name="Manage.php" hash="c525e34955df149b70d3a7fdde427672"/><dir name="Page"><file name="Menu.php" hash="891d6a4c075ba03c9a20658076c86ad0"/></dir><dir name="System"><dir name="Config"><file name="Signup.php" hash="235c27f236e45900eb94dea0181027cc"/></dir></dir></dir></dir><dir name="Helper"><file name="Autoloader.php" hash="393b3e2fc25e63ca28157152d2542b18"/><dir name="Configuration"><file name="Validation.php" hash="6d17d39ba39f67888701fadf0fe3de62"/></dir><file name="Configuration.php" hash="045cf15e781fac57b999551e1e5c88ea"/><file name="Console.php" hash="e4ca7f9bf450b05383def130b2819ce0"/><file name="Data.php" hash="42c9d44f1bbe530e30cf5379846dea65"/><file name="Image.php" hash="2b0b6a42a3f38203952547739e36632d"/><dir name="Util"><file name="ArrayUtils.php" hash="dbf5b1f86213f6e1ea34b1523b2b9ffe"/></dir></dir><dir name="Model"><dir name="Catalog"><dir name="Product"><file name="Image.php" hash="4c04c8db36be21aaecfc681fac429f69"/><dir name="Media"><file name="Config.php" hash="ff27ccd9fc2becce9feae31ffd1d59e2"/></dir><file name="Media.php" hash="05726616a07d7d08933e9654e6107283"/></dir></dir><dir name="Cms"><dir name="Adminhtml"><dir name="Template"><file name="Filter.php" hash="4ef453061d790fff6b772286e90439f2"/></dir></dir><file name="Synchronisation.php" hash="3bf5d872b6451cf3ce6f83ec92104415"/><dir name="Template"><file name="Filter.php" hash="5ec9589ef22b1e9c88b20c3272d01f8c"/></dir><file name="Uploader.php" hash="bcaca2303cf806d41bac82a009ba9eb6"/><dir name="Wysiwyg"><dir name="Images"><file name="Storage.php" hash="b3eae9a3a4810d9de5ab6a91243d047d"/></dir></dir></dir><file name="CollectionCounter.php" hash="e69953aee5d966a3ec13d33533f017e0"/><file name="Cron.php" hash="13ea00a9e40622912bf46ed367f9a214"/><dir name="Exception"><file name="BadFilePathException.php" hash="68135da8dfe2f0589a531b4bd36e3330"/></dir><file name="Image.php" hash="0377e2c24c5e2f23357a55d744098fda"/><file name="Logger.php" hash="226893f4a59d1431330688f455975d61"/><file name="MagentoFolderTranslator.php" hash="ad11d373bc6e193b689d29f16f5f6480"/><file name="Migration.php" hash="e923053b36d2ab469362b3590935ecfe"/><file name="MigrationError.php" hash="1c91373b020d639ae3fb8acfa099eea0"/><file name="Observer.php" hash="22a8e380ac895894f218e7239560b2e2"/><file name="PreConditionsValidator.php" hash="b7cf4e4b4372a2ea5b87d849b2a3d748"/><dir name="Resource"><dir name="Cms"><dir name="Synchronisation"><file name="Collection.php" hash="117085bb56d3f0db8f3d52f136a8b84b"/></dir></dir><dir name="Media"><file name="Collection.php" hash="f54d914a6f79c7b3ab51f822bf64de39"/></dir><file name="Migration.php" hash="69a545d0627016afc03ea097641aa749"/><dir name="MigrationError"><file name="Collection.php" hash="3c5ef530b18b4cd7763a610b84cd3d41"/></dir><file name="MigrationError.php" hash="e6de24a80cb0daed6ead44c699dce535"/><dir name="Synchronisation"><file name="Collection.php" hash="8abfc042f7c84f424015e8bc34dab0dc"/></dir><file name="Synchronisation.php" hash="5b721d854d8f89bc3310e46081be7153"/></dir><file name="Synchronisation.php" hash="553d95d0c40d677539b6e9f035c52b91"/><file name="SynchronisedMediaUnifier.php" hash="dd47a04cc2eaa2a81b6dce27f22301f2"/><dir name="System"><dir name="Config"><dir name="Source"><dir name="Dropdown"><file name="Dpr.php" hash="2b9bfd5f836dbdb5d7224d298264f540"/><file name="Gravity.php" hash="c241498e2093640892170673cd7550cd"/><file name="Quality.php" hash="e0c5902f5c36c96fb8a8ba7cc741ce1f"/></dir></dir></dir></dir></dir><dir name="controllers"><dir name="Adminhtml"><file name="CloudinaryController.php" hash="4e23d18f841d78abf8443de71a951ed6"/></dir></dir><dir name="data"><dir name="cloudinary_setup"><file name="data-upgrade-0.1.0-0.1.1.php" hash="4c6ce6cd9ab0d94654afb4a398fb3d6c"/><file name="data-upgrade-1.1.2-1.1.3.php" hash="fe2026874346017303a8f41a9d0d6c0d"/></dir></dir><dir name="etc"><file name="adminhtml.xml" hash="46e365e2f4b1d543aad248dfcfb99c50"/><file name="config.xml" hash="8449286938df94d41182641f54de1d06"/><file name="system.xml" hash="a4ae810df3e6587625d395985b79ee83"/></dir><dir name="sql"><dir name="cloudinary_setup"><file name="install-0.1.0.php" hash="55d93b3dab573c2a932edbb5a2fa4865"/><file name="upgrade-0.1.0-0.1.1.php" hash="6c8d430fbf7b9714586b67db3d455008"/><file name="upgrade-1.1.3-1.1.4.php" hash="d6314fc1843b2061d0d04ae60c4d8091"/><file name="upgrade-1.1.4-1.1.5.php" hash="5b035e4b600cbbc743e9ff6a7b505230"/></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Cloudinary_Cloudinary.xml" hash="9337962a4ccf8a43164d5d71dfd2d756"/></dir></target><target name="magelib"><dir name="CloudinaryExtension"><file name="Cloud.php" hash="59b0debf9ae297e4e824e39ba819b1d1"/><file name="CloudinaryImageProvider.php" hash="e75c77d6a169fec4c96c875de477fc75"/><file name="Configuration.php" hash="b4ece3c87e2aa2af83b8362311ad7850"/><file name="Credentials.php" hash="ccd2da6450df39f3218cfd64ac47103f"/><dir name="Exception"><file name="InvalidCredentials.php" hash="abecc635a25f6c9896c605ad16e1f7d7"/><file name="MigrationError.php" hash="1f37d28be668edb805e46fd207c72fd9"/></dir><file name="FolderTranslator.php" hash="19a335acf751d67bd7efe46829602490"/><dir name="Image"><file name="Synchronizable.php" hash="38a6b9db4cfc3fde3e94db5b35a92bf8"/><dir name="Transformation"><file name="Dimensions.php" hash="8b9a09da6980aa2ff42e8f00d79a1b83"/><file name="Dpr.php" hash="f78cd1bfabaf3088ca8d4af972bfd453"/><file name="FetchFormat.php" hash="b81c62dd756dee4ad085ee6f0a83356a"/><file name="Format.php" hash="ab8ea9b6a8c813a24f23b079ea6236da"/><file name="Gravity.php" hash="c1c2adf4dbbeaa6b06d67d2014300559"/><file name="Quality.php" hash="23a857f3910aecf6e45645194ff7f54e"/></dir><file name="Transformation.php" hash="fc443a6abad4a478e94df5661e4aef12"/></dir><file name="Image.php" hash="cf1b7cd018475520cda04a49780a17de"/><file name="ImageProvider.php" hash="f4eb49d5e1e4c1728a5dde29b6b5a3fa"/><dir name="Migration"><file name="BatchUploader.php" hash="5d25c21cf55040b79320221f815df8e8"/><file name="Logger.php" hash="648b47bb065de0c81b386ac300b4f9a3"/><file name="Queue.php" hash="add92864192b0950c29c91ffe5e5a3ee"/><file name="SynchronizedMediaRepository.php" hash="9e7e1dae66b40ce991b0e86ecdff4c24"/><file name="Task.php" hash="ac11d06c531d48b38cf88f6e8f2bdc19"/></dir><dir name="Security"><file name="ApiSignature.php" hash="049c7db2684ec2a6cf5bb4efcd064951"/><file name="CloudinaryEnvironmentVariable.php" hash="2b5c28381b52350586ea1589854ca498"/><file name="ConsoleUrl.php" hash="4e748cfe0f5a0aeab2307c623179c6f9"/><file name="EnvironmentVariable.php" hash="297fa60b819ffc028b9a32dae6eef63d"/><file name="Key.php" hash="ac3a50b59f2a7db1edcf30386759c7ec"/><file name="Secret.php" hash="b1010679976575d57752dbb07f1b94ed"/><file name="SignedConsoleUrl.php" hash="791e1f1080be23423c2ad87f431f6221"/></dir><file name="ValidateRemoteUrlRequest.php" hash="c2e2eb712e5293ad508a23610dfbbd6d"/></dir><dir name="Cloudinary"><file name="Api.php" hash="f046d7b1db05efb0997a458fb610a09f"/><file name="Cloudinary.php" hash="debd99bcb8076cf250f59a8925f8ba1b"/><file name="CloudinaryField.php" hash="411714580d21b58115ab07737367173a"/><file name="Helpers.php" hash="63035ebeaa237bd69dcad2d756a00b44"/><file name="PreloadedFile.php" hash="73cc9e276f96553814f05eae592d11ee"/><file name="Uploader.php" hash="2fbea92cd3e409ec62e61ecbf36e6f2c"/><file name="cacert.pem" hash="c4290b9deb70d0bef2f88b67fc68c8ec"/></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><dir name="cloudinary"><file name="cloudinary.xml" hash="8cf333ec4b49c684ea6a209061f5128b"/></dir></dir><dir name="template"><dir name="cloudinary"><file name="manage.phtml" hash="080ea639f961da33a5a3d2429da13edc"/><dir name="system"><dir name="config"><file name="signup.phtml" hash="2a0e06990eb542f22531ac2ebb5996f5"/></dir></dir></dir></dir></dir></dir></dir></target></contents>
20
  <compatible/>
21
  <dependencies><required><php><min>5.4.0</min><max>7.0.0</max></php></required></dependencies>
22
  </package>