Cloudinary_Cloudinary - Version 2.2.0

Version Notes

Release 2.2.0 notes:
- Existing images are not remigrated during migration, and do not fail migration
- Fix bug where migration status shows value above 100%
- Preserve original file extensions in Cloudinary image urls
- Retry migration when network failures occur

Download this release

Release Info

Developer Cloudinary
Extension Cloudinary_Cloudinary
Version 2.2.0
Comparing to
See all releases


Code changes from version 2.1.0 to 2.2.0

app/code/community/Cloudinary/Cloudinary/Model/Cron.php CHANGED
@@ -1,6 +1,7 @@
1
  <?php
2
 
3
  use CloudinaryExtension\CloudinaryImageProvider;
 
4
  use CloudinaryExtension\Migration\BatchUploader;
5
 
6
  class Cloudinary_Cloudinary_Model_Cron extends Mage_Core_Model_Abstract
@@ -22,7 +23,12 @@ class Cloudinary_Cloudinary_Model_Cron extends Mage_Core_Model_Abstract
22
  ),
23
  $migrationTask,
24
  Mage::getModel('cloudinary_cloudinary/logger'),
25
- null
 
 
 
 
 
26
  );
27
 
28
  $combinedMediaRepository = new Cloudinary_Cloudinary_Model_SynchronisedMediaUnifier(
@@ -41,10 +47,6 @@ class Cloudinary_Cloudinary_Model_Cron extends Mage_Core_Model_Abstract
41
 
42
  $migrationQueue->process();
43
 
44
- foreach ($batchUploader->getMigrationErrors() as $error) {
45
- Cloudinary_Cloudinary_Model_MigrationError::saveFromException($error);
46
- }
47
-
48
  return $this;
49
  }
50
  }
1
  <?php
2
 
3
  use CloudinaryExtension\CloudinaryImageProvider;
4
+ use CloudinaryExtension\Exception\MigrationError;
5
  use CloudinaryExtension\Migration\BatchUploader;
6
 
7
  class Cloudinary_Cloudinary_Model_Cron extends Mage_Core_Model_Abstract
23
  ),
24
  $migrationTask,
25
  Mage::getModel('cloudinary_cloudinary/logger'),
26
+ null,
27
+ function(\Exception $e) {
28
+ if ($e instanceof MigrationError) {
29
+ Cloudinary_Cloudinary_Model_MigrationError::saveFromException($e);
30
+ }
31
+ }
32
  );
33
 
34
  $combinedMediaRepository = new Cloudinary_Cloudinary_Model_SynchronisedMediaUnifier(
47
 
48
  $migrationQueue->process();
49
 
 
 
 
 
50
  return $this;
51
  }
52
  }
app/code/community/Cloudinary/Cloudinary/Model/MigrationError.php CHANGED
@@ -3,11 +3,16 @@
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();
@@ -23,4 +28,17 @@ class Cloudinary_Cloudinary_Model_MigrationError extends Mage_Core_Model_Abstrac
23
 
24
  $entry->save();
25
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
3
 
4
  class Cloudinary_Cloudinary_Model_MigrationError extends Mage_Core_Model_Abstract
5
  {
6
+ const REMOVE_ORPHAN_MESSAGE = 'Image found in sync table that no longer exists. Removing reference: %s';
7
+
8
  public function __construct()
9
  {
10
  $this->_init('cloudinary_cloudinary/migrationError');
11
  }
12
 
13
+ /**
14
+ * @param \CloudinaryExtension\Exception\MigrationError $e
15
+ */
16
  public static function saveFromException(\CloudinaryExtension\Exception\MigrationError $e)
17
  {
18
  $image = $e->getImage();
28
 
29
  $entry->save();
30
  }
31
+
32
+ /**
33
+ * @param Cloudinary_Cloudinary_Model_Synchronisation $orphanImage
34
+ * @return $this
35
+ */
36
+ public function orphanRemoved(Cloudinary_Cloudinary_Model_Synchronisation $orphanImage)
37
+ {
38
+ $this->setFilePath($orphanImage->getImageName());
39
+ $this->setRelativePath($orphanImage->getImageName());
40
+ $this->setMessage(sprintf(self::REMOVE_ORPHAN_MESSAGE, $orphanImage->getImageName()));
41
+ $this->setTimestamp(time());
42
+ return $this;
43
+ }
44
  }
app/code/community/Cloudinary/Cloudinary/Model/Resource/Cms/Synchronisation/Collection.php CHANGED
@@ -1,5 +1,6 @@
1
  <?php
2
 
 
3
  use CloudinaryExtension\Migration\SynchronizedMediaRepository;
4
 
5
  class Cloudinary_Cloudinary_Model_Resource_Cms_Synchronisation_Collection
@@ -38,6 +39,9 @@ class Cloudinary_Cloudinary_Model_Resource_Cms_Synchronisation_Collection
38
  }
39
  }
40
 
 
 
 
41
  public function findUnsynchronisedImages()
42
  {
43
  $helperConfig = Mage::getModel('cloudinary_cloudinary/configuration');
@@ -70,4 +74,55 @@ class Cloudinary_Cloudinary_Model_Resource_Cms_Synchronisation_Collection
70
  ->addFieldToFilter('media_gallery_id', array('null' => true))
71
  ->getData();
72
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  }
1
  <?php
2
 
3
+ use CloudinaryExtension\Image\Synchronizable;
4
  use CloudinaryExtension\Migration\SynchronizedMediaRepository;
5
 
6
  class Cloudinary_Cloudinary_Model_Resource_Cms_Synchronisation_Collection
39
  }
40
  }
41
 
42
+ /**
43
+ * @return [Cloudinary_Cloudinary_Model_Synchronisation]
44
+ */
45
  public function findUnsynchronisedImages()
46
  {
47
  $helperConfig = Mage::getModel('cloudinary_cloudinary/configuration');
74
  ->addFieldToFilter('media_gallery_id', array('null' => true))
75
  ->getData();
76
  }
77
+
78
+ private function _getSynchronisedRawImageNames()
79
+ {
80
+ $result = array_map(
81
+ function ($itemData) {
82
+ return $itemData['image_name'];
83
+ },
84
+ $this->_getSynchronisedImageData()
85
+ );
86
+
87
+ return $result;
88
+ }
89
+
90
+ /**
91
+ * @return [Cloudinary_Cloudinary_Model_Synchronisation]
92
+ */
93
+ public function findOrphanedSynchronisedImages()
94
+ {
95
+ return $this->_synchronisationCollectionFromImageNames(
96
+ array_diff(
97
+ $this->_getSynchronisedRawImageNames(),
98
+ $this->_extractRelativePaths($this->getItems())
99
+ )
100
+ );
101
+ }
102
+
103
+ /**
104
+ * @param [string] $imageNames
105
+ * @return [Cloudinary_Cloudinary_Model_Synchronisation]
106
+ */
107
+ private function _synchronisationCollectionFromImageNames(array $imageNames)
108
+ {
109
+ return Mage::getModel('cloudinary_cloudinary/synchronisation')
110
+ ->getCollection()
111
+ ->addFieldToFilter('image_name', ['in' => $imageNames])
112
+ ->getItems();
113
+ }
114
+
115
+ /**
116
+ * @param [Synchronizable] $items
117
+ * @return [string]
118
+ */
119
+ private function _extractRelativePaths(array $items)
120
+ {
121
+ return array_map(
122
+ function(Synchronizable $syncItem) {
123
+ return $syncItem->getRelativePath();
124
+ },
125
+ $items
126
+ );
127
+ }
128
  }
app/code/community/Cloudinary/Cloudinary/Model/Resource/Synchronisation/Collection.php CHANGED
@@ -60,4 +60,13 @@ class Cloudinary_Cloudinary_Model_Resource_Synchronisation_Collection
60
  $select->where('media_gallery_value is not null');
61
  return $select->columns('media_gallery_value');
62
  }
 
 
 
 
 
 
 
 
 
63
  }
60
  $select->where('media_gallery_value is not null');
61
  return $select->columns('media_gallery_value');
62
  }
63
+
64
+ /**
65
+ * Only applicable for cms instance
66
+ * @return [Cloudinary_Cloudinary_Model_Synchronisation]
67
+ */
68
+ public function findOrphanedSynchronisedImages()
69
+ {
70
+ return [];
71
+ }
72
  }
app/code/community/Cloudinary/Cloudinary/Model/SynchronisedMediaUnifier.php CHANGED
@@ -4,24 +4,54 @@ use CloudinaryExtension\Migration\SynchronizedMediaRepository;
4
 
5
  class Cloudinary_Cloudinary_Model_SynchronisedMediaUnifier implements SynchronizedMediaRepository
6
  {
7
-
 
 
8
  private $_synchronisedMediaRepositories;
9
- private $_unsychronisedImages = array();
10
 
 
 
 
 
11
  public function __construct(array $synchronisedMediaRepositories)
12
  {
13
  $this->_synchronisedMediaRepositories = $synchronisedMediaRepositories;
14
  }
15
 
 
 
 
 
16
  public function findUnsynchronisedImages($limit = 200)
17
  {
18
- foreach ($this->_synchronisedMediaRepositories as $synchronisedMediaRepository) {
19
- $this->_unsychronisedImages = array_merge(
20
- $this->_unsychronisedImages,
21
- $synchronisedMediaRepository->findUnsynchronisedImages()
22
- );
23
- }
24
- return array_slice($this->_unsychronisedImages, 0, $limit);
 
 
 
 
 
 
 
 
25
  }
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
4
 
5
  class Cloudinary_Cloudinary_Model_SynchronisedMediaUnifier implements SynchronizedMediaRepository
6
  {
7
+ /**
8
+ * @var [SynchronizedMediaRepository]
9
+ */
10
  private $_synchronisedMediaRepositories;
 
11
 
12
+ /**
13
+ * Cloudinary_Cloudinary_Model_SynchronisedMediaUnifier constructor.
14
+ * @param [SynchronizedMediaRepository]
15
+ */
16
  public function __construct(array $synchronisedMediaRepositories)
17
  {
18
  $this->_synchronisedMediaRepositories = $synchronisedMediaRepositories;
19
  }
20
 
21
+ /**
22
+ * @param int $limit
23
+ * @return [Cloudinary_Cloudinary_Model_Synchronisation]
24
+ */
25
  public function findUnsynchronisedImages($limit = 200)
26
  {
27
+ return array_slice($this->findUnlimitedUnsynchronisedImages(), 0, $limit);
28
+ }
29
+
30
+ /**
31
+ * @return [Cloudinary_Cloudinary_Model_Synchronisation]
32
+ */
33
+ private function findUnlimitedUnsynchronisedImages()
34
+ {
35
+ return array_reduce(
36
+ $this->_synchronisedMediaRepositories,
37
+ function($carry, $synchronisedMediaRepository) {
38
+ return $carry + $synchronisedMediaRepository->findUnsynchronisedImages();
39
+ },
40
+ []
41
+ );
42
  }
43
 
44
+ /**
45
+ * @return [Cloudinary_Cloudinary_Model_Synchronisation]
46
+ */
47
+ public function findOrphanedSynchronisedImages()
48
+ {
49
+ return array_reduce(
50
+ $this->_synchronisedMediaRepositories,
51
+ function($carry, $synchronisedMediaRepository) {
52
+ return $carry + $synchronisedMediaRepository->findOrphanedSynchronisedImages();
53
+ },
54
+ []
55
+ );
56
+ }
57
  }
app/code/community/Cloudinary/Cloudinary/controllers/Adminhtml/CloudinaryController.php CHANGED
@@ -4,6 +4,9 @@ class Cloudinary_Cloudinary_Adminhtml_CloudinaryController extends Mage_Adminhtm
4
  {
5
  const CRON_INTERVAL = 300;
6
 
 
 
 
7
  private $_migrationTask;
8
 
9
  /**
@@ -21,6 +24,8 @@ class Cloudinary_Cloudinary_Adminhtml_CloudinaryController extends Mage_Adminhtm
21
 
22
  public function indexAction()
23
  {
 
 
24
  $this->_displayMigrationMessages();
25
 
26
  $layout = $this->loadLayout();
@@ -43,6 +48,22 @@ class Cloudinary_Cloudinary_Adminhtml_CloudinaryController extends Mage_Adminhtm
43
  $this->renderLayout();
44
  }
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  public function configAction()
47
  {
48
  $this->_redirect("*/system_config/edit/section/cloudinary/");
4
  {
5
  const CRON_INTERVAL = 300;
6
 
7
+ /**
8
+ * @var Cloudinary_Cloudinary_Model_Migration
9
+ */
10
  private $_migrationTask;
11
 
12
  /**
24
 
25
  public function indexAction()
26
  {
27
+ $this->removeOrphanSyncEntries();
28
+
29
  $this->_displayMigrationMessages();
30
 
31
  $layout = $this->loadLayout();
48
  $this->renderLayout();
49
  }
50
 
51
+ public function removeOrphanSyncEntries()
52
+ {
53
+ $combinedMediaRepository = Mage::getModel(
54
+ 'cloudinary_cloudinary/synchronisedMediaUnifier',
55
+ [
56
+ Mage::getResourceModel('cloudinary_cloudinary/synchronisation_collection'),
57
+ Mage::getResourceModel('cloudinary_cloudinary/cms_synchronisation_collection')
58
+ ]
59
+ );
60
+
61
+ foreach ($combinedMediaRepository->findOrphanedSynchronisedImages() as $orphanImage) {
62
+ Mage::getModel('cloudinary_cloudinary/migrationError')->orphanRemoved($orphanImage)->save();
63
+ $orphanImage->delete();
64
+ }
65
+ }
66
+
67
  public function configAction()
68
  {
69
  $this->_redirect("*/system_config/edit/section/cloudinary/");
app/code/community/Cloudinary/Cloudinary/etc/config.xml CHANGED
@@ -2,7 +2,7 @@
2
  <config>
3
  <modules>
4
  <Cloudinary_Cloudinary>
5
- <version>2.1.0</version>
6
  </Cloudinary_Cloudinary>
7
  </modules>
8
  <global>
2
  <config>
3
  <modules>
4
  <Cloudinary_Cloudinary>
5
+ <version>2.2.0</version>
6
  </Cloudinary_Cloudinary>
7
  </modules>
8
  <global>
lib/CloudinaryExtension/CloudinaryImageManager.php CHANGED
@@ -1,12 +1,21 @@
1
  <?php
 
2
  namespace CloudinaryExtension;
3
 
 
 
 
4
  /**
5
  * Class CloudinaryImageManager
6
  * @package CloudinaryExtension
7
  */
8
  class CloudinaryImageManager
9
  {
 
 
 
 
 
10
  /**
11
  * @var ImageProvider
12
  */
@@ -33,12 +42,29 @@ class CloudinaryImageManager
33
 
34
  /**
35
  * @param Image $image
 
 
36
  */
37
- public function uploadAndSynchronise(Image $image)
38
  {
39
- $this->cloudinaryImageProvider->upload($image);
40
- $this->synchronisationRepository->saveAsSynchronized($image->getRelativePath());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
 
42
  }
43
 
44
  /**
@@ -49,4 +75,15 @@ class CloudinaryImageManager
49
  $this->cloudinaryImageProvider->delete($image);
50
  $this->synchronisationRepository->removeSynchronised($image->getRelativePath());
51
  }
52
- }
 
 
 
 
 
 
 
 
 
 
 
1
  <?php
2
+
3
  namespace CloudinaryExtension;
4
 
5
+ use CloudinaryExtension\Exception\FileExists;
6
+ use Symfony\Component\Console\Output\OutputInterface;
7
+
8
  /**
9
  * Class CloudinaryImageManager
10
  * @package CloudinaryExtension
11
  */
12
  class CloudinaryImageManager
13
  {
14
+ const MESSAGE_UPLOADING_IMAGE = 'Uploading image: %s';
15
+ const MESSAGE_UPLOADED_EXISTS = 'Image exists - marked as synchronised: %s';
16
+ const MESSAGE_RETRY = 'Failed with error: %s - attempting retry %d';
17
+ const MAXIMUM_RETRY_ATTEMPTS = 3;
18
+
19
  /**
20
  * @var ImageProvider
21
  */
42
 
43
  /**
44
  * @param Image $image
45
+ * @param OutputInterface|null $output
46
+ * @throws \Exception
47
  */
48
+ public function uploadAndSynchronise(Image $image, OutputInterface $output = null, $retryAttempt = 0)
49
  {
50
+ try {
51
+ $this->report($output, sprintf(self::MESSAGE_UPLOADING_IMAGE, $image));
52
+ $this->cloudinaryImageProvider->upload($image);
53
+ } catch (FileExists $e) {
54
+ $this->report($output, sprintf(self::MESSAGE_UPLOADED_EXISTS, $image));
55
+ } catch (\Exception $e) {
56
+ if ($retryAttempt < self::MAXIMUM_RETRY_ATTEMPTS) {
57
+ $retryAttempt++;
58
+ $this->report($output, sprintf(self::MESSAGE_RETRY, $e->getMessage(), $retryAttempt));
59
+ usleep(rand(10, 1000) * 1000);
60
+ $this->uploadAndSynchronise($image, $output, $retryAttempt);
61
+ return;
62
+ }
63
+
64
+ throw $e;
65
+ }
66
 
67
+ $this->synchronisationRepository->saveAsSynchronized($image->getRelativePath());
68
  }
69
 
70
  /**
75
  $this->cloudinaryImageProvider->delete($image);
76
  $this->synchronisationRepository->removeSynchronised($image->getRelativePath());
77
  }
78
+
79
+ /**
80
+ * @param OutputInterface|null $output
81
+ * @param string $message
82
+ */
83
+ private function report(OutputInterface $output = null, $message = '')
84
+ {
85
+ if ($output) {
86
+ $output->writeln($message);
87
+ }
88
+ }
89
+ }
lib/CloudinaryExtension/CloudinaryImageProvider.php CHANGED
@@ -5,8 +5,7 @@ namespace CloudinaryExtension;
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
  use CloudinaryExtension\Image\Transformation\Format;
@@ -52,17 +51,18 @@ class CloudinaryImageProvider implements ImageProvider
52
 
53
  public function upload(Image $image)
54
  {
 
 
55
  try {
56
  $uploadResult = Uploader::upload(
57
  (string)$image,
58
  $this->configuration->getUploadConfig()->toArray() + [ "folder" => $image->getRelativeFolder()]
59
  );
60
-
61
- return $this->uploadResponseValidator->validateResponse($image, $uploadResult);
62
-
63
  } catch (\Exception $e) {
64
- MigrationError::throwWith($image, MigrationError::CODE_API_ERROR, $e->getMessage());
65
  }
 
 
66
  }
67
 
68
  public function retrieveTransformed(Image $image, Transformation $transformation)
@@ -80,7 +80,7 @@ class CloudinaryImageProvider implements ImageProvider
80
 
81
  public function delete(Image $image)
82
  {
83
- Uploader::destroy($image->getId());
84
  }
85
 
86
  public function validateCredentials()
@@ -93,4 +93,4 @@ class CloudinaryImageProvider implements ImageProvider
93
  Cloudinary::config($this->configurationBuilder->build());
94
  Cloudinary::$USER_PLATFORM = $this->configuration->getUserPlatform();
95
  }
96
- }
5
 
6
  use Cloudinary;
7
  use Cloudinary\Uploader;
8
+ use CloudinaryExtension\Exception\ApiError;
 
9
  use CloudinaryExtension\Image\Transformation;
10
  use CloudinaryExtension\Security;
11
  use CloudinaryExtension\Image\Transformation\Format;
51
 
52
  public function upload(Image $image)
53
  {
54
+ $uploadResult = null;
55
+
56
  try {
57
  $uploadResult = Uploader::upload(
58
  (string)$image,
59
  $this->configuration->getUploadConfig()->toArray() + [ "folder" => $image->getRelativeFolder()]
60
  );
 
 
 
61
  } catch (\Exception $e) {
62
+ ApiError::throwWith($image, $e->getMessage());
63
  }
64
+
65
+ return $this->uploadResponseValidator->validateResponse($image, $uploadResult);
66
  }
67
 
68
  public function retrieveTransformed(Image $image, Transformation $transformation)
80
 
81
  public function delete(Image $image)
82
  {
83
+ Uploader::destroy($image->getIdWithoutExtension());
84
  }
85
 
86
  public function validateCredentials()
93
  Cloudinary::config($this->configurationBuilder->build());
94
  Cloudinary::$USER_PLATFORM = $this->configuration->getUserPlatform();
95
  }
96
+ }
lib/CloudinaryExtension/Exception/ApiError.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace CloudinaryExtension\Exception;
4
+
5
+ class ApiError extends MigrationError
6
+ {
7
+ const DEFAULT_MESSAGE = 'Internal API error';
8
+ }
lib/CloudinaryExtension/Exception/FileExists.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace CloudinaryExtension\Exception;
4
+
5
+ class FileExists extends MigrationError
6
+ {
7
+ const DEFAULT_MESSAGE = 'File already exists (cloudinary is case insensitive!!).';
8
+ }
lib/CloudinaryExtension/Exception/MigrationError.php CHANGED
@@ -11,16 +11,21 @@ use 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
  */
@@ -31,20 +36,13 @@ class MigrationError extends Exception
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
  }
11
  */
12
  class MigrationError extends Exception
13
  {
14
+ const DEFAULT_MESSAGE = 'Unknown error';
 
 
 
 
 
 
15
 
16
+ /**
17
+ * @var Image
18
+ */
19
  private $image;
20
 
21
+ /**
22
+ * @param string $suffix
23
+ */
24
+ public function suffixMessage($suffix)
25
+ {
26
+ $this->message = sprintf('%s%s', $this->message, $suffix);
27
+ }
28
+
29
  /**
30
  * @return Image
31
  */
36
 
37
  /**
38
  * @param Image $image
39
+ * @param string $message
40
+ * @throws MigrationError
 
41
  */
42
+ public static function throwWith(Image $image, $message = '')
 
 
 
 
 
 
 
43
  {
44
+ $exception = new static($message ?: static::DEFAULT_MESSAGE);
45
+ $exception->image = $image;
46
+ throw $exception;
47
  }
 
48
  }
lib/CloudinaryExtension/Image.php CHANGED
@@ -40,11 +40,20 @@ class Image implements ImageInterface
40
 
41
  public function getId()
42
  {
43
- if ($this->relativePath) {
44
- return $this->getRelativeFolder() . DIRECTORY_SEPARATOR . $this->pathInfo['filename'];
45
- } else {
46
- return $this->pathInfo['filename'];
47
- }
 
 
 
 
 
 
 
 
 
48
  }
49
 
50
  public function getExtension()
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()
lib/CloudinaryExtension/Image/Transformation.php CHANGED
@@ -17,17 +17,12 @@ class Transformation
17
  private $crop;
18
  private $fetchFormat;
19
  private $quality;
20
- private $format;
21
  private $dpr;
22
- private $validFormats;
23
  private $flags;
24
 
25
  public function __construct()
26
  {
27
- $this->fetchFormat = FetchFormat::fromString(Format::FETCH_FORMAT_AUTO);
28
  $this->crop = 'pad';
29
- $this->format = Format::fromExtension('jpg');
30
- $this->validFormats = array('gif', 'jpg', 'png', 'svg', 'webp');
31
  $this->flags = [];
32
  }
33
 
@@ -56,21 +51,6 @@ class Transformation
56
  return $this;
57
  }
58
 
59
- public function withFormat(Format $format)
60
- {
61
- if (in_array((string)$format, $this->validFormats)) {
62
- $this->format = $format;
63
- }
64
-
65
- return $this;
66
- }
67
-
68
- public function withoutFormat()
69
- {
70
- $this->format = null;
71
- return $this;
72
- }
73
-
74
  public function withQuality(Quality $quality)
75
  {
76
  $this->quality = $quality;
@@ -83,11 +63,6 @@ class Transformation
83
  return $this;
84
  }
85
 
86
- public function withOptimisationDisabled()
87
- {
88
- return $this->withFetchFormat(FetchFormat::fromString(''));
89
- }
90
-
91
  public function addFlags(array $flags = [])
92
  {
93
  $this->flags += $flags;
@@ -108,7 +83,6 @@ class Transformation
108
  'gravity' => (string)$this->gravity ?: null,
109
  'width' => $this->dimensions ? $this->dimensions->getWidth() : null,
110
  'height' => $this->dimensions ? $this->dimensions->getHeight() : null,
111
- 'format' => (string)$this->format,
112
  'dpr' => (string)$this->dpr,
113
  'flags' => $this->flags
114
  );
17
  private $crop;
18
  private $fetchFormat;
19
  private $quality;
 
20
  private $dpr;
 
21
  private $flags;
22
 
23
  public function __construct()
24
  {
 
25
  $this->crop = 'pad';
 
 
26
  $this->flags = [];
27
  }
28
 
51
  return $this;
52
  }
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  public function withQuality(Quality $quality)
55
  {
56
  $this->quality = $quality;
63
  return $this;
64
  }
65
 
 
 
 
 
 
66
  public function addFlags(array $flags = [])
67
  {
68
  $this->flags += $flags;
83
  'gravity' => (string)$this->gravity ?: null,
84
  'width' => $this->dimensions ? $this->dimensions->getWidth() : null,
85
  'height' => $this->dimensions ? $this->dimensions->getHeight() : null,
 
86
  'dpr' => (string)$this->dpr,
87
  'flags' => $this->flags
88
  );
lib/CloudinaryExtension/Image/Transformation/FetchFormat.php CHANGED
@@ -13,6 +13,11 @@ class FetchFormat
13
  $this->value = $value;
14
  }
15
 
 
 
 
 
 
16
  public static function fromString($value)
17
  {
18
  return new FetchFormat($value);
13
  $this->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);
lib/CloudinaryExtension/Image/Transformation/Format.php DELETED
@@ -1,25 +0,0 @@
1
- <?php
2
-
3
- namespace CloudinaryExtension\Image\Transformation;
4
-
5
- class Format
6
- {
7
- const FETCH_FORMAT_AUTO = 'auto';
8
-
9
- private $value;
10
-
11
- private function __construct($value)
12
- {
13
- $this->value = $value;
14
- }
15
-
16
- public static function fromExtension($value)
17
- {
18
- return new Format($value);
19
- }
20
-
21
- public function __toString()
22
- {
23
- return $this->value;
24
- }
25
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/CloudinaryExtension/Migration/BatchUploader.php CHANGED
@@ -3,10 +3,10 @@
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
  {
@@ -16,27 +16,70 @@ class BatchUploader
16
 
17
  const MESSAGE_UPLOAD_ERROR = 'Cloudinary migration: %s trying to upload %s';
18
 
 
 
 
 
 
 
 
19
  private $imageProvider;
20
 
 
 
 
21
  private $baseMediaPath;
22
 
 
 
 
23
  private $logger;
24
 
 
 
 
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
- {
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  $this->imageProvider = $imageProvider;
35
  $this->migrationTask = $migrationTask;
36
- $this->baseMediaPath = $baseMediaPath;
37
  $this->logger = $logger;
 
 
38
  }
39
 
 
 
 
40
  public function uploadImages(array $images)
41
  {
42
  $this->countMigrated = 0;
@@ -50,42 +93,87 @@ class BatchUploader
50
  $this->logger->notice(sprintf(self::MESSAGE_STATUS, $this->countMigrated, $this->countFailed));
51
  }
52
 
 
 
 
 
53
  private function getAbsolutePath(Synchronizable $image)
54
  {
55
  return sprintf('%s%s', $this->baseMediaPath, $image->getFilename());
56
  }
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
  $this->imageProvider->upload($apiImage);
66
  $image->tagAsSynchronized();
67
  $this->countMigrated++;
68
- $this->logger->notice(sprintf(self::MESSAGE_UPLOADED, $absolutePath . ' - ' . $relativePath));
 
 
 
 
69
  } catch (\Exception $e) {
70
- $this->errors[] = $e;
 
 
 
 
 
 
 
 
 
 
 
71
  $this->countFailed++;
72
- $this->logger->error(sprintf(self::MESSAGE_UPLOAD_ERROR, $e->getMessage(), $absolutePath . ' - ' . $relativePath));
73
  }
74
  }
75
 
76
  /**
77
- * @return array
78
  */
79
- public function getErrors()
80
  {
81
- return $this->errors;
 
82
  }
83
 
84
- public function getMigrationErrors()
 
 
 
 
 
85
  {
86
- return array_filter($this->errors, function ($val) {
87
- return $val instanceof MigrationError;
88
- });
 
 
 
89
  }
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
3
  namespace CloudinaryExtension\Migration;
4
 
5
  use CloudinaryExtension\Exception\MigrationError;
6
+ use CloudinaryExtension\Exception\FileExists;
7
  use CloudinaryExtension\Image;
8
  use CloudinaryExtension\Image\Synchronizable;
9
  use CloudinaryExtension\ImageProvider;
 
10
 
11
  class BatchUploader
12
  {
16
 
17
  const MESSAGE_UPLOAD_ERROR = 'Cloudinary migration: %s trying to upload %s';
18
 
19
+ const MAXIMUM_RETRY_ATTEMPTS = 3;
20
+
21
+ const MESSAGE_UPLOADED_EXISTS = 'Cloudinary migration: %s exists - tagged as synchronized';
22
+
23
+ /**
24
+ * @var ImageProvider
25
+ */
26
  private $imageProvider;
27
 
28
+ /**
29
+ * @var string
30
+ */
31
  private $baseMediaPath;
32
 
33
+ /**
34
+ * @var Logger
35
+ */
36
  private $logger;
37
 
38
+ /**
39
+ * @var Task
40
+ */
41
  private $migrationTask;
42
 
43
+ /**
44
+ * @var callable
45
+ */
46
+ private $exceptionCallback;
47
+
48
+ /**
49
+ * @var int
50
+ */
51
  private $countMigrated = 0;
 
52
 
53
+ /**
54
+ * @var int
55
+ */
56
+ private $countFailed = 0;
57
 
58
+ /**
59
+ * BatchUploader constructor.
60
+ * @param ImageProvider $imageProvider
61
+ * @param Task $migrationTask
62
+ * @param Logger $logger
63
+ * @param string $baseMediaPath
64
+ * @param callable $exceptionCallback
65
+ */
66
+ public function __construct(
67
+ ImageProvider $imageProvider,
68
+ Task $migrationTask,
69
+ Logger $logger,
70
+ $baseMediaPath,
71
+ callable $exceptionCallback
72
+ ) {
73
  $this->imageProvider = $imageProvider;
74
  $this->migrationTask = $migrationTask;
 
75
  $this->logger = $logger;
76
+ $this->baseMediaPath = $baseMediaPath;
77
+ $this->exceptionCallback = $exceptionCallback;
78
  }
79
 
80
+ /**
81
+ * @param [Image]
82
+ */
83
  public function uploadImages(array $images)
84
  {
85
  $this->countMigrated = 0;
93
  $this->logger->notice(sprintf(self::MESSAGE_STATUS, $this->countMigrated, $this->countFailed));
94
  }
95
 
96
+ /**
97
+ * @param Synchronizable $image
98
+ * @return string
99
+ */
100
  private function getAbsolutePath(Synchronizable $image)
101
  {
102
  return sprintf('%s%s', $this->baseMediaPath, $image->getFilename());
103
  }
104
 
105
+ /**
106
+ * @param Synchronizable $image
107
+ * @param int $retryAttempt
108
+ */
109
+ private function uploadImage(Synchronizable $image, $retryAttempt = 0)
110
  {
111
  $absolutePath = $this->getAbsolutePath($image);
112
  $relativePath = $image->getRelativePath();
113
+ $pathDescription = sprintf('%s - %s', $absolutePath, $relativePath);
114
  $apiImage = Image::fromPath($absolutePath, $relativePath);
 
115
  try {
116
  $this->imageProvider->upload($apiImage);
117
  $image->tagAsSynchronized();
118
  $this->countMigrated++;
119
+ $this->logger->notice(sprintf(self::MESSAGE_UPLOADED, $pathDescription));
120
+ } catch (FileExists $e) {
121
+ $image->tagAsSynchronized();
122
+ $this->countMigrated++;
123
+ $this->logger->notice(sprintf(self::MESSAGE_UPLOADED_EXISTS, $image->getFilename()));
124
  } catch (\Exception $e) {
125
+ if ($retryAttempt < self::MAXIMUM_RETRY_ATTEMPTS) {
126
+ $retryAttempt++;
127
+ $retryMessage = sprintf(' - attempting retry %d', $retryAttempt);
128
+ $this->notify($this->addRetryMessage($retryMessage, $e));
129
+ $this->logger->error($this->buildUploadErrorMessage($e, $pathDescription . $retryMessage));
130
+ usleep(rand(10, 1000) * 1000);
131
+ $this->uploadImage($image, $retryAttempt);
132
+ return;
133
+ }
134
+
135
+ $retryMessage = sprintf('- failed after %d retry attempts', $retryAttempt);
136
+ $this->notify($this->addRetryMessage($retryMessage, $e));
137
  $this->countFailed++;
138
+ $this->logger->error($this->buildUploadErrorMessage($e, $pathDescription . $retryMessage));
139
  }
140
  }
141
 
142
  /**
143
+ * @param \Exception $e
144
  */
145
+ private function notify(\Exception $e)
146
  {
147
+ $callback = $this->exceptionCallback;
148
+ $callback($e);
149
  }
150
 
151
+ /**
152
+ * @param $retryMessage
153
+ * @param \Exception $e
154
+ * @return \Exception
155
+ */
156
+ private function addRetryMessage($retryMessage, \Exception $e)
157
  {
158
+ if ($e instanceof MigrationError) {
159
+ $e->suffixMessage($retryMessage);
160
+ } else {
161
+ $e = new \Exception($e->getMessage() . $retryMessage);
162
+ }
163
+ return $e;
164
  }
165
 
166
+ /**
167
+ * @param \Exception $e
168
+ * @param $message
169
+ * @return string
170
+ */
171
+ private function buildUploadErrorMessage(\Exception $e, $message)
172
+ {
173
+ return sprintf(
174
+ self::MESSAGE_UPLOAD_ERROR,
175
+ $e->getMessage(),
176
+ $message
177
+ );
178
+ }
179
  }
lib/CloudinaryExtension/Migration/SynchronizedMediaRepository.php CHANGED
@@ -5,4 +5,5 @@ namespace CloudinaryExtension\Migration;
5
  interface SynchronizedMediaRepository
6
  {
7
  public function findUnsynchronisedImages();
 
8
  }
5
  interface SynchronizedMediaRepository
6
  {
7
  public function findUnsynchronisedImages();
8
+ public function findOrphanedSynchronisedImages();
9
  }
lib/CloudinaryExtension/UploadResponseValidator.php CHANGED
@@ -2,14 +2,14 @@
2
 
3
  namespace CloudinaryExtension;
4
 
5
- use CloudinaryExtension\Exception\MigrationError;
6
 
7
  class UploadResponseValidator
8
  {
9
  public function validateResponse($image, $uploadResponse)
10
  {
11
  if ($uploadResponse['existing'] == 1) {
12
- MigrationError::throwWith($image, MigrationError::CODE_FILE_ALREADY_EXISTS);
13
  }
14
 
15
  return $uploadResponse;
2
 
3
  namespace CloudinaryExtension;
4
 
5
+ use CloudinaryExtension\Exception\FileExists;
6
 
7
  class UploadResponseValidator
8
  {
9
  public function validateResponse($image, $uploadResponse)
10
  {
11
  if ($uploadResponse['existing'] == 1) {
12
+ FileExists::throwWith($image);
13
  }
14
 
15
  return $uploadResponse;
lib/CloudinaryExtension/UrlGenerator.php CHANGED
@@ -41,13 +41,10 @@ class UrlGenerator
41
  return (string)$image;
42
  }
43
 
44
- $transformation = clone ($transformation ?: $this->configuration->getDefaultTransformation());
45
-
46
- if (in_array($image->getExtension(), $this->configuration->getFormatsToPreserve())) {
47
- $transformation->withoutFormat();
48
- }
49
-
50
- return (string)$this->imageProvider->retrieveTransformed($image, $transformation);
51
  }
52
 
53
  /**
@@ -58,7 +55,7 @@ class UrlGenerator
58
  */
59
  public function generateWithDimensions(ImageInterface $image, Dimensions $dimensions)
60
  {
61
- $transformation = clone $this->configuration->getDefaultTransformation();
62
 
63
  return $this->generateFor($image, $transformation->withDimensions($dimensions));
64
  }
41
  return (string)$image;
42
  }
43
 
44
+ return (string)$this->imageProvider->retrieveTransformed(
45
+ $image,
46
+ $transformation ?: $this->configuration->getDefaultTransformation()
47
+ );
 
 
 
48
  }
49
 
50
  /**
55
  */
56
  public function generateWithDimensions(ImageInterface $image, Dimensions $dimensions)
57
  {
58
+ $transformation = $this->configuration->getDefaultTransformation();
59
 
60
  return $this->generateFor($image, $transformation->withDimensions($dimensions));
61
  }
package.xml CHANGED
@@ -1,22 +1,23 @@
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Cloudinary_Cloudinary</name>
4
- <version>2.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.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>2017-04-07</date>
18
- <time>10:22:05</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="9cd8997737c1191cff57dc8530daa26c"/><dir name="Page"><file name="Menu.php" hash="891d6a4c075ba03c9a20658076c86ad0"/></dir><dir name="System"><dir name="Config"><file name="Signup.php" hash="ed6accbe7a4ce16bb0679eaf0c2dbb22"/></dir></dir></dir></dir><dir name="Helper"><file name="Autoloader.php" hash="393b3e2fc25e63ca28157152d2542b18"/><file name="Console.php" hash="7c909e3226c51c05d6da1f6ff9cbbfc9"/><file name="Cron.php" hash="805557370a2006b15444b7a62bbbc65a"/><file name="Data.php" hash="42c9d44f1bbe530e30cf5379846dea65"/><file name="Image.php" hash="af1c1d734793d6b08feaa7e1abd591d0"/></dir><dir name="Model"><dir name="Catalog"><dir name="Product"><file name="Image.php" hash="b5d14bcb836158890152c9fed191f8bc"/><dir name="Media"><file name="Config.php" hash="c2dbac447d4a22c920c19b0d4eb2672e"/></dir><file name="Media.php" hash="05726616a07d7d08933e9654e6107283"/></dir></dir><dir name="Cms"><dir name="Adminhtml"><dir name="Template"><file name="Filter.php" hash="792893f6b4e884a8e42847d457a6068c"/></dir></dir><file name="Synchronisation.php" hash="8d830a18f169a0ff5f7d865e3afcb710"/><dir name="Template"><file name="Filter.php" hash="c3fe64f98128043de13e92156a26ab02"/></dir><file name="Uploader.php" hash="021195c01a7e6fd9e72c5a30ebd11554"/><dir name="Wysiwyg"><dir name="Images"><file name="Storage.php" hash="0d23e557d6db06308886d9307fe92665"/></dir></dir></dir><file name="CollectionCounter.php" hash="e69953aee5d966a3ec13d33533f017e0"/><file name="Configuration.php" hash="fa6fb95c166048005143bc52d85933f9"/><file name="Cron.php" hash="423c66992195d830973bb9c2d10e4551"/><dir name="Exception"><file name="BadFilePathException.php" hash="68135da8dfe2f0589a531b4bd36e3330"/></dir><file name="Image.php" hash="84d42417651e2feefdb1b0c048f61ca4"/><file name="Logger.php" hash="ab5e45b0769c9dbee33025a202a87fee"/><file name="MagentoFolderTranslator.php" hash="37219fc1804d6ad8d1686af8509e1963"/><file name="Migration.php" hash="30f671877307d93904fd823d01d35c1d"/><file name="MigrationError.php" hash="1c91373b020d639ae3fb8acfa099eea0"/><file name="Observer.php" hash="78fdf1b2d98864f2aa0538b5eb203275"/><dir name="Resource"><dir name="Cms"><dir name="Synchronisation"><file name="Collection.php" hash="b8cbe30bf2a3b03baea4d62bd7127323"/></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="1868b1d1a33a7343162c56f86235623b"/></dir><file name="Synchronisation.php" hash="5b721d854d8f89bc3310e46081be7153"/></dir><file name="Synchronisation.php" hash="05414c5959efc7656b8e101617005d9a"/><file name="SynchronisedMediaUnifier.php" hash="dd47a04cc2eaa2a81b6dce27f22301f2"/><file name="SynchronizationChecker.php" hash="fd2a1fd763b7f5b895a392b48e209f00"/><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="cb874253fcee0f772eae77db9935dcf1"/></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="24a67a12b2a796abfdd2a990b1a54c54"/><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"/><file name="upgrade-1.1.5-1.1.6.php" hash="323c5e50635018be420cf524072f6a92"/><file name="upgrade-2.0.0-2.1.0.php" hash="9d053bed5099e064eed808a090755b03"/></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="CloudinaryImageManager.php" hash="a5037ca0a15c864ffb5809e5be0fa507"/><file name="CloudinaryImageProvider.php" hash="53fc1d86819319c451198a753c0dfd38"/><file name="ConfigurationBuilder.php" hash="c8832b207d9228ef14b3c74100ee74c4"/><file name="ConfigurationInterface.php" hash="4129b1e282fb0000b78946f07baa740c"/><file name="CredentialValidator.php" hash="965b0bd024f668aabcc9f30ef2e3c240"/><file name="Credentials.php" hash="71054eb4af7b6496608ffd14912bdbe4"/><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="ImageFactory.php" hash="0a2e066331584d33a9c4ec03787fd6e5"/><file name="LocalImage.php" hash="ab9b814b1a006baf05b9904af3ebce74"/><file name="Synchronizable.php" hash="b842f71ed25718838233207b7748f1bf"/><file name="SynchronizationChecker.php" hash="f2c45545766a81fede68138cf84dd1af"/><dir name="Transformation"><file name="Crop.php" hash="84e57281780a57326c938ac776641e8b"/><file name="Dimensions.php" hash="86a36c564aa41a08da2cf383d611c060"/><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="a6281e9e7535ddc01729585ffc8bb036"/></dir><file name="Image.php" hash="896d409ebebf8af12dfe0225b21f4c83"/><file name="ImageInterface.php" hash="4a7c7e39d7fda0b0fa99affcac78ec8c"/><file name="ImageProvider.php" hash="a615c472cdc8a6ad7d887133db35c262"/><dir name="Migration"><file name="BatchUploader.php" hash="6aae7adc0f4f99dab7836976d0a06d75"/><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="418af61bdbcfef955df29ac47c54415b"/><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="SynchroniseAssetsRepositoryInterface.php" hash="e0d8e270ae2c74214e82e53e04e3dc0f"/><file name="UploadConfig.php" hash="a68f1ea7b84574ec36a8d2fac9bd6054"/><file name="UploadResponseValidator.php" hash="9fc81798b1c1319b04c71079d8de2bd5"/><file name="UrlGenerator.php" hash="4f4f40a76cbea2efa239084bdf0f8a65"/><file name="ValidateRemoteUrlRequest.php" hash="c2e2eb712e5293ad508a23610dfbbd6d"/></dir><dir name="Cloudinary"><file name="Api.php" hash="d71322346c3625db7c3563cdad191e8c"/><file name="AuthToken.php" hash="bec8b856baf85d89a249c932c3eba39f"/><file name="Cloudinary.php" hash="f2ec7b7bc8fc7c978f7773c3d4ccc5dd"/><file name="CloudinaryField.php" hash="411714580d21b58115ab07737367173a"/><file name="Helpers.php" hash="4db8371fc84d34be49c8ea04eee7d6eb"/><file name="PreloadedFile.php" hash="73cc9e276f96553814f05eae592d11ee"/><file name="Uploader.php" hash="eae92a330d19654028a8d16410616421"/><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="e5a2def3fccdb6a365364dcf23964d08"/></dir></dir><dir name="template"><dir name="cloudinary"><file name="manage.phtml" hash="7630d346f45d7118748b647be3501fa3"/><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.1.0</max></php></required></dependencies>
22
  </package>
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Cloudinary_Cloudinary</name>
4
+ <version>2.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> Release 2.2.0 notes:&#xD;
12
+ - Existing images are not remigrated during migration, and do not fail migration&#xD;
13
+ - Fix bug where migration status shows value above 100%&#xD;
14
+ - Preserve original file extensions in Cloudinary image urls&#xD;
15
+ - Retry migration when network failures occur&#xD;
16
+ </notes>
17
  <authors><author><name>Cloudinary</name><user>cloudinary</user><email>accounts+magento@cloudinary.com</email></author></authors>
18
+ <date>2017-05-04</date>
19
+ <time>07:33:31</time>
20
+ <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="9cd8997737c1191cff57dc8530daa26c"/><dir name="Page"><file name="Menu.php" hash="891d6a4c075ba03c9a20658076c86ad0"/></dir><dir name="System"><dir name="Config"><file name="Signup.php" hash="ed6accbe7a4ce16bb0679eaf0c2dbb22"/></dir></dir></dir></dir><dir name="Helper"><file name="Autoloader.php" hash="393b3e2fc25e63ca28157152d2542b18"/><file name="Console.php" hash="7c909e3226c51c05d6da1f6ff9cbbfc9"/><file name="Cron.php" hash="805557370a2006b15444b7a62bbbc65a"/><file name="Data.php" hash="42c9d44f1bbe530e30cf5379846dea65"/><file name="Image.php" hash="af1c1d734793d6b08feaa7e1abd591d0"/></dir><dir name="Model"><dir name="Catalog"><dir name="Product"><file name="Image.php" hash="b5d14bcb836158890152c9fed191f8bc"/><dir name="Media"><file name="Config.php" hash="c2dbac447d4a22c920c19b0d4eb2672e"/></dir><file name="Media.php" hash="05726616a07d7d08933e9654e6107283"/></dir></dir><dir name="Cms"><dir name="Adminhtml"><dir name="Template"><file name="Filter.php" hash="792893f6b4e884a8e42847d457a6068c"/></dir></dir><file name="Synchronisation.php" hash="8d830a18f169a0ff5f7d865e3afcb710"/><dir name="Template"><file name="Filter.php" hash="c3fe64f98128043de13e92156a26ab02"/></dir><file name="Uploader.php" hash="021195c01a7e6fd9e72c5a30ebd11554"/><dir name="Wysiwyg"><dir name="Images"><file name="Storage.php" hash="0d23e557d6db06308886d9307fe92665"/></dir></dir></dir><file name="CollectionCounter.php" hash="e69953aee5d966a3ec13d33533f017e0"/><file name="Configuration.php" hash="fa6fb95c166048005143bc52d85933f9"/><file name="Cron.php" hash="a7296d26862df0d382023d33496bb80a"/><dir name="Exception"><file name="BadFilePathException.php" hash="68135da8dfe2f0589a531b4bd36e3330"/></dir><file name="Image.php" hash="84d42417651e2feefdb1b0c048f61ca4"/><file name="Logger.php" hash="ab5e45b0769c9dbee33025a202a87fee"/><file name="MagentoFolderTranslator.php" hash="37219fc1804d6ad8d1686af8509e1963"/><file name="Migration.php" hash="30f671877307d93904fd823d01d35c1d"/><file name="MigrationError.php" hash="67eca6725679a5dae6eab65efae39c64"/><file name="Observer.php" hash="78fdf1b2d98864f2aa0538b5eb203275"/><dir name="Resource"><dir name="Cms"><dir name="Synchronisation"><file name="Collection.php" hash="226db91ad96a348f0d937781358f9dec"/></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="d16275121feb30e57b9b54122e26f05c"/></dir><file name="Synchronisation.php" hash="5b721d854d8f89bc3310e46081be7153"/></dir><file name="Synchronisation.php" hash="05414c5959efc7656b8e101617005d9a"/><file name="SynchronisedMediaUnifier.php" hash="899abd4ef47be0ce3604cfc8790c582a"/><file name="SynchronizationChecker.php" hash="fd2a1fd763b7f5b895a392b48e209f00"/><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="d44f610256d0d7a5633a94977442787b"/></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="109fe3a888071222230cbd8804fa8123"/><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"/><file name="upgrade-1.1.5-1.1.6.php" hash="323c5e50635018be420cf524072f6a92"/><file name="upgrade-2.0.0-2.1.0.php" hash="9d053bed5099e064eed808a090755b03"/></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="CloudinaryImageManager.php" hash="94af20e7b144adc4a27ee011aabbefe3"/><file name="CloudinaryImageProvider.php" hash="c4199a5384e6c42316ab4c5c74ed35e3"/><file name="ConfigurationBuilder.php" hash="c8832b207d9228ef14b3c74100ee74c4"/><file name="ConfigurationInterface.php" hash="4129b1e282fb0000b78946f07baa740c"/><file name="CredentialValidator.php" hash="965b0bd024f668aabcc9f30ef2e3c240"/><file name="Credentials.php" hash="71054eb4af7b6496608ffd14912bdbe4"/><dir name="Exception"><file name="ApiError.php" hash="d6fb1e32ca96183e9800c3706de37206"/><file name="FileExists.php" hash="6969387c67deef15a378cb94b9d269ac"/><file name="InvalidCredentials.php" hash="abecc635a25f6c9896c605ad16e1f7d7"/><file name="MigrationError.php" hash="92ea7aa65f4e14e606fa107d33f3ad30"/></dir><file name="FolderTranslator.php" hash="19a335acf751d67bd7efe46829602490"/><dir name="Image"><file name="ImageFactory.php" hash="0a2e066331584d33a9c4ec03787fd6e5"/><file name="LocalImage.php" hash="ab9b814b1a006baf05b9904af3ebce74"/><file name="Synchronizable.php" hash="b842f71ed25718838233207b7748f1bf"/><file name="SynchronizationChecker.php" hash="f2c45545766a81fede68138cf84dd1af"/><dir name="Transformation"><file name="Crop.php" hash="84e57281780a57326c938ac776641e8b"/><file name="Dimensions.php" hash="86a36c564aa41a08da2cf383d611c060"/><file name="Dpr.php" hash="f78cd1bfabaf3088ca8d4af972bfd453"/><file name="FetchFormat.php" hash="c745a6d80b509755cb6ae9fe37b95d76"/><file name="Gravity.php" hash="c1c2adf4dbbeaa6b06d67d2014300559"/><file name="Quality.php" hash="23a857f3910aecf6e45645194ff7f54e"/></dir><file name="Transformation.php" hash="7037bb18dcf82ee729deb6a06344faab"/></dir><file name="Image.php" hash="3090cfbaa3b2a90b5ef4a2a94b165581"/><file name="ImageInterface.php" hash="4a7c7e39d7fda0b0fa99affcac78ec8c"/><file name="ImageProvider.php" hash="a615c472cdc8a6ad7d887133db35c262"/><dir name="Migration"><file name="BatchUploader.php" hash="87d9dcf1c07fb9975283d7e4f577f1b9"/><file name="Logger.php" hash="648b47bb065de0c81b386ac300b4f9a3"/><file name="Queue.php" hash="add92864192b0950c29c91ffe5e5a3ee"/><file name="SynchronizedMediaRepository.php" hash="6b4e07f253aad9b845c68d51a1ab4166"/><file name="Task.php" hash="ac11d06c531d48b38cf88f6e8f2bdc19"/></dir><dir name="Security"><file name="ApiSignature.php" hash="049c7db2684ec2a6cf5bb4efcd064951"/><file name="CloudinaryEnvironmentVariable.php" hash="418af61bdbcfef955df29ac47c54415b"/><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="SynchroniseAssetsRepositoryInterface.php" hash="e0d8e270ae2c74214e82e53e04e3dc0f"/><file name="UploadConfig.php" hash="a68f1ea7b84574ec36a8d2fac9bd6054"/><file name="UploadResponseValidator.php" hash="2d20dba12898b277b20b010d24f18859"/><file name="UrlGenerator.php" hash="06d223e5628c68570a2af4f8fb2306ce"/><file name="ValidateRemoteUrlRequest.php" hash="c2e2eb712e5293ad508a23610dfbbd6d"/></dir><dir name="Cloudinary"><file name="Api.php" hash="d71322346c3625db7c3563cdad191e8c"/><file name="AuthToken.php" hash="bec8b856baf85d89a249c932c3eba39f"/><file name="Cloudinary.php" hash="f2ec7b7bc8fc7c978f7773c3d4ccc5dd"/><file name="CloudinaryField.php" hash="411714580d21b58115ab07737367173a"/><file name="Helpers.php" hash="4db8371fc84d34be49c8ea04eee7d6eb"/><file name="PreloadedFile.php" hash="73cc9e276f96553814f05eae592d11ee"/><file name="Uploader.php" hash="eae92a330d19654028a8d16410616421"/><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="e5a2def3fccdb6a365364dcf23964d08"/></dir></dir><dir name="template"><dir name="cloudinary"><file name="manage.phtml" hash="7630d346f45d7118748b647be3501fa3"/><dir name="system"><dir name="config"><file name="signup.phtml" hash="2a0e06990eb542f22531ac2ebb5996f5"/></dir></dir></dir></dir></dir></dir></dir></target></contents>
21
  <compatible/>
22
  <dependencies><required><php><min>5.4.0</min><max>7.1.0</max></php></required></dependencies>
23
  </package>