From 97a3402b252464724349232c1797a0bd5356ee3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 18 Jun 2015 01:06:46 +0200 Subject: [PATCH 001/144] Updating .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 53360d44..945f2a7b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /vendor /plugins -/tmp \ No newline at end of file +/tmp +/composer.lock From b288fd4ca35f012ab3492dc1fb5cc334df521048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 18 Jun 2015 01:07:17 +0200 Subject: [PATCH 002/144] Started on refactoring the event listeners --- src/Storage/Listener/AbstractListener.php | 280 ++++++++++++++++++ src/Storage/Listener/ImageProcessingTrait.php | 27 ++ src/Storage/Listener/LocalListener.php | 122 ++++++++ src/Storage/PathBuilder/BasePathBuilder.php | 129 ++++++++ src/Storage/PathBuilder/LocalPathBuilder.php | 16 + .../PathBuilder/LocalPathBuilderTest.php | 43 +++ 6 files changed, 617 insertions(+) create mode 100644 src/Storage/Listener/AbstractListener.php create mode 100644 src/Storage/Listener/ImageProcessingTrait.php create mode 100644 src/Storage/Listener/LocalListener.php create mode 100644 src/Storage/PathBuilder/BasePathBuilder.php create mode 100644 src/Storage/PathBuilder/LocalPathBuilder.php create mode 100644 tests/TestCase/Storage/PathBuilder/LocalPathBuilderTest.php diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php new file mode 100644 index 00000000..a17ccea3 --- /dev/null +++ b/src/Storage/Listener/AbstractListener.php @@ -0,0 +1,280 @@ + false, + 'stripUuid' => true, + 'preserveFilename' => false, + 'preserveExtension' => true, + 'uuidFolder' => true, + 'randomPath' => true, + 'tableFolder' => false + ); + +/** + * Constructor + * + * @param array $config + * @return AbstractListener + */ + public function __construct(array $config = []) { + $this->config($config); + $this->initialize(); + } + + public function initialize() {} + +/** + * Implemented Events + * + * @return array + */ + abstract public function implementedEvents(); + +/** + * Check if the event is of a type or subject object of type model we want to + * process with this listener. + * + * @throws \InvalidArgumentException + * @param Event $event + * @return boolean + */ + protected function _checkEvent(Event $event) { + if (!in_array($this->storageTableClass, array('\Burzum\FileStorage\Model\Table\FileStorageTable', '\Burzum\FileStorage\Model\Table\ImageStorageTable'))) { + throw new \InvalidArgumentException(sprintf('Invalid storage table `%s`! Table must be FileStorage or ImageStorage or extend one of both!', $this->storageTableClass)); + } + return ( + $this->_checkTable($event) + && $this->getAdapterClassName($event->data['record']['adapter']) + && $this->_modelFilter($event) + ); + } + +/** + * Detects if an entities model field has name of one of the allowed models set. + * + * @param Event $event + * @return boolean + */ + protected function _modelFilter(Event $event) { + if (is_array($this->_config['models'])) { + $model = $event->data['record']['model']; + if (!in_array($model, $this->_config['models'])) { + return false; + } + } + return true; + } + +/** + * Checks if the events subject is a model and extending FileStorage or ImageStorage. + * + * @param Event $event + * @return boolean + */ + protected function _checkTable(Event $event) { + return ($event->subject() instanceOf $this->storageTableClass); + } + +/** + * Gets the adapter class name from the adapter config + * + * @param string $configName Name of the configuration + * @return boolean|string False if the config is not present + */ + protected function _getAdapterClassFromConfig($configName) { + $config = $this->getAdapterconfig($configName); + if (!empty($config['adapterClass'])) { + return $config['adapterClass']; + } + return false; + } + +/** + * Gets the adapter class name from the adapter configuration key and checks if + * it is in the list of supported adapters for the listener. + * + * You must define a list of supported classes via AbstractStorageEventListener::$_adapterClasses. + * + * @param string $configName Name of the adapter configuration. + * @return boolean|string String, the adapter class name or false if it was not found. + */ + public function getAdapterClassName($configName) { + $className = $this->_getAdapterClassFromConfig($configName); + if (in_array($className, $this->_adapterClasses)) { + $position = strripos($className, '\\'); + $this->adapterClass = substr($className, $position + 1, strlen($className)); + return $this->adapterClass; + } + return false; + } + +/** + * Wrapper around the singleton call to StorageManager::config + * + * Makes it easy to mock the adapter in tests. + * + * @param string $configName + * @return array + */ + public function getAdapterconfig($configName) { + return StorageManager::config($configName); + } + +/** + * Wrapper around the singleton call to StorageManager::config + * + * Makes it easy to mock the adapter in tests. + * + * @param string $configName + * @return Object + */ + public function getAdapter($configName) { + return StorageManager::adapter($configName); + } + +/** + * Create a temporary file locally based on a file from an adapter. + * + * A common case is image manipulation or video processing for example. It is + * required to get the file first from the adapter and then write it to + * a tmp file. Then manipulate it and upload the changed file. + * + * The adapter might not be one that is using a local file system, so we first + * get the file from the storage system, store it locally in a tmp file and + * later load the new file that was generated based on the tmp file into the + * storage adapter. This method here just generates the tmp file. + * + * @param Adapter $Storage Storage adapter + * @param string $path Path / key of the storage adapter file + * @param string $tmpFolder + * @throws Exception + * @return bool|string + */ + protected function _tmpFile($Storage, $path, $tmpFolder = null) { + try { + $tmpFile = $this->createTmpFile($tmpFolder); + file_put_contents($tmpFile, $Storage->read($path)); + return $tmpFile; + } catch (Exception $e) { + $this->log($e->getMessage(), 'file_storage'); + throw $e; + } + } + +/** + * Creates a temporary file name and checks the tmp path, creates one if required. + * + * This method is thought to be used to generate tmp file locations for use cases + * like audio or image process were you need copies of a file and want to avoid + * conflicts. By default the tmp file is generated using cakes TMP constant + + * folder if passed and a uuid as filename. + * + * @param string $folder + * @param boolean $checkAndCreatePath + * @return string For example /var/www/app/tmp/ or /var/www/app/tmp// + */ + public function createTmpFile($folder = null, $checkAndCreatePath = true) { + if (is_null($folder)) { + $folder = TMP; + } + if ($checkAndCreatePath === true && !is_dir($folder)) { + new Folder($folder, true); + } + return $folder . Text::uuid(); + } + + public function pathBuilder($class = null) { + if (empty($class)) { + if (empty($this->_pathBuilder)) { + throw new \RuntimeException(sprintf('No path builder loaded!')); + } + return $this->_pathBuilder; + } + $classname = '\Burzum\FileStorage\Storage\PathBuilder\\' . $class . 'Builder'; + if (class_exists($classname)) { + $this->_pathBuilder = new $class(); + return $this->_pathBuilder; + } + $classname = '\App\Storage\PathBuilder\\' . $class . 'Builder'; + if (class_exists($classname)) { + $this->_pathBuilder = new $class(); + return $this->_pathBuilder; + } + if (class_exists($class)) { + $this->_pathBuilder = new $class(); + return $this->_pathBuilder; + } + throw new \RuntimeException(sprintf('Could not find path builder %s!', $class)); + } +} diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php new file mode 100644 index 00000000..3d1d0f57 --- /dev/null +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -0,0 +1,27 @@ +_imageProcessor) && $renew === false) { + return $this->_imageProcessor; + } + $this->_imageProcessor = new ImageProcessor($config); + return $this->_imageProcessor; + } + + public function createImageVersions() { + + } + + public function storeImageVersions() { + + } + + public function removeImageVersions() { + + } +} diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php new file mode 100644 index 00000000..f0de9a98 --- /dev/null +++ b/src/Storage/Listener/LocalListener.php @@ -0,0 +1,122 @@ +_defaultConfig['legacyPath'] = true; + parent::__construct($config); + } + +/** + * Implemented Events + * + * @return array + */ + public function implementedEvents() { + return [ + 'FileStorage.afterSave' => [ + 'callable' => 'afterSave', + 'priority' => 50, + ], + 'FileStorage.afterDelete' => [ + 'callable' => 'afterDelete', + 'priority' => 50 + ] + ]; + } + +/** + * afterDelete + * + * No need to use an adapter here, just delete the whole folder using cakes Folder class + * + * @param Event $event + * @return void + */ + public function afterDelete(Event $event) { + if ($this->_checkEvent($event)) { + $entity = $event->data['record']; + $storageConfig = StorageManager::config($entity['adapter']); + $path = $storageConfig['adapterOptions'][0] . $event->data['record']['path']; + if (is_dir($path)) { + $Folder = new Folder($path); + return $Folder->delete(); + } + return false; + } + } + +/** + * Builds the path under which the data gets stored in the storage adapter + * + * @param Table $table + * @param Entity $entity + * @return string + */ + public function buildPath($table, $entity) { + $path = parent::buildPath($table, $entity); + // Backward compatibility + if ($this->_config['legacyPath'] === true) { + return 'files' . DS . $path; + } + return $path; + } + +/** + * afterSave + * + * @param Event $event + * @return void + */ + public function afterSave(Event $event) { + if ($this->_checkEvent($event) && $event->data['record']->isNew()) { + $table = $event->subject(); + $entity = $event->data['record']; + $Storage = StorageManager::adapter($entity['adapter']); + try { + $filename = $this->buildFileName($table, $entity); + $entity['path'] = $this->buildPath($table, $entity); + $Storage->write($entity['path'] . $filename, file_get_contents($entity['file']['tmp_name']), true); + $table->save($entity, array( + 'validate' => false, + 'callbacks' => false + )); + } catch (Exception $e) { + $this->log($e->getMessage(), 'file_storage'); + } + } + } +} diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php new file mode 100644 index 00000000..23314827 --- /dev/null +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -0,0 +1,129 @@ + false, + 'stripUuid' => true, + 'preserveFilename' => false, + 'preserveExtension' => true, + 'uuidFolder' => true, + 'randomPath' => true, + 'tableFolder' => false, + 'modelFolder' => false + ); + + public function __construct(Entity $entity, array $config = []) { + $this->_entity = $entity; + $this->config($config); + } + +/** + * Strips dashes from a string + * + * @param string + * @return string String without the dashed + */ + public function stripDashes($uuid) { + return str_replace('-', '', $uuid); + } + +/** + * Builds the path under which the data gets stored in the storage adapter. + * + * @param Table $table + * @param Entity $entity + * @return string + */ + public function path($entity, array $options = []) { + $path = ''; + if ($this->_config['tableFolder'] && is_string($this->_config['tableFolder'])) { + $path .= $this->_config['tableFolder'] . DS; + } + if ($this->_config['modelFolder'] === true) { + $path .= $entity->model; + } + if ($this->_config['randomPath'] === true) { + $path .= FileStorageUtils::randomPath($entity->id); + } + if ($this->_config['uuidFolder'] === true) { + $path .= $this->stripDashes($entity->id) . DS; + } + return $path; + } + +/** + * Builds the filename of under which the data gets saved in the storage adapter. + * + * @param \Cake\ORM\Entity $entity + * @param array $options + * @return string + */ + public function filename($entity, array $options = []) { + if ($this->_config['preserveFilename'] === true) { + return $entity['filename']; + } + $filename = $this->_entity['id']; + if ($this->_config['stripUuid'] === true) { + $filename = $this->stripDashes($filename); + } + if ($this->_config['preserveExtension'] === true) { + $filename = $filename . '.' . $entity['extension']; + } + return $filename; + } + +/** + * Returns the path + filename. + * + * @param \Cake\ORM\Entity $entity + * @param array $options + * @return string + */ + public function fullPath($entity, array $options = []) { + return $this->path($entity) . $this->filename($entity); + } + +/** + * Builds the URL under which the file is accessible. + * + * This is for example important for S3 and Dropbox but also the Local adapter + * if you symlink a folder to your webroot and allow direct access to a file. + * + * @param \Cake\ORM\Entity $entity + * @param array $options + * @return string + */ + public function url($entity, array $options = []) { + $url = $this->path($entity) . $this->filename($entity); + return str_replace('\\', '/', $url); + } + + public function randomPath($string) { + return FileStorageUtils::randomPath($string); + } + +} diff --git a/src/Storage/PathBuilder/LocalPathBuilder.php b/src/Storage/PathBuilder/LocalPathBuilder.php new file mode 100644 index 00000000..b8eb4c22 --- /dev/null +++ b/src/Storage/PathBuilder/LocalPathBuilder.php @@ -0,0 +1,16 @@ +_entity = $entity; + $this->config($config); + } +} diff --git a/tests/TestCase/Storage/PathBuilder/LocalPathBuilderTest.php b/tests/TestCase/Storage/PathBuilder/LocalPathBuilderTest.php new file mode 100644 index 00000000..2b18dffe --- /dev/null +++ b/tests/TestCase/Storage/PathBuilder/LocalPathBuilderTest.php @@ -0,0 +1,43 @@ +FileStorage = TableRegistry::get('Burzum/FileStorage.FileStorage'); + $this->entity = $this->FileStorage->newEntity([ + 'id' => 'be4e23a0-142f-11e5-b60b-1697f925ec7b', + 'foreign_key' => 'c1a6ed2a-142f-11e5-b60b-1697f925ec7b', + 'model' => 'Photos', + 'filesize' => 252576, + 'filename' => 'somefancyphoto.jpg', + 'extension' => 'png', + 'mime_type' => 'image/jpeg' + ]); + $this->entity->accessible('id', true); + } + + public function testWebPath() { + //debug($this->entity->toArray()); + $builder = new LocalPathBuilder($this->entity); + $result = $builder->url($this->entity); + debug($result); + $result = $builder->fullPath($this->entity); + debug($result); + } +} From 440d0684e84e94ae7333b5cb85433e11e6d06ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 28 Jun 2015 19:24:28 +0200 Subject: [PATCH 003/144] Working on the path builders --- src/Storage/Listener/AbstractListener.php | 40 +++++++++---- src/Storage/Listener/LocalListener.php | 56 +++++++++---------- src/Storage/PathBuilder/BasePathBuilder.php | 20 +++---- src/Storage/PathBuilder/LocalPathBuilder.php | 6 +- .../Storage/Listener/AbstractListenerTest.php | 32 +++++++++++ .../Storage/Listener/LocalListenerTest.php | 24 ++++++++ .../PathBuilder/LocalPathBuilderTest.php | 4 +- 7 files changed, 126 insertions(+), 56 deletions(-) create mode 100644 tests/TestCase/Storage/Listener/AbstractListenerTest.php create mode 100644 tests/TestCase/Storage/Listener/LocalListenerTest.php diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index a17ccea3..f707673a 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -1,5 +1,5 @@ initialize(); } +/** + * Helper method to bypass the need to override the constructor. + * + * Called last inside __construct() + * + * @return void + */ public function initialize() {} /** @@ -254,7 +266,14 @@ public function createTmpFile($folder = null, $checkAndCreatePath = true) { return $folder . Text::uuid(); } - public function pathBuilder($class = null) { +/** + * Path builder. + * + * @param string Class name of a path builder. + * @param array Options for the path builder. + * @return \Burzum\FileStorage\Storage\PathBuilder\BasePathBuilder + */ + public function pathBuilder($class = null, array $options = []) { if (empty($class)) { if (empty($this->_pathBuilder)) { throw new \RuntimeException(sprintf('No path builder loaded!')); @@ -263,18 +282,19 @@ public function pathBuilder($class = null) { } $classname = '\Burzum\FileStorage\Storage\PathBuilder\\' . $class . 'Builder'; if (class_exists($classname)) { - $this->_pathBuilder = new $class(); + $this->_pathBuilder = new $classname(); return $this->_pathBuilder; } $classname = '\App\Storage\PathBuilder\\' . $class . 'Builder'; if (class_exists($classname)) { - $this->_pathBuilder = new $class(); + $this->_pathBuilder = new $classname(); return $this->_pathBuilder; } - if (class_exists($class)) { - $this->_pathBuilder = new $class(); + $classname = $class; + if (class_exists($classname)) { + $this->_pathBuilder = new $classname(); return $this->_pathBuilder; } - throw new \RuntimeException(sprintf('Could not find path builder %s!', $class)); + throw new \RuntimeException(sprintf('Could not find path builder %s!', $classname)); } } diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index f0de9a98..894b3eb7 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -1,10 +1,11 @@ _defaultConfig['legacyPath'] = true; - parent::__construct($config); + public function initialize() { + parent::initialize(); + $this->pathBuilder('Local', ['legacyPath' => true]);; } /** @@ -49,17 +44,15 @@ public function implementedEvents() { return [ 'FileStorage.afterSave' => [ 'callable' => 'afterSave', - 'priority' => 50, ], 'FileStorage.afterDelete' => [ 'callable' => 'afterDelete', - 'priority' => 50 ] ]; } /** - * afterDelete + * File removal is handled AFTER the database record was deleted. * * No need to use an adapter here, just delete the whole folder using cakes Folder class * @@ -69,31 +62,32 @@ public function implementedEvents() { public function afterDelete(Event $event) { if ($this->_checkEvent($event)) { $entity = $event->data['record']; - $storageConfig = StorageManager::config($entity['adapter']); - $path = $storageConfig['adapterOptions'][0] . $event->data['record']['path']; - if (is_dir($path)) { - $Folder = new Folder($path); - return $Folder->delete(); + $path = $this->pathBuilder()->fullPath($entity); + if (StorageManager::adapter($entity->adapter)->delete($path)) { + return true; } return false; } } /** - * Builds the path under which the data gets stored in the storage adapter + * Builds the path under which the data gets stored in the storage adapter. * * @param Table $table * @param Entity $entity * @return string */ - public function buildPath($table, $entity) { - $path = parent::buildPath($table, $entity); - // Backward compatibility - if ($this->_config['legacyPath'] === true) { - return 'files' . DS . $path; - } - return $path; - } +// public function buildPath($table, $entity) { +// $path = parent::buildPath($table, $entity); +// // Backward compatibility +// if ($this->_config['legacyPath'] === true) { +// return 'files' . DS . $path; +// } +// if (is_string($this->_config['legacyPath'])) { +// return $this->_config['legacyPath'] . DS . $path; +// } +// return $path; +// } /** * afterSave @@ -107,8 +101,8 @@ public function afterSave(Event $event) { $entity = $event->data['record']; $Storage = StorageManager::adapter($entity['adapter']); try { - $filename = $this->buildFileName($table, $entity); - $entity['path'] = $this->buildPath($table, $entity); + $filename = $this->pathBuilder->filename($entity); + $entity['path'] = $this->pathBuilder->path($entity); $Storage->write($entity['path'] . $filename, file_get_contents($entity['file']['tmp_name']), true); $table->save($entity, array( 'validate' => false, diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index 23314827..4584ff3a 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -13,13 +13,6 @@ class BasePathBuilder { use InstanceConfigTrait; -/** - * Entity instance. - * - * @var \Cake\ORM\Entity; - */ - protected $_entity; - /** * Default settings * @@ -36,8 +29,7 @@ class BasePathBuilder { 'modelFolder' => false ); - public function __construct(Entity $entity, array $config = []) { - $this->_entity = $entity; + public function __construct(array $config = []) { $this->config($config); } @@ -86,7 +78,7 @@ public function filename($entity, array $options = []) { if ($this->_config['preserveFilename'] === true) { return $entity['filename']; } - $filename = $this->_entity['id']; + $filename = $entity['id']; if ($this->_config['stripUuid'] === true) { $filename = $this->stripDashes($filename); } @@ -122,6 +114,14 @@ public function url($entity, array $options = []) { return str_replace('\\', '/', $url); } +/** + * Proxy to FileStorageUtils::randomPath. + * + * Makes it possible to overload this functionality. + * + * @param string $string + * @return string + */ public function randomPath($string) { return FileStorageUtils::randomPath($string); } diff --git a/src/Storage/PathBuilder/LocalPathBuilder.php b/src/Storage/PathBuilder/LocalPathBuilder.php index b8eb4c22..ccf04ebd 100644 --- a/src/Storage/PathBuilder/LocalPathBuilder.php +++ b/src/Storage/PathBuilder/LocalPathBuilder.php @@ -5,12 +5,10 @@ class LocalPathBuilder extends BasePathBuilder { - public function __construct(Entity $entity, array $config = []) { + public function __construct(array $config = []) { if (empty($config['tableFolder'])) { $config['tableFolder'] = true; } - parent::__construct($entity, $config); - $this->_entity = $entity; - $this->config($config); + parent::__construct($config); } } diff --git a/tests/TestCase/Storage/Listener/AbstractListenerTest.php b/tests/TestCase/Storage/Listener/AbstractListenerTest.php new file mode 100644 index 00000000..aedd79d0 --- /dev/null +++ b/tests/TestCase/Storage/Listener/AbstractListenerTest.php @@ -0,0 +1,32 @@ +pathBuilder('LocalPath'); + $this->assertInstanceOf('\Burzum\FileStorage\Storage\PathBuilder\BasePathBuilder', $result); + } +} diff --git a/tests/TestCase/Storage/Listener/LocalListenerTest.php b/tests/TestCase/Storage/Listener/LocalListenerTest.php new file mode 100644 index 00000000..14cb94d2 --- /dev/null +++ b/tests/TestCase/Storage/Listener/LocalListenerTest.php @@ -0,0 +1,24 @@ +entity->toArray()); - $builder = new LocalPathBuilder($this->entity); + $builder = new LocalPathBuilder(); $result = $builder->url($this->entity); debug($result); $result = $builder->fullPath($this->entity); debug($result); + $result = $builder->fullPath($this->entity); + debug($result); } } From 572cfd56355fc0cf8ccdf6cb4ed18788137d58bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 2 Jul 2015 11:11:30 +0200 Subject: [PATCH 004/144] Working on the path builder implementation. --- docs/Documentation/Path-Builders.md | 15 ++++ docs/Home.md | 1 + src/Storage/PathBuilder/BasePathBuilder.php | 42 +++++++++-- .../PathBuilder/LocalPathBuilderTest.php | 70 +++++++++++++++---- 4 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 docs/Documentation/Path-Builders.md diff --git a/docs/Documentation/Path-Builders.md b/docs/Documentation/Path-Builders.md new file mode 100644 index 00000000..82d78bc7 --- /dev/null +++ b/docs/Documentation/Path-Builders.md @@ -0,0 +1,15 @@ +Path Builders +============= + +Path builders are classes that are used to build the storage paths for a file based on the information coming from the `file_storage` table. + +They implement at least these methods: + + * filename + * path + * fullPath + * url + +Each of them will take a `FileStorage` entity as first argument. Based on that entity it will generate a path depending on the logic implemented in the path builder. + +The reason for this is to separate or share, just as needed, the path building logic between different storage systems. diff --git a/docs/Home.md b/docs/Home.md index 12d0f7dd..10a58067 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -19,6 +19,7 @@ Documentation * [The Image Helper](Documentation/The-Image-Helper.md) * [Specific Adapter Configurations](Documentation/Specific-Adapter-Configurations.md) * [Included Event Listeners](Documentation/Included-Event-Listeners.md) +* [Path Builders](Documentation/Path-Builders.md) Tutorials --------- diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index 4584ff3a..95b48a0b 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -14,18 +14,17 @@ class BasePathBuilder { use InstanceConfigTrait; /** - * Default settings + * Default settings. * * @var array */ protected $_defaultConfig = array( - 'models' => false, 'stripUuid' => true, + 'pathPrefix' => '', 'preserveFilename' => false, 'preserveExtension' => true, 'uuidFolder' => true, 'randomPath' => true, - 'tableFolder' => false, 'modelFolder' => false ); @@ -52,16 +51,17 @@ public function stripDashes($uuid) { */ public function path($entity, array $options = []) { $path = ''; - if ($this->_config['tableFolder'] && is_string($this->_config['tableFolder'])) { - $path .= $this->_config['tableFolder'] . DS; + if ($this->_config['pathPrefix'] && is_string($this->_config['pathPrefix'])) { + $path .= $this->_config['pathPrefix'] . DS; } if ($this->_config['modelFolder'] === true) { $path .= $entity->model; } if ($this->_config['randomPath'] === true) { - $path .= FileStorageUtils::randomPath($entity->id); + $path .= $this->randomPath($entity->id); } - if ($this->_config['uuidFolder'] === true) { + // uuidFolder for backward compatibility + if ($this->_config['uuidFolder'] === true || $this->_config['idFolder'] === true) { $path .= $this->stripDashes($entity->id) . DS; } return $path; @@ -126,4 +126,32 @@ public function randomPath($string) { return FileStorageUtils::randomPath($string); } +/** + * Ensures that a path has a leading and/or trailing (back-) slash. + * + * @param string $string + * @param string $position Can be `before`, `after` or `both` + * @param string $ds Directory separator should be / or \ + * @throws \InvalidArgumentException + * @return string + */ + public function ensureSlash($string, $position, $ds = null) { + if (!in_array($position, ['before', 'after', 'both'])) { + throw new \InvalidArgumentException(sprintf('Invalid position `%s`!', $position)); + } + if (is_null($ds)) { + $ds = DIRECTORY_SEPARATOR; + } + if ($position === 'before' || $position === 'both') { + if (strpos($string, $ds) !== 0) { + $string = $ds . $string; + } + } + if ($position === 'after' || $position === 'both') { + if (substr($string, -1, 1) !== $ds ) { + $string = $string . $ds; + } + } + return $string; + } } diff --git a/tests/TestCase/Storage/PathBuilder/LocalPathBuilderTest.php b/tests/TestCase/Storage/PathBuilder/LocalPathBuilderTest.php index 0cb2fb0c..e7828f19 100644 --- a/tests/TestCase/Storage/PathBuilder/LocalPathBuilderTest.php +++ b/tests/TestCase/Storage/PathBuilder/LocalPathBuilderTest.php @@ -21,25 +21,69 @@ public function setUp() { parent::setUp(); $this->FileStorage = TableRegistry::get('Burzum/FileStorage.FileStorage'); $this->entity = $this->FileStorage->newEntity([ - 'id' => 'be4e23a0-142f-11e5-b60b-1697f925ec7b', - 'foreign_key' => 'c1a6ed2a-142f-11e5-b60b-1697f925ec7b', - 'model' => 'Photos', - 'filesize' => 252576, - 'filename' => 'somefancyphoto.jpg', + 'id' => 'file-storage-1', + 'user_id' => 'user-1', + 'foreign_key' => 'item-1', + 'model' => 'Item', + 'filename' => 'cake.icon.png', + 'filesize' => '', + 'mime_type' => 'image/png', 'extension' => 'png', - 'mime_type' => 'image/jpeg' + 'hash' => '', + 'path' => '', + 'adapter' => 'Local', ]); $this->entity->accessible('id', true); } - public function testWebPath() { - //debug($this->entity->toArray()); + public function testPathbuilding() { $builder = new LocalPathBuilder(); - $result = $builder->url($this->entity); - debug($result); - $result = $builder->fullPath($this->entity); - debug($result); + + $result = $builder->filename($this->entity); + $this->assertEquals($result, 'filestorage1.png'); + + $result = $builder->path($this->entity); + $this->assertEquals($result, '00' . DS . '14' . DS . '90' . DS . 'filestorage1' . DS); + $result = $builder->fullPath($this->entity); - debug($result); + $this->assertEquals($result, '00' . DS . '14' . DS . '90' . DS . 'filestorage1' . DS . 'filestorage1.png'); + + $builder->config('pathPrefix', 'files'); + $result = $builder->path($this->entity); + $this->assertEquals($result, 'files' . DS . '00' . DS . '14' . DS . '90' . DS . 'filestorage1' . DS); + + $builder->config('pathPrefix', 'files'); + $result = $builder->filename($this->entity); + $this->assertEquals($result, 'filestorage1.png'); + + $builder->config('preserveFilename', true); + $result = $builder->filename($this->entity); + $this->assertEquals($result, 'cake.icon.png'); + } + +/** + * testEnsureSlash + * + * @return void + */ + public function testEnsureSlash() { + $string = 'foo/bar'; + $builder = new LocalPathBuilder(); + $result = $builder->ensureSlash($string, 'both'); + $this->assertEquals($result, DS . $string . DS); + + $result = $builder->ensureSlash(DS . $string . DS, 'both'); + $this->assertEquals($result, DS . $string . DS); + } + +/** + * testEnsureSlashInvalidArgumentException + * + * @expectedException \InvalidArgumentException + */ + public function testEnsureSlashInvalidArgumentException() { + $string = 'foo/bar'; + $builder = new LocalPathBuilder(); + $builder->ensureSlash($string, 'INVALID!'); } } From 41033795172a1109e6b6ffa5fc14b13b976f1982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 2 Jul 2015 11:12:57 +0200 Subject: [PATCH 005/144] Renaming the LocalPathBuilderTest to BasePathBuilderTest --- .../{LocalPathBuilderTest.php => BasePathBuilderTest.php} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/TestCase/Storage/PathBuilder/{LocalPathBuilderTest.php => BasePathBuilderTest.php} (98%) diff --git a/tests/TestCase/Storage/PathBuilder/LocalPathBuilderTest.php b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php similarity index 98% rename from tests/TestCase/Storage/PathBuilder/LocalPathBuilderTest.php rename to tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php index e7828f19..e209a35d 100644 --- a/tests/TestCase/Storage/PathBuilder/LocalPathBuilderTest.php +++ b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php @@ -6,7 +6,7 @@ use Cake\ORM\TableRegistry; use Cake\TestSuite\TestCase; -class LocalPathBuilderTest extends TestCase { +class BasePathBuilderTest extends TestCase { /** * Fixtures From 54000bc95fdf6028ac8a0b6610cb97cf1903691c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 2 Jul 2015 11:14:23 +0200 Subject: [PATCH 006/144] Renaming the Path Builder class in the BasePathBuilderTest --- .../TestCase/Storage/PathBuilder/BasePathBuilderTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php index e209a35d..60dc16af 100644 --- a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php +++ b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php @@ -1,7 +1,7 @@ filename($this->entity); $this->assertEquals($result, 'filestorage1.png'); @@ -68,7 +68,7 @@ public function testPathbuilding() { */ public function testEnsureSlash() { $string = 'foo/bar'; - $builder = new LocalPathBuilder(); + $builder = new BasePathBuilder(); $result = $builder->ensureSlash($string, 'both'); $this->assertEquals($result, DS . $string . DS); @@ -83,7 +83,7 @@ public function testEnsureSlash() { */ public function testEnsureSlashInvalidArgumentException() { $string = 'foo/bar'; - $builder = new LocalPathBuilder(); + $builder = new BasePathBuilder(); $builder->ensureSlash($string, 'INVALID!'); } } From cb92b616a4e616b09caf615a5ac95ad454d3f56c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sat, 4 Jul 2015 14:23:48 +0200 Subject: [PATCH 007/144] Working on the path builders and image processing. --- src/Lib/FileStorageUtils.php | 13 ++- src/Storage/Listener/ImageProcessingTrait.php | 52 +++++++++++- src/Storage/PathBuilder/BasePathBuilder.php | 75 ++++++++++++++--- src/Storage/PathBuilder/S3PathBuilder.php | 45 ++++++++++ .../PathBuilder/BasePathBuilderTest.php | 28 +++++++ .../PathBuilder/ImageProcessingTraitTest.php | 82 +++++++++++++++++++ .../Storage/PathBuilder/S3PathBuilderTest.php | 47 +++++++++++ 7 files changed, 321 insertions(+), 21 deletions(-) create mode 100644 src/Storage/PathBuilder/S3PathBuilder.php create mode 100644 tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php create mode 100644 tests/TestCase/Storage/PathBuilder/S3PathBuilderTest.php diff --git a/src/Lib/FileStorageUtils.php b/src/Lib/FileStorageUtils.php index f7aecf47..d6bcae40 100644 --- a/src/Lib/FileStorageUtils.php +++ b/src/Lib/FileStorageUtils.php @@ -120,13 +120,17 @@ public static function hashOperations($operations) { } /** - * Generate hashes + * Generates the hashes for the different image version configurations. * - * @param string - * @return void + * @param string|array $configPath + * @return array */ public static function generateHashes($configPath = 'FileStorage') { - $imageSizes = Configure::read($configPath . '.imageSizes'); + if (is_array($configPath)) { + $imageSizes = $configPath; + } else { + $imageSizes = Configure::read($configPath . '.imageSizes'); + } if (is_null($imageSizes)) { throw new \RuntimeException(sprintf('Image processing configuration in %s is missing!', $configPath . '.imageSizes')); } @@ -136,6 +140,7 @@ public static function generateHashes($configPath = 'FileStorage') { Configure::write($configPath . '.imageHashes.' . $model . '.' . $name, self::hashOperations($operations)); } } + return Configure::read($configPath . '.imageHashes'); } /** diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 3d1d0f57..8ea6546b 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -1,27 +1,71 @@ _imageVersions = Configure::read('FileStorage.imageSizes'); + $this->_imageVersionHashes = FileStorageUtils::generateHashes(); + } + public function imageProcessor(array $config = [], $renew = false) { if (!empty($this->_imageProcessor) && $renew === false) { return $this->_imageProcessor; } + $this->_loadImageProcessingFromConfig(); $this->_imageProcessor = new ImageProcessor($config); return $this->_imageProcessor; } - public function createImageVersions() { + public function createImageVersions($entity, array $options = []) { + $result = []; + if (!isset($this->_imageVersions[$entity->model])) { + throw new \RuntimeException(sprintf('No image version config found for `%s`!', $entity->model)); + } + foreach ($this->_imageVersions[$entity->model] as $version => $config) { + $output = $this->createTmpFile(); + try { + $this->imageProcessor()->open($entity->file['tmp_name']); + $this->imageProcessor()->batchProcess($output, $config); + $result[$version] = [ + 'status' => 'success', + 'imageFile' => $output, + 'hash' => $this->_imageVersionHashes[$entity->model][$version], + ]; + } catch (\Exception $e) { + $result[$version] = [ + 'status' => 'error', + 'error' => $e->getMessage(), + ]; + } + } + //debug($this->_imageVersions); + //debug($this->_imageVersionHashes); + return $result; + } + + public function storeImageVersions($entity, array $options = []) { } - public function storeImageVersions() { + public function removeImageVersions($entity, array $options = []) { } - public function removeImageVersions() { + public function imageVersionFilename() { } } diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index 95b48a0b..cc828d69 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -21,6 +21,9 @@ class BasePathBuilder { protected $_defaultConfig = array( 'stripUuid' => true, 'pathPrefix' => '', + 'pathSuffix' => '', + 'filePrefix' => '', + 'fileSuffix' => '', 'preserveFilename' => false, 'preserveExtension' => true, 'uuidFolder' => true, @@ -28,6 +31,11 @@ class BasePathBuilder { 'modelFolder' => false ); +/** + * Constructor + * + * @param array $config Configuration options. + */ public function __construct(array $config = []) { $this->config($config); } @@ -49,10 +57,11 @@ public function stripDashes($uuid) { * @param Entity $entity * @return string */ - public function path($entity, array $options = []) { + public function path(Entity $entity, array $options = []) { + $config = array_merge($this->config(), $options); $path = ''; - if ($this->_config['pathPrefix'] && is_string($this->_config['pathPrefix'])) { - $path .= $this->_config['pathPrefix'] . DS; + if (!empty($config['pathPrefix']) && is_string($config['pathPrefix'])) { + $path = $config['pathPrefix'] . DS . $path; } if ($this->_config['modelFolder'] === true) { $path .= $entity->model; @@ -64,7 +73,31 @@ public function path($entity, array $options = []) { if ($this->_config['uuidFolder'] === true || $this->_config['idFolder'] === true) { $path .= $this->stripDashes($entity->id) . DS; } - return $path; + if (!empty($this->_config['pathSuffix']) && is_string($this->_config['pathSuffix'])) { + $path = $path . $this->_config['pathSuffix'] . DS; + } + return $this->ensureSlash($path, 'after'); + } + +/** + * Splits the filename in name and extension. + * + * @param string $filename Filename to split in name and extension. + * @param boolean $keepDot Keeps the dot in front of the extension. + * @return array + */ + public function splitFilename($filename, $keepDot = false) { + $position = strrpos($filename, '.'); + if ($position === false) { + $extension = ''; + } else { + $extension = substr($filename, $position, strlen($filename)); + $filename = substr($filename, 0, $position); + if ($keepDot === false) { + $extension = substr($extension, 1); + } + } + return compact('filename', 'extension'); } /** @@ -74,17 +107,33 @@ public function path($entity, array $options = []) { * @param array $options * @return string */ - public function filename($entity, array $options = []) { - if ($this->_config['preserveFilename'] === true) { - return $entity['filename']; + public function filename(Entity $entity, array $options = []) { + $config = array_merge($this->config(), $options); + if ($config['preserveFilename'] === true) { + $filename = $entity['filename']; + if (!empty($config['filePrefix'])) { + $filename = $config['filePrefix'] . $entity['filename']; + } + if (!empty($config['fileSuffix'])) { + $split = $this->splitFilename($filename, true); + $filename = $split['filename'] . $config['fileSuffix'] . $split['extension']; + } + return $filename; } - $filename = $entity['id']; - if ($this->_config['stripUuid'] === true) { + + $filename = $entity->id; + if ($config['stripUuid'] === true) { $filename = $this->stripDashes($filename); } - if ($this->_config['preserveExtension'] === true) { + if ($config['preserveExtension'] === true) { + if (!empty($config['fileSuffix'])) { + $filename = $filename . $config['fileSuffix']; + } $filename = $filename . '.' . $entity['extension']; } + if (!empty($config['filePrefix'])) { + $filename = $config['filePrefix'] . $filename; + } return $filename; } @@ -95,7 +144,7 @@ public function filename($entity, array $options = []) { * @param array $options * @return string */ - public function fullPath($entity, array $options = []) { + public function fullPath(Entity $entity, array $options = []) { return $this->path($entity) . $this->filename($entity); } @@ -109,7 +158,7 @@ public function fullPath($entity, array $options = []) { * @param array $options * @return string */ - public function url($entity, array $options = []) { + public function url(Entity $entity, array $options = []) { $url = $this->path($entity) . $this->filename($entity); return str_replace('\\', '/', $url); } @@ -140,7 +189,7 @@ public function ensureSlash($string, $position, $ds = null) { throw new \InvalidArgumentException(sprintf('Invalid position `%s`!', $position)); } if (is_null($ds)) { - $ds = DIRECTORY_SEPARATOR; + $ds = DS; } if ($position === 'before' || $position === 'both') { if (strpos($string, $ds) !== 0) { diff --git a/src/Storage/PathBuilder/S3PathBuilder.php b/src/Storage/PathBuilder/S3PathBuilder.php new file mode 100644 index 00000000..62fa54e5 --- /dev/null +++ b/src/Storage/PathBuilder/S3PathBuilder.php @@ -0,0 +1,45 @@ +_getBucketFromAdapter($entity->adapter); + $pathPrefix = $this->_buildCloudBaseUrl('http', $bucket); + $path = parent::path($entity); + $path = str_replace('\\', '/', $path); + return $pathPrefix . $path; + } +} diff --git a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php index 60dc16af..07e5eec1 100644 --- a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php +++ b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php @@ -38,6 +38,7 @@ public function setUp() { public function testPathbuilding() { $builder = new BasePathBuilder(); + $config = $builder->config(); $result = $builder->filename($this->entity); $this->assertEquals($result, 'filestorage1.png'); @@ -59,6 +60,11 @@ public function testPathbuilding() { $builder->config('preserveFilename', true); $result = $builder->filename($this->entity); $this->assertEquals($result, 'cake.icon.png'); + + $builder->config($config); + $builder->config('pathSuffix', 'files'); + $result = $builder->path($this->entity); + $this->assertEquals($result, '00' . DS . '14' . DS . '90' . DS . 'filestorage1' . DS . 'files' . DS); } /** @@ -86,4 +92,26 @@ public function testEnsureSlashInvalidArgumentException() { $builder = new BasePathBuilder(); $builder->ensureSlash($string, 'INVALID!'); } + +/** + * testSplitFilename + * + * @return void + */ + public function testSplitFilename() { + $builder = new BasePathBuilder(); + $result = $builder->splitFilename('some.fancy.name.jpg'); + $expected = [ + 'filename' => 'some.fancy.name', + 'extension' => 'jpg' + ]; + $this->assertEquals($result, $expected); + + $result = $builder->splitFilename('no-extension'); + $expected = [ + 'filename' => 'no-extension', + 'extension' => '' + ]; + $this->assertEquals($result, $expected); + } } diff --git a/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php b/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php new file mode 100644 index 00000000..6ef361cb --- /dev/null +++ b/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php @@ -0,0 +1,82 @@ +FileStorage = TableRegistry::get('Burzum/FileStorage.FileStorage'); + $this->entity = $this->FileStorage->newEntity([ + 'id' => 'file-storage-1', + 'user_id' => 'user-1', + 'foreign_key' => 'item-1', + 'model' => 'Item', + 'filename' => 'cake.icon.png', + 'filesize' => '', + 'mime_type' => 'image/png', + 'extension' => 'png', + 'hash' => '', + 'path' => '', + 'adapter' => 'Local', + ]); + $this->entity->accessible('id', true); + + Configure::write('FileStorage.imageSizes', [ + 'Item' => [ + 't100' => [ + 'thumbnail' => [ + 'width' => 300, + 'height' => 300 + ] + ], + 'crop50' => [ + 'squareCenterCrop' => [ + 'size' => 300, + ] + ] + ] + ]); + } + +/** + * testEnsureSlash + * + * @return void + */ + public function testCreateImageVersions() { + $entity = $this->FileStorage->get('file-storage-1'); + $entity->isNew(true); + $entity->file = [ + 'tmp_name' => $this->fileFixtures . 'titus.jpg', + ]; + + $builder = new TraitTestClass(); + $builder->imageProcessor(); + $result = $builder->createImageVersions($entity); + debug($result); + } +} diff --git a/tests/TestCase/Storage/PathBuilder/S3PathBuilderTest.php b/tests/TestCase/Storage/PathBuilder/S3PathBuilderTest.php new file mode 100644 index 00000000..e6e62d3f --- /dev/null +++ b/tests/TestCase/Storage/PathBuilder/S3PathBuilderTest.php @@ -0,0 +1,47 @@ +FileStorage = TableRegistry::get('Burzum/FileStorage.FileStorage'); + $this->entity = $this->FileStorage->newEntity([ + 'id' => 'file-storage-1', + 'user_id' => 'user-1', + 'foreign_key' => 'item-1', + 'model' => 'Item', + 'filename' => 'cake.icon.png', + 'filesize' => '', + 'mime_type' => 'image/png', + 'extension' => 'png', + 'hash' => '', + 'path' => '', + 'adapter' => 'S3', + ]); + $this->entity->accessible('id', true); + } + +/** + * @todo finish me + */ + public function testUrl() { + $builder = new S3PathBuilder(); + $result = $builder->url($this->entity); + //debug($result); + } +} From 5cb7f7a6b8cbc69bc324d5ad010bba269ac7c7fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sat, 4 Jul 2015 16:20:40 +0200 Subject: [PATCH 008/144] Working on refactoring the image processing --- src/Storage/Listener/AbstractListener.php | 5 ++ src/Storage/Listener/ImageProcessingTrait.php | 72 +++++++++++++++---- src/Storage/Listener/LocalListener.php | 25 ++----- src/Storage/PathBuilder/BasePathBuilder.php | 7 +- src/Storage/PathBuilder/LocalPathBuilder.php | 5 ++ src/Storage/PathBuilder/S3PathBuilder.php | 5 ++ .../Storage/Listener/AbstractListenerTest.php | 5 ++ .../PathBuilder/ImageProcessingTraitTest.php | 6 +- 8 files changed, 94 insertions(+), 36 deletions(-) diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index f707673a..c84e865c 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -1,4 +1,9 @@ _imageVersions = Configure::read('FileStorage.imageSizes'); $this->_imageVersionHashes = FileStorageUtils::generateHashes(); } +/** + * + */ public function imageProcessor(array $config = [], $renew = false) { if (!empty($this->_imageProcessor) && $renew === false) { return $this->_imageProcessor; @@ -30,21 +42,27 @@ public function imageProcessor(array $config = [], $renew = false) { return $this->_imageProcessor; } - public function createImageVersions($entity, array $options = []) { - $result = []; +/** + * + */ + public function createImageVersions(Entity $entity) { if (!isset($this->_imageVersions[$entity->model])) { throw new \RuntimeException(sprintf('No image version config found for `%s`!', $entity->model)); } + $result = []; foreach ($this->_imageVersions[$entity->model] as $version => $config) { $output = $this->createTmpFile(); + $hash = $this->getImageVersionHash($entity->model, $version); + $path = $this->pathBuilder()->fullPath($entity, ['fileSuffix' => '.' . $hash]); + $result[$version] = [ + 'status' => 'success', + 'path' => $path, + 'hash' => $this->_imageVersionHashes[$entity->model][$version], + ]; try { $this->imageProcessor()->open($entity->file['tmp_name']); $this->imageProcessor()->batchProcess($output, $config); - $result[$version] = [ - 'status' => 'success', - 'imageFile' => $output, - 'hash' => $this->_imageVersionHashes[$entity->model][$version], - ]; + $this->getAdapter($entity->adapter)->write($path, file_get_contents($output)); } catch (\Exception $e) { $result[$version] = [ 'status' => 'error', @@ -52,20 +70,46 @@ public function createImageVersions($entity, array $options = []) { ]; } } - //debug($this->_imageVersions); - //debug($this->_imageVersionHashes); return $result; } - public function storeImageVersions($entity, array $options = []) { - +/** + * + */ + public function getImageVersionHash($model, $version) { + if (empty($this->_imageVersionHashes[$model][$version])) { + throw new \RuntimeException(sprintf('Version "%s" for model "%s" does not exist!', $model, $version)); + } + return $this->_imageVersionHashes[$model][$version]; } - public function removeImageVersions($entity, array $options = []) { +/** + * + */ + public function storeImageVersions($entity, array $versions) { } - public function imageVersionFilename() { - +/** + * + */ + public function removeImageVersions($entity, array $versions) { + $result = []; + foreach ($versions as $version) { + $hash = $this->getImageVersionHash($entity->model, $version); + $path = $this->pathBuilder()->fullPath($entity, ['fileSuffix' => '.' . $hash]); + $result[$version] = [ + 'status' => 'success', + 'hash' => $hash, + 'path' => $path + ]; + try { + $this->getAdapter($entity->adapter)->delete($path); + } catch (\Exception $e) { + $result[$version]['status'] = 'error'; + $result[$version]['error'] = $e->getMessage(); + } + } + return $result; } } diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 894b3eb7..405b7865 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -1,11 +1,15 @@ _config['legacyPath'] === true) { -// return 'files' . DS . $path; -// } -// if (is_string($this->_config['legacyPath'])) { -// return $this->_config['legacyPath'] . DS . $path; -// } -// return $path; -// } - /** * afterSave * diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index cc828d69..59fa3687 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -1,4 +1,9 @@ path($entity) . $this->filename($entity); + return $this->path($entity, $options) . $this->filename($entity, $options); } /** diff --git a/src/Storage/PathBuilder/LocalPathBuilder.php b/src/Storage/PathBuilder/LocalPathBuilder.php index ccf04ebd..ced42d1f 100644 --- a/src/Storage/PathBuilder/LocalPathBuilder.php +++ b/src/Storage/PathBuilder/LocalPathBuilder.php @@ -1,4 +1,9 @@ pathBuilder('LocalPath'); diff --git a/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php b/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php index 6ef361cb..0b1718dc 100644 --- a/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php +++ b/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php @@ -63,7 +63,7 @@ public function setUp() { } /** - * testEnsureSlash + * testCreateImageVersions * * @return void */ @@ -75,8 +75,12 @@ public function testCreateImageVersions() { ]; $builder = new TraitTestClass(); + $builder->pathBuilder('LocalPath'); $builder->imageProcessor(); $result = $builder->createImageVersions($entity); + //debug($result); + + $result = $builder->removeImageVersions($entity, ['crop50']); debug($result); } } From 8d5def78b4349b0b760beedc25f2e3993c396f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sat, 4 Jul 2015 16:47:04 +0200 Subject: [PATCH 009/144] Working on the image processing. --- src/Storage/Listener/AbstractListener.php | 1 + src/Storage/Listener/ImageProcessingTrait.php | 49 +++++++++++++++---- .../PathBuilder/ImageProcessingTraitTest.php | 19 +++++++ 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index c84e865c..5a0e9084 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -6,6 +6,7 @@ */ namespace Burzum\FileStorage\Storage\Listener; +use Burzum\FileStorage\Lib\StorageManager; use Cake\Core\Configure; use Cake\Core\InstanceConfigTrait; use Cake\Event\Event; diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 15e332fb..699350cf 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -23,15 +23,18 @@ trait ImageProcessingTrait { protected $_imageVersionHashes = []; /** - * + * Loads the image processing configuration into the class. */ protected function _loadImageProcessingFromConfig() { - $this->_imageVersions = Configure::read('FileStorage.imageSizes'); + $this->_imageVersions = (array)Configure::read('FileStorage.imageSizes'); $this->_imageVersionHashes = FileStorageUtils::generateHashes(); } /** + * Gets the image processor instance. * + * @param array $config + * @return mixed */ public function imageProcessor(array $config = [], $renew = false) { if (!empty($this->_imageProcessor) && $renew === false) { @@ -43,7 +46,24 @@ public function imageProcessor(array $config = [], $renew = false) { } /** + * Gets the hash of a specific image version for an entity. + * + * @param string $model Model identifier. + * @param string $version Version identifier. + * @return string + */ + public function getImageVersionHash($model, $version) { + if (empty($this->_imageVersionHashes[$model][$version])) { + throw new \RuntimeException(sprintf('Version "%s" for model "%s" does not exist!', $model, $version)); + } + return $this->_imageVersionHashes[$model][$version]; + } + +/** + * Creates the image versions of an entity. * + * @param \Cake\ORM\Entity $entity + * @return array */ public function createImageVersions(Entity $entity) { if (!isset($this->_imageVersions[$entity->model])) { @@ -74,26 +94,37 @@ public function createImageVersions(Entity $entity) { } /** + * Gets all image version config keys for a specific identifier. * + * @param string $identifier + * @throws \RuntimeException + * @return array */ - public function getImageVersionHash($model, $version) { - if (empty($this->_imageVersionHashes[$model][$version])) { - throw new \RuntimeException(sprintf('Version "%s" for model "%s" does not exist!', $model, $version)); + public function getAllVersionsKeysForModel($identifier) { + if (!isset($this->_imageVersions[$identifier])) { + throw new \RuntimeException(sprintf('No image config present for identifier "%s"!', $identifier)); } - return $this->_imageVersionHashes[$model][$version]; + return array_keys($this->_imageVersions[$identifier]); } /** + * Convenience method to delete ALL versions for an entity. * + * @param \Cake\ORM\Entity + * @return array */ - public function storeImageVersions($entity, array $versions) { - + public function removeAllImageVersions(Entity $entity) { + return $this->removeAllImageVersions($entity, $this->getAllVersionsKeysForModel($entity->model)); } /** + * Removes image versions of an entity. * + * @param \Cake\ORM\Entity $entity + * @param array List of image version to remove for that entity. + * @return array */ - public function removeImageVersions($entity, array $versions) { + public function removeImageVersions(Entity $entity, array $versions) { $result = []; foreach ($versions as $version) { $hash = $this->getImageVersionHash($entity->model, $version); diff --git a/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php b/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php index 0b1718dc..c78fbb3f 100644 --- a/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php +++ b/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php @@ -11,6 +11,10 @@ class TraitTestClass extends AbstractListener { use ImageProcessingTrait; + public function __construct(array $config = []) { + parent::__construct($config); + $this->_loadImageProcessingFromConfig(); + } public function implementedEvents() { return []; } @@ -83,4 +87,19 @@ public function testCreateImageVersions() { $result = $builder->removeImageVersions($entity, ['crop50']); debug($result); } + +/** + * getAllVersionsKeysForModel + * + * @return void + */ + public function testGetAllVersionsKeysForModel() { + $builder = new TraitTestClass(); + $result = $builder->getAllVersionsKeysForModel('Item'); + $expected = [ + 0 => 't100', + 1 => 'crop50' + ]; + $this->assertEquals($result, $expected); + } } From 285bee8748e54da30ad8b2d31d89fbdcc0b3e205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sat, 4 Jul 2015 17:59:38 +0200 Subject: [PATCH 010/144] Working on the tests --- src/Event/AbstractStorageEventListener.php | 11 ++--- src/Storage/Listener/AbstractListener.php | 15 +++--- src/Storage/Listener/ImageProcessingTrait.php | 12 +++-- src/TestSuite/FileStorageTestCase.php | 3 +- tests/Fixture/FileStorageFixture.php | 15 ++++++ .../PathBuilder/BasePathBuilderTest.php | 26 ++++++++++ .../PathBuilder/ImageProcessingTraitTest.php | 49 +++++++++++++------ 7 files changed, 97 insertions(+), 34 deletions(-) diff --git a/src/Event/AbstractStorageEventListener.php b/src/Event/AbstractStorageEventListener.php index 87fd28c1..08f16913 100644 --- a/src/Event/AbstractStorageEventListener.php +++ b/src/Event/AbstractStorageEventListener.php @@ -271,14 +271,9 @@ public function getAdapter($configName) { * @return bool|string */ protected function _tmpFile($Storage, $path, $tmpFolder = null) { - try { - $tmpFile = $this->createTmpFile($tmpFolder); - file_put_contents($tmpFile, $Storage->read($path)); - return $tmpFile; - } catch (Exception $e) { - $this->log($e->getMessage(), 'file_storage'); - throw $e; - } + $tmpFile = $this->createTmpFile($tmpFolder); + file_put_contents($tmpFile, $Storage->read($path)); + return $tmpFile; } /** diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index 5a0e9084..f3c53800 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -275,11 +275,14 @@ public function createTmpFile($folder = null, $checkAndCreatePath = true) { /** * Path builder. * - * @param string Class name of a path builder. - * @param array Options for the path builder. + * @param string $class Class name of a path builder. + * @param array $config for the path builder. * @return \Burzum\FileStorage\Storage\PathBuilder\BasePathBuilder */ - public function pathBuilder($class = null, array $options = []) { + public function pathBuilder($class = null, array $config = []) { + if (!empty($this->_pathBuilder)) { + return $this->_pathBuilder; + } if (empty($class)) { if (empty($this->_pathBuilder)) { throw new \RuntimeException(sprintf('No path builder loaded!')); @@ -288,17 +291,17 @@ public function pathBuilder($class = null, array $options = []) { } $classname = '\Burzum\FileStorage\Storage\PathBuilder\\' . $class . 'Builder'; if (class_exists($classname)) { - $this->_pathBuilder = new $classname(); + $this->_pathBuilder = new $classname($config); return $this->_pathBuilder; } $classname = '\App\Storage\PathBuilder\\' . $class . 'Builder'; if (class_exists($classname)) { - $this->_pathBuilder = new $classname(); + $this->_pathBuilder = new $classname($config); return $this->_pathBuilder; } $classname = $class; if (class_exists($classname)) { - $this->_pathBuilder = new $classname(); + $this->_pathBuilder = new $classname($config); return $this->_pathBuilder; } throw new \RuntimeException(sprintf('Could not find path builder %s!', $classname)); diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 699350cf..9fed4422 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -62,6 +62,7 @@ public function getImageVersionHash($model, $version) { /** * Creates the image versions of an entity. * + * @todo finish me * @param \Cake\ORM\Entity $entity * @return array */ @@ -70,6 +71,7 @@ public function createImageVersions(Entity $entity) { throw new \RuntimeException(sprintf('No image version config found for `%s`!', $entity->model)); } $result = []; + $storage = $this->getAdapter($entity->adapter); foreach ($this->_imageVersions[$entity->model] as $version => $config) { $output = $this->createTmpFile(); $hash = $this->getImageVersionHash($entity->model, $version); @@ -80,13 +82,17 @@ public function createImageVersions(Entity $entity) { 'hash' => $this->_imageVersionHashes[$entity->model][$version], ]; try { - $this->imageProcessor()->open($entity->file['tmp_name']); - $this->imageProcessor()->batchProcess($output, $config); - $this->getAdapter($entity->adapter)->write($path, file_get_contents($output)); + $tmpFile = $this->_tmpFile($storage, $this->pathBuilder()->fullPath($entity)); + $this->imageProcessor()->open($tmpFile); + $this->imageProcessor()->batchProcess($output, $config, ['format' => $entity->extension]); + $storage->write($path, file_get_contents($output)); + unlink($tmpFile); } catch (\Exception $e) { $result[$version] = [ 'status' => 'error', 'error' => $e->getMessage(), + 'line' => $e->getLine(), + 'file' => $e->getFile() ]; } } diff --git a/src/TestSuite/FileStorageTestCase.php b/src/TestSuite/FileStorageTestCase.php index a0299e57..03f69acd 100644 --- a/src/TestSuite/FileStorageTestCase.php +++ b/src/TestSuite/FileStorageTestCase.php @@ -94,7 +94,6 @@ public function setUp() { public function tearDown() { parent::tearDown(); $Folder = new Folder(TMP . 'file-storage-test'); - $Folder->delete(); + //$Folder->delete(); } - } diff --git a/tests/Fixture/FileStorageFixture.php b/tests/Fixture/FileStorageFixture.php index 8c841f80..f9ec94a5 100644 --- a/tests/Fixture/FileStorageFixture.php +++ b/tests/Fixture/FileStorageFixture.php @@ -85,6 +85,21 @@ class FileStorageFixture extends TestFixture { 'adapter' => 'Local', 'created' => '2012-01-01 12:00:00', 'modified' => '2012-01-01 12:00:00', + ), + array( + 'id' => 'file-storage-3', + 'user_id' => 'user-1', + 'foreign_key' => 'item-2', + 'model' => 'Item', + 'filename' => 'titus.jpg', + 'filesize' => '335872', + 'mime_type' => 'image/jpg', + 'extension' => 'jpg', + 'hash' => '', + 'path' => '', + 'adapter' => 'Local', + 'created' => '2012-01-01 12:00:00', + 'modified' => '2012-01-01 12:00:00', ) ); } \ No newline at end of file diff --git a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php index 07e5eec1..f409264d 100644 --- a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php +++ b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php @@ -65,6 +65,21 @@ public function testPathbuilding() { $builder->config('pathSuffix', 'files'); $result = $builder->path($this->entity); $this->assertEquals($result, '00' . DS . '14' . DS . '90' . DS . 'filestorage1' . DS . 'files' . DS); + + $result = $builder->url($this->entity); + $expected = '00/14/90/filestorage1/files/filestorage1.png'; + $this->assertEquals($result, $expected); + } + +/** + * testRandomPath + * + * @return void + */ + public function testRandomPath() { + $builder = new BasePathBuilder(); + $result = $builder->randomPath('test'); + $this->assertInternalType('string', $result); } /** @@ -114,4 +129,15 @@ public function testSplitFilename() { ]; $this->assertEquals($result, $expected); } + +/** + * testStripDashes + * + * @return void + */ + public function testStripDashes() { + $builder = new BasePathBuilder(); + $result = $builder->stripDashes('with-dashes-!'); + $this->assertEquals($result, 'withdashes!'); + } } diff --git a/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php b/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php index c78fbb3f..6d6cc11a 100644 --- a/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php +++ b/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php @@ -5,9 +5,10 @@ use Burzum\FileStorage\Storage\Listener\AbstractListener; use Burzum\FileStorage\Storage\Listener\ImageProcessingTrait; use Cake\Core\InstanceConfigTrait; -use \Cake\Core\Configure; +use Cake\Core\Configure; use Cake\ORM\TableRegistry; use Cake\TestSuite\TestCase; +use Cake\Filesystem\Folder; class TraitTestClass extends AbstractListener { use ImageProcessingTrait; @@ -53,8 +54,8 @@ public function setUp() { 'Item' => [ 't100' => [ 'thumbnail' => [ - 'width' => 300, - 'height' => 300 + 'width' => 200, + 'height' => 200 ] ], 'crop50' => [ @@ -64,28 +65,31 @@ public function setUp() { ] ] ]); + + $this->Listener = $this->getMockBuilder('TraitTestClass') + ->setMethods([ + 'getAdapter' + ]) + ->getMock(); } /** * testCreateImageVersions * + * @todo finish me * @return void */ public function testCreateImageVersions() { - $entity = $this->FileStorage->get('file-storage-1'); - $entity->isNew(true); - $entity->file = [ - 'tmp_name' => $this->fileFixtures . 'titus.jpg', - ]; + $entity = $this->FileStorage->get('file-storage-3'); + $listener = new TraitTestClass(); + $path = $listener->pathBuilder('LocalPath', ['preserveFilename' => true])->path($entity); - $builder = new TraitTestClass(); - $builder->pathBuilder('LocalPath'); - $builder->imageProcessor(); - $result = $builder->createImageVersions($entity); - //debug($result); + new Folder($this->testPath . $path, true); + copy($this->fileFixtures . 'titus.jpg', $this->testPath . $path . 'titus.jpg'); - $result = $builder->removeImageVersions($entity, ['crop50']); - debug($result); + $listener->imageProcessor(); + $result = $listener->createImageVersions($entity); + //debug($result); } /** @@ -102,4 +106,19 @@ public function testGetAllVersionsKeysForModel() { ]; $this->assertEquals($result, $expected); } + +/** + * testRemoveImageVersions + * + * @return void + */ + public function testRemoveImageVersions() { +// $this->Listener->expects($this->at(0)) +// ->method('getAdapter') +// ->with(['Local']) +// ->will($this->returnValue(true)); +// +// $result = $this->Listener->removeImageVersions($this->entity, ['t100', 'crop50']); +// debug($result); + } } From 14786c839378210dadffa71f2dc746bab547fdc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sat, 4 Jul 2015 18:00:06 +0200 Subject: [PATCH 011/144] Removing a comment --- src/TestSuite/FileStorageTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TestSuite/FileStorageTestCase.php b/src/TestSuite/FileStorageTestCase.php index 03f69acd..4d8be3e0 100644 --- a/src/TestSuite/FileStorageTestCase.php +++ b/src/TestSuite/FileStorageTestCase.php @@ -94,6 +94,6 @@ public function setUp() { public function tearDown() { parent::tearDown(); $Folder = new Folder(TMP . 'file-storage-test'); - //$Folder->delete(); + $Folder->delete(); } } From 199f4081c1e2b1936d478da98b051450100d1355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 5 Jul 2015 13:13:40 +0200 Subject: [PATCH 012/144] Working on the image processing --- src/Storage/Listener/AbstractListener.php | 8 +++++-- src/Storage/Listener/ImageProcessingTrait.php | 3 +-- src/Storage/Listener/LocalListener.php | 11 ++++----- src/Storage/PathBuilder/S3PathBuilder.php | 24 ++++++++++++------- .../PathBuilder/ImageProcessingTraitTest.php | 17 +++++++++++-- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index f3c53800..c233d4f3 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -128,7 +128,11 @@ abstract public function implementedEvents(); * @return boolean */ protected function _checkEvent(Event $event) { - if (!in_array($this->storageTableClass, array('\Burzum\FileStorage\Model\Table\FileStorageTable', '\Burzum\FileStorage\Model\Table\ImageStorageTable'))) { + $classes = [ + '\Burzum\FileStorage\Model\Table\FileStorageTable', + '\Burzum\FileStorage\Model\Table\ImageStorageTable' + ]; + if (!in_array($this->storageTableClass, $classes)) { throw new \InvalidArgumentException(sprintf('Invalid storage table `%s`! Table must be FileStorage or ImageStorage or extend one of both!', $this->storageTableClass)); } return ( @@ -304,6 +308,6 @@ public function pathBuilder($class = null, array $config = []) { $this->_pathBuilder = new $classname($config); return $this->_pathBuilder; } - throw new \RuntimeException(sprintf('Could not find path builder %s!', $classname)); + throw new \RuntimeException(sprintf('Could not find path builder "%s"!', $classname)); } } diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 9fed4422..2427b591 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -62,7 +62,6 @@ public function getImageVersionHash($model, $version) { /** * Creates the image versions of an entity. * - * @todo finish me * @param \Cake\ORM\Entity $entity * @return array */ @@ -73,7 +72,6 @@ public function createImageVersions(Entity $entity) { $result = []; $storage = $this->getAdapter($entity->adapter); foreach ($this->_imageVersions[$entity->model] as $version => $config) { - $output = $this->createTmpFile(); $hash = $this->getImageVersionHash($entity->model, $version); $path = $this->pathBuilder()->fullPath($entity, ['fileSuffix' => '.' . $hash]); $result[$version] = [ @@ -82,6 +80,7 @@ public function createImageVersions(Entity $entity) { 'hash' => $this->_imageVersionHashes[$entity->model][$version], ]; try { + $output = $this->createTmpFile(); $tmpFile = $this->_tmpFile($storage, $this->pathBuilder()->fullPath($entity)); $this->imageProcessor()->open($tmpFile); $this->imageProcessor()->batchProcess($output, $config, ['format' => $entity->extension]); diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 405b7865..77fae7a7 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -34,11 +34,6 @@ class LocalListener extends AbstractListener { '\Gaufrette\Adapter\Local' ); - public function initialize() { - parent::initialize(); - $this->pathBuilder('Local', ['legacyPath' => true]);; - } - /** * Implemented Events * @@ -68,9 +63,9 @@ public function afterDelete(Event $event) { $entity = $event->data['record']; $path = $this->pathBuilder()->fullPath($entity); if (StorageManager::adapter($entity->adapter)->delete($path)) { - return true; + $event->result = true; } - return false; + $event->result = false; } } @@ -93,8 +88,10 @@ public function afterSave(Event $event) { 'validate' => false, 'callbacks' => false )); + $event->result = true; } catch (Exception $e) { $this->log($e->getMessage(), 'file_storage'); + $event->result = false; } } } diff --git a/src/Storage/PathBuilder/S3PathBuilder.php b/src/Storage/PathBuilder/S3PathBuilder.php index d9f9d4e5..bd39760e 100644 --- a/src/Storage/PathBuilder/S3PathBuilder.php +++ b/src/Storage/PathBuilder/S3PathBuilder.php @@ -12,19 +12,18 @@ class S3PathBuilder extends BasePathBuilder { public function __construct(array $config = []) { - if (empty($config['tableFolder'])) { - $config['tableFolder'] = true; - } + $this->_defaultConfig['https'] = false; + $this->_defaultConfig['modelFolder'] = true; parent::__construct($config); } - protected function _getBucketFromAdapter($adapter) { + protected function _getBucket($adapter) { $config = StorageManager::config($adapter); return $config['adapterOptions'][1]; } - protected function _buildCloudBaseUrl($protocol, $bucket, $bucketPrefix = null, $cfDist = null) { - $path = $protocol . '://'; + protected function _buildCloudUrl($bucket, $bucketPrefix = null, $cfDist = null) { + $path = $this->config('https') === true ? 'https://' : 'http://'; if ($cfDist) { $path .= $cfDist; } else { @@ -38,11 +37,18 @@ protected function _buildCloudBaseUrl($protocol, $bucket, $bucketPrefix = null, } /** - * @todo finish me + * Builds the URL under which the file is accessible. + * + * This is for example important for S3 and Dropbox but also the Local adapter + * if you symlink a folder to your webroot and allow direct access to a file. + * + * @param \Cake\ORM\Entity $entity + * @param array $options + * @return string */ public function url(Entity $entity, array $options = []) { - $bucket = $this->_getBucketFromAdapter($entity->adapter); - $pathPrefix = $this->_buildCloudBaseUrl('http', $bucket); + $bucket = $this->_getBucket($entity->adapter); + $pathPrefix = $this->_buildCloudUrl($bucket); $path = parent::path($entity); $path = str_replace('\\', '/', $path); return $pathPrefix . $path; diff --git a/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php b/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php index 6d6cc11a..045b8c64 100644 --- a/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php +++ b/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php @@ -76,7 +76,6 @@ public function setUp() { /** * testCreateImageVersions * - * @todo finish me * @return void */ public function testCreateImageVersions() { @@ -89,7 +88,21 @@ public function testCreateImageVersions() { $listener->imageProcessor(); $result = $listener->createImageVersions($entity); - //debug($result); + $expected = [ + 't100' => [ + 'status' => 'success', + 'path' => '48' . DS . '75' . DS . '05' . DS . 'filestorage3' . DS . 'titus.20876bcd.jpg', + 'hash' => '20876bcd' + ], + 'crop50' => [ + 'status' => 'success', + 'path' => '48' . DS . '75' . DS . '05' . DS . 'filestorage3' . DS . 'titus.41e51a3f.jpg', + 'hash' => '41e51a3f' + ] + ]; + $this->assertEquals($result, $expected); + $this->assertFileExists($this->testPath . '48' . DS . '75' . DS . '05' . DS . 'filestorage3' . DS . 'titus.20876bcd.jpg'); + $this->assertFileExists($this->testPath . '48' . DS . '75' . DS . '05' . DS . 'filestorage3' . DS . 'titus.41e51a3f.jpg'); } /** From 614339ad04ef151a6ef3b74e6f0650a18d1f144d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 5 Jul 2015 13:15:45 +0200 Subject: [PATCH 013/144] Scrutinizer --- .scrutinizer.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 4f78cc93..b498bad2 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -5,9 +5,6 @@ checks: remove_php_closing_tag: true remove_trailing_whitespace: true tools: - external_code_coverage: - timeout: 1800 - runs: 1 php_code_coverage: false php_loc: enabled: true From 6e07b47f5e2d0e0ff726e3bbb3bbcf63d7e34035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 5 Jul 2015 17:56:19 +0200 Subject: [PATCH 014/144] Adding another test for the randomPath, looks like CRC32 is causing trouble --- src/Lib/FileStorageUtils.php | 1 + tests/TestCase/Lib/Utility/FileStorageUtilsTest.php | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/Lib/FileStorageUtils.php b/src/Lib/FileStorageUtils.php index d6bcae40..be3bd128 100644 --- a/src/Lib/FileStorageUtils.php +++ b/src/Lib/FileStorageUtils.php @@ -37,6 +37,7 @@ public static function fileExtension($name, $realFile = false) { * * Works up to 5 level deep * + * @link https://www.box.com/blog/crc32-checksums-the-good-the-bad-and-the-ugly/ * @throws InvalidArgumentException * @param mixed $string * @param integer $level 1 to 5 diff --git a/tests/TestCase/Lib/Utility/FileStorageUtilsTest.php b/tests/TestCase/Lib/Utility/FileStorageUtilsTest.php index 78a41dbb..7829bbb2 100644 --- a/tests/TestCase/Lib/Utility/FileStorageUtilsTest.php +++ b/tests/TestCase/Lib/Utility/FileStorageUtilsTest.php @@ -22,6 +22,9 @@ class FileStorageUtilsTest extends FileStorageTestCase { public function testRandomPath() { $result = FileStorageUtils::randomPath('someteststring'); $this->assertEquals($result, '38' . DS . '88' . DS . '98' . DS); + + $result = FileStorageUtils::randomPath('file-storage-3'); + $this->assertEquals($result, '48' . DS . '75' . DS . '05' . DS); } /** From 04ac0be6e2aeade271f7475b3f5bf242aca3767a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 5 Jul 2015 19:56:28 +0200 Subject: [PATCH 015/144] Working on the tests --- src/Lib/FileStorageUtils.php | 1 + src/Storage/Listener/AbstractListener.php | 2 + src/Storage/Listener/LocalListener.php | 30 +++++++----- src/Storage/PathBuilder/BasePathBuilder.php | 21 +++++++-- src/TestSuite/FileStorageTestCase.php | 4 ++ .../ImageProcessingTraitTest.php | 0 .../Storage/Listener/LocalListenerTest.php | 47 ++++++++++++++++++- .../PathBuilder/BasePathBuilderTest.php | 7 ++- .../Storage/PathBuilder/S3PathBuilderTest.php | 18 +++++-- 9 files changed, 107 insertions(+), 23 deletions(-) rename tests/TestCase/Storage/{PathBuilder => Listener}/ImageProcessingTraitTest.php (100%) diff --git a/src/Lib/FileStorageUtils.php b/src/Lib/FileStorageUtils.php index be3bd128..cd9491ff 100644 --- a/src/Lib/FileStorageUtils.php +++ b/src/Lib/FileStorageUtils.php @@ -37,6 +37,7 @@ public static function fileExtension($name, $realFile = false) { * * Works up to 5 level deep * + * @deprecated Use the randomPath() method from the BasePathBuilder instead. * @link https://www.box.com/blog/crc32-checksums-the-good-the-bad-and-the-ugly/ * @throws InvalidArgumentException * @param mixed $string diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index c233d4f3..521f6761 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -11,6 +11,7 @@ use Cake\Core\InstanceConfigTrait; use Cake\Event\Event; use Cake\Event\EventListenerInterface; +use Cake\Log\LogTrait; use Cake\ORM\Table; use Cake\ORM\Entity; use Cake\Utility\Text; @@ -38,6 +39,7 @@ abstract class AbstractListener implements EventListenerInterface { use InstanceConfigTrait; + use LogTrait; /** * The adapter class diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 77fae7a7..76cb7c01 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -6,7 +6,6 @@ */ namespace Burzum\FileStorage\Storage\Listener; -use Burzum\FileStorage\Lib\StorageManager; use Cake\Core\Configure; use Cake\Event\Event; use Cake\Filesystem\Folder; @@ -21,7 +20,7 @@ class LocalListener extends AbstractListener { /** - * List of adapter classes the event listener can work with + * List of adapter classes the event listener can work with. * * It is used in FileStorageEventListenerBase::getAdapterClassName to get the * class, to detect if an event passed to this listener should be processed or @@ -30,9 +29,18 @@ class LocalListener extends AbstractListener { * * @var array */ - public $_adapterClasses = array( + public $_adapterClasses = [ '\Gaufrette\Adapter\Local' - ); + ]; + +/** + * Initialize callback + * + * @return void + */ + public function Initialize() { + $this->pathBuilder('LocalPath'); + } /** * Implemented Events @@ -55,14 +63,14 @@ public function implementedEvents() { * * No need to use an adapter here, just delete the whole folder using cakes Folder class * - * @param Event $event + * @param \Cake\Event\Event $event * @return void */ public function afterDelete(Event $event) { if ($this->_checkEvent($event)) { $entity = $event->data['record']; $path = $this->pathBuilder()->fullPath($entity); - if (StorageManager::adapter($entity->adapter)->delete($path)) { + if ($this->getAdapter($entity->adapter)->delete($path)) { $event->result = true; } $event->result = false; @@ -70,19 +78,19 @@ public function afterDelete(Event $event) { } /** - * afterSave + * Save the file to the storage backend after the record was created. * - * @param Event $event + * @param \Cake\Event\Event $event * @return void */ public function afterSave(Event $event) { if ($this->_checkEvent($event) && $event->data['record']->isNew()) { $table = $event->subject(); $entity = $event->data['record']; - $Storage = StorageManager::adapter($entity['adapter']); + $Storage = $this->getAdapter($entity['adapter']); try { - $filename = $this->pathBuilder->filename($entity); - $entity['path'] = $this->pathBuilder->path($entity); + $filename = $this->pathBuilder()->filename($entity); + $entity['path'] = $this->pathBuilder()->path($entity); $Storage->write($entity['path'] . $filename, file_get_contents($entity['file']['tmp_name']), true); $table->save($entity, array( 'validate' => false, diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index 59fa3687..49c5ab20 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -173,11 +173,26 @@ public function url(Entity $entity, array $options = []) { * * Makes it possible to overload this functionality. * - * @param string $string + * @param string $string Input string + * @param int $level Depth of the path to generate. + * @param string $method Hash method, crc32 or sha1. * @return string */ - public function randomPath($string) { - return FileStorageUtils::randomPath($string); + public function randomPath($string, $level = 3, $method = 'crc32') { + if ($method === 'sha1') { + $result = sha1($string); + $randomString = ''; + $counter = 0; + for ($i = 1; $i <= $level; $i++) { + $counter = $counter + 2; + $randomString .= substr($result, $counter, 2) . DS; + } + return $randomString; + } + // Keeping this for backward compatibility + if ($method === 'crc32') { + return FileStorageUtils::randomPath($string); + } } /** diff --git a/src/TestSuite/FileStorageTestCase.php b/src/TestSuite/FileStorageTestCase.php index 4d8be3e0..0452badf 100644 --- a/src/TestSuite/FileStorageTestCase.php +++ b/src/TestSuite/FileStorageTestCase.php @@ -84,6 +84,9 @@ public function setUp() { 'adapterClass' => '\Gaufrette\Adapter\Local', 'class' => '\Gaufrette\Filesystem' )); + + $this->FileStorage = TableRegistry::get('Burzum/FileStorage.FileStorage'); + $this->ImageStorage = TableRegistry::get('Burzum/FileStorage.ImageStorage'); } /** @@ -93,6 +96,7 @@ public function setUp() { */ public function tearDown() { parent::tearDown(); + TableRegistry::clear(); $Folder = new Folder(TMP . 'file-storage-test'); $Folder->delete(); } diff --git a/tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php b/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php similarity index 100% rename from tests/TestCase/Storage/PathBuilder/ImageProcessingTraitTest.php rename to tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php diff --git a/tests/TestCase/Storage/Listener/LocalListenerTest.php b/tests/TestCase/Storage/Listener/LocalListenerTest.php index 14cb94d2..a2f28de3 100644 --- a/tests/TestCase/Storage/Listener/LocalListenerTest.php +++ b/tests/TestCase/Storage/Listener/LocalListenerTest.php @@ -1,12 +1,14 @@ listener = $this->getMockBuilder('Burzum\FileStorage\Storage\Listener\LocalListener') + ->setMethods(['getAdapter']) + ->getMock(); + + $this->adapterMock = $this->getMock('\Gaufrette\Adapter\Local', [], ['']); + + $this->FileStorage = TableRegistry::get('Burzum/FileStorage.FileStorage'); + } + +/** + * testAfterSave + * + * @todo finish me + * @return void + */ public function testAfterSave() { + $entity = $this->FileStorage->get('file-storage-3'); + $entity->isNew(true); + $entity->file = ['tmp_name' => $this->fileFixtures . 'titus.jpg']; + $event = new Event('FileStorage.afterSave', $this->FileStorage, [ + 'record' => $entity + ]); + $this->listener->expects($this->at(0)) + ->method('getAdapter') + ->will($this->returnValue($this->adapterMock)); + + $this->listener->afterSave($event); } +/** + * testAfterDelete + * + * @return void + */ + public function testAfterDelete() { + + } } diff --git a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php index f409264d..84eb365c 100644 --- a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php +++ b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php @@ -78,8 +78,11 @@ public function testPathbuilding() { */ public function testRandomPath() { $builder = new BasePathBuilder(); - $result = $builder->randomPath('test'); - $this->assertInternalType('string', $result); + $result = $builder->randomPath('test', 5, 'sha1'); + $this->assertEquals($result, '4a\8f\e5\cc\b1\\'); + + $result = $builder->randomPath('test', 3, 'sha1'); + $this->assertEquals($result, '4a\8f\e5\\'); } /** diff --git a/tests/TestCase/Storage/PathBuilder/S3PathBuilderTest.php b/tests/TestCase/Storage/PathBuilder/S3PathBuilderTest.php index e6e62d3f..ebe8ab9f 100644 --- a/tests/TestCase/Storage/PathBuilder/S3PathBuilderTest.php +++ b/tests/TestCase/Storage/PathBuilder/S3PathBuilderTest.php @@ -8,15 +8,20 @@ class S3PathBuilderTest extends TestCase { - /** - * Fixtures - * - * @var array - */ +/** + * Fixtures + * + * @var array + */ public $fixtures = array( 'plugin.Burzum\FileStorage.FileStorage' ); +/** + * setUp + * + * @return void + */ public function setUp() { parent::setUp(); $this->FileStorage = TableRegistry::get('Burzum/FileStorage.FileStorage'); @@ -37,6 +42,9 @@ public function setUp() { } /** + * testUrl + * + * @return void * @todo finish me */ public function testUrl() { From dfb52a5f0f2d43d3c18eaf5e1385aef2c32c16eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 5 Jul 2015 22:19:44 +0200 Subject: [PATCH 016/144] Fixing log and exceptions in the old listeners --- src/Event/AbstractStorageEventListener.php | 2 ++ src/Event/ImageProcessingListener.php | 4 ++-- src/Event/LocalFileStorageListener.php | 2 +- src/Event/S3StorageListener.php | 4 ++-- src/Storage/PathBuilder/BasePathBuilder.php | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Event/AbstractStorageEventListener.php b/src/Event/AbstractStorageEventListener.php index 08f16913..75e1ff1e 100644 --- a/src/Event/AbstractStorageEventListener.php +++ b/src/Event/AbstractStorageEventListener.php @@ -5,6 +5,7 @@ use Cake\Core\InstanceConfigTrait; use Cake\Event\Event; use Cake\Event\EventListenerInterface; +use Cake\Log\LogTrait; use Cake\ORM\Table; use Cake\ORM\Entity; use Cake\Utility\Text; @@ -34,6 +35,7 @@ abstract class AbstractStorageEventListener implements EventListenerInterface { use InstanceConfigTrait; + use LogTrait; /** * The adapter class diff --git a/src/Event/ImageProcessingListener.php b/src/Event/ImageProcessingListener.php index d16d396d..f76203bb 100644 --- a/src/Event/ImageProcessingListener.php +++ b/src/Event/ImageProcessingListener.php @@ -132,7 +132,7 @@ protected function _createVersions(Table $table, $entity, array $operations) { try { $image = $table->processImage($tmpFile, null, array('format' => $entity['extension']), $imageOperations); - $result = $Storage->write($string, $image->get($entity['extension']), true); + $Storage->write($string, $image->get($entity['extension']), true); } catch (\Exception $e) { $this->log($e->getMessage(), 'file_storage'); unlink($tmpFile); @@ -214,7 +214,7 @@ public function afterDelete(Event $Event) { return false; } $Storage->delete($string); - } catch (Exception $e) { + } catch (\Exception $e) { $this->log($e->getMessage(), 'file_storage'); return false; } diff --git a/src/Event/LocalFileStorageListener.php b/src/Event/LocalFileStorageListener.php index a8546165..bda29ca5 100644 --- a/src/Event/LocalFileStorageListener.php +++ b/src/Event/LocalFileStorageListener.php @@ -100,7 +100,7 @@ public function afterSave(Event $event) { 'validate' => false, 'callbacks' => false )); - } catch (Exception $e) { + } catch (\Exception $e) { $this->log($e->getMessage(), 'file_storage'); } } diff --git a/src/Event/S3StorageListener.php b/src/Event/S3StorageListener.php index 3fa4f14a..4375c38a 100644 --- a/src/Event/S3StorageListener.php +++ b/src/Event/S3StorageListener.php @@ -76,12 +76,12 @@ public function afterSave(Event $Event) { try { $path = $this->buildPath($Event->subject(), $Event->data['record']); $record['path'] = $path['path']; - $result = $Storage->write($path['combined'], file_get_contents($record['file']['tmp_name']), true); + $Storage->write($path['combined'], file_get_contents($record['file']['tmp_name']), true); $table->save($record, array( 'validate' => false, 'callbacks' => false) ); - } catch (Exception $e) { + } catch (\Exception $e) { $this->log($e->getMessage(), 'file_storage'); } } diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index 49c5ab20..e54e052d 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -58,8 +58,8 @@ public function stripDashes($uuid) { /** * Builds the path under which the data gets stored in the storage adapter. * - * @param Table $table * @param Entity $entity + * @param array $options * @return string */ public function path(Entity $entity, array $options = []) { From 2ed30f18291a2368519ecca88cc0919f78d467db Mon Sep 17 00:00:00 2001 From: Scrutinizer Auto-Fixer Date: Sun, 5 Jul 2015 20:32:04 +0000 Subject: [PATCH 017/144] Scrutinizer Auto-Fixes This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com --- src/Event/ImageProcessingListener.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Event/ImageProcessingListener.php b/src/Event/ImageProcessingListener.php index f76203bb..b696f31d 100644 --- a/src/Event/ImageProcessingListener.php +++ b/src/Event/ImageProcessingListener.php @@ -111,7 +111,7 @@ protected function _autoRotate($imageFile, $format) { * @param array $operations * @throws \Burzum\FileStorage\Event\Exception * @throws \Exception - * @return boolean + * @return false|null */ protected function _createVersions(Table $table, $entity, array $operations) { $Storage = StorageManager::adapter($entity['adapter']); @@ -122,7 +122,7 @@ protected function _createVersions(Table $table, $entity, array $operations) { $hash = FileStorageUtils::hashOperations($imageOperations); $string = $this->_buildPath($entity, true, $hash); - if ($this->adapterClass === 'AmazonS3' || $this->adapterClass === 'AwsS3' ) { + if ($this->adapterClass === 'AmazonS3' || $this->adapterClass === 'AwsS3') { $string = str_replace('\\', '/', $string); } @@ -180,7 +180,7 @@ protected function _removeVersions(Event $Event) { foreach ($Event->data['operations'] as $version => $operations) { $hash = FileStorageUtils::hashOperations($operations); $string = $this->_buildPath($record, true, $hash); - if ($this->adapterClass === 'AmazonS3' || $this->adapterClass === 'AwsS3' ) { + if ($this->adapterClass === 'AmazonS3' || $this->adapterClass === 'AwsS3') { $string = str_replace('\\', '/', $string); } try { @@ -199,13 +199,13 @@ protected function _removeVersions(Event $Event) { * afterDelete * * @param Event $Event - * @return void + * @return boolean|null */ public function afterDelete(Event $Event) { if ($this->_checkEvent($Event)) { $record = $Event->data['record']; $string = $this->_buildPath($record, true, null); - if ($this->adapterClass === 'AmazonS3' || $this->adapterClass === 'AwsS3' ) { + if ($this->adapterClass === 'AmazonS3' || $this->adapterClass === 'AwsS3') { $string = str_replace('\\', '/', $string); } try { @@ -265,7 +265,7 @@ public function afterSave(Event $Event) { $path = $record['path'] . $filename . '.' . $record['extension']; } - if ($this->adapterClass === 'AmazonS3' || $this->adapterClass === 'AwsS3' ) { + if ($this->adapterClass === 'AmazonS3' || $this->adapterClass === 'AwsS3') { $path = str_replace('\\', '/', $path); $record['path'] = str_replace('\\', '/', $record['path']); } @@ -379,6 +379,7 @@ protected function _buildAmazonS3Path(Event $Event) { * @param string $bucket * @param string null $bucketPrefix * @param string $cfDist + * @param boolean $bucketPrefix * @return string */ protected function _buildCloudFrontDistributionUrl($protocol, $image, $bucket, $bucketPrefix = null, $cfDist = null) { @@ -422,7 +423,7 @@ protected function _buildPath($record, $extension = true, $hash = null) { } } - if ($this->adapterClass === 'AmazonS3' || $this->adapterClass === 'AwsS3' ) { + if ($this->adapterClass === 'AmazonS3' || $this->adapterClass === 'AwsS3') { return str_replace('\\', '/', $path); } @@ -433,7 +434,7 @@ protected function _buildPath($record, $extension = true, $hash = null) { * Gets the adapter class name from the adapter configuration key * * @param string - * @return void + * @return string|false */ public function getAdapterClassName($adapterConfigName) { $config = StorageManager::config($adapterConfigName); From 919e7ecd734657ef62c8dae18efc5984eca0bfe2 Mon Sep 17 00:00:00 2001 From: Scrutinizer Auto-Fixer Date: Sun, 5 Jul 2015 20:57:34 +0000 Subject: [PATCH 018/144] Scrutinizer Auto-Fixes This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com --- src/Event/AbstractStorageEventListener.php | 7 +++---- src/Event/LocalFileStorageListener.php | 3 +-- src/Event/S3StorageListener.php | 2 +- src/Lib/FileStorageUtils.php | 8 ++++---- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Event/AbstractStorageEventListener.php b/src/Event/AbstractStorageEventListener.php index 75e1ff1e..6182ee67 100644 --- a/src/Event/AbstractStorageEventListener.php +++ b/src/Event/AbstractStorageEventListener.php @@ -1,7 +1,6 @@ _config['stripUuid'] === true) { + if ($this->_config['stripUuid'] === true) { $filename = $this->stripDashes($filename); } if ($this->_config['preserveExtension'] === true) { @@ -218,7 +217,7 @@ protected function _getAdapterClassFromConfig($configName) { * You must define a list of supported classes via AbstractStorageEventListener::$_adapterClasses. * * @param string $configName Name of the adapter configuration. - * @return boolean|string String, the adapter class name or false if it was not found. + * @return string|false String, the adapter class name or false if it was not found. */ public function getAdapterClassName($configName) { $className = $this->_getAdapterClassFromConfig($configName); @@ -270,7 +269,7 @@ public function getAdapter($configName) { * @param string $path Path / key of the storage adapter file * @param string $tmpFolder * @throws Exception - * @return bool|string + * @return string */ protected function _tmpFile($Storage, $path, $tmpFolder = null) { $tmpFile = $this->createTmpFile($tmpFolder); diff --git a/src/Event/LocalFileStorageListener.php b/src/Event/LocalFileStorageListener.php index bda29ca5..7d7ae4ca 100644 --- a/src/Event/LocalFileStorageListener.php +++ b/src/Event/LocalFileStorageListener.php @@ -1,7 +1,6 @@ _checkEvent($event)) { diff --git a/src/Event/S3StorageListener.php b/src/Event/S3StorageListener.php index 4375c38a..31d267f3 100644 --- a/src/Event/S3StorageListener.php +++ b/src/Event/S3StorageListener.php @@ -40,7 +40,7 @@ public function implementedEvents() { * afterDelete * * @param \Cake\Event\Event $Event - * @return void + * @return boolean|null */ public function afterDelete(Event $Event) { if ($this->_checkEvent($Event)) { diff --git a/src/Lib/FileStorageUtils.php b/src/Lib/FileStorageUtils.php index cd9491ff..dd91ed71 100644 --- a/src/Lib/FileStorageUtils.php +++ b/src/Lib/FileStorageUtils.php @@ -19,7 +19,7 @@ class FileStorageUtils { * @param string $name * @param boolean $realFile * @link http://php.net/manual/en/function.pathinfo.php - * @return boolean string or false + * @return false|string string or false */ public static function fileExtension($name, $realFile = false) { if ($realFile) { @@ -42,7 +42,7 @@ public static function fileExtension($name, $realFile = false) { * @throws InvalidArgumentException * @param mixed $string * @param integer $level 1 to 5 - * @return mixed + * @return null|string */ public static function randomPath($string, $level = 3) { if (!$string) { @@ -114,7 +114,7 @@ public static function normalizeGlobalFilesArray($array = null) { * Serializes and then hashes an array of operations that are applied to an image * * @param array $operations - * @return array + * @return string */ public static function hashOperations($operations) { self::ksortRecursive($operations); @@ -150,7 +150,7 @@ public static function generateHashes($configPath = 'FileStorage') { * * @param array $array * @param integer - * @return void + * @return boolean * @link https://gist.github.com/601849 */ public static function ksortRecursive(&$array, $sortFlags = SORT_REGULAR) { From 2206e9139ec9d9a23d32e95980fe0cdad1be6b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 5 Jul 2015 23:23:47 +0200 Subject: [PATCH 019/144] Working on the image processing --- src/Storage/Listener/ImageProcessingTrait.php | 105 +++++++++++++----- 1 file changed, 76 insertions(+), 29 deletions(-) diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 2427b591..6a250aa7 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -24,6 +24,8 @@ trait ImageProcessingTrait { /** * Loads the image processing configuration into the class. + * + * @return void */ protected function _loadImageProcessingFromConfig() { $this->_imageVersions = (array)Configure::read('FileStorage.imageSizes'); @@ -54,24 +56,47 @@ public function imageProcessor(array $config = [], $renew = false) { */ public function getImageVersionHash($model, $version) { if (empty($this->_imageVersionHashes[$model][$version])) { - throw new \RuntimeException(sprintf('Version "%s" for model "%s" does not exist!', $model, $version)); + throw new \RuntimeException(sprintf('Version "%s" for identifier "%s" does not exist!', $model, $version)); } return $this->_imageVersionHashes[$model][$version]; } +/** + * Check that the image versions exist before doing something with them. + * + * @throws \RuntimeException + * @param string $identifier + * @param array $versions + * @return void + */ + protected function _checkImageVersions($identifier, array $versions) { + if (!isset($this->_imageVersions[$identifier])) { + throw new \RuntimeException(sprintf('No image version config found for identifier "%s"!', $identifier)); + } + foreach ($versions as $version) { + if (!isset($this->_imageVersions[$identifier][$version])) { + throw new \RuntimeException(sprintf('Invalid version "%s" for identifier "%s"!', $identifier, $version)); + } + } + } + /** * Creates the image versions of an entity. * * @param \Cake\ORM\Entity $entity + * @param array $versions $options + * @param array $options * @return array */ - public function createImageVersions(Entity $entity) { - if (!isset($this->_imageVersions[$entity->model])) { - throw new \RuntimeException(sprintf('No image version config found for `%s`!', $entity->model)); - } + public function createImageVersions(Entity $entity, array $versions, array $options = []) { + $this->_checkImageVersions($entity->model, $versions); + $result = []; $storage = $this->getAdapter($entity->adapter); foreach ($this->_imageVersions[$entity->model] as $version => $config) { + if (!in_array($version, $versions)) { + continue; + } $hash = $this->getImageVersionHash($entity->model, $version); $path = $this->pathBuilder()->fullPath($entity, ['fileSuffix' => '.' . $hash]); $result[$version] = [ @@ -98,6 +123,37 @@ public function createImageVersions(Entity $entity) { return $result; } +/** + * Removes image versions of an entity. + * + * @param \Cake\ORM\Entity $entity + * @param array List of image version to remove for that entity. + * @param array $versions + * @param array $options + * @return array + */ + public function removeImageVersions(Entity $entity, array $versions, array $options = []) { + $this->_checkImageVersions($entity->model, $versions); + + $result = []; + foreach ($versions as $version) { + $hash = $this->getImageVersionHash($entity->model, $version); + $path = $this->pathBuilder()->fullPath($entity, ['fileSuffix' => '.' . $hash]); + $result[$version] = [ + 'status' => 'success', + 'hash' => $hash, + 'path' => $path + ]; + try { + $this->getAdapter($entity->adapter)->delete($path); + } catch (\Exception $e) { + $result[$version]['status'] = 'error'; + $result[$version]['error'] = $e->getMessage(); + } + } + return $result; + } + /** * Gets all image version config keys for a specific identifier. * @@ -113,39 +169,30 @@ public function getAllVersionsKeysForModel($identifier) { } /** - * Convenience method to delete ALL versions for an entity. + * Convenience method to create ALL versions for an entity. * * @param \Cake\ORM\Entity * @return array */ - public function removeAllImageVersions(Entity $entity) { - return $this->removeAllImageVersions($entity, $this->getAllVersionsKeysForModel($entity->model)); + public function createAllImageVersions(Entity $entity, array $options = []) { + return $this->createImageVersions( + $entity, + $this->getAllVersionsKeysForModel($entity->model), + $options + ); } /** - * Removes image versions of an entity. + * Convenience method to delete ALL versions for an entity. * - * @param \Cake\ORM\Entity $entity - * @param array List of image version to remove for that entity. + * @param \Cake\ORM\Entity * @return array */ - public function removeImageVersions(Entity $entity, array $versions) { - $result = []; - foreach ($versions as $version) { - $hash = $this->getImageVersionHash($entity->model, $version); - $path = $this->pathBuilder()->fullPath($entity, ['fileSuffix' => '.' . $hash]); - $result[$version] = [ - 'status' => 'success', - 'hash' => $hash, - 'path' => $path - ]; - try { - $this->getAdapter($entity->adapter)->delete($path); - } catch (\Exception $e) { - $result[$version]['status'] = 'error'; - $result[$version]['error'] = $e->getMessage(); - } - } - return $result; + public function removeAllImageVersions(Entity $entity, array $options = []) { + return $this->removeImageVersions( + $entity, + $this->getAllVersionsKeysForModel($entity->model), + $options + ); } } From eb538e8958b79cbf2a3fded091b6d748546f11c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 6 Jul 2015 00:07:00 +0200 Subject: [PATCH 020/144] Working on the tests --- src/Model/Table/FileStorageTable.php | 5 +- src/Model/Table/ImageStorageTable.php | 2 +- .../Listener/ImageProcessingTraitTest.php | 56 +++++++++++++------ .../Storage/Listener/LocalListenerTest.php | 4 ++ 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index c49b2d6e..f49a4a1b 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -66,6 +66,8 @@ public function configureUploadValidation($options) { /** * beforeSave callback * + * @param \Cake\Event\Event $event + * @param \Cake\ORM\Entity $entity * @param array $options * @return boolean true on success */ @@ -150,7 +152,7 @@ public function afterDelete(\Cake\Event\Event $event, Entity $entity, $options) try { $Storage = $this->getStorageAdapter($entity['adapter']); $Storage->delete($entity['path']); - } catch (Exception $e) { + } catch (\Exception $e) { $this->log($e->getMessage(), 'file_storage'); return false; } @@ -158,6 +160,7 @@ public function afterDelete(\Cake\Event\Event $event, Entity $entity, $options) $Event = new Event('FileStorage.afterDelete', $this, array( 'record' => $event->data['record'], 'storage' => $this->getStorageAdapter($entity['adapter']))); + $this->getEventManager()->dispatch($Event); return true; diff --git a/src/Model/Table/ImageStorageTable.php b/src/Model/Table/ImageStorageTable.php index 16d52fb7..3ea3ca47 100644 --- a/src/Model/Table/ImageStorageTable.php +++ b/src/Model/Table/ImageStorageTable.php @@ -68,7 +68,7 @@ public function beforeSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, * Does not call the parent to avoid that the regular file storage event listener saves the image already * * @param \Cake\Event\Event $event - * @param \Burzum\FileStorage\Model\Table\Entity $entity + * @param \Cake\ORM\Entity $entity * @param array $options * @return boolean */ diff --git a/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php b/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php index 045b8c64..edd6ee33 100644 --- a/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php +++ b/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php @@ -16,6 +16,9 @@ public function __construct(array $config = []) { parent::__construct($config); $this->_loadImageProcessingFromConfig(); } + public function checkImageVersions($identifier, array $versions) { + return $this->_checkImageVersions($identifier, $versions); + } public function implementedEvents() { return []; } @@ -87,7 +90,7 @@ public function testCreateImageVersions() { copy($this->fileFixtures . 'titus.jpg', $this->testPath . $path . 'titus.jpg'); $listener->imageProcessor(); - $result = $listener->createImageVersions($entity); + $result = $listener->createImageVersions($entity, ['t100', 'crop50']); $expected = [ 't100' => [ 'status' => 'success', @@ -103,6 +106,38 @@ public function testCreateImageVersions() { $this->assertEquals($result, $expected); $this->assertFileExists($this->testPath . '48' . DS . '75' . DS . '05' . DS . 'filestorage3' . DS . 'titus.20876bcd.jpg'); $this->assertFileExists($this->testPath . '48' . DS . '75' . DS . '05' . DS . 'filestorage3' . DS . 'titus.41e51a3f.jpg'); + + $result = $listener->removeImageVersions($entity, ['t100']); + $expected = [ + 't100' => [ + 'status' => 'success', + 'hash' => '20876bcd', + 'path' => '48\75\05\filestorage3\titus.20876bcd.jpg' + ] + ]; + $this->assertEquals($result, $expected); + $this->assertFileNotExists($this->testPath . '48' . DS . '75' . DS . '05' . DS . 'filestorage3' . DS . 'titus.20876bcd.jpg'); + $this->assertFileExists($this->testPath . '48' . DS . '75' . DS . '05' . DS . 'filestorage3' . DS . 'titus.41e51a3f.jpg'); + } + +/** + * testCheckImageVersionsRuntimeExceptionIdentifier + * + * @expectedException \RuntimeException + */ + public function testCheckImageVersionsRuntimeExceptionIdentifier() { + $listener = new TraitTestClass(); + $listener->checkImageVersions('does not exist', []); + } + +/** + * testCheckImageVersionsRuntimeExceptionVersion + * + * @expectedException \RuntimeException + */ + public function testCheckImageVersionsRuntimeExceptionVersion() { + $listener = new TraitTestClass(); + $listener->checkImageVersions('Item', ['does not exist!']); } /** @@ -111,27 +146,12 @@ public function testCreateImageVersions() { * @return void */ public function testGetAllVersionsKeysForModel() { - $builder = new TraitTestClass(); - $result = $builder->getAllVersionsKeysForModel('Item'); + $listener = new TraitTestClass(); + $result = $listener->getAllVersionsKeysForModel('Item'); $expected = [ 0 => 't100', 1 => 'crop50' ]; $this->assertEquals($result, $expected); } - -/** - * testRemoveImageVersions - * - * @return void - */ - public function testRemoveImageVersions() { -// $this->Listener->expects($this->at(0)) -// ->method('getAdapter') -// ->with(['Local']) -// ->will($this->returnValue(true)); -// -// $result = $this->Listener->removeImageVersions($this->entity, ['t100', 'crop50']); -// debug($result); - } } diff --git a/tests/TestCase/Storage/Listener/LocalListenerTest.php b/tests/TestCase/Storage/Listener/LocalListenerTest.php index a2f28de3..7c426e00 100644 --- a/tests/TestCase/Storage/Listener/LocalListenerTest.php +++ b/tests/TestCase/Storage/Listener/LocalListenerTest.php @@ -53,6 +53,10 @@ public function testAfterSave() { ->method('getAdapter') ->will($this->returnValue($this->adapterMock)); + $this->adapterMock->expects($this->at(0)) + ->method('write') + ->will($this->returnValue(true)); + $this->listener->afterSave($event); } From 5c245128e70db4544a849df82d13cfb7fc2a1537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 9 Jul 2015 02:26:20 +0200 Subject: [PATCH 021/144] Working on the new listeners, preliminary commit --- docs/Documentation/Path-Builders.md | 6 +- src/Lib/FileStorageUtils.php | 116 +++------- src/Lib/StorageManager.php | 3 +- src/Storage/Listener/AbstractListener.php | 80 ++++--- src/Storage/Listener/ImageProcessingTrait.php | 4 +- src/Storage/Listener/LocalListener.php | 13 +- src/Storage/PathBuilder/BasePathBuilder.php | 2 +- .../PathBuilder/PathBuilderInterface.php | 51 +++++ src/Storage/StorageTrait.php | 43 ++++ src/Storage/StorageUtils.php | 210 ++++++++++++++++++ .../Storage/Listener/LocalListenerTest.php | 4 +- 11 files changed, 396 insertions(+), 136 deletions(-) create mode 100644 src/Storage/PathBuilder/PathBuilderInterface.php create mode 100644 src/Storage/StorageTrait.php create mode 100644 src/Storage/StorageUtils.php diff --git a/docs/Documentation/Path-Builders.md b/docs/Documentation/Path-Builders.md index 82d78bc7..dd8f4100 100644 --- a/docs/Documentation/Path-Builders.md +++ b/docs/Documentation/Path-Builders.md @@ -3,6 +3,8 @@ Path Builders Path builders are classes that are used to build the storage paths for a file based on the information coming from the `file_storage` table. +A path builder *should but doesn't have to* build a unique path per entity based on all the data available in the entity. + They implement at least these methods: * filename @@ -12,4 +14,6 @@ They implement at least these methods: Each of them will take a `FileStorage` entity as first argument. Based on that entity it will generate a path depending on the logic implemented in the path builder. -The reason for this is to separate or share, just as needed, the path building logic between different storage systems. +The reason for this is to separate or share, just as needed, the path building logic between different storage systems. For example S3 differs in it's first part of the path, it's using a bucket while locally you usually have something like a base path instead of the bucket. + +If you want to change the way your files are saved extend the `BasePathBuilder` class. diff --git a/src/Lib/FileStorageUtils.php b/src/Lib/FileStorageUtils.php index dd91ed71..c073191e 100644 --- a/src/Lib/FileStorageUtils.php +++ b/src/Lib/FileStorageUtils.php @@ -1,15 +1,19 @@ $fieldvalue) { - foreach ($fieldvalue as $paramname => $paramvalue) { - foreach ((array)$paramvalue as $index => $value) { - $newfiles[$fieldname][$index][$paramname] = $value; - } - } - } - } - return $newfiles; + return StorageUtils::normalizeGlobalFilesArray($array); } /** @@ -117,8 +84,7 @@ public static function normalizeGlobalFilesArray($array = null) { * @return string */ public static function hashOperations($operations) { - self::ksortRecursive($operations); - return substr(md5(serialize($operations)), 0, 8); + return StorageUtils::hashOperations($operations); } /** @@ -128,21 +94,7 @@ public static function hashOperations($operations) { * @return array */ public static function generateHashes($configPath = 'FileStorage') { - if (is_array($configPath)) { - $imageSizes = $configPath; - } else { - $imageSizes = Configure::read($configPath . '.imageSizes'); - } - if (is_null($imageSizes)) { - throw new \RuntimeException(sprintf('Image processing configuration in %s is missing!', $configPath . '.imageSizes')); - } - self::ksortRecursive($imageSizes); - foreach ($imageSizes as $model => $version) { - foreach ($version as $name => $operations) { - Configure::write($configPath . '.imageHashes.' . $model . '.' . $name, self::hashOperations($operations)); - } - } - return Configure::read($configPath . '.imageHashes'); + return StorageUtils::generateHashes($configPath); } /** @@ -153,15 +105,8 @@ public static function generateHashes($configPath = 'FileStorage') { * @return boolean * @link https://gist.github.com/601849 */ - public static function ksortRecursive(&$array, $sortFlags = SORT_REGULAR) { - if (!is_array($array)) { - return false; - } - ksort($array, $sortFlags); - foreach ($array as &$arr) { - self::ksortRecursive($arr, $sortFlags); - } - return true; + public static function ksortRecursive(&$array, $sortFlags) { + return StorageUtils::getFileHash($array, $sortFlags); } /** @@ -172,16 +117,23 @@ public static function ksortRecursive(&$array, $sortFlags = SORT_REGULAR) { * @return array Array that matches the structure of a regular upload */ public static function uploadArray($file, $filename = null) { - $File = new File($file); - if (empty($fileName)) { - $filename = basename($file); - } - return [ - 'name' => $filename, - 'tmp_name' => $file, - 'error' => 0, - 'type' => $File->mime(), - 'size' => $File->size() - ]; + return StorageUtils::uploadArray($file, $filename); + } + +/** + * Gets the hash of a file. + * + * You can use this to compare if you got two times the same file uploaded. + * + * @param string $file Path to the file on your local machine. + * @param string $method 'md5' or 'sha1' + * @throws \InvalidArgumentException + * @link http://php.net/manual/en/function.md5-file.php + * @link http://php.net/manual/en/function.sha1-file.php + * @link http://php.net/manual/en/function.sha1-file.php#104748 + * @return string + */ + public static function getFileHash($file, $method = 'sha1') { + return StorageUtils::getFileHash($file, $method); } } diff --git a/src/Lib/StorageManager.php b/src/Lib/StorageManager.php index 253b3a14..aa101154 100644 --- a/src/Lib/StorageManager.php +++ b/src/Lib/StorageManager.php @@ -116,5 +116,4 @@ public static function adapter($adapterName, $renewObject = false) { } return $engineObject; } - -} \ No newline at end of file +} diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index 521f6761..1674e39b 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -6,7 +6,8 @@ */ namespace Burzum\FileStorage\Storage\Listener; -use Burzum\FileStorage\Lib\StorageManager; +use Burzum\FileStorage\Storage\StorageTrait; +use Burzum\FileStorage\Storage\StorageUtils; use Cake\Core\Configure; use Cake\Core\InstanceConfigTrait; use Cake\Event\Event; @@ -14,6 +15,7 @@ use Cake\Log\LogTrait; use Cake\ORM\Table; use Cake\ORM\Entity; +use Cake\Utility\MergeVariablesTrait; use Cake\Utility\Text; use Cake\Filesystem\Folder; @@ -32,21 +34,25 @@ * - filename * - path * - * @author Florian Krämer - * @copyright 2012 - 2015 Florian Krämer - * @license MIT */ abstract class AbstractListener implements EventListenerInterface { use InstanceConfigTrait; use LogTrait; + use MergeVariablesTrait; + use StorageTrait; /** * The adapter class * * @param null|string */ - public $adapterClass = null; + protected $_adapterClass = null; + +/** + * + */ + public $pathBuilderClass = null; /** * The class used to generate path and file names. @@ -77,22 +83,19 @@ abstract class AbstractListener implements EventListenerInterface { * * @var array */ - protected $_adapterClasses = array(); + protected $_adapterClasses = []; /** * Default settings * * @var array */ - protected $_defaultConfig = array( - 'models' => false, - 'stripUuid' => true, - 'preserveFilename' => false, - 'preserveExtension' => true, - 'uuidFolder' => true, - 'randomPath' => true, - 'tableFolder' => false - ); + protected $_defaultConfig = [ + 'pathBuilder' => '', + 'pathBuilderOptions' => [], + 'models' => [], + 'fileHash' => 'sha1' + ]; /** * Constructor @@ -177,7 +180,7 @@ protected function _checkTable(Event $event) { * @return boolean|string False if the config is not present */ protected function _getAdapterClassFromConfig($configName) { - $config = $this->getAdapterconfig($configName); + $config = $this->storageConfig($configName); if (!empty($config['adapterClass'])) { return $config['adapterClass']; } @@ -197,36 +200,12 @@ public function getAdapterClassName($configName) { $className = $this->_getAdapterClassFromConfig($configName); if (in_array($className, $this->_adapterClasses)) { $position = strripos($className, '\\'); - $this->adapterClass = substr($className, $position + 1, strlen($className)); - return $this->adapterClass; + $this->_adapterClass = substr($className, $position + 1, strlen($className)); + return $this->_adapterClass; } return false; } -/** - * Wrapper around the singleton call to StorageManager::config - * - * Makes it easy to mock the adapter in tests. - * - * @param string $configName - * @return array - */ - public function getAdapterconfig($configName) { - return StorageManager::config($configName); - } - -/** - * Wrapper around the singleton call to StorageManager::config - * - * Makes it easy to mock the adapter in tests. - * - * @param string $configName - * @return Object - */ - public function getAdapter($configName) { - return StorageManager::adapter($configName); - } - /** * Create a temporary file locally based on a file from an adapter. * @@ -256,6 +235,23 @@ protected function _tmpFile($Storage, $path, $tmpFolder = null) { } } +/** + * Gets the hash of a file. + * + * You can use this to compare if you got two times the same file uploaded. + * + * @param string $file Path to the file on your local machine. + * @param string $method 'md5' or 'sha1' + * @throws \InvalidArgumentException + * @link http://php.net/manual/en/function.md5-file.php + * @link http://php.net/manual/en/function.sha1-file.php + * @link http://php.net/manual/en/function.sha1-file.php#104748 + * @return string + */ + public function getFileHash($file, $method = 'sha1') { + return StorageUtils::getFileHash($file, $method); + } + /** * Creates a temporary file name and checks the tmp path, creates one if required. * diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 6a250aa7..e8647634 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -92,7 +92,7 @@ public function createImageVersions(Entity $entity, array $versions, array $opti $this->_checkImageVersions($entity->model, $versions); $result = []; - $storage = $this->getAdapter($entity->adapter); + $storage = $this->storageAdapter($entity->adapter); foreach ($this->_imageVersions[$entity->model] as $version => $config) { if (!in_array($version, $versions)) { continue; @@ -145,7 +145,7 @@ public function removeImageVersions(Entity $entity, array $versions, array $opti 'path' => $path ]; try { - $this->getAdapter($entity->adapter)->delete($path); + $this->storageAdapter($entity->adapter)->delete($path); } catch (\Exception $e) { $result[$version]['status'] = 'error'; $result[$version]['error'] = $e->getMessage(); diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 76cb7c01..3d5b2bb2 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -70,7 +70,7 @@ public function afterDelete(Event $event) { if ($this->_checkEvent($event)) { $entity = $event->data['record']; $path = $this->pathBuilder()->fullPath($entity); - if ($this->getAdapter($entity->adapter)->delete($path)) { + if ($this->storageAdapter($entity->adapter)->delete($path)) { $event->result = true; } $event->result = false; @@ -87,10 +87,15 @@ public function afterSave(Event $event) { if ($this->_checkEvent($event) && $event->data['record']->isNew()) { $table = $event->subject(); $entity = $event->data['record']; - $Storage = $this->getAdapter($entity['adapter']); + $Storage = $this->storageAdapter($entity['adapter']); + + if ($this->config('fileHash') !== false) { + $entity->hash = $this->getFileHash($entity['file']['tmp_name'], $this->config('fileHash')); + } + $filename = $this->pathBuilder()->filename($entity); + $entity['path'] = $this->pathBuilder()->path($entity); + try { - $filename = $this->pathBuilder()->filename($entity); - $entity['path'] = $this->pathBuilder()->path($entity); $Storage->write($entity['path'] . $filename, file_get_contents($entity['file']['tmp_name']), true); $table->save($entity, array( 'validate' => false, diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index e54e052d..bdda35e4 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -14,7 +14,7 @@ * A path builder is an utility class that generates a path and filename for a * file storage entity. */ -class BasePathBuilder { +class BasePathBuilder implements PathBuilderInterface { use InstanceConfigTrait; diff --git a/src/Storage/PathBuilder/PathBuilderInterface.php b/src/Storage/PathBuilder/PathBuilderInterface.php new file mode 100644 index 00000000..e9e14fc1 --- /dev/null +++ b/src/Storage/PathBuilder/PathBuilderInterface.php @@ -0,0 +1,51 @@ + $fieldvalue) { + foreach ($fieldvalue as $paramname => $paramvalue) { + foreach ((array)$paramvalue as $index => $value) { + $newfiles[$fieldname][$index][$paramname] = $value; + } + } + } + } + return $newfiles; + } + +/** + * Serializes and then hashes an array of operations that are applied to an image + * + * @param array $operations + * @return string + */ + public static function hashOperations($operations) { + self::ksortRecursive($operations); + return substr(md5(serialize($operations)), 0, 8); + } + +/** + * Generates the hashes for the different image version configurations. + * + * @param string|array $configPath + * @return array + */ + public static function generateHashes($configPath = 'FileStorage') { + if (is_array($configPath)) { + $imageSizes = $configPath; + } else { + $imageSizes = Configure::read($configPath . '.imageSizes'); + } + if (is_null($imageSizes)) { + throw new \RuntimeException(sprintf('Image processing configuration in %s is missing!', $configPath . '.imageSizes')); + } + self::ksortRecursive($imageSizes); + foreach ($imageSizes as $model => $version) { + foreach ($version as $name => $operations) { + Configure::write($configPath . '.imageHashes.' . $model . '.' . $name, self::hashOperations($operations)); + } + } + return Configure::read($configPath . '.imageHashes'); + } + +/** + * Recursive ksort() implementation + * + * @param array $array + * @param integer + * @return boolean + * @link https://gist.github.com/601849 + */ + public static function ksortRecursive(&$array, $sortFlags = SORT_REGULAR) { + if (!is_array($array)) { + return false; + } + ksort($array, $sortFlags); + foreach ($array as &$arr) { + self::ksortRecursive($arr, $sortFlags); + } + return true; + } + +/** + * Returns an array that matches the structure of a regular upload for a local file + * + * @param $file + * @param string File with path + * @return array Array that matches the structure of a regular upload + */ + public static function uploadArray($file, $filename = null) { + $File = new File($file); + if (empty($fileName)) { + $filename = basename($file); + } + return [ + 'name' => $filename, + 'tmp_name' => $file, + 'error' => 0, + 'type' => $File->mime(), + 'size' => $File->size() + ]; + } + +/** + * Gets the hash of a file. + * + * You can use this to compare if you got two times the same file uploaded. + * + * @param string $file Path to the file on your local machine. + * @param string $method 'md5' or 'sha1' + * @throws \InvalidArgumentException + * @link http://php.net/manual/en/function.md5-file.php + * @link http://php.net/manual/en/function.sha1-file.php + * @link http://php.net/manual/en/function.sha1-file.php#104748 + * @return string + */ + public static function getFileHash($file, $method = 'sha1') { + if ($method === 'md5') { + return md5_file($file); + } + if ($method === 'sha1') { + return sha1_file($file); + } + throw new \InvalidArgumentException(sprintf('Invalid hash method "%s" provided!')); + } +} diff --git a/tests/TestCase/Storage/Listener/LocalListenerTest.php b/tests/TestCase/Storage/Listener/LocalListenerTest.php index 7c426e00..8d90653b 100644 --- a/tests/TestCase/Storage/Listener/LocalListenerTest.php +++ b/tests/TestCase/Storage/Listener/LocalListenerTest.php @@ -27,7 +27,7 @@ class LocalListenerTest extends FileStorageTestCase { public function setUp() { parent::setUp(); $this->listener = $this->getMockBuilder('Burzum\FileStorage\Storage\Listener\LocalListener') - ->setMethods(['getAdapter']) + ->setMethods(['storageAdapter']) ->getMock(); $this->adapterMock = $this->getMock('\Gaufrette\Adapter\Local', [], ['']); @@ -50,7 +50,7 @@ public function testAfterSave() { ]); $this->listener->expects($this->at(0)) - ->method('getAdapter') + ->method('storageAdapter') ->will($this->returnValue($this->adapterMock)); $this->adapterMock->expects($this->at(0)) From d270b6bc118cd273b960b29f5fec951aaaba9c33 Mon Sep 17 00:00:00 2001 From: Scrutinizer Auto-Fixer Date: Thu, 9 Jul 2015 00:32:34 +0000 Subject: [PATCH 022/144] Scrutinizer Auto-Fixes This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com --- src/Lib/FileStorageUtils.php | 3 +-- src/Model/Table/FileStorageTable.php | 6 ++---- src/Model/Table/ImageStorageTable.php | 5 ++--- src/Shell/ImageVersionShell.php | 3 +-- src/Storage/Listener/AbstractListener.php | 6 ++---- 5 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/Lib/FileStorageUtils.php b/src/Lib/FileStorageUtils.php index c073191e..1ff5b2f2 100644 --- a/src/Lib/FileStorageUtils.php +++ b/src/Lib/FileStorageUtils.php @@ -7,7 +7,6 @@ namespace Burzum\FileStorage\Lib; use Burzum\FileStorage\Storage\StorageUtils; -use Cake\Core\Configure; use Cake\Filesystem\File; /** @@ -102,7 +101,7 @@ public static function generateHashes($configPath = 'FileStorage') { * * @param array $array * @param integer - * @return boolean + * @return string * @link https://gist.github.com/601849 */ public static function ksortRecursive(&$array, $sortFlags) { diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index f49a4a1b..0b67684f 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -6,7 +6,6 @@ use Cake\ORM\Entity; use Cake\Event\Event; use Cake\Event\EventManager; -use Cake\Filesystem\Folder; use Cake\Filesystem\File; use Burzum\FileStorage\Lib\StorageManager; @@ -104,7 +103,7 @@ public function beforeSave(Event $event, Entity $entity, $options) { * @param Event $event * @param Entity $entity * @param array $options - * @return void + * @return boolean */ public function afterSave(Event $event, Entity $entity, $options) { $Event = new Event('FileStorage.afterSave', $this, [ @@ -121,8 +120,7 @@ public function afterSave(Event $event, Entity $entity, $options) { * Get a copy of the actual record before we delete it to have it present in afterDelete * * @param \Cake\Event\Event $event - * @param \Burzum\FileStorage\Model\Table\Entity $entity - * @param array $options + * @param Entity $entity * @return boolean */ public function beforeDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity) { diff --git a/src/Model/Table/ImageStorageTable.php b/src/Model/Table/ImageStorageTable.php index 3ea3ca47..f176ce4e 100644 --- a/src/Model/Table/ImageStorageTable.php +++ b/src/Model/Table/ImageStorageTable.php @@ -3,7 +3,6 @@ use Cake\Core\Configure; use Cake\Event\Event; -use Cake\Utility\Folder; use Cake\Validation\Validation; /** @@ -48,7 +47,7 @@ public function initialize(array $config) { * @param array $options * @return boolean true on success */ - public function beforeSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $options) { + public function beforeSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $options) { if (!parent::beforeSave($event, $entity, $options)) { return false; } @@ -72,7 +71,7 @@ public function beforeSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, * @param array $options * @return boolean */ - public function afterSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $options) { + public function afterSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $options) { if ($entity->isNew()) { $imageEvent = new Event('ImageStorage.afterSave', $this, [ 'storage' => $this->getStorageAdapter($entity['adapter']), diff --git a/src/Shell/ImageVersionShell.php b/src/Shell/ImageVersionShell.php index cc26e37e..5ecb65c8 100644 --- a/src/Shell/ImageVersionShell.php +++ b/src/Shell/ImageVersionShell.php @@ -154,7 +154,6 @@ public function initialize() { /** * Generate all image versions. * - * @param string $model */ public function regenerate() { $operations = Configure::read('FileStorage.imageSizes.' . $this->args[0]); @@ -221,7 +220,7 @@ public function remove($model, $version) { /** * Loops through image records and performs requested operation on them. * - * @param $action + * @param string $action * @param $model * @param array $operations */ diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index 1674e39b..f17b4c85 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -8,13 +8,11 @@ use Burzum\FileStorage\Storage\StorageTrait; use Burzum\FileStorage\Storage\StorageUtils; -use Cake\Core\Configure; use Cake\Core\InstanceConfigTrait; use Cake\Event\Event; use Cake\Event\EventListenerInterface; use Cake\Log\LogTrait; use Cake\ORM\Table; -use Cake\ORM\Entity; use Cake\Utility\MergeVariablesTrait; use Cake\Utility\Text; use Cake\Filesystem\Folder; @@ -194,7 +192,7 @@ protected function _getAdapterClassFromConfig($configName) { * You must define a list of supported classes via AbstractStorageEventListener::$_adapterClasses. * * @param string $configName Name of the adapter configuration. - * @return boolean|string String, the adapter class name or false if it was not found. + * @return string|false String, the adapter class name or false if it was not found. */ public function getAdapterClassName($configName) { $className = $this->_getAdapterClassFromConfig($configName); @@ -222,7 +220,7 @@ public function getAdapterClassName($configName) { * @param string $path Path / key of the storage adapter file * @param string $tmpFolder * @throws Exception - * @return bool|string + * @return string */ protected function _tmpFile($Storage, $path, $tmpFolder = null) { try { From 6b9f568077a65b45c8c221f0d0fea78c54481d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 9 Jul 2015 12:50:56 +0200 Subject: [PATCH 023/144] Working on the tests. --- src/Storage/Listener/LocalListener.php | 2 +- src/TestSuite/FileStorageTestCase.php | 25 ++++++++++++++----- .../Storage/Listener/LocalListenerTest.php | 9 ++++--- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 3d5b2bb2..da672e33 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -87,7 +87,6 @@ public function afterSave(Event $event) { if ($this->_checkEvent($event) && $event->data['record']->isNew()) { $table = $event->subject(); $entity = $event->data['record']; - $Storage = $this->storageAdapter($entity['adapter']); if ($this->config('fileHash') !== false) { $entity->hash = $this->getFileHash($entity['file']['tmp_name'], $this->config('fileHash')); @@ -96,6 +95,7 @@ public function afterSave(Event $event) { $entity['path'] = $this->pathBuilder()->path($entity); try { + $Storage = $this->storageAdapter($entity['adapter']); $Storage->write($entity['path'] . $filename, file_get_contents($entity['file']['tmp_name']), true); $table->save($entity, array( 'validate' => false, diff --git a/src/TestSuite/FileStorageTestCase.php b/src/TestSuite/FileStorageTestCase.php index 0452badf..547a6f5a 100644 --- a/src/TestSuite/FileStorageTestCase.php +++ b/src/TestSuite/FileStorageTestCase.php @@ -31,6 +31,8 @@ class FileStorageTestCase extends TestCase { 'plugin.Burzum\FileStorage.FileStorage' ); + public $listeners = []; + /** * Setup test folders and files * @@ -38,12 +40,7 @@ class FileStorageTestCase extends TestCase { */ public function setUp() { parent::setUp(); - - $listener = new ImageProcessingListener(); - EventManager::instance()->on($listener); - - $listener = new LocalFileStorageListener(); - EventManager::instance()->on($listener); + $this->_setupListeners(); $this->testPath = TMP . 'file-storage-test' . DS; $this->fileFixtures = Plugin::path('Burzum/FileStorage') . 'tests' . DS . 'Fixture' . DS . 'File' . DS; @@ -89,6 +86,13 @@ public function setUp() { $this->ImageStorage = TableRegistry::get('Burzum/FileStorage.ImageStorage'); } + protected function _setupListeners() { + $this->listeners['ImageProcessingListener'] = new ImageProcessingListener(); + $this->listeners['LocalFileStorageListener'] = new LocalFileStorageListener(); + EventManager::instance()->on($this->listeners['ImageProcessingListener']); + EventManager::instance()->on($this->listeners['LocalFileStorageListener']); + } + /** * Cleanup test files * @@ -96,8 +100,17 @@ public function setUp() { */ public function tearDown() { parent::tearDown(); + + $this->_removeListeners(); + TableRegistry::clear(); $Folder = new Folder(TMP . 'file-storage-test'); $Folder->delete(); } + + protected function _removeListeners() { + foreach ($this->listeners as $listener) { + EventManager::instance()->off($listener); + } + } } diff --git a/tests/TestCase/Storage/Listener/LocalListenerTest.php b/tests/TestCase/Storage/Listener/LocalListenerTest.php index 8d90653b..ac219ec6 100644 --- a/tests/TestCase/Storage/Listener/LocalListenerTest.php +++ b/tests/TestCase/Storage/Listener/LocalListenerTest.php @@ -1,14 +1,13 @@ fileFixtures = Plugin::path('Burzum/FileStorage') . 'tests' . DS . 'Fixture' . DS . 'File' . DS; + $this->listener = $this->getMockBuilder('Burzum\FileStorage\Storage\Listener\LocalListener') ->setMethods(['storageAdapter']) + ->setConstructorArgs([['models' => ['Item']]]) ->getMock(); $this->adapterMock = $this->getMock('\Gaufrette\Adapter\Local', [], ['']); @@ -38,7 +40,6 @@ public function setUp() { /** * testAfterSave * - * @todo finish me * @return void */ public function testAfterSave() { From 3f669aa691515cdde1223e27edf6ef215b035f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 9 Jul 2015 13:43:11 +0200 Subject: [PATCH 024/144] Fixing tests. --- src/Model/Entity/FileStorage.php | 1 + src/Model/Entity/ImageStorage.php | 15 ++++++++++ src/Model/Table/FileStorageTable.php | 1 - src/TestSuite/FileStorageTestCase.php | 2 +- .../TestCase/Model/Table/ImageStorageTest.php | 30 +++++++++++-------- 5 files changed, 35 insertions(+), 14 deletions(-) create mode 100644 src/Model/Entity/ImageStorage.php diff --git a/src/Model/Entity/FileStorage.php b/src/Model/Entity/FileStorage.php index 73f06fed..bc4cf917 100644 --- a/src/Model/Entity/FileStorage.php +++ b/src/Model/Entity/FileStorage.php @@ -33,6 +33,7 @@ class FileStorage extends Entity { 'created' => true, 'modified' => true, 'file' => true, + 'old_file_id' => true ]; } diff --git a/src/Model/Entity/ImageStorage.php b/src/Model/Entity/ImageStorage.php new file mode 100644 index 00000000..1f4f47a5 --- /dev/null +++ b/src/Model/Entity/ImageStorage.php @@ -0,0 +1,15 @@ +_removeListeners(); TableRegistry::clear(); - $Folder = new Folder(TMP . 'file-storage-test'); + $Folder = new Folder($this->testPath); $Folder->delete(); } diff --git a/tests/TestCase/Model/Table/ImageStorageTest.php b/tests/TestCase/Model/Table/ImageStorageTest.php index 997c21da..8dc8bbea 100644 --- a/tests/TestCase/Model/Table/ImageStorageTest.php +++ b/tests/TestCase/Model/Table/ImageStorageTest.php @@ -223,34 +223,40 @@ public function testDeleteOldFileOnSave() { ]) ->first(); + // Get the old file path to assert late that it doesn't exist anymore + $Folder = new Folder($this->testPath . $result['path']); + $files = $Folder->read(); + $oldFile = $result['path'] . $files[1][2]; + $this->assertTrue(!empty($result) && is_a($result, '\Cake\ORM\Entity')); $this->assertTrue(file_exists($this->testPath . $result['path'])); - - $oldImageFile = $this->testPath . $result['path']; + $secondEntity = $this->Image->newEntity([ 'foreign_key' => 'test-1', 'model' => 'Test', 'file' => [ - 'name' => 'titus.jpg', + 'name' => 'cake.icon.png', 'size' => 332643, - 'tmp_name' => Plugin::path('Burzum/FileStorage') . DS . 'tests' . DS . 'Fixture' . DS . 'File' . DS . 'titus.jpg', + 'tmp_name' => Plugin::path('Burzum/FileStorage') . DS . 'tests' . DS . 'Fixture' . DS . 'File' . DS . 'cake.icon.png', 'error' => 0 ], 'old_file_id' => $entity->id ]); $this->Image->save($secondEntity); - $result = $this->Image->find() + $result2 = $this->Image->find() ->where([ 'id' => $secondEntity->id ]) ->first(); - - $this->assertTrue(!empty($result) && is_a($result, '\Cake\ORM\Entity')); - $this->assertTrue(file_exists($this->testPath . $result['path'])); - - $Folder = new Folder($oldImageFile); - $folderResult = $Folder->read(); - $this->assertEquals(count($folderResult[1]), 0); + + $this->assertTrue(!empty($result) && is_a($result2, '\Cake\ORM\Entity')); + $this->assertFileExists($this->testPath . $result2['path']); + + // Assert that the old file was removed + $this->assertFileNotExists($this->testPath . $oldFile); + + $this->assertNotEquals($result['path'], $result2['path']); + $this->assertNotEquals($result['filename'], $result2['filename']); } } From eb50bc53d9ac1a68fe46704e9aef08e3add1ef04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 9 Jul 2015 22:38:48 +0200 Subject: [PATCH 025/144] Working on the new listeners. --- src/Lib/StorageManager.php | 83 ++------------- src/Model/Table/FileStorageTable.php | 6 +- src/Storage/Listener/AbstractListener.php | 52 ++++++---- src/Storage/Listener/LocalListener.php | 30 ++++-- src/Storage/StorageManager.php | 119 ++++++++++++++++++++++ src/Storage/StorageTrait.php | 7 +- src/Storage/StorageUtils.php | 2 +- src/TestSuite/FileStorageTestCase.php | 4 +- 8 files changed, 190 insertions(+), 113 deletions(-) create mode 100644 src/Storage/StorageManager.php diff --git a/src/Lib/StorageManager.php b/src/Lib/StorageManager.php index aa101154..23294db7 100644 --- a/src/Lib/StorageManager.php +++ b/src/Lib/StorageManager.php @@ -1,40 +1,18 @@ [ - 'adapterOptions' => [TMP, true], - 'adapterClass' => '\Gaufrette\Adapter\Local', - 'class' => '\Gaufrette\Filesystem' - ] - ]; - -/** - * Return a singleton instance of the StorageManager. - * - * @return ClassRegistry instance - */ - public static function &getInstance() { - static $instance = array(); - if (!$instance) { - $instance[0] = new StorageManager(); - } - return $instance[0]; - } +class StorageManager { /** * Gets the configuration array for an adapter. @@ -44,17 +22,7 @@ public static function &getInstance() { * @return mixed */ public static function config($adapter, $options = array()) { - $_this = StorageManager::getInstance(); - - if (!empty($adapter) && !empty($options)) { - return $_this->_adapterConfig[$adapter] = $options; - } - - if (isset($_this->_adapterConfig[$adapter])) { - return $_this->_adapterConfig[$adapter]; - } - - return false; + return NewStorageManager::config($adapter, $options); } /** @@ -65,14 +33,7 @@ public static function config($adapter, $options = array()) { * @return boolean True on success */ public static function flush($name = null) { - $_this = StorageManager::getInstance(); - - if (isset($_this->_adapterConfig[$name])) { - unset($_this->_adapterConfig[$name]); - return true; - } - - return false; + return NewStorageManager::flush($name); } /** @@ -84,36 +45,6 @@ public static function flush($name = null) { * @return Gaufrette object as configured by first argument */ public static function adapter($adapterName, $renewObject = false) { - $_this = StorageManager::getInstance(); - - $isConfigured = true; - if (is_string($adapterName)) { - if (!empty($_this->_adapterConfig[$adapterName])) { - $adapter = $_this->_adapterConfig[$adapterName]; - } else { - throw new \RuntimeException(sprintf('Invalid Storage Adapter %s!', $adapterName)); - } - - if (!empty($_this->_adapterConfig[$adapterName]['object']) && $renewObject === false) { - return $_this->_adapterConfig[$adapterName]['object']; - } - } - - if (is_array($adapterName)) { - $adapter = $adapterName; - $isConfigured = false; - } - - $class = $adapter['adapterClass']; - $Reflection = new \ReflectionClass($class); - if (!is_array($adapter['adapterOptions'])) { - throw new \InvalidArgumentException(sprintf('%s: The adapter options must be an array!', $adapterName)); - } - $adapterObject = $Reflection->newInstanceArgs($adapter['adapterOptions']); - $engineObject = new $adapter['class']($adapterObject); - if ($isConfigured) { - $_this->_adapterConfig[$adapterName]['object'] = &$engineObject; - } - return $engineObject; + return NewStorageManager::adapter($adapterName, $renewObject); } } diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index da75f526..056c4703 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -190,10 +190,10 @@ public function deleteOldFileOnSave(Entity $entity, $oldIdField = 'old_file_id') if (!empty($entity[$oldIdField]) && $entity['model']) { $oldEntity = $this->find() ->contain([]) - ->where( - [$this->alias() . '.' . $this->primaryKey() => $entity[$oldIdField], 'model' => $entity['model']]) + ->where([ + $this->alias() . '.' . $this->primaryKey() => $entity[$oldIdField], 'model' => $entity['model'] + ]) ->first(); - if (!empty($oldEntity)) { return $this->delete($oldEntity); } diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index f17b4c85..62fb6d03 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -21,24 +21,27 @@ * AbstractListener * * Filters events and entities to decide if they should be processed or not by - * a specific adapter. + * a specific storage adapter. * * - Filter by table base class name * - Filter by the entities model field * - Filter by adapter class * - * Provides basic functionality to build + * These abstracted features are provided by the class as well: * - * - filename - * - path + * - Provides access to the path builders to build file names and paths. + * - Provides access to the storage adapters. * + * All of this in combination allows you to build event listeners to handle the + * storage of files in any place and storage backend very well and in a clean + * abstracted way. */ abstract class AbstractListener implements EventListenerInterface { use InstanceConfigTrait; use LogTrait; - use MergeVariablesTrait; use StorageTrait; + use MergeVariablesTrait; /** * The adapter class @@ -47,11 +50,6 @@ abstract class AbstractListener implements EventListenerInterface { */ protected $_adapterClass = null; -/** - * - */ - public $pathBuilderClass = null; - /** * The class used to generate path and file names. * @@ -91,8 +89,9 @@ abstract class AbstractListener implements EventListenerInterface { protected $_defaultConfig = [ 'pathBuilder' => '', 'pathBuilderOptions' => [], - 'models' => [], - 'fileHash' => 'sha1' + 'fileHash' => 'sha1', + 'fileField' => 'file', + 'models' => false, ]; /** @@ -102,8 +101,21 @@ abstract class AbstractListener implements EventListenerInterface { * @return AbstractListener */ public function __construct(array $config = []) { + $this->_mergeListenerVars(); $this->config($config); - $this->initialize(); + $this->initialize($config); + } + +/** + * Merges properties. + * + * @return void + */ + protected function _mergeListenerVars() { + $this->_mergeVars( + ['_defaultConfig'], + ['associative' => ['_defaultConfig']] + ); } /** @@ -113,7 +125,7 @@ public function __construct(array $config = []) { * * @return void */ - public function initialize() {} + public function initialize($config) {} /** * Implemented Events @@ -280,15 +292,18 @@ public function createTmpFile($folder = null, $checkAndCreatePath = true) { * @return \Burzum\FileStorage\Storage\PathBuilder\BasePathBuilder */ public function pathBuilder($class = null, array $config = []) { - if (!empty($this->_pathBuilder)) { + if (!empty($this->_pathBuilder) && empty($class)) { return $this->_pathBuilder; } - if (empty($class)) { - if (empty($this->_pathBuilder)) { + + if (empty($this->_pathBuilder) && empty($class)) { + $class = $this->_config['pathBuilder']; + $config = $this->_config['pathBuilderOptions']; + if (empty($class)) { throw new \RuntimeException(sprintf('No path builder loaded!')); } - return $this->_pathBuilder; } + $classname = '\Burzum\FileStorage\Storage\PathBuilder\\' . $class . 'Builder'; if (class_exists($classname)) { $this->_pathBuilder = new $classname($config); @@ -304,6 +319,7 @@ public function pathBuilder($class = null, array $config = []) { $this->_pathBuilder = new $classname($config); return $this->_pathBuilder; } + throw new \RuntimeException(sprintf('Could not find path builder "%s"!', $classname)); } } diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index da672e33..e92c53ae 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -19,6 +19,15 @@ */ class LocalListener extends AbstractListener { +/** + * Default settings + * + * @var array + */ + protected $_defaultConfig = [ + 'pathBuilder' => 'LocalPath', + ]; + /** * List of adapter classes the event listener can work with. * @@ -33,15 +42,6 @@ class LocalListener extends AbstractListener { '\Gaufrette\Adapter\Local' ]; -/** - * Initialize callback - * - * @return void - */ - public function Initialize() { - $this->pathBuilder('LocalPath'); - } - /** * Implemented Events * @@ -88,15 +88,23 @@ public function afterSave(Event $event) { $table = $event->subject(); $entity = $event->data['record']; + if (!empty($event->data['fileField'])) { + $this->config('fileField', $event->data['fileField']); + } + if ($this->config('fileHash') !== false) { - $entity->hash = $this->getFileHash($entity['file']['tmp_name'], $this->config('fileHash')); + $entity->hash = $this->getFileHash( + $entity[$this->config('fileField')]['tmp_name'], + $this->config('fileHash') + ); } + $filename = $this->pathBuilder()->filename($entity); $entity['path'] = $this->pathBuilder()->path($entity); try { $Storage = $this->storageAdapter($entity['adapter']); - $Storage->write($entity['path'] . $filename, file_get_contents($entity['file']['tmp_name']), true); + $Storage->write($entity['path'] . $filename, file_get_contents($entity[$this->config('fileField')]['tmp_name']), true); $table->save($entity, array( 'validate' => false, 'callbacks' => false diff --git a/src/Storage/StorageManager.php b/src/Storage/StorageManager.php new file mode 100644 index 00000000..8d59e3ce --- /dev/null +++ b/src/Storage/StorageManager.php @@ -0,0 +1,119 @@ + [ + 'adapterOptions' => [TMP, true], + 'adapterClass' => '\Gaufrette\Adapter\Local', + 'class' => '\Gaufrette\Filesystem' + ] + ]; + +/** + * Return a singleton instance of the StorageManager. + * + * @return ClassRegistry instance + */ + public static function &getInstance() { + static $instance = array(); + if (!$instance) { + $instance[0] = new StorageManager(); + } + return $instance[0]; + } + +/** + * Gets the configuration array for an adapter. + * + * @param string $adapter + * @param array $options + * @return mixed + */ + public static function config($adapter, $options = array()) { + $_this = StorageManager::getInstance(); + + if (!empty($adapter) && !empty($options)) { + return $_this->_adapterConfig[$adapter] = $options; + } + + if (isset($_this->_adapterConfig[$adapter])) { + return $_this->_adapterConfig[$adapter]; + } + + return false; + } + +/** + * Flush all or a single adapter from the config. + * + * @param string $name Config name, if none all adapters are flushed. + * @throws RuntimeException + * @return boolean True on success + */ + public static function flush($name = null) { + $_this = StorageManager::getInstance(); + + if (isset($_this->_adapterConfig[$name])) { + unset($_this->_adapterConfig[$name]); + return true; + } + + return false; + } + +/** + * StorageAdapter + * + * @param mixed $adapterName string of adapter configuration or array of settings + * @param boolean $renewObject Creates a new instance of the given adapter in the configuration + * @throws RuntimeException + * @return Gaufrette object as configured by first argument + */ + public static function adapter($adapterName, $renewObject = false) { + $_this = StorageManager::getInstance(); + + $isConfigured = true; + if (is_string($adapterName)) { + if (!empty($_this->_adapterConfig[$adapterName])) { + $adapter = $_this->_adapterConfig[$adapterName]; + } else { + throw new \RuntimeException(sprintf('Invalid Storage Adapter %s!', $adapterName)); + } + + if (!empty($_this->_adapterConfig[$adapterName]['object']) && $renewObject === false) { + return $_this->_adapterConfig[$adapterName]['object']; + } + } + + if (is_array($adapterName)) { + $adapter = $adapterName; + $isConfigured = false; + } + + $class = $adapter['adapterClass']; + $Reflection = new \ReflectionClass($class); + if (!is_array($adapter['adapterOptions'])) { + throw new \InvalidArgumentException(sprintf('%s: The adapter options must be an array!', $adapterName)); + } + $adapterObject = $Reflection->newInstanceArgs($adapter['adapterOptions']); + $engineObject = new $adapter['class']($adapterObject); + if ($isConfigured) { + $_this->_adapterConfig[$adapterName]['object'] = &$engineObject; + } + return $engineObject; + } +} diff --git a/src/Storage/StorageTrait.php b/src/Storage/StorageTrait.php index 9ec7a112..10a5bef2 100644 --- a/src/Storage/StorageTrait.php +++ b/src/Storage/StorageTrait.php @@ -1,8 +1,11 @@ [$this->testPath, true], From e318cf059c52c66b8adc492f4775a1d220c3d42f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 9 Jul 2015 23:06:56 +0200 Subject: [PATCH 026/144] Fixing tests and using a better way than crc32() for randomPath() --- src/Storage/PathBuilder/BasePathBuilder.php | 10 +++++----- .../Listener/ImageProcessingTraitTest.php | 19 ++++++++++++------- .../PathBuilder/BasePathBuilderTest.php | 10 +++++----- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index bdda35e4..0ee28f05 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -178,7 +178,11 @@ public function url(Entity $entity, array $options = []) { * @param string $method Hash method, crc32 or sha1. * @return string */ - public function randomPath($string, $level = 3, $method = 'crc32') { + public function randomPath($string, $level = 3, $method = 'sha1') { + // Keeping this for backward compatibility + if ($method === 'crc32') { + return StorageUtils::randomPath($string); + } if ($method === 'sha1') { $result = sha1($string); $randomString = ''; @@ -189,10 +193,6 @@ public function randomPath($string, $level = 3, $method = 'crc32') { } return $randomString; } - // Keeping this for backward compatibility - if ($method === 'crc32') { - return FileStorageUtils::randomPath($string); - } } /** diff --git a/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php b/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php index edd6ee33..9c68cd6b 100644 --- a/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php +++ b/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php @@ -1,4 +1,9 @@ [ 'status' => 'success', - 'path' => '48' . DS . '75' . DS . '05' . DS . 'filestorage3' . DS . 'titus.20876bcd.jpg', + 'path' => '95' . DS . '61' . DS . '80' . DS . 'filestorage3' . DS . 'titus.20876bcd.jpg', 'hash' => '20876bcd' ], 'crop50' => [ 'status' => 'success', - 'path' => '48' . DS . '75' . DS . '05' . DS . 'filestorage3' . DS . 'titus.41e51a3f.jpg', + 'path' => '95' . DS . '61' . DS . '80' . DS . 'filestorage3' . DS . 'titus.41e51a3f.jpg', 'hash' => '41e51a3f' ] ]; $this->assertEquals($result, $expected); - $this->assertFileExists($this->testPath . '48' . DS . '75' . DS . '05' . DS . 'filestorage3' . DS . 'titus.20876bcd.jpg'); - $this->assertFileExists($this->testPath . '48' . DS . '75' . DS . '05' . DS . 'filestorage3' . DS . 'titus.41e51a3f.jpg'); + $this->assertFileExists($this->testPath . '95' . DS . '61' . DS . '80' . DS . 'filestorage3' . DS . 'titus.20876bcd.jpg'); + $this->assertFileExists($this->testPath . '95' . DS . '61' . DS . '80' . DS . 'filestorage3' . DS . 'titus.41e51a3f.jpg'); $result = $listener->removeImageVersions($entity, ['t100']); $expected = [ 't100' => [ 'status' => 'success', 'hash' => '20876bcd', - 'path' => '48\75\05\filestorage3\titus.20876bcd.jpg' + 'path' => '95' . DS . '61' . DS . '80' . DS . 'filestorage3' . DS . 'titus.20876bcd.jpg' ] ]; $this->assertEquals($result, $expected); - $this->assertFileNotExists($this->testPath . '48' . DS . '75' . DS . '05' . DS . 'filestorage3' . DS . 'titus.20876bcd.jpg'); - $this->assertFileExists($this->testPath . '48' . DS . '75' . DS . '05' . DS . 'filestorage3' . DS . 'titus.41e51a3f.jpg'); + $this->assertFileNotExists($this->testPath . '95' . DS . '61' . DS . '80' . DS . 'filestorage3' . DS . 'titus.20876bcd.jpg'); + $this->assertFileExists($this->testPath . '95' . DS . '61' . DS . '80' . DS . 'filestorage3' . DS . 'titus.41e51a3f.jpg'); } /** diff --git a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php index 84eb365c..2ac2d892 100644 --- a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php +++ b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php @@ -44,14 +44,14 @@ public function testPathbuilding() { $this->assertEquals($result, 'filestorage1.png'); $result = $builder->path($this->entity); - $this->assertEquals($result, '00' . DS . '14' . DS . '90' . DS . 'filestorage1' . DS); + $this->assertEquals($result, '14' . DS . '83' . DS . '23' . DS . 'filestorage1' . DS); $result = $builder->fullPath($this->entity); - $this->assertEquals($result, '00' . DS . '14' . DS . '90' . DS . 'filestorage1' . DS . 'filestorage1.png'); + $this->assertEquals($result, '14' . DS . '83' . DS . '23' . DS . 'filestorage1' . DS . 'filestorage1.png'); $builder->config('pathPrefix', 'files'); $result = $builder->path($this->entity); - $this->assertEquals($result, 'files' . DS . '00' . DS . '14' . DS . '90' . DS . 'filestorage1' . DS); + $this->assertEquals($result, 'files' . DS . '14' . DS . '83' . DS . '23' . DS . 'filestorage1' . DS); $builder->config('pathPrefix', 'files'); $result = $builder->filename($this->entity); @@ -64,10 +64,10 @@ public function testPathbuilding() { $builder->config($config); $builder->config('pathSuffix', 'files'); $result = $builder->path($this->entity); - $this->assertEquals($result, '00' . DS . '14' . DS . '90' . DS . 'filestorage1' . DS . 'files' . DS); + $this->assertEquals($result, '14' . DS . '83' . DS . '23' . DS . 'filestorage1' . DS . 'files' . DS); $result = $builder->url($this->entity); - $expected = '00/14/90/filestorage1/files/filestorage1.png'; + $expected = '14/83/23/filestorage1/files/filestorage1.png'; $this->assertEquals($result, $expected); } From 9471fe370334be441f1cd9812c5bae9a08d93131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 9 Jul 2015 23:16:08 +0200 Subject: [PATCH 027/144] Fixing tests --- src/Storage/PathBuilder/BasePathBuilder.php | 4 ++-- tests/TestCase/Lib/Utility/FileStorageUtilsTest.php | 2 ++ tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index 0ee28f05..762b1176 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -169,7 +169,7 @@ public function url(Entity $entity, array $options = []) { } /** - * Proxy to FileStorageUtils::randomPath. + * Creates a semi-random path based on a string. * * Makes it possible to overload this functionality. * @@ -179,7 +179,7 @@ public function url(Entity $entity, array $options = []) { * @return string */ public function randomPath($string, $level = 3, $method = 'sha1') { - // Keeping this for backward compatibility + // Keeping this for backward compatibility but please stop using crc32()! if ($method === 'crc32') { return StorageUtils::randomPath($string); } diff --git a/tests/TestCase/Lib/Utility/FileStorageUtilsTest.php b/tests/TestCase/Lib/Utility/FileStorageUtilsTest.php index 7829bbb2..f2b3d025 100644 --- a/tests/TestCase/Lib/Utility/FileStorageUtilsTest.php +++ b/tests/TestCase/Lib/Utility/FileStorageUtilsTest.php @@ -20,6 +20,8 @@ class FileStorageUtilsTest extends FileStorageTestCase { * @return void */ public function testRandomPath() { + $this->skipIf(PHP_INT_SIZE === 8); + $result = FileStorageUtils::randomPath('someteststring'); $this->assertEquals($result, '38' . DS . '88' . DS . '98' . DS); diff --git a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php index 2ac2d892..411b6dfd 100644 --- a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php +++ b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php @@ -79,10 +79,10 @@ public function testPathbuilding() { public function testRandomPath() { $builder = new BasePathBuilder(); $result = $builder->randomPath('test', 5, 'sha1'); - $this->assertEquals($result, '4a\8f\e5\cc\b1\\'); + $this->assertEquals($result, '4a' . DS . '8f' . DS . 'e5' . DS . 'cc ' . DS . 'b1' . DS); $result = $builder->randomPath('test', 3, 'sha1'); - $this->assertEquals($result, '4a\8f\e5\\'); + $this->assertEquals($result, '4a' . DS . '8f' . DS . 'e5' . DS); } /** From 341a1c6f2f9bf36fc48cd5e23cdc5779faf1eb94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 9 Jul 2015 23:19:46 +0200 Subject: [PATCH 028/144] Fixing an unintentional space... --- tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php index 411b6dfd..90b70f82 100644 --- a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php +++ b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php @@ -79,7 +79,7 @@ public function testPathbuilding() { public function testRandomPath() { $builder = new BasePathBuilder(); $result = $builder->randomPath('test', 5, 'sha1'); - $this->assertEquals($result, '4a' . DS . '8f' . DS . 'e5' . DS . 'cc ' . DS . 'b1' . DS); + $this->assertEquals($result, '4a' . DS . '8f' . DS . 'e5' . DS . 'cc' . DS . 'b1' . DS); $result = $builder->randomPath('test', 3, 'sha1'); $this->assertEquals($result, '4a' . DS . '8f' . DS . 'e5' . DS); From 3c2101fa8337337d06c9cd565c355011b15f63c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Thu, 9 Jul 2015 23:24:43 +0200 Subject: [PATCH 029/144] Lets see if scrutinizer can exclude the deprecated folders. --- .scrutinizer.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index b498bad2..08715e8a 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -8,7 +8,7 @@ tools: php_code_coverage: false php_loc: enabled: true - excluded_dirs: [vendor, tests, config, docs] + excluded_dirs: [vendor, tests, config, docs, 'src/Lib', 'src/Event'] php_cpd: enabled: true - excluded_dirs: [vendor, tests, config, docs] \ No newline at end of file + excluded_dirs: [vendor, tests, config, docs, 'src/Lib', 'src/Event'] \ No newline at end of file From c59e948dbc4538c60a1ff1f573336f57cab9de7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 10 Jul 2015 01:54:20 +0200 Subject: [PATCH 030/144] Working on the documentation. --- README.md | 8 ++- docs/Documentation/How-To-Use.md | 10 ++-- .../Documentation/Included-Event-Listeners.md | 45 ++--------------- docs/Documentation/Legacy-Event-Listeners.md | 50 +++++++++++++++++++ docs/Documentation/Path-Builders.md | 21 ++++++++ docs/Documentation/Requirements.md | 3 +- .../Specific-Adapter-Configurations.md | 17 +++++-- docs/Home.md | 1 + src/Storage/StorageTrait.php | 3 ++ 9 files changed, 102 insertions(+), 56 deletions(-) create mode 100644 docs/Documentation/Legacy-Event-Listeners.md diff --git a/README.md b/README.md index 9559bcf5..bbbe999d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,13 @@ Storage adapters are an unified interface that allow you to store file data to y **If you're upgrading from CakePHP 2.x please read [the migration guide](docs/Documentation/Migrating-from-CakePHP-2.md).** -**List of supported Adapters** +[Please donate if you like it!](https://pledgie.com/campaigns/29682) +------------------------------ + +Already thought of how many hours development time saved you already? It would be *awesome* if you don't mind sharing some of your success by donating a small amount! + +List of supported Adapters +-------------------------- * Apc * Amazon S3 diff --git a/docs/Documentation/How-To-Use.md b/docs/Documentation/How-To-Use.md index 4ec208ca..e092dd7d 100644 --- a/docs/Documentation/How-To-Use.md +++ b/docs/Documentation/How-To-Use.md @@ -26,7 +26,7 @@ public function initialize(array $config) } ``` -In your add.ctp or edit.ctp views you would add something like: +In your `add.ctp` or `edit.ctp` views you would add something like: ```php echo $this->Form->input('Report.title'); @@ -78,7 +78,7 @@ When you create a new listener it is important that you check the model field an List of events -------------- -Events triggered in the ImageStorage model: +Events triggered in the `ImageStorage` model: * ImageVersion.createVersion * ImageVersion.removeVersion @@ -87,7 +87,7 @@ Events triggered in the ImageStorage model: * ImageStorage.beforeDelete * ImageStorage.afterDelete -Events triggered in the FileStorage model: +Events triggered in the `FileStorage` model: * FileStorage.beforeSave * FileStorage.afterSave @@ -108,7 +108,3 @@ The ``$key`` is also a key aspect of it: Different adapters might expect a diffe It is up to you how you want to generate the key and build your path. You can customize the way paths and file names are build by writing a custom event listener for that. It is highly recommended to read the Gaufrette documentation for the read() and write() methods of the adapters. - - - - diff --git a/docs/Documentation/Included-Event-Listeners.md b/docs/Documentation/Included-Event-Listeners.md index ab2c7bc5..be7f05ae 100644 --- a/docs/Documentation/Included-Event-Listeners.md +++ b/docs/Documentation/Included-Event-Listeners.md @@ -1,46 +1,9 @@ Included Event Listeners ======================== -LocalFileStorageListener ------------------------- +**[For the deprecated event listeners please click here](Legacy-Event-Listeners.md)** -The file and folder structure it will generate looks like that: +LocalPathBuilder +---------------- -``` -basePath/files/xx/xx/xx//. -``` - -ImageProcessingListener ------------------------ - -This listener will create versions of images if Configure::read('Media.imageSizes.' . $model); is not empty. If no processing operations for that model were specified it will just save the image. - -This adapter replaces LocalImageProcessingListener and currently supports the Local and AmazonS3 adapter. - -The file and folder structure it will generate looks like that: - -``` -basePath/images/xx/xx/xx//. -``` - -Versioned images will look like that - -``` -basePath/images/xx/xx/xx//.. -``` - - * For the Local adapter basePath is the value configured for this adapter, by default the `TMP` constant. - * For AmazonS3 the basePath will be the bucket and Amazon S3 URL prefix. - -xx stands for a semi random alphanumerical value calculated based on the given file name if the Local adapter was used. - -**Some important notes about the path the processor generates:** - -The path stored to the database is **not** going to be the complete path, it won't add the filename for a reason. - -The filename is generated by the processor on the fly when adding/deleting/modifying images because the versions are build on the fly and not stored to the database. See `ImageProcessingListener::_buildPath()`. - -LocalImageProcessingListener (deprecated) ------------------------------------------ - -The LocalImageProcessingListener is **deprecated**, use ImageProcessingListener. +To be done. diff --git a/docs/Documentation/Legacy-Event-Listeners.md b/docs/Documentation/Legacy-Event-Listeners.md new file mode 100644 index 00000000..dae72531 --- /dev/null +++ b/docs/Documentation/Legacy-Event-Listeners.md @@ -0,0 +1,50 @@ +Included Event Listeners +======================== + +**THESE LISTENERS ARE DEPRECATED!** + +Please use the new listeners from the `\Burzum\FileStorage\Storage\Listener` namespace! + +LocalFileStorageListener +------------------------ + +The file and folder structure it will generate looks like that: + +``` +basePath/files/xx/xx/xx//. +``` + +ImageProcessingListener +----------------------- + +This listener will create versions of images if Configure::read('Media.imageSizes.' . $model); is not empty. If no processing operations for that model were specified it will just save the image. + +This adapter replaces LocalImageProcessingListener and currently supports the Local and AmazonS3 adapter. + +The file and folder structure it will generate looks like that: + +``` +basePath/images/xx/xx/xx//. +``` + +Versioned images will look like that + +``` +basePath/images/xx/xx/xx//.. +``` + + * For the Local adapter basePath is the value configured for this adapter, by default the `TMP` constant. + * For AmazonS3 the basePath will be the bucket and Amazon S3 URL prefix. + +xx stands for a semi random alphanumerical value calculated based on the given file name if the Local adapter was used. + +**Some important notes about the path the processor generates:** + +The path stored to the database is **not** going to be the complete path, it won't add the filename for a reason. + +The filename is generated by the processor on the fly when adding/deleting/modifying images because the versions are build on the fly and not stored to the database. See `ImageProcessingListener::_buildPath()`. + +LocalImageProcessingListener (deprecated) +----------------------------------------- + +The LocalImageProcessingListener is **deprecated**, use ImageProcessingListener. diff --git a/docs/Documentation/Path-Builders.md b/docs/Documentation/Path-Builders.md index dd8f4100..f2d0a46b 100644 --- a/docs/Documentation/Path-Builders.md +++ b/docs/Documentation/Path-Builders.md @@ -17,3 +17,24 @@ Each of them will take a `FileStorage` entity as first argument. Based on that e The reason for this is to separate or share, just as needed, the path building logic between different storage systems. For example S3 differs in it's first part of the path, it's using a bucket while locally you usually have something like a base path instead of the bucket. If you want to change the way your files are saved extend the `BasePathBuilder` class. + +BasePathBuilder +--------------- + +This is the path builder all other BP's should inherit from. But if you like to write your very own BP you're free to implement it from the ground up but you'll have to use the PathBuilderInterface. + +The BasePathBuilder comes with a set of configuration options: + +```php +[ + 'stripUuid' => true, + 'pathPrefix' => '', + 'pathSuffix' => '', + 'filePrefix' => '', + 'fileSuffix' => '', + 'preserveFilename' => false, + 'preserveExtension' => true, + 'uuidFolder' => true, + 'randomPath' => 'sha1' +] +``` \ No newline at end of file diff --git a/docs/Documentation/Requirements.md b/docs/Documentation/Requirements.md index 3b9836c5..1c707c45 100644 --- a/docs/Documentation/Requirements.md +++ b/docs/Documentation/Requirements.md @@ -2,9 +2,8 @@ Requirements ============ * CakePHP 3.0+ - * PHP 5.4.19+ * Gaufrette Library (included as composer dependency) Optional but required for image processing: - * The Imagine Image processing plugin https://github.com/burzum/imagine if you want to process and storage images. + * [The Imagine Image processing plugin](https://github.com/burzum/cakephp-imagine-plugin) if you want to process and store images. diff --git a/docs/Documentation/Specific-Adapter-Configurations.md b/docs/Documentation/Specific-Adapter-Configurations.md index 86390209..fb2da749 100644 --- a/docs/Documentation/Specific-Adapter-Configurations.md +++ b/docs/Documentation/Specific-Adapter-Configurations.md @@ -1,10 +1,14 @@ -# Specific Addapter Configuration +Specific Addapter Configuration +=============================== Gaufrette does not come with a lot detail about what exactly some adapters expect so here is a list to help you with that. But you should not blindly copy and paste that code, get an understanding of the storage service you want to use before! -## Local Filesystem +Keep in mind that the instructions here might be outdated as external APIs and SDKs can and probably will change at some time! If this happens please create an issue ticket on Github and include the way to configure the adapter. + +Local Filesystem +---------------- By default the StorageManager already comes with a pre-configured adapter instance for the local file system adapter. @@ -36,7 +40,8 @@ Symlink Windows Example: mklink /D "C:\webstack\htdocs\my-app\webroot\img\uploads" "C:\webstack\htdocs\my-app\file_storage" ``` -## AmazonS3 - AwsS3 Adapter +AmazonS3 - AwsS3 Adapter +------------------------ Get the SDK from here https://github.com/aws/aws-sdk-php or get it via composer ```aws/aws-sdk-php```. If you're not using composer you'll have to add it to your own autoloader or load it manually. @@ -60,7 +65,8 @@ StorageManager::config('S3Image', array( ); ``` -## AmazonS3 - AmazonS3 Adapter (legacy) +AmazonS3 - AmazonS3 Adapter (legacy) +------------------------------------ *This adapter is legacy code, you should use the AwsS3 adapter instead!* @@ -89,7 +95,8 @@ StorageManager::config('S3', array( ); ``` -## OpenCloud (Rackspace) +OpenCloud (Rackspace) +--------------------- Get the SDK from here http://github.com/rackspace/php-opencloud and add it to your class autoloader diff --git a/docs/Home.md b/docs/Home.md index 10a58067..b86df700 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -19,6 +19,7 @@ Documentation * [The Image Helper](Documentation/The-Image-Helper.md) * [Specific Adapter Configurations](Documentation/Specific-Adapter-Configurations.md) * [Included Event Listeners](Documentation/Included-Event-Listeners.md) +* [Legacy Event Listeners](Documentation/Legacy-Event-Listeners.md) * [Path Builders](Documentation/Path-Builders.md) Tutorials diff --git a/src/Storage/StorageTrait.php b/src/Storage/StorageTrait.php index 10a5bef2..70f9dd5b 100644 --- a/src/Storage/StorageTrait.php +++ b/src/Storage/StorageTrait.php @@ -6,6 +6,9 @@ */ namespace Burzum\FileStorage\Storage; +/** + * Use the StorageTrait for convenient access to the storage adapters in any class. + */ trait StorageTrait { /** From d0a6dfd1e109679b355c38c69c5a95bec3b7c5e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 10 Jul 2015 01:54:46 +0200 Subject: [PATCH 031/144] Improving the BasePathBuilders code. --- src/Storage/PathBuilder/BasePathBuilder.php | 62 ++++++++++++++++----- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index 762b1176..49c73f89 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -32,7 +32,7 @@ class BasePathBuilder implements PathBuilderInterface { 'preserveFilename' => false, 'preserveExtension' => true, 'uuidFolder' => true, - 'randomPath' => true, + 'randomPath' => 'sha1', 'modelFolder' => false ); @@ -74,6 +74,9 @@ public function path(Entity $entity, array $options = []) { if ($this->_config['randomPath'] === true) { $path .= $this->randomPath($entity->id); } + if (is_string($this->_config['randomPath'])) { + $path .= $this->randomPath($entity->id, 3, $this->_config['randomPath']); + } // uuidFolder for backward compatibility if ($this->_config['uuidFolder'] === true || $this->_config['idFolder'] === true) { $path .= $this->stripDashes($entity->id) . DS; @@ -115,29 +118,58 @@ public function splitFilename($filename, $keepDot = false) { public function filename(Entity $entity, array $options = []) { $config = array_merge($this->config(), $options); if ($config['preserveFilename'] === true) { - $filename = $entity['filename']; - if (!empty($config['filePrefix'])) { - $filename = $config['filePrefix'] . $entity['filename']; - } - if (!empty($config['fileSuffix'])) { - $split = $this->splitFilename($filename, true); - $filename = $split['filename'] . $config['fileSuffix'] . $split['extension']; - } - return $filename; + return $this->_preserveFilename($entity, $config); } + return $this->_buildFilename($entity, $config); + } +/** + * Used to build a completely customized filename. + * + * The default behavior is to use the UUID from the entities primary key to + * generate a filename based of the UUID that gets the dashes stripped and the + * extension added if you configured the path builder to preserve it. + * + * The filePrefix and fileSuffix options are also supported. + * + * @param \Cake\ORM\Entity $entity + * @param array $options + * @return string + */ + protected function _buildFilename(Entity $entity, array $options = []) { $filename = $entity->id; - if ($config['stripUuid'] === true) { + if ($options['stripUuid'] === true) { $filename = $this->stripDashes($filename); } - if ($config['preserveExtension'] === true) { + if ($options['preserveExtension'] === true) { if (!empty($config['fileSuffix'])) { - $filename = $filename . $config['fileSuffix']; + $filename = $filename . $options['fileSuffix']; } $filename = $filename . '.' . $entity['extension']; } - if (!empty($config['filePrefix'])) { - $filename = $config['filePrefix'] . $filename; + if (!empty($options['filePrefix'])) { + $filename = $options['filePrefix'] . $filename; + } + return $filename; + } + +/** + * Keeps the original filename but is able to inject pre- and suffix. + * + * This can be useful to create versions of files for example. + * + * @param \Cake\ORM\Entity $entity + * @param array $options + * @return string + */ + protected function _preserveFilename(Entity $entity, array $options = []) { + $filename = $entity['filename']; + if (!empty($options['filePrefix'])) { + $filename = $options['filePrefix'] . $entity['filename']; + } + if (!empty($options['fileSuffix'])) { + $split = $this->splitFilename($filename, true); + $filename = $split['filename'] . $options['fileSuffix'] . $split['extension']; } return $filename; } From a61721c20b8a771743ad58fa944b6eaac821746e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 10 Jul 2015 02:00:52 +0200 Subject: [PATCH 032/144] Fixing a typo and a bug. --- README.md | 6 +++--- src/Storage/PathBuilder/BasePathBuilder.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bbbe999d..1912bbef 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -FileStorage Plugin for CakePHP 2.x and 3.x -========================================== +FileStorage Plugin for CakePHP +============================== [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.txt) [![Build Status](https://img.shields.io/travis/burzum/cakephp-file-storage/3.0.svg?style=flat-square)](https://travis-ci.org/burzum/cakephp-file-storage) @@ -14,7 +14,7 @@ Storage adapters are an unified interface that allow you to store file data to y [Please donate if you like it!](https://pledgie.com/campaigns/29682) ------------------------------ -Already thought of how many hours development time saved you already? It would be *awesome* if you don't mind sharing some of your success by donating a small amount! +Already thought of how many hours development time this plugin saved you already? It would be *awesome* if you don't mind sharing some of your success by donating a small amount! Thank you. List of supported Adapters -------------------------- diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index 49c73f89..cece45ec 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -142,7 +142,7 @@ protected function _buildFilename(Entity $entity, array $options = []) { $filename = $this->stripDashes($filename); } if ($options['preserveExtension'] === true) { - if (!empty($config['fileSuffix'])) { + if (!empty($options['fileSuffix'])) { $filename = $filename . $options['fileSuffix']; } $filename = $filename . '.' . $entity['extension']; From 7604439b06aadebfb1cc73cb56a792ea8750c662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 10 Jul 2015 12:23:07 +0200 Subject: [PATCH 033/144] Working on the documentation. --- README.md | 5 +++++ docs/Documentation/Database-Setup.md | 21 ------------------- docs/Documentation/How-it-works.md | 8 +++++++ docs/Documentation/Installation.md | 15 +++++++++++-- .../Documentation/Migrating-from-CakePHP-2.md | 12 ++++++----- docs/Documentation/The-Storage-Manager.md | 9 ++++++-- docs/Home.md | 2 +- 7 files changed, 41 insertions(+), 31 deletions(-) delete mode 100644 docs/Documentation/Database-Setup.md create mode 100644 docs/Documentation/How-it-works.md diff --git a/README.md b/README.md index 1912bbef..6237b0a4 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,11 @@ Storage adapters are an unified interface that allow you to store file data to y Already thought of how many hours development time this plugin saved you already? It would be *awesome* if you don't mind sharing some of your success by donating a small amount! Thank you. +How it works +------------ + +The whole plugin is build with clear [Separation of Concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) in mind: A file is *always* an entry in the `file_storage` table from the app perspective. The table is the *reference* to the real place of where the file is stored and keeps some meta information like mime type, filename, file hash (optional) and size as well. You associate the `file_storage` table with your model using the FileStorage or ImageStorage model from the plugin via hasOne, hasMany or HABTM. When you upload a file you save it to the FileStorage model through the associations, `Documents.file` for example. The FileStorage model dispatches then file storage specific events, the listeners listening to these events process the file and put it in the configured storage backend using adapters for different backends and build the storage path using a path builder class. + List of supported Adapters -------------------------- diff --git a/docs/Documentation/Database-Setup.md b/docs/Documentation/Database-Setup.md deleted file mode 100644 index 943ffa10..00000000 --- a/docs/Documentation/Database-Setup.md +++ /dev/null @@ -1,21 +0,0 @@ -Database Setup -============== - -You need to setup the plugin database using [the official migrations plugin for CakePHP](https://github.com/cakephp/migrations). - -``` -cake migrations migrate -p Burzum/FileStorage -``` - -If you're coming from the CakePHP 2.0 version of the plugin, the support for the CakeDC Migrations plugin has been dropped in favor of the official migrations plugin. - -Integer type IDs vs UUIDs -------------------------- - -If you want to use integers instead of [UUIDs](http://en.wikipedia.org/wiki/Universally_unique_identifier) put this into your ```bootstrap.php``` *before* you're running the migrations: - -```php -Configure::write('FileStorage.schema.useIntegers', true); -``` - -This config option is **not** available for the regular CakePHP schema that comes with the plugin. It seems to be impossible to override the type on the fly. If you can figure out how to do it a pull request is welcome. diff --git a/docs/Documentation/How-it-works.md b/docs/Documentation/How-it-works.md new file mode 100644 index 00000000..cc973a4a --- /dev/null +++ b/docs/Documentation/How-it-works.md @@ -0,0 +1,8 @@ +How it works +------------ + +The whole plugin is build with clear [Separation of Concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) in mind: A file is *always* an entry in the `file_storage` table from the app perspective. + +The table is the *reference* to the real place of where the file is stored and keeps some meta information like mime type, filename, file hash (optional) and size as well. You associate the `file_storage` table with your model using the FileStorage or ImageStorage model from the plugin via hasOne, hasMany or HABTM. + +When you upload a file you save it to the FileStorage model through the associations, `Documents.file` for example. The FileStorage model dispatches then file storage specific events. The listeners listening to these events process the file and put it in the configured storage backend using adapters for different backends and build the storage path using a path builder class. \ No newline at end of file diff --git a/docs/Documentation/Installation.md b/docs/Documentation/Installation.md index 8cd81d27..8eba4f8c 100644 --- a/docs/Documentation/Installation.md +++ b/docs/Documentation/Installation.md @@ -12,6 +12,17 @@ Installing the plugin via [Composer](https://getcomposer.org/) is very simple, j composer require burzum/file-storage:3.0.*@dev ``` +Database Setup +-------------- + +You need to setup the plugin database using [the official migrations plugin for CakePHP](https://github.com/cakephp/migrations). + +``` +cake migrations migrate -p Burzum/FileStorage +``` + +If you're coming from the CakePHP 2.0 version of the plugin, the support for the CakeDC Migrations plugin has been dropped in favor of [the official migrations plugin](https://github.com/cakephp/migrations). + CakePHP Bootstrap ----------------- @@ -43,12 +54,12 @@ Depending on the storage backend of your choice, for example Amazon S3 or Dropbo Please see the [Specific Adapter Configuration](Specific-Adapter-Configurations.md) page of the documentation for more information about then. It is also worth checking the Gaufrette documentation for additonal adapters. Running Tests -============= +------------- The plugin tests are set up in a way that you can run them without putting the plugin into a CakePHP3 application. All you need to do is to go into the FileStorage folder and run these commands: ``` cd -composer install +composer update phpunit ``` diff --git a/docs/Documentation/Migrating-from-CakePHP-2.md b/docs/Documentation/Migrating-from-CakePHP-2.md index 1ade14ca..9a5486e6 100644 --- a/docs/Documentation/Migrating-from-CakePHP-2.md +++ b/docs/Documentation/Migrating-from-CakePHP-2.md @@ -1,14 +1,16 @@ Migrating from CakePHP 2 ======================== +Here is a list of things that have changed: + * The plugin doesn't any longer use the configuration namespace `Media` but instead uses now the more appropriate namespace `FileStorage`. * The plugin is not using the CakeDC Migrations plugin any more but [the official CakePHP Migrations plugin](https://github.com/cakephp/migrations). -* `Lib\Utility\FileStorageUtils` has been moved to `Lib\FileStorageUtils`. +* `Lib\Utility\StorageUtils` has been moved to `Storage\StorageUtils`. * `FileStorageTable::fileExtension()` has been removed, use `pathinfo($path, PATHINFO_EXTENSION)` instead. * `FileStorageTable::stripUuid()` has been removed, use events to handle the file saving and `AbstractStorageEventListener::stripDashes()`. * `FileStorageTable::tmpFile()` has been removed, use events to handle the file saving and `AbstractStorageEventListener::createTmpFile()`. * `FileStorageTable::tmpFile()` has been moved to `AbstractStorageEventListener::fsPath()`, use events to handle the file saving. -* `ImageStorageTable::hashOperations()` has been removed, use `FileStorageUtils::hashOperations()`. -* `ImageStorageTable::generateHashes()` has been removed, use `FileStorageUtils::generateHashes()`. -* `ImageStorageTable::ksortRecursive()` has been removed, use `FileStorageUtils::ksortRecursive()`. -* Former `UploadValidatorBehavior::uploadArray()` has been moved to `FileStorageUtils::uploadArray()`. +* `ImageStorageTable::hashOperations()` has been removed, use `StorageUtils::hashOperations()`. +* `ImageStorageTable::generateHashes()` has been removed, use `StorageUtils::generateHashes()`. +* `ImageStorageTable::ksortRecursive()` has been removed, use `StorageUtils::ksortRecursive()`. +* Former `UploadValidatorBehavior::uploadArray()` has been moved to `StorageUtils::uploadArray()`. diff --git a/docs/Documentation/The-Storage-Manager.md b/docs/Documentation/The-Storage-Manager.md index 4227f5e9..4a0eeaa6 100644 --- a/docs/Documentation/The-Storage-Manager.md +++ b/docs/Documentation/The-Storage-Manager.md @@ -1,7 +1,7 @@ The Storage Manager =================== -The [Storage Manager](Lib/StorageManager.php) class is a singleton class that manages a collection of storage adapter instances. +The [Storage Manager](../../src/Storage/StorageManager.php) class is a singleton class that manages a collection of storage adapter instances. To configure adapters use the ```StorageManager::config()``` method. First argument is the name of the config, second an array of options for that adapter. The options array keys can be different for each adapter, depending on the storage system it connects to. @@ -39,4 +39,9 @@ If you want to flush *all* adapter configs and instances simply call it without StorageManager::flush(); ``` -There will be no adapter instance left after this, you must add a new config to use any adapter. \ No newline at end of file +There will be no adapter instance left after this, you must add a new config to use any adapter. + +Adapter Configuration +--------------------- + +Some adapters require a more or less complex configuration and setup depending on their API and provided SDK that the adapter class is using. Please see the [specific adapter configuration](Specific-Adapter-Configurations.md) section of the documentation for some of them. diff --git a/docs/Home.md b/docs/Home.md index b86df700..8fba809d 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -12,7 +12,7 @@ Documentation * [Requirements](Documentation/Requirements.md) * [Installation](Documentation/Installation.md) -* [Database Setup](Documentation/Database-Setup.md) +* [How it works](Documentation/How-it-works.md) * [The Storage Manager](Documentation/The-Storage-Manager.md) * [How to Use it](Documentation/How-To-Use.md) * [Image Storage and Versioning](Documentation/Image-Storage-And-Versioning.md) From d1085b684931609a695fcee63ee0588577b870b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 10 Jul 2015 15:22:06 +0200 Subject: [PATCH 034/144] Working on the automated image processing. --- docs/Documentation/How-To-Use.md | 41 +++++++++++-------- src/Storage/Listener/ImageProcessingTrait.php | 29 ++++++++++++- src/Storage/Listener/LocalListener.php | 10 +++++ .../Storage/Listener/LocalListenerTest.php | 3 ++ 4 files changed, 64 insertions(+), 19 deletions(-) diff --git a/docs/Documentation/How-To-Use.md b/docs/Documentation/How-To-Use.md index e092dd7d..c254d9aa 100644 --- a/docs/Documentation/How-To-Use.md +++ b/docs/Documentation/How-To-Use.md @@ -9,8 +9,8 @@ The basic idea of this plugin is that files are always handled as separate entit This plugin resolves that issue by handling each file as a completely separate entity in the application. There is just one table `file_storage` that will keep the reference to all your files, no matter where they're stored. -How to Store an Uploaded File ------------------------------ +Preparing the File Upload +------------------------- This section is going to show how to store a file using the Storage Manager directly. @@ -20,8 +20,11 @@ For example you have a Report model and want to save a pdf to it, you would then public function initialize(array $config) { $this->hasOne('PdfFiles', [ - 'className' => 'FileStorage.PdfFiles', - 'foreignKey' => 'foreign_key' + 'className' => 'Burzum/FileStorage.PdfFiles', + 'foreignKey' => 'foreign_key', + 'conditions' => [ + 'PdfFiles.model' => 'Reports' + ] ]); } ``` @@ -29,11 +32,14 @@ public function initialize(array $config) In your `add.ctp` or `edit.ctp` views you would add something like: ```php -echo $this->Form->input('Report.title'); -echo $this->Form->input('PdfFile.file'); -echo $this->Form->input('Report.description'); +echo $this->Form->input('title'); +echo $this->Form->input('pdf_files.file'); +echo $this->Form->input('description'); ``` +Handling the File Upload +------------------------ + **Now comes the crucial point of the whole implementation** Because of to many different requirements and personal preferences out there the plugin is *not* automatically storing the file. You'll have to customize it a little but its just a matter for a few lines. @@ -41,15 +47,16 @@ Because of to many different requirements and personal preferences out there the Lets go by this scenario inside the report model, assuming there is an add() method: ```php -$entity = $this->newEntity($data); +$entity = $this->newEntity($postData); $saved = $this->save($entity); if ($saved) { $key = 'your-file-name'; - if (StorageManager::adapter('Local')->write($key, file_get_contents($this->data['PdfFile']['file']['tmp_name']))) { - $this->data['PdfFile']['foreign_key'] = $saved->id; - $this->data['PdfFile']['model'] = 'Report'; - $this->data['PdfFile']['path'] = $key; - $this->data['PdfFile']['adapter'] = 'Local'; + if (StorageManager::adapter('Local')->write($key, file_get_contents($this->data['pdf_files']['file']['tmp_name']))) { + $postData['pdf_files']['foreign_key'] = $saved->id; + $postData['pdf_files']['model'] = 'Reports'; + $postData['pdf_files']['path'] = $key; + $postData['pdf_files']['adapter'] = 'Local'; + $this->PdfDocuments->save($this->PdfDocuments->newEntity($postData)); } } ``` @@ -69,11 +76,11 @@ The **FileStorage** plugin comes with a class that acts just as a listener to so This class will listen to all the ImageStorage model events and save the uploaded image and then create the versions for that image and storage adapter. -It is important to understand that each storage adapter requires a different handling. You can not threat a local file the same as a file you store in a cloud service. The interface that this plugin and Gaufrette provide is the same but not the internals. +It is important to understand that nearly each storage adapter requires a little different handling: Most of the time you can't threat a local file the same as a file you store in a cloud service. The interface that this plugin and Gaufrette provide is the same but not the internals. So a path that works for your local file system might not work for your remote storage system because it has other requirements or limitations. -So if you want to store a file using Amazon S3 you would have to store it, create all the versions of that image locally and then upload each of them and then delete the local temp files. +So if you want to store a file using Amazon S3 you would have to store it, create all the versions of that image locally and then upload each of them and then delete the local temp files. The good news is the plugin can already take care of that. -When you create a new listener it is important that you check the model field and the event subject object if it matches what you expect. Using the event system you could create any kind of storage and upload behavior without inheriting or touching the model code. Just write a listener class and attach it to the global CakeEventManager. +When you create a new listener it is important that you check the `model` field and the event subject object (usually a table object inheriting \Cake\ORM\Table) if it matches what you expect. Using the event system you could create any kind of storage and upload behavior without inheriting or touching the model code. Just write a listener class and attach it to the global EventManager. List of events -------------- @@ -101,7 +108,7 @@ See [this page](Included-Event-Listeners.md) for the event listeners that are in Why is it done like this? ------------------------- -Every developer might want to store the file at a different point or apply other operations on the file before or after it is store. Based on different circumstances you might want to save an associated file even before you created the record its going to get attached to, in other scenarios like in this documentation you might want to do it after. +Every developer might want to store the file at a different point or apply other operations on the file before or after it is stored. Based on different circumstances you might want to save an associated file even before you created the record its going to get attached to, in other scenarios like in this documentation you might want to do it after. The ``$key`` is also a key aspect of it: Different adapters might expect a different key. A key for the Local adapter of Gaufrette is usually a path and a file name under which the data gets stored. That's also the reason why you use `file_get_contents()` instead of simply passing the tmp path as it is. diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index e8647634..d7a478c3 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -9,7 +9,7 @@ use \Cake\Core\Configure; use \Cake\ORM\Entity; use \Burzum\Imagine\Lib\ImageProcessor; -use \Burzum\FileStorage\Lib\FileStorageUtils; +use \Burzum\FileStorage\Storage\StorageUtils; /** * ImageProcessingTrait @@ -22,6 +22,31 @@ trait ImageProcessingTrait { protected $_imageVersions = []; protected $_imageVersionHashes = []; +/** + * Convenience method to auto create ALL and auto remove ALL image versions for + * an entity. + * + * Call this in your listener after you stored or removed a file that has image + * versions. If you need more details in your logic around creating or removing + * image versions use the other methods from this trait to implement the checks + * and behavior you need. + * + * @param \Cake\ORM\Entity + * @param string $action `create` or `remove` + * @return array + */ + public function autoProcessImageVersions(Entity $entity, $action) { + if (!in_array($action, ['create', 'remove'])) { + throw new \InvalidArgumentException(); + } + $this->_loadImageProcessingFromConfig(); + if (!isset($this->_imageVersions[$entity->model])) { + return false; + } + $method = $action . 'AllImageVersions'; + return $this->{$method}($entity); + } + /** * Loads the image processing configuration into the class. * @@ -29,7 +54,7 @@ trait ImageProcessingTrait { */ protected function _loadImageProcessingFromConfig() { $this->_imageVersions = (array)Configure::read('FileStorage.imageSizes'); - $this->_imageVersionHashes = FileStorageUtils::generateHashes(); + $this->_imageVersionHashes = StorageUtils::generateHashes(); } /** diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index e92c53ae..4b79807d 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -19,6 +19,8 @@ */ class LocalListener extends AbstractListener { + use ImageProcessingTrait; + /** * Default settings * @@ -26,6 +28,7 @@ class LocalListener extends AbstractListener { */ protected $_defaultConfig = [ 'pathBuilder' => 'LocalPath', + 'imageProcessing' => false, ]; /** @@ -71,6 +74,9 @@ public function afterDelete(Event $event) { $entity = $event->data['record']; $path = $this->pathBuilder()->fullPath($entity); if ($this->storageAdapter($entity->adapter)->delete($path)) { + if ($this->_config['imageProcessing'] === true) { + $this->autoProcessImageVersions($entity, 'remove'); + } $event->result = true; } $event->result = false; @@ -114,6 +120,10 @@ public function afterSave(Event $event) { $this->log($e->getMessage(), 'file_storage'); $event->result = false; } + + if ($this->_config['imageProcessing'] === true) { + $this->autoProcessImageVersions($entity, 'create'); + } } } } diff --git a/tests/TestCase/Storage/Listener/LocalListenerTest.php b/tests/TestCase/Storage/Listener/LocalListenerTest.php index ac219ec6..91f38b39 100644 --- a/tests/TestCase/Storage/Listener/LocalListenerTest.php +++ b/tests/TestCase/Storage/Listener/LocalListenerTest.php @@ -3,6 +3,7 @@ use Cake\Event\Event; use Cake\Core\Plugin; +use Cake\Core\Configure; use Cake\Core\InstanceConfigTrait; use Cake\ORM\TableRegistry; use Cake\TestSuite\TestCase; @@ -25,6 +26,8 @@ class LocalListenerTest extends TestCase { */ public function setUp() { parent::setUp(); + + Configure::write('FileStorage.imageSizes', []); $this->fileFixtures = Plugin::path('Burzum/FileStorage') . 'tests' . DS . 'Fixture' . DS . 'File' . DS; $this->listener = $this->getMockBuilder('Burzum\FileStorage\Storage\Listener\LocalListener') From 8e17643267624883dbad8a195f589c44efb142c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 10 Jul 2015 15:48:15 +0200 Subject: [PATCH 035/144] Fixing minor issues. --- docs/Tutorials/Quick-Start.md | 2 +- docs/Tutorials/Replacing-Files.md | 2 +- src/Event/AbstractStorageEventListener.php | 12 ++++++------ src/Event/ImageProcessingListener.php | 2 +- src/Storage/Listener/AbstractListener.php | 1 - src/Storage/Listener/LocalListener.php | 2 +- src/Storage/PathBuilder/BasePathBuilder.php | 2 +- src/Validation/UploadValidator.php | 18 ++++++++---------- 8 files changed, 19 insertions(+), 22 deletions(-) diff --git a/docs/Tutorials/Quick-Start.md b/docs/Tutorials/Quick-Start.md index cb858ae5..2c92034d 100644 --- a/docs/Tutorials/Quick-Start.md +++ b/docs/Tutorials/Quick-Start.md @@ -184,7 +184,7 @@ class ProductsController extends AppController { Products Upload View -------------------- -View for the controller action above. +View for the controller action above `Products/upload.ctp`: ```php echo $this->Form->create($productImage, array( diff --git a/docs/Tutorials/Replacing-Files.md b/docs/Tutorials/Replacing-Files.md index 98ae2d20..02873a1d 100644 --- a/docs/Tutorials/Replacing-Files.md +++ b/docs/Tutorials/Replacing-Files.md @@ -22,4 +22,4 @@ The the trick here is the `old_file_id`. The `FileStorageTable` table, which `Im So all you have to do to replace an image is to pass the `old_file_id` along with your new file data. -Just make sure that nobody can tamper your forms with unwanted data! If somebody can do that they can pass any id to delete *any* file! It is recommended to use the [Security component](http://book.cakephp.org/3.0/en/core-libraries/components/security-component.html) of the framework to avoid that. +**Just make sure that nobody can tamper your forms with unwanted data!** If somebody can do that they can pass any id to delete *any* file! It is recommended to use the [Security component](http://book.cakephp.org/3.0/en/core-libraries/components/security-component.html) of the framework to avoid that. diff --git a/src/Event/AbstractStorageEventListener.php b/src/Event/AbstractStorageEventListener.php index 6182ee67..e9af72b8 100644 --- a/src/Event/AbstractStorageEventListener.php +++ b/src/Event/AbstractStorageEventListener.php @@ -9,8 +9,8 @@ use Cake\ORM\Entity; use Cake\Utility\Text; use Cake\Filesystem\Folder; -use Burzum\FileStorage\Lib\StorageManager; -use Burzum\FileStorage\Lib\FileStorageUtils; +use Burzum\FileStorage\Storage\StorageManager; +use Burzum\FileStorage\Storage\StorageUtils; /** * AbstractStorageEventListener @@ -142,10 +142,10 @@ public function buildPath($table, $entity) { if ($this->_config['tableFolder']) { $path .= $table->table() . DS; } - if ($this->_config['randomPath'] == true) { - $path .= FileStorageUtils::randomPath($entity[$table->primaryKey()]); + if ($this->_config['randomPath'] === true) { + $path .= StorageUtils::randomPath($entity[$table->primaryKey()]); } - if ($this->_config['uuidFolder'] == true) { + if ($this->_config['uuidFolder'] === true) { $path .= $this->stripDashes($entity[$table->primaryKey()]) . DS; } return $path; @@ -310,7 +310,7 @@ public function createTmpFile($folder = null, $checkAndCreatePath = true) { */ public function fsPath($type, $string, $idFolder = true) { $string = str_replace('-', '', $string); - $path = $type . DS . FileStorageUtils::randomPath($string); + $path = $type . DS . StorageUtils::randomPath($string); if ($idFolder) { $path .= $string . DS; } diff --git a/src/Event/ImageProcessingListener.php b/src/Event/ImageProcessingListener.php index b696f31d..e339b399 100644 --- a/src/Event/ImageProcessingListener.php +++ b/src/Event/ImageProcessingListener.php @@ -418,7 +418,7 @@ protected function _buildPath($record, $extension = true, $hash = null) { if (!empty($hash)) { $path .= '.' . $hash; } - if ($extension == true) { + if ($extension === true) { $path .= '.' . $record['extension']; } } diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index 62fb6d03..3743b425 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -98,7 +98,6 @@ abstract class AbstractListener implements EventListenerInterface { * Constructor * * @param array $config - * @return AbstractListener */ public function __construct(array $config = []) { $this->_mergeListenerVars(); diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 4b79807d..2a3120da 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -116,7 +116,7 @@ public function afterSave(Event $event) { 'callbacks' => false )); $event->result = true; - } catch (Exception $e) { + } catch (\Exception $e) { $this->log($e->getMessage(), 'file_storage'); $event->result = false; } diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index cece45ec..f86add90 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -232,7 +232,7 @@ public function randomPath($string, $level = 3, $method = 'sha1') { * * @param string $string * @param string $position Can be `before`, `after` or `both` - * @param string $ds Directory separator should be / or \ + * @param string $ds Directory separator should be / or \, if not set the DS constant is used. * @throws \InvalidArgumentException * @return string */ diff --git a/src/Validation/UploadValidator.php b/src/Validation/UploadValidator.php index c24bf895..b40fd910 100644 --- a/src/Validation/UploadValidator.php +++ b/src/Validation/UploadValidator.php @@ -146,7 +146,7 @@ public function imageSize($value, $options) { if (!isset($options['height']) && !isset($options['width'])) { throw new \InvalidArgumentException(__d('file_storage', 'Invalid image size validation parameters!')); } - list($width, $height, $type, $attr) = getimagesize($value['tmp_name']); + list($width, $height) = getimagesize($value['tmp_name']); if (isset($options['height'])) { $validHeight = Validation::comparison($height, $options['height'][1], $options['height'][0]); } @@ -219,35 +219,33 @@ public function uploadErrors($value, $options = array()) { switch ($value['error']) { case UPLOAD_ERR_OK: return true; - break; case UPLOAD_ERR_INI_SIZE: $this->_uploadError = __d('file_storage', 'The uploaded file exceeds limit of %s.', Number::toReadableSize(ini_get('upload_max_filesize'))); - break; + return false; case UPLOAD_ERR_FORM_SIZE: $this->_uploadError = __d('file_storage', 'The uploaded file is to big, please choose a smaller file or try to compress it.'); - break; + return false; case UPLOAD_ERR_PARTIAL: $this->_uploadError = __d('file_storage', 'The uploaded file was only partially uploaded.'); - break; + return false; case UPLOAD_ERR_NO_FILE: if ($options['allowNoFileError'] === false) { $this->_uploadError = __d('file_storage', 'No file was uploaded.'); return false; } return true; - break; case UPLOAD_ERR_NO_TMP_DIR: $this->_uploadError = __d('file_storage', 'The remote server has no temporary folder for file uploads. Please contact the site admin.'); - break; + return false; case UPLOAD_ERR_CANT_WRITE: $this->_uploadError = __d('file_storage', 'Failed to write file to disk. Please contact the site admin.'); - break; + return false; case UPLOAD_ERR_EXTENSION: $this->_uploadError = __d('file_storage', 'File upload stopped by extension. Please contact the site admin.'); - break; + return false; default: $this->_uploadError = __d('file_storage', 'Unknown File Error. Please contact the site admin.'); - break; + return false; } return false; } From 0e69df2842993480cce3fae291c3546fa417a938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 10 Jul 2015 15:50:42 +0200 Subject: [PATCH 036/144] Trying to enable code coverage for Scrutinizer --- .scrutinizer.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 08715e8a..b7c7d152 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -5,10 +5,18 @@ checks: remove_php_closing_tag: true remove_trailing_whitespace: true tools: - php_code_coverage: false + php_code_coverage: true php_loc: enabled: true - excluded_dirs: [vendor, tests, config, docs, 'src/Lib', 'src/Event'] + excluded_dirs: [vendor, tests, config, docs, './src/Lib', './src/Event'] php_cpd: enabled: true - excluded_dirs: [vendor, tests, config, docs, 'src/Lib', 'src/Event'] \ No newline at end of file + excluded_dirs: [vendor, tests, config, docs, './src/Lib', './src/Event'] +build: + tests: + override: + - + command: 'phpunit --coverage-clover=coverage.xml' + coverage: + file: 'coverage.xml' + format: 'php-clover' \ No newline at end of file From c407dc19adc37e9a0d3bbc05a8f367251fdb442b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 10 Jul 2015 15:55:34 +0200 Subject: [PATCH 037/144] Minor fixes and changing namespaces in use statements. --- src/Event/ImageProcessingListener.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Event/ImageProcessingListener.php b/src/Event/ImageProcessingListener.php index e339b399..11d02973 100644 --- a/src/Event/ImageProcessingListener.php +++ b/src/Event/ImageProcessingListener.php @@ -6,8 +6,8 @@ use Cake\Event\Event; use Cake\Core\Configure; use Cake\ORM\Table; -use Burzum\FileStorage\Lib\StorageManager; -use Burzum\FileStorage\Lib\FileStorageUtils; +use Burzum\FileStorage\Storage\StorageManager; +use Burzum\FileStorage\Storage\StorageUtils; /** * @author Florian Krämer @@ -119,7 +119,7 @@ protected function _createVersions(Table $table, $entity, array $operations) { $tmpFile = $this->_tmpFile($Storage, $path, TMP . 'image-processing'); foreach ($operations as $version => $imageOperations) { - $hash = FileStorageUtils::hashOperations($imageOperations); + $hash = StorageUtils::hashOperations($imageOperations); $string = $this->_buildPath($entity, true, $hash); if ($this->adapterClass === 'AmazonS3' || $this->adapterClass === 'AwsS3') { @@ -178,7 +178,7 @@ protected function _removeVersions(Event $Event) { $Storage = $Event->data['storage']; $record = $Event->data['record']; foreach ($Event->data['operations'] as $version => $operations) { - $hash = FileStorageUtils::hashOperations($operations); + $hash = StorageUtils::hashOperations($operations); $string = $this->_buildPath($record, true, $hash); if ($this->adapterClass === 'AmazonS3' || $this->adapterClass === 'AwsS3') { $string = str_replace('\\', '/', $string); @@ -237,7 +237,7 @@ public function beforeSave(Event $Event) { if ($this->_checkEvent($Event)) { if (in_array($Event->data['record']['model'], (array)$this->config('autoRotate'))) { $imageFile = $Event->data['record']['file']['tmp_name']; - $format = FileStorageUtils::fileExtension($Event->data['record']['file']['name']); + $format = StorageUtils::fileExtension($Event->data['record']['file']['name']); $this->_autoRotate($imageFile, $format); } } @@ -384,7 +384,7 @@ protected function _buildAmazonS3Path(Event $Event) { */ protected function _buildCloudFrontDistributionUrl($protocol, $image, $bucket, $bucketPrefix = null, $cfDist = null) { $path = $protocol . '://'; - if ($cfDist) { + if (is_string($cfDist)) { $path .= $cfDist; } else { if ($bucketPrefix) { From 2c42f2d92db61eefeb360211cbde55750d382e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 10 Jul 2015 19:50:12 +0200 Subject: [PATCH 038/144] Refactoring the path builder construction. --- src/Storage/Listener/AbstractListener.php | 42 ++++++++++++------- src/Storage/PathBuilder/BasePathBuilder.php | 2 +- src/Storage/PathBuilder/LocalPathBuilder.php | 6 --- .../Storage/Listener/AbstractListenerTest.php | 6 ++- .../Listener/ImageProcessingTraitTest.php | 8 +++- 5 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index 3743b425..12389194 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -102,6 +102,10 @@ abstract class AbstractListener implements EventListenerInterface { public function __construct(array $config = []) { $this->_mergeListenerVars(); $this->config($config); + $this->_constructPathBuilder( + $this->_config['pathBuilder'], + $this->_config['pathBuilderOptions'] + ); $this->initialize($config); } @@ -290,35 +294,41 @@ public function createTmpFile($folder = null, $checkAndCreatePath = true) { * @param array $config for the path builder. * @return \Burzum\FileStorage\Storage\PathBuilder\BasePathBuilder */ - public function pathBuilder($class = null, array $config = []) { - if (!empty($this->_pathBuilder) && empty($class)) { + public function pathBuilder() { + if (!empty($this->_pathBuilder)) { return $this->_pathBuilder; } + throw \RuntimeException(sprintf('No path builder configured! Call _constructPathBuilder() and construct one!')); + } - if (empty($this->_pathBuilder) && empty($class)) { - $class = $this->_config['pathBuilder']; - $config = $this->_config['pathBuilderOptions']; - if (empty($class)) { - throw new \RuntimeException(sprintf('No path builder loaded!')); - } - } - +/** + * Constructs a path builder instance. + * + * @param string $class + * @param array $options + * @return \Burzum\FileStorage\Storage\PathBuilder\BasePathBuilder + * @throws \RuntimeException + */ + public function _constructPathBuilder($class, array $options = []) { $classname = '\Burzum\FileStorage\Storage\PathBuilder\\' . $class . 'Builder'; if (class_exists($classname)) { - $this->_pathBuilder = new $classname($config); + $this->_pathBuilder = new $classname($options); return $this->_pathBuilder; } $classname = '\App\Storage\PathBuilder\\' . $class . 'Builder'; if (class_exists($classname)) { - $this->_pathBuilder = new $classname($config); - return $this->_pathBuilder; + $this->_pathBuilder = new $classname($options); } $classname = $class; if (class_exists($classname)) { - $this->_pathBuilder = new $classname($config); + $this->_pathBuilder = new $classname($options); + } + if (empty($this->_pathBuilder)) { + throw new \RuntimeException(sprintf('Could not find path builder "%s"!', $classname)); + } + if ($this->_pathBuilder instanceof \Burzum\FileStorage\Storage\PathBuilder\PathBuilderInterface) { return $this->_pathBuilder; } - - throw new \RuntimeException(sprintf('Could not find path builder "%s"!', $classname)); + throw new \RuntimeException(sprintf('Path builder class "%s" does not implement the PathBuilderInterface interface!')); } } diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index f86add90..e23584a3 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -69,7 +69,7 @@ public function path(Entity $entity, array $options = []) { $path = $config['pathPrefix'] . DS . $path; } if ($this->_config['modelFolder'] === true) { - $path .= $entity->model; + $path .= $entity->model . DS; } if ($this->_config['randomPath'] === true) { $path .= $this->randomPath($entity->id); diff --git a/src/Storage/PathBuilder/LocalPathBuilder.php b/src/Storage/PathBuilder/LocalPathBuilder.php index ced42d1f..b6d98934 100644 --- a/src/Storage/PathBuilder/LocalPathBuilder.php +++ b/src/Storage/PathBuilder/LocalPathBuilder.php @@ -10,10 +10,4 @@ class LocalPathBuilder extends BasePathBuilder { - public function __construct(array $config = []) { - if (empty($config['tableFolder'])) { - $config['tableFolder'] = true; - } - parent::__construct($config); - } } diff --git a/tests/TestCase/Storage/Listener/AbstractListenerTest.php b/tests/TestCase/Storage/Listener/AbstractListenerTest.php index 045b8d3a..e74401e8 100644 --- a/tests/TestCase/Storage/Listener/AbstractListenerTest.php +++ b/tests/TestCase/Storage/Listener/AbstractListenerTest.php @@ -30,8 +30,10 @@ class AbstractListenerTest extends TestCase { * @return void */ public function testPathBuilder() { - $Listener = new TestAbstractListener(); - $result = $Listener->pathBuilder('LocalPath'); + $Listener = new TestAbstractListener([ + 'pathBuilder' => 'LocalPath' + ]); + $result = $Listener->pathBuilder(); $this->assertInstanceOf('\Burzum\FileStorage\Storage\PathBuilder\BasePathBuilder', $result); } } diff --git a/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php b/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php index 9c68cd6b..43595fb2 100644 --- a/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php +++ b/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php @@ -17,6 +17,12 @@ class TraitTestClass extends AbstractListener { use ImageProcessingTrait; + public $_defaultConfig = [ + 'pathBuilder' => 'LocalPath', + 'pathBuilderOptions' => [ + 'preserveFilename' => true + ] + ]; public function __construct(array $config = []) { parent::__construct($config); $this->_loadImageProcessingFromConfig(); @@ -89,7 +95,7 @@ public function setUp() { public function testCreateImageVersions() { $entity = $this->FileStorage->get('file-storage-3'); $listener = new TraitTestClass(); - $path = $listener->pathBuilder('LocalPath', ['preserveFilename' => true])->path($entity); + $path = $listener->pathBuilder()->path($entity); new Folder($this->testPath . $path, true); copy($this->fileFixtures . 'titus.jpg', $this->testPath . $path . 'titus.jpg'); From 0a1c6311fd439bfa47c4e3252427bf3a70ba3bbe Mon Sep 17 00:00:00 2001 From: Scrutinizer Auto-Fixer Date: Fri, 10 Jul 2015 17:53:00 +0000 Subject: [PATCH 039/144] Scrutinizer Auto-Fixes This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com --- src/Model/Table/ImageStorageTable.php | 4 ++-- src/Storage/Listener/ImageProcessingTrait.php | 2 +- src/Storage/PathBuilder/BasePathBuilder.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Model/Table/ImageStorageTable.php b/src/Model/Table/ImageStorageTable.php index f176ce4e..d8080fd5 100644 --- a/src/Model/Table/ImageStorageTable.php +++ b/src/Model/Table/ImageStorageTable.php @@ -43,7 +43,7 @@ public function initialize(array $config) { * beforeSave callback * * @param \Cake\Event\Event $event - * @param \Burzum\FileStorage\Model\Table\Entity $entity + * @param \Cake\ORM\Entity $entity * @param array $options * @return boolean true on success */ @@ -116,7 +116,7 @@ public function beforeDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity) * @param \Cake\Event\Event $event * @param \Cake\ORM\Entity $entity * @param array $options - * @return void + * @return boolean */ public function afterDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $options) { $imageEvent = new Event('ImageStorage.afterDelete', $this, [ diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index d7a478c3..63d95b5c 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -131,7 +131,7 @@ public function createImageVersions(Entity $entity, array $versions, array $opti ]; try { $output = $this->createTmpFile(); - $tmpFile = $this->_tmpFile($storage, $this->pathBuilder()->fullPath($entity)); + $tmpFile = $this->_tmpFile($storage, $this->pathBuilder()->fullPath($entity)); $this->imageProcessor()->open($tmpFile); $this->imageProcessor()->batchProcess($output, $config, ['format' => $entity->extension]); $storage->write($path, file_get_contents($output)); diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index e23584a3..059aac23 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -138,7 +138,7 @@ public function filename(Entity $entity, array $options = []) { */ protected function _buildFilename(Entity $entity, array $options = []) { $filename = $entity->id; - if ($options['stripUuid'] === true) { + if ($options['stripUuid'] === true) { $filename = $this->stripDashes($filename); } if ($options['preserveExtension'] === true) { @@ -249,7 +249,7 @@ public function ensureSlash($string, $position, $ds = null) { } } if ($position === 'after' || $position === 'both') { - if (substr($string, -1, 1) !== $ds ) { + if (substr($string, -1, 1) !== $ds) { $string = $string . $ds; } } From d2564ca8aefaa20b80088e0b5a310d12d56ba48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 10 Jul 2015 20:01:24 +0200 Subject: [PATCH 040/144] Configure Scrutinizer to ignore legacy code. --- .scrutinizer.yml | 8 +++++--- src/Storage/Listener/AbstractListener.php | 11 +++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index b7c7d152..6483d8eb 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -8,10 +8,12 @@ tools: php_code_coverage: true php_loc: enabled: true - excluded_dirs: [vendor, tests, config, docs, './src/Lib', './src/Event'] + excluded_dirs: [vendor, tests, config, docs] php_cpd: enabled: true - excluded_dirs: [vendor, tests, config, docs, './src/Lib', './src/Event'] + excluded_dirs: [vendor, tests, config, docs] +filter: + paths: [src/Event/*, src/Lib/*] build: tests: override: @@ -19,4 +21,4 @@ build: command: 'phpunit --coverage-clover=coverage.xml' coverage: file: 'coverage.xml' - format: 'php-clover' \ No newline at end of file + format: 'php-clover' diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index 12389194..062cf601 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -234,7 +234,7 @@ public function getAdapterClassName($configName) { * @param Adapter $Storage Storage adapter * @param string $path Path / key of the storage adapter file * @param string $tmpFolder - * @throws Exception + * @throws \Exception * @return string */ protected function _tmpFile($Storage, $path, $tmpFolder = null) { @@ -242,7 +242,7 @@ protected function _tmpFile($Storage, $path, $tmpFolder = null) { $tmpFile = $this->createTmpFile($tmpFolder); file_put_contents($tmpFile, $Storage->read($path)); return $tmpFile; - } catch (Exception $e) { + } catch (\Exception $e) { $this->log($e->getMessage(), 'file_storage'); throw $e; } @@ -288,12 +288,11 @@ public function createTmpFile($folder = null, $checkAndCreatePath = true) { } /** - * Path builder. + * Gets the configured path builder instance. * - * @param string $class Class name of a path builder. - * @param array $config for the path builder. * @return \Burzum\FileStorage\Storage\PathBuilder\BasePathBuilder - */ + * @throws + */ public function pathBuilder() { if (!empty($this->_pathBuilder)) { return $this->_pathBuilder; From def257a22aefdbfd0320e4e71db6ab451ac21ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 10 Jul 2015 20:20:23 +0200 Subject: [PATCH 041/144] Minor fixes. --- src/Event/AbstractStorageEventListener.php | 1 - src/Storage/Listener/LocalListener.php | 5 ++++- src/Storage/PathBuilder/BasePathBuilder.php | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Event/AbstractStorageEventListener.php b/src/Event/AbstractStorageEventListener.php index e9af72b8..52157301 100644 --- a/src/Event/AbstractStorageEventListener.php +++ b/src/Event/AbstractStorageEventListener.php @@ -86,7 +86,6 @@ abstract class AbstractStorageEventListener implements EventListenerInterface { * Constructor * * @param array $config - * @return AbstractStorageEventListener */ public function __construct(array $config = []) { $this->config($config); diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 2a3120da..799c2e88 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -27,7 +27,10 @@ class LocalListener extends AbstractListener { * @var array */ protected $_defaultConfig = [ - 'pathBuilder' => 'LocalPath', + 'pathBuilder' => 'BasePathBuilder', + 'pathBuilderConfig' => [ + 'modelFolder' => true, + ], 'imageProcessing' => false, ]; diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index 059aac23..6815bb17 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -8,7 +8,7 @@ use Cake\Core\InstanceConfigTrait; use Cake\ORM\Entity; -use Burzum\FileStorage\Lib\FileStorageUtils; +use Burzum\FileStorage\Lib\StorageUtils; /** * A path builder is an utility class that generates a path and filename for a From 0fde94ac9e19168008b7d75a6ff6d393453ded83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 10 Jul 2015 20:58:31 +0200 Subject: [PATCH 042/144] Fixing the path builder class name in the LocalListener config. --- src/Storage/Listener/LocalListener.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 799c2e88..da5d62ed 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -27,7 +27,7 @@ class LocalListener extends AbstractListener { * @var array */ protected $_defaultConfig = [ - 'pathBuilder' => 'BasePathBuilder', + 'pathBuilder' => 'BasePath', 'pathBuilderConfig' => [ 'modelFolder' => true, ], From 425476b957f428cbec5a339d2b857d9444d29945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sat, 11 Jul 2015 00:54:26 +0200 Subject: [PATCH 043/144] Minor fixes. --- src/Event/AbstractStorageEventListener.php | 2 +- src/Event/ImageProcessingListener.php | 1 + src/Storage/PathBuilder/S3PathBuilder.php | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Event/AbstractStorageEventListener.php b/src/Event/AbstractStorageEventListener.php index 52157301..02776c43 100644 --- a/src/Event/AbstractStorageEventListener.php +++ b/src/Event/AbstractStorageEventListener.php @@ -164,7 +164,7 @@ protected function _checkEvent(Event $event) { } return ( $this->_checkTable($event) - && $this->getAdapterClassName($event->data['record']['adapter']) + && $this->getAdapterClassName($event->data['record']['adapter'] !== false) && $this->_modelFilter($event) ); } diff --git a/src/Event/ImageProcessingListener.php b/src/Event/ImageProcessingListener.php index 11d02973..8e96c3c7 100644 --- a/src/Event/ImageProcessingListener.php +++ b/src/Event/ImageProcessingListener.php @@ -79,6 +79,7 @@ public function implementedEvents() { */ protected function _autoRotate($imageFile, $format) { $orientation = ImagineUtility::getImageOrientation($imageFile); + $degree = 0; if ($orientation === false) { return false; } diff --git a/src/Storage/PathBuilder/S3PathBuilder.php b/src/Storage/PathBuilder/S3PathBuilder.php index bd39760e..3469398d 100644 --- a/src/Storage/PathBuilder/S3PathBuilder.php +++ b/src/Storage/PathBuilder/S3PathBuilder.php @@ -6,7 +6,7 @@ */ namespace Burzum\FileStorage\Storage\PathBuilder; -use Burzum\FileStorage\Lib\StorageManager; +use Burzum\FileStorage\Storage\StorageManager; use Cake\ORM\Entity; class S3PathBuilder extends BasePathBuilder { From b329def2d557a722365f2ce645aff0c0f8e4eb1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sat, 11 Jul 2015 14:58:03 +0200 Subject: [PATCH 044/144] Working on the tests --- src/Event/AbstractStorageEventListener.php | 2 +- src/Event/LocalFileStorageListener.php | 5 +- .../LegacyLocalFileStorageListener.php | 37 +++++++++++++ src/Storage/Listener/LocalListener.php | 2 +- src/Storage/PathBuilder/BasePathBuilder.php | 2 +- .../Event/LocalFileStorageListenerTest.php | 2 +- .../LegacyLocalFileStorageListenerTest.php | 52 +++++++++++++++++++ 7 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 src/Storage/Listener/LegacyLocalFileStorageListener.php create mode 100644 tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php diff --git a/src/Event/AbstractStorageEventListener.php b/src/Event/AbstractStorageEventListener.php index 02776c43..18ad4e81 100644 --- a/src/Event/AbstractStorageEventListener.php +++ b/src/Event/AbstractStorageEventListener.php @@ -164,7 +164,7 @@ protected function _checkEvent(Event $event) { } return ( $this->_checkTable($event) - && $this->getAdapterClassName($event->data['record']['adapter'] !== false) + && (bool)$this->getAdapterClassName($event->data['record']['adapter']) && $this->_modelFilter($event) ); } diff --git a/src/Event/LocalFileStorageListener.php b/src/Event/LocalFileStorageListener.php index 7d7ae4ca..1cb8c612 100644 --- a/src/Event/LocalFileStorageListener.php +++ b/src/Event/LocalFileStorageListener.php @@ -3,7 +3,7 @@ use Cake\Event\Event; use Cake\Filesystem\Folder; -use Burzum\FileStorage\Lib\StorageManager; +use Burzum\FileStorage\Storage\StorageManager; /** * Local FileStorage Event Listener for the CakePHP FileStorage plugin @@ -93,7 +93,8 @@ public function afterSave(Event $event) { $Storage = StorageManager::adapter($entity['adapter']); try { $filename = $this->buildFileName($table, $entity); - $entity['path'] = $this->buildPath($table, $entity); + $entity->path = $this->buildPath($table, $entity); + $Storage->write($entity['path'] . $filename, file_get_contents($entity['file']['tmp_name']), true); $table->save($entity, array( 'validate' => false, diff --git a/src/Storage/Listener/LegacyLocalFileStorageListener.php b/src/Storage/Listener/LegacyLocalFileStorageListener.php new file mode 100644 index 00000000..81dc504e --- /dev/null +++ b/src/Storage/Listener/LegacyLocalFileStorageListener.php @@ -0,0 +1,37 @@ + 'BasePath', + 'pathBuilderOptions' => [ + 'pathPrefix' => 'files', + 'modelFolder' => false, + 'preserveFilename' => true, + 'randomPath' => 'crc32' + ], + 'imageProcessing' => false, + ]; +} diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index da5d62ed..77f1168a 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -28,7 +28,7 @@ class LocalListener extends AbstractListener { */ protected $_defaultConfig = [ 'pathBuilder' => 'BasePath', - 'pathBuilderConfig' => [ + 'pathBuilderOptions' => [ 'modelFolder' => true, ], 'imageProcessing' => false, diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index 6815bb17..8c0808c9 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -8,7 +8,7 @@ use Cake\Core\InstanceConfigTrait; use Cake\ORM\Entity; -use Burzum\FileStorage\Lib\StorageUtils; +use Burzum\FileStorage\Storage\StorageUtils; /** * A path builder is an utility class that generates a path and filename for a diff --git a/tests/TestCase/Event/LocalFileStorageListenerTest.php b/tests/TestCase/Event/LocalFileStorageListenerTest.php index b4ba9cec..db6b45f0 100644 --- a/tests/TestCase/Event/LocalFileStorageListenerTest.php +++ b/tests/TestCase/Event/LocalFileStorageListenerTest.php @@ -101,7 +101,7 @@ public function testAfterSave() { $entity->file = [ 'tmp_name' => $this->fileFixtures . 'titus.jpg', ]; - $event = new Event('FileStorage.afterDelete', $this->FileStorage, [ + $event = new Event('FileStorage.afterSave', $this->FileStorage, [ 'record' => $entity, ]); $this->Listener->afterSave($event); diff --git a/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php b/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php new file mode 100644 index 00000000..53836769 --- /dev/null +++ b/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php @@ -0,0 +1,52 @@ +fileFixtures = Plugin::path('Burzum/FileStorage') . 'tests' . DS . 'Fixture' . DS . 'File' . DS; + + $this->listener = $this->getMockBuilder('Burzum\FileStorage\Storage\Listener\LegacyLocalFileStorageListener') + ->setMethods(['storageAdapter']) + ->setConstructorArgs([['models' => ['Item']]]) + ->getMock(); + + $this->adapterMock = $this->getMock('\Gaufrette\Adapter\Local', [], ['']); + + $this->FileStorage = TableRegistry::get('Burzum/FileStorage.FileStorage'); + } + +/** + * Testing that the path is the same as in the old LocalFileStorageListener class. + */ + public function testPath() { + $entity = $this->FileStorage->get('file-storage-1'); + $result = $this->listener->pathBuilder()->path($entity); + $expected = 'files' . DS . '00' . DS . '14' . DS . '90' . DS . 'filestorage1' . DS; + $this->assertEquals($result, $expected); + } +} \ No newline at end of file From 9c7548e313db7139b47df8c3eda102b83eabc9df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 13 Jul 2015 14:45:48 +0200 Subject: [PATCH 045/144] Fixing the log() calls. Custom error levels are no longer supported. --- src/Event/ImageProcessingListener.php | 8 ++++---- src/Event/LocalFileStorageListener.php | 2 +- src/Event/S3StorageListener.php | 4 ++-- src/Model/Table/FileStorageTable.php | 2 +- src/Storage/Listener/AbstractListener.php | 2 +- src/Storage/Listener/LocalListener.php | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Event/ImageProcessingListener.php b/src/Event/ImageProcessingListener.php index 8e96c3c7..fa5f3a99 100644 --- a/src/Event/ImageProcessingListener.php +++ b/src/Event/ImageProcessingListener.php @@ -135,7 +135,7 @@ protected function _createVersions(Table $table, $entity, array $operations) { $image = $table->processImage($tmpFile, null, array('format' => $entity['extension']), $imageOperations); $Storage->write($string, $image->get($entity['extension']), true); } catch (\Exception $e) { - $this->log($e->getMessage(), 'file_storage'); + $this->log($e->getMessage()); unlink($tmpFile); throw $e; } @@ -189,7 +189,7 @@ protected function _removeVersions(Event $Event) { $Storage->delete($string); } } catch (\Exception $e) { - $this->log($e->getMessage(), 'file_storage'); + $this->log($e->getMessage()); } } $Event->stopPropagation(); @@ -216,7 +216,7 @@ public function afterDelete(Event $Event) { } $Storage->delete($string); } catch (\Exception $e) { - $this->log($e->getMessage(), 'file_storage'); + $this->log($e->getMessage()); return false; } $operations = Configure::read('FileStorage.imageSizes.' . $record['model']); @@ -285,7 +285,7 @@ public function afterSave(Event $Event) { $table->data = $data; } catch (\Exception $e) { - $this->log($e->getMessage(), 'file_storage'); + $this->log($e->getMessage()); } } } diff --git a/src/Event/LocalFileStorageListener.php b/src/Event/LocalFileStorageListener.php index 1cb8c612..c86269db 100644 --- a/src/Event/LocalFileStorageListener.php +++ b/src/Event/LocalFileStorageListener.php @@ -101,7 +101,7 @@ public function afterSave(Event $event) { 'callbacks' => false )); } catch (\Exception $e) { - $this->log($e->getMessage(), 'file_storage'); + $this->log($e->getMessage()); } } } diff --git a/src/Event/S3StorageListener.php b/src/Event/S3StorageListener.php index 31d267f3..99615f1c 100644 --- a/src/Event/S3StorageListener.php +++ b/src/Event/S3StorageListener.php @@ -54,7 +54,7 @@ public function afterDelete(Event $Event) { } $Storage->delete($path['combined']); } catch (\Exception $e) { - $this->log($e->getMessage(), 'file_storage'); + $this->log($e->getMessage()); return false; } return true; @@ -82,7 +82,7 @@ public function afterSave(Event $Event) { 'callbacks' => false) ); } catch (\Exception $e) { - $this->log($e->getMessage(), 'file_storage'); + $this->log($e->getMessage()); } } } diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index 056c4703..7034a946 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -151,7 +151,7 @@ public function afterDelete(\Cake\Event\Event $event, Entity $entity, $options) $Storage = $this->getStorageAdapter($entity['adapter']); $Storage->delete($entity['path']); } catch (\Exception $e) { - $this->log($e->getMessage(), 'file_storage'); + $this->log($e->getMessage()); return false; } diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index 062cf601..2e67c9f9 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -243,7 +243,7 @@ protected function _tmpFile($Storage, $path, $tmpFolder = null) { file_put_contents($tmpFile, $Storage->read($path)); return $tmpFile; } catch (\Exception $e) { - $this->log($e->getMessage(), 'file_storage'); + $this->log($e->getMessage()); throw $e; } } diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 77f1168a..87f05448 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -120,7 +120,7 @@ public function afterSave(Event $event) { )); $event->result = true; } catch (\Exception $e) { - $this->log($e->getMessage(), 'file_storage'); + $this->log($e->getMessage()); $event->result = false; } From 0e2dbd9eec8d92c286d4bd601991bc20ff3b9413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 13 Jul 2015 16:15:38 +0200 Subject: [PATCH 046/144] Refactoring the getStorageAdapter() method in the table classes. --- src/Model/Table/FileStorageTable.php | 22 +++------- src/Model/Table/ImageStorageTable.php | 6 +-- src/Storage/Listener/LocalListener.php | 16 +++++-- src/Storage/StorageTrait.php | 4 +- .../TestCase/Model/Table/FileStorageTest.php | 10 ----- .../PathBuilder/BasePathBuilderTest.php | 5 +++ tests/TestCase/Storage/StorageTraitTest.php | 43 +++++++++++++++++++ 7 files changed, 71 insertions(+), 35 deletions(-) create mode 100644 tests/TestCase/Storage/StorageTraitTest.php diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index 7034a946..5b7fcbc2 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -7,7 +7,7 @@ use Cake\Event\Event; use Cake\Event\EventManager; use Cake\Filesystem\File; -use Burzum\FileStorage\Lib\StorageManager; +use Burzum\FileStorage\Storage\StorageTrait; /** * FileStorageTable @@ -19,6 +19,7 @@ class FileStorageTable extends Table { use LogTrait; + use StorageTrait; /** * Name @@ -88,7 +89,7 @@ public function beforeSave(Event $event, Entity $entity, $options) { } $Event = new Event('FileStorage.beforeSave', $this, array( 'record' => $entity, - 'storage' => $this->getStorageAdapter($event->data['entity']['adapter']) + 'storage' => $this->storageAdapter($event->data['entity']['adapter']) )); $this->getEventManager()->dispatch($Event); if ($Event->isStopped()) { @@ -109,7 +110,7 @@ public function afterSave(Event $event, Entity $entity, $options) { $Event = new Event('FileStorage.afterSave', $this, [ 'created' => $event->data['entity']->isNew(), 'record' => $entity, - 'storage' => $this->getStorageAdapter($event->data['entity']['adapter']) + 'storage' => $this->storageAdapter($event->data['entity']['adapter']) ]); $this->getEventManager()->dispatch($Event); $this->deleteOldFileOnSave($entity); @@ -148,7 +149,7 @@ public function beforeDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity) */ public function afterDelete(\Cake\Event\Event $event, Entity $entity, $options) { try { - $Storage = $this->getStorageAdapter($entity['adapter']); + $Storage = $this->storageAdapter($entity['adapter']); $Storage->delete($entity['path']); } catch (\Exception $e) { $this->log($e->getMessage()); @@ -157,24 +158,13 @@ public function afterDelete(\Cake\Event\Event $event, Entity $entity, $options) $Event = new Event('FileStorage.afterDelete', $this, array( 'record' => $event->data['record'], - 'storage' => $this->getStorageAdapter($entity['adapter']))); + 'storage' => $this->storageAdapter($entity['adapter']))); $this->getEventManager()->dispatch($Event); return true; } -/** - * Get a storage adapter from the StorageManager - * - * @param string $adapterName - * @param boolean $renewObject - * @return \Gaufrette\Adapter - */ - public function getStorageAdapter($adapterName, $renewObject = false) { - return StorageManager::adapter($adapterName, $renewObject); - } - /** * Deletes an old file to replace it with the new one if an old id was passed. * diff --git a/src/Model/Table/ImageStorageTable.php b/src/Model/Table/ImageStorageTable.php index d8080fd5..ec59fc4d 100644 --- a/src/Model/Table/ImageStorageTable.php +++ b/src/Model/Table/ImageStorageTable.php @@ -74,7 +74,7 @@ public function beforeSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $ public function afterSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $options) { if ($entity->isNew()) { $imageEvent = new Event('ImageStorage.afterSave', $this, [ - 'storage' => $this->getStorageAdapter($entity['adapter']), + 'storage' => $this->storageAdapter($entity['adapter']), 'record' => $entity ]); $this->getEventManager()->dispatch($imageEvent); @@ -97,7 +97,7 @@ public function beforeDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity) $imageEvent = new Event('ImageStorage.beforeDelete', $this, [ 'record' => $this->record, - 'storage' => $this->getStorageAdapter($this->record['adapter']) + 'storage' => $this->storageAdapter($this->record['adapter']) ]); $this->getEventManager()->dispatch($imageEvent); @@ -121,7 +121,7 @@ public function beforeDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity) public function afterDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $options) { $imageEvent = new Event('ImageStorage.afterDelete', $this, [ 'record' => $entity, - 'storage' => $this->getStorageAdapter($entity['adapter']) + 'storage' => $this->storageAdapter($entity['adapter']) ]); $this->getEventManager()->dispatch($imageEvent); return true; diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 87f05448..5fbec21a 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -76,13 +76,19 @@ public function afterDelete(Event $event) { if ($this->_checkEvent($event)) { $entity = $event->data['record']; $path = $this->pathBuilder()->fullPath($entity); - if ($this->storageAdapter($entity->adapter)->delete($path)) { - if ($this->_config['imageProcessing'] === true) { - $this->autoProcessImageVersions($entity, 'remove'); + try { + if ($this->storageAdapter($entity->adapter)->delete($path)) { + if ($this->_config['imageProcessing'] === true) { + $this->autoProcessImageVersions($entity, 'remove'); + } + $event->result = true; + return; } - $event->result = true; + } catch (\Exception $e) { + $this->log($e->getMessage()); } $event->result = false; + $event->stopPropagation(); } } @@ -127,6 +133,8 @@ public function afterSave(Event $event) { if ($this->_config['imageProcessing'] === true) { $this->autoProcessImageVersions($entity, 'create'); } + + $event->stopPropagation(); } } } diff --git a/src/Storage/StorageTrait.php b/src/Storage/StorageTrait.php index 70f9dd5b..a150daac 100644 --- a/src/Storage/StorageTrait.php +++ b/src/Storage/StorageTrait.php @@ -31,8 +31,8 @@ public function storageConfig($configName) { * @param string $configName * @return array */ - public function storageAdapter($configName) { - return StorageManager::adapter($configName); + public function storageAdapter($configName, $renewObject = false) { + return StorageManager::adapter($configName, $renewObject); } /** diff --git a/tests/TestCase/Model/Table/FileStorageTest.php b/tests/TestCase/Model/Table/FileStorageTest.php index 9b56f67c..0f16b4e8 100644 --- a/tests/TestCase/Model/Table/FileStorageTest.php +++ b/tests/TestCase/Model/Table/FileStorageTest.php @@ -57,16 +57,6 @@ public function testBeforeDelete() { $this->assertEquals($this->FileStorage->record, $entity); } -/** - * testBeforeDelete - * - * @return void - */ - public function testGetStorageAdapter() { - $result = $this->FileStorage->getStorageAdapter('Local'); - $this->assertTrue(is_a($result, '\Gaufrette\Filesystem')); - } - /** * testGetEventManager * diff --git a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php index 90b70f82..394d3847 100644 --- a/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php +++ b/tests/TestCase/Storage/PathBuilder/BasePathBuilderTest.php @@ -36,6 +36,11 @@ public function setUp() { $this->entity->accessible('id', true); } +/** + * testPathbuilding + * + * @return void + */ public function testPathbuilding() { $builder = new BasePathBuilder(); $config = $builder->config(); diff --git a/tests/TestCase/Storage/StorageTraitTest.php b/tests/TestCase/Storage/StorageTraitTest.php new file mode 100644 index 00000000..6a02fe9a --- /dev/null +++ b/tests/TestCase/Storage/StorageTraitTest.php @@ -0,0 +1,43 @@ +StorageTrait = new TestStorageTrait(); + } + +/** + * testBeforeDelete + * + * @return void + */ + public function testGetStorageAdapter() { + $result = $this->StorageTrait->storageAdapter('Local'); + $this->assertTrue(is_a($result, '\Gaufrette\Filesystem')); + } +} From e82124e9fce06207018fadce8d2ed83b9e45ea7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 13 Jul 2015 16:18:27 +0200 Subject: [PATCH 047/144] Bug: Removing the storage adapter delete call in FileStorage::afterSave() This could have caused issues for adapters that we're not using the `path`field in the DB or didn't store the whole required path there. It's the duty of the storage listeners to deal with that event. --- src/Model/Table/FileStorageTable.php | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index 5b7fcbc2..0f002bbf 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -148,20 +148,11 @@ public function beforeDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity) * @return boolean */ public function afterDelete(\Cake\Event\Event $event, Entity $entity, $options) { - try { - $Storage = $this->storageAdapter($entity['adapter']); - $Storage->delete($entity['path']); - } catch (\Exception $e) { - $this->log($e->getMessage()); - return false; - } - - $Event = new Event('FileStorage.afterDelete', $this, array( + $Event = new Event('FileStorage.afterDelete', $this, [ 'record' => $event->data['record'], - 'storage' => $this->storageAdapter($entity['adapter']))); - + 'storage' => $this->storageAdapter($entity['adapter']) + ]); $this->getEventManager()->dispatch($Event); - return true; } From a72095c42f20e4b0937bea83e3e029e484c7dadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 13 Jul 2015 16:35:42 +0200 Subject: [PATCH 048/144] Refactoring FileStorageTable::beforeSave(). --- src/Model/Table/FileStorageTable.php | 47 +++++++++++++++++++--------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index 0f002bbf..3e3e3d56 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -72,21 +72,7 @@ public function configureUploadValidation($options) { * @return boolean true on success */ public function beforeSave(Event $event, Entity $entity, $options) { - if (!empty($event->data['entity']['file']['tmp_name'])) { - $File = new File($event->data['entity']['file']['tmp_name']); - $event->data['entity']['filesize'] = $File->size(); - $event->data['entity']['mime_type'] = $File->mime(); - } - if (!empty($event->data['entity']['file']['name'])) { - $event->data['entity']['extension'] = pathinfo($event->data['entity']['file']['name'], PATHINFO_EXTENSION); - $event->data['entity']['filename'] = $event->data['entity']['file']['name']; - } - if (empty($event->data['entity']['model'])) { - $event->data['entity']['model'] = $this->table(); - } - if (empty($event->data['entity']['adapter'])) { - $event->data['entity']['adapter'] = 'Local'; - } + $this->getFileInfoFromUpload($event->data['entity']); $Event = new Event('FileStorage.beforeSave', $this, array( 'record' => $entity, 'storage' => $this->storageAdapter($event->data['entity']['adapter']) @@ -98,6 +84,37 @@ public function beforeSave(Event $event, Entity $entity, $options) { return true; } +/** + * Gets information about the file that is being uploaded. + * + * - gets the file size + * - gets the mime type + * - gets the extension if present + * - sets the adapter by default to local if not already set + * - sets the model field to the table name if not already set + * + * @param \Cake\ORM\Entity + * @param string $field + * @return void + */ + public function getFileInfoFromUpload(&$entity, $field = 'file') { + if (!empty($entity[$field]['tmp_name'])) { + $File = new File($entity[$field]['tmp_name']); + $entity['filesize'] = $File->size(); + $entity['mime_type'] = $File->mime(); + } + if (!empty($entity[$field]['name'])) { + $entity['extension'] = pathinfo($entity[$field]['name'], PATHINFO_EXTENSION); + $entity['filename'] = $entity[$field]['name']; + } + if (empty($entity['model'])) { + $entity['model'] = $this->table(); + } + if (empty($entity['adapter'])) { + $entity['adapter'] = 'Local'; + } + } + /** * afterSave callback * From 6c82a3d7df43737e64a5cb47db8ef0ce26fc6544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 13 Jul 2015 16:42:42 +0200 Subject: [PATCH 049/144] Refactoring getEventManager() to eventManager(). --- src/Model/Table/FileStorageTable.php | 15 +++------------ src/Model/Table/ImageStorageTable.php | 10 +++++----- tests/TestCase/Model/Table/FileStorageTest.php | 10 ---------- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index 3e3e3d56..7b3f9b73 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -77,7 +77,7 @@ public function beforeSave(Event $event, Entity $entity, $options) { 'record' => $entity, 'storage' => $this->storageAdapter($event->data['entity']['adapter']) )); - $this->getEventManager()->dispatch($Event); + $this->eventManager()->dispatch($Event); if ($Event->isStopped()) { return false; } @@ -129,7 +129,7 @@ public function afterSave(Event $event, Entity $entity, $options) { 'record' => $entity, 'storage' => $this->storageAdapter($event->data['entity']['adapter']) ]); - $this->getEventManager()->dispatch($Event); + $this->eventManager()->dispatch($Event); $this->deleteOldFileOnSave($entity); return true; } @@ -169,7 +169,7 @@ public function afterDelete(\Cake\Event\Event $event, Entity $entity, $options) 'record' => $event->data['record'], 'storage' => $this->storageAdapter($entity['adapter']) ]); - $this->getEventManager()->dispatch($Event); + $this->eventManager()->dispatch($Event); return true; } @@ -198,13 +198,4 @@ public function deleteOldFileOnSave(Entity $entity, $oldIdField = 'old_file_id') } return false; } - -/** - * Returns an EventManager instance - * - * @return \Cake\Event\EventManager - */ - public function getEventManager() { - return EventManager::instance(); - } } diff --git a/src/Model/Table/ImageStorageTable.php b/src/Model/Table/ImageStorageTable.php index ec59fc4d..56f3dca0 100644 --- a/src/Model/Table/ImageStorageTable.php +++ b/src/Model/Table/ImageStorageTable.php @@ -54,7 +54,7 @@ public function beforeSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $ $Event = new Event('ImageStorage.beforeSave', $this, array( 'record' => $entity )); - $this->getEventManager()->dispatch($Event); + $this->eventManager()->dispatch($Event); if ($Event->isStopped()) { return false; } @@ -77,7 +77,7 @@ public function afterSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $o 'storage' => $this->storageAdapter($entity['adapter']), 'record' => $entity ]); - $this->getEventManager()->dispatch($imageEvent); + $this->eventManager()->dispatch($imageEvent); $this->deleteOldFileOnSave($entity); } return true; @@ -99,7 +99,7 @@ public function beforeDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity) 'record' => $this->record, 'storage' => $this->storageAdapter($this->record['adapter']) ]); - $this->getEventManager()->dispatch($imageEvent); + $this->eventManager()->dispatch($imageEvent); if ($imageEvent->isStopped()) { return false; @@ -123,7 +123,7 @@ public function afterDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, 'record' => $entity, 'storage' => $this->storageAdapter($entity['adapter']) ]); - $this->getEventManager()->dispatch($imageEvent); + $this->eventManager()->dispatch($imageEvent); return true; } @@ -209,7 +209,7 @@ public function getImageVersions($entity, $options = []) { 'options' => [] ] ); - $this->getEventManager()->dispatch($Event); + $this->eventManager()->dispatch($Event); if ($Event->isStopped()) { $versions[$version] = str_replace('\\', '/', $Event->data['path']); } diff --git a/tests/TestCase/Model/Table/FileStorageTest.php b/tests/TestCase/Model/Table/FileStorageTest.php index 0f16b4e8..6804c148 100644 --- a/tests/TestCase/Model/Table/FileStorageTest.php +++ b/tests/TestCase/Model/Table/FileStorageTest.php @@ -57,16 +57,6 @@ public function testBeforeDelete() { $this->assertEquals($this->FileStorage->record, $entity); } -/** - * testGetEventManager - * - * @return void - */ - public function testGetEventManager() { - $result = $this->FileStorage->getEventManager(); - $this->assertTrue(is_a($result, '\Cake\Event\EventManager')); - } - /** * testAfterDelete * From edf97a1ae95341d3c2dd0ab590bbdcf5be0a2d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Tue, 14 Jul 2015 00:22:17 +0200 Subject: [PATCH 050/144] Refactoring a part of the ImageVersionShell. --- src/Shell/ImageVersionShell.php | 47 ++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/Shell/ImageVersionShell.php b/src/Shell/ImageVersionShell.php index 5ecb65c8..ad5a2425 100644 --- a/src/Shell/ImageVersionShell.php +++ b/src/Shell/ImageVersionShell.php @@ -6,7 +6,7 @@ use Cake\Event\Event; use Cake\Event\EventManager; use Cake\ORM\TableRegistry; -use Burzum\FileStorage\Lib\StorageManager; +use Burzum\FileStorage\Storage\StorageManager; use Burzum\FileStorage\Model\Table\ImageStorageTable; /** @@ -229,13 +229,9 @@ protected function _loop($action, $model, $operations = array()) { $this->_stop(); } - $this->totalImageCount = $this->Table - ->find() - ->where(['model' => $model]) - ->andWhere(['extension IN' => ['jpg', 'png']]) - ->count(); + $totalImageCount = $this->_getCount($model); - if ($this->totalImageCount == 0) { + if ($totalImageCount === 0) { $this->out(__d('file_storage', 'No Images for model {0} found', $model)); $this->_stop(); } @@ -246,14 +242,7 @@ protected function _loop($action, $model, $operations = array()) { $limit = $this->limit; do { - $images = $this->Table - ->find() - ->where(['model' => $model]) - ->andWhere(['extension IN' => ['jpg', 'png']]) - ->limit($limit) - ->offset($offset) - ->all(); - + $images = $this->_getRecords($model, $limit, $offset); if (!empty($images)) { foreach ($images as $image) { $Storage = StorageManager::adapter($image->adapter); @@ -263,7 +252,8 @@ protected function _loop($action, $model, $operations = array()) { $payload = array( 'record' => $image, 'storage' => $Storage, - 'operations' => $operations); + 'operations' => $operations + ); if ($action == 'generate' || $action == 'regenerate') { $Event = new Event('ImageVersion.createVersion', $this->Table, $payload); @@ -282,4 +272,29 @@ protected function _loop($action, $model, $operations = array()) { $offset += $limit; } while ($images->count() > 0); } + +/** + * Gets the amount of images for a model in the DB. + * + * @param string $identifier + * @param array $extensions + * @return integer + */ + protected function _getCount($identifier, array $extensions = ['jpg', 'png', 'jpeg']) { + return $this->Table + ->find() + ->where(['model' => $identifier]) + ->andWhere(['extension IN' => $extensions]) + ->count(); + } + + protected function _getRecords($identifier, $limit, $offset, array $extensions = ['jpg', 'png', 'jpeg']) { + return $this->Table + ->find() + ->where(['model' => $identifier]) + ->andWhere(['extension IN' => $extensions]) + ->limit($limit) + ->offset($offset) + ->all(); + } } From 8807fcc0e2fdf3d06b7a4d17e4b2cf551100447b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Tue, 14 Jul 2015 00:24:26 +0200 Subject: [PATCH 051/144] Working on the listeners --- src/Model/Table/FileStorageTable.php | 2 +- .../LegacyLocalFileStorageListener.php | 47 +++++++++++++++++++ src/Storage/Listener/LocalListener.php | 12 ++--- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index 7b3f9b73..aff48661 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -166,7 +166,7 @@ public function beforeDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity) */ public function afterDelete(\Cake\Event\Event $event, Entity $entity, $options) { $Event = new Event('FileStorage.afterDelete', $this, [ - 'record' => $event->data['record'], + 'record' => $entity, 'storage' => $this->storageAdapter($entity['adapter']) ]); $this->eventManager()->dispatch($Event); diff --git a/src/Storage/Listener/LegacyLocalFileStorageListener.php b/src/Storage/Listener/LegacyLocalFileStorageListener.php index 81dc504e..c2d7a0e2 100644 --- a/src/Storage/Listener/LegacyLocalFileStorageListener.php +++ b/src/Storage/Listener/LegacyLocalFileStorageListener.php @@ -34,4 +34,51 @@ class LegacyLocalFileStorageListener extends LocalListener { ], 'imageProcessing' => false, ]; + +/** + * Save the file to the storage backend after the record was created. + * + * @param \Cake\Event\Event $event + * @return void + */ + public function afterSave(Event $event) { + if ($this->_checkEvent($event) && $event->data['record']->isNew()) { + $table = $event->subject(); + $entity = $event->data['record']; + + if (!empty($event->data['fileField'])) { + $this->config('fileField', $event->data['fileField']); + } + $fileField = $this->config('fileField'); + + if ($this->config('fileHash') !== false) { + $entity->hash = $this->getFileHash( + $entity[]['tmp_name'], + $this->config('fileHash') + ); + } + + $entity['path'] = $this->pathBuilder()->path($entity); + + try { + $Storage = $this->storageAdapter($entity['adapter']); + $Storage->write($entity['path'], file_get_contents($entity[$fileField]['tmp_name']), true); + $table->save($entity, array( + 'checkRules' => false + )); + $event->result = true; + } catch (\Exception $e) { + $this->log($e->getMessage()); + $event->result = false; + return; + } + + if ($this->_config['imageProcessing'] === true) { + $this->autoProcessImageVersions($entity, 'create'); + } + + $event->result = true; + $event->stopPropagation(); + } + } } diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 5fbec21a..15d258ee 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -106,28 +106,28 @@ public function afterSave(Event $event) { if (!empty($event->data['fileField'])) { $this->config('fileField', $event->data['fileField']); } + $fileField = $this->config('fileField'); if ($this->config('fileHash') !== false) { $entity->hash = $this->getFileHash( - $entity[$this->config('fileField')]['tmp_name'], + $entity[$fileField]['tmp_name'], $this->config('fileHash') ); } - $filename = $this->pathBuilder()->filename($entity); - $entity['path'] = $this->pathBuilder()->path($entity); + $entity['path'] = $this->pathBuilder()->fullPath($entity); try { $Storage = $this->storageAdapter($entity['adapter']); - $Storage->write($entity['path'] . $filename, file_get_contents($entity[$this->config('fileField')]['tmp_name']), true); + $Storage->write($entity['path'], file_get_contents($entity[$fileField]['tmp_name']), true); $table->save($entity, array( - 'validate' => false, - 'callbacks' => false + 'checkRules' => false )); $event->result = true; } catch (\Exception $e) { $this->log($e->getMessage()); $event->result = false; + } if ($this->_config['imageProcessing'] === true) { From a8a913b3c1c2ca555829de6c1cb6aba2a2d65ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 17 Jul 2015 12:48:42 +0200 Subject: [PATCH 052/144] Working on the event listeners. Added and throwing a new StorageException. --- .../Documentation/Included-Event-Listeners.md | 52 ++++++++++- src/Model/Table/FileStorageTable.php | 1 + src/Shell/ImageVersionShell.php | 2 +- src/Storage/Listener/AbstractListener.php | 25 ++++- .../LegacyLocalFileStorageListener.php | 44 ++------- src/Storage/Listener/LocalListener.php | 93 +++++++++++++------ src/Storage/StorageUtils.php | 2 +- .../Storage/Listener/LocalListenerTest.php | 5 +- tests/TestCase/Storage/StorageException.php | 18 ++++ 9 files changed, 170 insertions(+), 72 deletions(-) create mode 100644 tests/TestCase/Storage/StorageException.php diff --git a/docs/Documentation/Included-Event-Listeners.md b/docs/Documentation/Included-Event-Listeners.md index be7f05ae..abf55aeb 100644 --- a/docs/Documentation/Included-Event-Listeners.md +++ b/docs/Documentation/Included-Event-Listeners.md @@ -3,7 +3,53 @@ Included Event Listeners **[For the deprecated event listeners please click here](Legacy-Event-Listeners.md)** -LocalPathBuilder ----------------- +--- -To be done. +Introduction +------------ + +The included event listeners will throw a StorageException when something went wrong. It's your duty to handle them. Also you can configure a logger to the `storage` log scope to filter logs by this scope. + +Each listener has a configured *Path Builder*, check the [path builder documentation] to see what they do and what their purpose is. + +To change the path builder config for a listener check what path builder the listener is using and pass the path builder config to the constructor of the listener: + +```php +$listener = new LocalListener([ + 'pathBuilderOptions' => [ + // options go here + ] +]); +``` + +If you want to implement your own listeners you'll have to extend them from the [AbstractListener](../../src/Storage/Listener/AbstractListener.php) and implement the event callbacks. + +Local Listener +-------------- + +The local listener will store files by default in this kind of path: + +``` +////. +``` + +Example: + +``` +/var/www/my_app/files/Documents/05/51/68/38f684612c6f11e5a2cb0800200c9a66/38f684612c6f11e5a2cb0800200c9a66.jpg +``` + +The listener is using by default the `LocalPathBuilder` to generate the path. + +The reason for the UUID folder name is simply to ensure it is unique per file and it makes it easy to store versions of the same file in the same folder. + +AWS S3 Listener +--------------- + +There is no new AWS S3 listener yet, you can either use the old legacy listener or write your own based on the new listeners. A contribution of a new listener is highly welcome! + +Legacy Local File Storage Listener +---------------------------------- + +This listener mimics the behavior of the deprecated `LocalFileStorageEventListener`. + \ No newline at end of file diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index aff48661..7c6173a6 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -1,6 +1,7 @@ _stop(); } diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index 2e67c9f9..3ce64f21 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -13,6 +13,7 @@ use Cake\Event\EventListenerInterface; use Cake\Log\LogTrait; use Cake\ORM\Table; +use Cake\ORM\Entity; use Cake\Utility\MergeVariablesTrait; use Cake\Utility\Text; use Cake\Filesystem\Folder; @@ -249,7 +250,7 @@ protected function _tmpFile($Storage, $path, $tmpFolder = null) { } /** - * Gets the hash of a file. + * Calculates the hash of a file. * * You can use this to compare if you got two times the same file uploaded. * @@ -261,10 +262,30 @@ protected function _tmpFile($Storage, $path, $tmpFolder = null) { * @link http://php.net/manual/en/function.sha1-file.php#104748 * @return string */ - public function getFileHash($file, $method = 'sha1') { + public function calculateFileHash($file, $method = 'sha1') { return StorageUtils::getFileHash($file, $method); } +/** + * Gets the hash for a file storage entity that is going to be stored. + * + * It first checks if hashing is enabled, if it is enabled it uses the the + * configured hashMethod to generate the hash and returns that hash. + * + * @param \Cake\ORM\Entity + * @param string $fileField + * @return null|string + */ + public function getFileHash(Entity $entity, $fileField) { + if ($this->config('fileHash') !== false) { + return $this->calculateFileHash( + $entity[$fileField]['tmp_name'], + $this->config('fileHash') + ); + } + return null; + } + /** * Creates a temporary file name and checks the tmp path, creates one if required. * diff --git a/src/Storage/Listener/LegacyLocalFileStorageListener.php b/src/Storage/Listener/LegacyLocalFileStorageListener.php index c2d7a0e2..af868514 100644 --- a/src/Storage/Listener/LegacyLocalFileStorageListener.php +++ b/src/Storage/Listener/LegacyLocalFileStorageListener.php @@ -31,8 +31,7 @@ class LegacyLocalFileStorageListener extends LocalListener { 'modelFolder' => false, 'preserveFilename' => true, 'randomPath' => 'crc32' - ], - 'imageProcessing' => false, + ] ]; /** @@ -43,42 +42,19 @@ class LegacyLocalFileStorageListener extends LocalListener { */ public function afterSave(Event $event) { if ($this->_checkEvent($event) && $event->data['record']->isNew()) { - $table = $event->subject(); - $entity = $event->data['record']; + if ($this->_checkEvent($event) && $event->data['record']->isNew()) { + $entity = $event->data['record']; + $fileField = $this->config('fileField'); - if (!empty($event->data['fileField'])) { - $this->config('fileField', $event->data['fileField']); - } - $fileField = $this->config('fileField'); - - if ($this->config('fileHash') !== false) { - $entity->hash = $this->getFileHash( - $entity[]['tmp_name'], - $this->config('fileHash') - ); - } + $this->entity['hash'] = $this->getHash($entity, $fileField); + $entity['path'] = $this->pathBuilder()->path($entity); - $entity['path'] = $this->pathBuilder()->path($entity); + if (!$this->_storeFile($entity)) { + return; + } - try { - $Storage = $this->storageAdapter($entity['adapter']); - $Storage->write($entity['path'], file_get_contents($entity[$fileField]['tmp_name']), true); - $table->save($entity, array( - 'checkRules' => false - )); - $event->result = true; - } catch (\Exception $e) { - $this->log($e->getMessage()); - $event->result = false; - return; + $event->stopPropagation(); } - - if ($this->_config['imageProcessing'] === true) { - $this->autoProcessImageVersions($entity, 'create'); - } - - $event->result = true; - $event->stopPropagation(); } } } diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 15d258ee..f062a350 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -6,9 +6,11 @@ */ namespace Burzum\FileStorage\Storage\Listener; +use Burzum\FileStorage\Storage\StorageException; use Cake\Core\Configure; use Cake\Event\Event; use Cake\Filesystem\Folder; +use Psr\Log\LogLevel; /** * Local FileStorage Event Listener for the CakePHP FileStorage plugin @@ -31,6 +33,7 @@ class LocalListener extends AbstractListener { 'pathBuilderOptions' => [ 'modelFolder' => true, ], + 'fileHash' => false, 'imageProcessing' => false, ]; @@ -55,12 +58,10 @@ class LocalListener extends AbstractListener { */ public function implementedEvents() { return [ - 'FileStorage.afterSave' => [ - 'callable' => 'afterSave', - ], - 'FileStorage.afterDelete' => [ - 'callable' => 'afterDelete', - ] + 'FileStorage.afterSave' => 'afterSave', + 'FileStorage.afterDelete' => 'afterDelete', + 'ImageVersion.removeVersion' => 'removeImageVersion', + 'ImageVersion.createVersion' => 'createImageVersion' ]; } @@ -70,6 +71,7 @@ public function implementedEvents() { * No need to use an adapter here, just delete the whole folder using cakes Folder class * * @param \Cake\Event\Event $event + * @throws \Burzum\Filestorage\Storage\StorageException * @return void */ public function afterDelete(Event $event) { @@ -85,7 +87,8 @@ public function afterDelete(Event $event) { return; } } catch (\Exception $e) { - $this->log($e->getMessage()); + $this->log($e->getMessage(), LOG_ERR, ['scope' => ['storage']]); + throw new StorageException($e->getMessage()); } $event->result = false; $event->stopPropagation(); @@ -100,34 +103,14 @@ public function afterDelete(Event $event) { */ public function afterSave(Event $event) { if ($this->_checkEvent($event) && $event->data['record']->isNew()) { - $table = $event->subject(); $entity = $event->data['record']; - - if (!empty($event->data['fileField'])) { - $this->config('fileField', $event->data['fileField']); - } $fileField = $this->config('fileField'); - if ($this->config('fileHash') !== false) { - $entity->hash = $this->getFileHash( - $entity[$fileField]['tmp_name'], - $this->config('fileHash') - ); - } - + $entity['hash'] = $this->getFileHash($entity, $fileField); $entity['path'] = $this->pathBuilder()->fullPath($entity); - try { - $Storage = $this->storageAdapter($entity['adapter']); - $Storage->write($entity['path'], file_get_contents($entity[$fileField]['tmp_name']), true); - $table->save($entity, array( - 'checkRules' => false - )); - $event->result = true; - } catch (\Exception $e) { - $this->log($e->getMessage()); - $event->result = false; - + if (!$this->_storeFile($event)) { + return; } if ($this->_config['imageProcessing'] === true) { @@ -137,4 +120,54 @@ public function afterSave(Event $event) { $event->stopPropagation(); } } + +/** + * Stores the file in the configured storage backend. + * + * @param $event \Cake\Event\Event $event + * @throws \Burzum\Filestorage\Storage\StorageException + * @return boolean + */ + protected function _storeFile(Event $event) { + try { + $fileField = $this->config('fileField'); + $entity = $event->data['record']; + $Storage = $this->storageAdapter($entity['adapter']); + $Storage->write($entity['path'], file_get_contents($entity[$fileField]['tmp_name']), true); + $event->result = $event->subject()->save($entity, array( + 'checkRules' => false + )); + return true; + } catch (\Exception $e) { + $this->log($e->getMessage(), LogLevel::ERROR, ['scope' => ['storage']]); + throw new StorageException($e->getMessage()); + } + } + +/** + * + */ + public function removeImageVersion(Event $event) { + $this->_processImages($event, 'removeImageVersions'); + } + +/** + * + */ + public function createImageVersion(Event $event) { + $this->_processImages($event, 'createImageVersions'); + } + +/** + * + */ + protected function _processImages(Event $event, $method) { + if ($this->config('imageProcessing') !== true) { + return; + } + $event->result = $this->{$method}( + $event->data['record'], + $event->data['operations'] + ); + } } diff --git a/src/Storage/StorageUtils.php b/src/Storage/StorageUtils.php index b14a3e9a..f24d6ced 100644 --- a/src/Storage/StorageUtils.php +++ b/src/Storage/StorageUtils.php @@ -134,7 +134,7 @@ public static function generateHashes($configPath = 'FileStorage') { $imageSizes = Configure::read($configPath . '.imageSizes'); } if (is_null($imageSizes)) { - throw new \RuntimeException(sprintf('Image processing configuration in %s is missing!', $configPath . '.imageSizes')); + throw new \RuntimeException(sprintf('Image processing configuration in "%s" is missing!', $configPath . '.imageSizes')); } self::ksortRecursive($imageSizes); foreach ($imageSizes as $model => $version) { diff --git a/tests/TestCase/Storage/Listener/LocalListenerTest.php b/tests/TestCase/Storage/Listener/LocalListenerTest.php index 91f38b39..c1ecbff0 100644 --- a/tests/TestCase/Storage/Listener/LocalListenerTest.php +++ b/tests/TestCase/Storage/Listener/LocalListenerTest.php @@ -48,7 +48,10 @@ public function setUp() { public function testAfterSave() { $entity = $this->FileStorage->get('file-storage-3'); $entity->isNew(true); - $entity->file = ['tmp_name' => $this->fileFixtures . 'titus.jpg']; + $entity->file = [ + 'name' => 'titus.jpg', + 'tmp_name' => $this->fileFixtures . 'titus.jpg' + ]; $event = new Event('FileStorage.afterSave', $this->FileStorage, [ 'record' => $entity ]); diff --git a/tests/TestCase/Storage/StorageException.php b/tests/TestCase/Storage/StorageException.php new file mode 100644 index 00000000..17b7c458 --- /dev/null +++ b/tests/TestCase/Storage/StorageException.php @@ -0,0 +1,18 @@ +_entity = $entity; + } + + public function getEntity() { + return $this->_entity; + } +} From b3441664806fc5954577acb9bda3a79311e7bf0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Fri, 17 Jul 2015 12:55:39 +0200 Subject: [PATCH 053/144] #67 Fixing the documentation. --- docs/Documentation/Specific-Adapter-Configurations.md | 2 +- docs/Tutorials/Quick-Start.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Documentation/Specific-Adapter-Configurations.md b/docs/Documentation/Specific-Adapter-Configurations.md index fb2da749..1dfa32d6 100644 --- a/docs/Documentation/Specific-Adapter-Configurations.md +++ b/docs/Documentation/Specific-Adapter-Configurations.md @@ -61,7 +61,7 @@ StorageManager::config('S3Image', array( true ), 'adapterClass' => '\Gaufrette\Adapter\AwsS3', - 'class' => '\Gaufrette\FileSystem') + 'class' => '\Gaufrette\Filesystem') ); ``` diff --git a/docs/Tutorials/Quick-Start.md b/docs/Tutorials/Quick-Start.md index 2c92034d..7435eda4 100644 --- a/docs/Tutorials/Quick-Start.md +++ b/docs/Tutorials/Quick-Start.md @@ -79,7 +79,7 @@ StorageManager::config('S3Image', array( true ), 'adapterClass' => '\Gaufrette\Adapter\AwsS3', - 'class' => '\Gaufrette\FileSystem') + 'class' => '\Gaufrette\Filesystem') ); ``` From deb9bd2d72605e352cd3f9c888ee71fb6097ef1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 20 Jul 2015 00:31:39 +0200 Subject: [PATCH 054/144] Working on the tests. --- docs/Tutorials/Quick-Start.md | 4 +- src/Storage/StorageUtils.php | 2 +- .../Lib/Utility/FileStorageUtilsTest.php | 92 ++++++++++++++++--- tests/TestCase/Storage/StorageTraitTest.php | 10 ++ 4 files changed, 94 insertions(+), 14 deletions(-) diff --git a/docs/Tutorials/Quick-Start.md b/docs/Tutorials/Quick-Start.md index 7435eda4..6bf70ae6 100644 --- a/docs/Tutorials/Quick-Start.md +++ b/docs/Tutorials/Quick-Start.md @@ -42,7 +42,9 @@ Configure::write('FileStorage', array( 'thumbnail' => array( 'mode' => 'inbound', 'width' => 800, - 'height' => 800)), + 'height' => 800 + ) + ), 'medium' => array( 'thumbnail' => array( 'mode' => 'inbound', diff --git a/src/Storage/StorageUtils.php b/src/Storage/StorageUtils.php index f24d6ced..b957669f 100644 --- a/src/Storage/StorageUtils.php +++ b/src/Storage/StorageUtils.php @@ -14,7 +14,7 @@ class StorageUtils { /** - * Return file extension from a given filename + * Return file extension from a given filename. * * @param string $name * @param boolean $realFile diff --git a/tests/TestCase/Lib/Utility/FileStorageUtilsTest.php b/tests/TestCase/Lib/Utility/FileStorageUtilsTest.php index f2b3d025..ac97929f 100644 --- a/tests/TestCase/Lib/Utility/FileStorageUtilsTest.php +++ b/tests/TestCase/Lib/Utility/FileStorageUtilsTest.php @@ -2,17 +2,23 @@ namespace Burzum\FileStorage\Test\TestCase\Lib\Utility; use Cake\Core\Configure; +use Cake\Core\Plugin; use Burzum\FileStorage\TestSuite\FileStorageTestCase; -use Burzum\FileStorage\Lib\FileStorageUtils; +use Burzum\FileStorage\Storage\StorageUtils; /** - * StorageManagerTest + * Storage Utils Test * * @author Florian Krämer * @copyright 2012 - 2015 Florian Krämer * @license MIT */ -class FileStorageUtilsTest extends FileStorageTestCase { +class StorageUtilsTest extends FileStorageTestCase { + + public function setUp() { + parent::setUp(); + $this->fileFixtures = Plugin::path('Burzum/FileStorage') . 'tests' . DS . 'Fixture' . DS . 'File' . DS; + } /** * testRandomPath @@ -22,10 +28,10 @@ class FileStorageUtilsTest extends FileStorageTestCase { public function testRandomPath() { $this->skipIf(PHP_INT_SIZE === 8); - $result = FileStorageUtils::randomPath('someteststring'); + $result = StorageUtils::randomPath('someteststring'); $this->assertEquals($result, '38' . DS . '88' . DS . '98' . DS); - $result = FileStorageUtils::randomPath('file-storage-3'); + $result = StorageUtils::randomPath('file-storage-3'); $this->assertEquals($result, '48' . DS . '75' . DS . '05' . DS); } @@ -35,7 +41,7 @@ public function testRandomPath() { * @return void */ public function testTrimPath() { - $result = FileStorageUtils::trimPath('foobar/'); + $result = StorageUtils::trimPath('foobar/'); $this->assertEquals($result, 'foobar'); } @@ -46,14 +52,19 @@ public function testTrimPath() { */ public function testNormalizePath() { if (DS == '\\') { - $result = FileStorageUtils::normalizePath('/nice/path/test'); + $result = StorageUtils::normalizePath('/nice/path/test'); $this->assertEquals($result, '\nice\path\test'); } else { - $result = FileStorageUtils::normalizePath('\nice\path\test'); + $result = StorageUtils::normalizePath('\nice\path\test'); $this->assertEquals($result, '/nice/path/test'); } } +/** + * testNormalizeGlobalFilesArray + * + * @return void + */ public function testNormalizeGlobalFilesArray() { $data = array( 'name' => array @@ -114,12 +125,20 @@ public function testNormalizeGlobalFilesArray() { ] ] ]; - $result = FileStorageUtils::normalizeGlobalFilesArray($data); + $result = StorageUtils::normalizeGlobalFilesArray($data); $this->assertEquals($result, $expected); + + $result = StorageUtils::normalizeGlobalFilesArray([]); + $this->assertEquals($result, []); } +/** + * testHashOperations + * + * @return void + */ public function testHashOperations() { - $result = FileStorageUtils::hashOperations(array( + $result = StorageUtils::hashOperations(array( 'mode' => 'inbound', 'width' => 80, 'height' => 80 @@ -127,6 +146,11 @@ public function testHashOperations() { $this->assertEquals($result, '8c70933e'); } +/** + * testGenerateHashes + * + * @return void + */ public function testGenerateHashes() { Configure::write('FileStorage.imageSizes', array( 'Test' => array( @@ -161,11 +185,34 @@ public function testGenerateHashes() { 'small' => '19e760eb' ] ]; - FileStorageUtils::generateHashes(); + StorageUtils::generateHashes(); $result = Configure::read('FileStorage.imageHashes'); $this->assertEquals($result, $expected); } +/** + * testGenerateHashesRuntimeException + * + * @expectedException \RuntimeException + */ + public function testGenerateHashesRuntimeException() { + Configure::write('FileStorage.imageSizes', null); + StorageUtils::generateHashes(); + } + +/** + * testFileExtension + * + * @return void + */ + public function testFileExtension() { + $result = StorageUtils::fileExtension($this->fileFixtures . 'titus.jpg', true); + $this->assertEquals($result, 'jpg'); + + $result = StorageUtils::fileExtension('something.else'); + $this->assertEquals($result, 'else'); + } + /** * testUploadArray * @@ -179,8 +226,29 @@ public function testUploadArray() { 'type' => 'image/jpeg', 'size' => 332643 ]; - $result = FileStorageUtils::uploadArray($this->fileFixtures . 'titus.jpg'); + $result = StorageUtils::uploadArray($this->fileFixtures . 'titus.jpg'); $this->assertEquals($result, $expected); } +/** + * testGetFileHash + * + * @return void + */ + public function testGetFileHash() { + $result = StorageUtils::getFileHash($this->fileFixtures . 'titus.jpg'); + $this->assertEquals($result, 'd68da24d79835d70d5d8a544f62616d0e51af191'); + + $result = StorageUtils::getFileHash($this->fileFixtures . 'titus.jpg', 'md5'); + $this->assertEquals($result, '29574141b2c44cc029828f6c5c6d3cd2'); + } + +/** + * testGetFileHashInvalidArgumentException + * + * @expectedException \InvalidArgumentException + */ + public function testGetFileHashInvalidArgumentException() { + StorageUtils::getFileHash($this->fileFixtures . 'titus.jpg', 'invalid-hash-method!'); + } } diff --git a/tests/TestCase/Storage/StorageTraitTest.php b/tests/TestCase/Storage/StorageTraitTest.php index 6a02fe9a..37615ea8 100644 --- a/tests/TestCase/Storage/StorageTraitTest.php +++ b/tests/TestCase/Storage/StorageTraitTest.php @@ -40,4 +40,14 @@ public function testGetStorageAdapter() { $result = $this->StorageTrait->storageAdapter('Local'); $this->assertTrue(is_a($result, '\Gaufrette\Filesystem')); } + +/** + * testBeforeDelete + * + * @return void + */ + public function testGetStorageManagerInstance() { + $result = $this->StorageTrait->storageManager(); + $this->assertTrue(is_a($result, '\Burzum\FileStorage\Storage\StorageManager')); + } } From a49412136042b26fa79e305a89586b91861ca8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 20 Jul 2015 11:21:52 +0200 Subject: [PATCH 055/144] Working on the new shell. --- src/Shell/StorageShell.php | 33 +++++++ src/Shell/Task/ImageTask.php | 185 +++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 src/Shell/StorageShell.php create mode 100644 src/Shell/Task/ImageTask.php diff --git a/src/Shell/StorageShell.php b/src/Shell/StorageShell.php new file mode 100644 index 00000000..d762470c --- /dev/null +++ b/src/Shell/StorageShell.php @@ -0,0 +1,33 @@ +addSubcommand('image', [ + 'help' => __('Image Processing Task.'), + 'parser' => $this->Image->getOptionParser() + ]); + return $parser; + } +} diff --git a/src/Shell/Task/ImageTask.php b/src/Shell/Task/ImageTask.php new file mode 100644 index 00000000..b0cc9f89 --- /dev/null +++ b/src/Shell/Task/ImageTask.php @@ -0,0 +1,185 @@ + + * bin\cake burzum/FileStorage.storage image remove ProfilePicture "thumb60, crop50" + */ +class ImageTask extends Shell { + + use StorageTrait; + use EventManagerTrait; + + public function initialize() { + $this->Table = TableRegistry::get('Burzum/FileStorage.ImageStorage'); + } + +/** + * Remove image versions. + * + * @return void + */ + public function remove() { + $this->_loop($this->args[0], explode(',', $this->args[1]), 'remove'); + } + +/** + * Create image versions. + * + * @return void + */ + public function generate() { + $this->_loop($this->args[0], explode(',', $this->args[1]), 'generate'); + } + +/** + * Loops through image records and performs requested operations on them. + * + * @param string $identifier + * @return void + */ + protected function _loop($identifier, $options, $action) { + $count = $this->_getCount($identifier); + $offset = 0; + $limit = $this->params['limit']; + + $this->out(__d('file_storage', '{0} record(s) will be processed.' . "\n", $count)); + + do { + $records = $this->_getRecords($identifier, $limit, $offset); + if (!empty($records)) { + foreach ($records as $record) { + $method = '_' . $action . 'Image'; + try { + $this->{$method}($record, $options); + } catch (StorageException $e) { + $this->err($e->getMessage()); + } + } + } + $offset += $limit; + $this->out(__d('file_storage', '{0} of {1} records processed.', [$limit, $count])); + } while ($records->count() > 0); + } + +/** + * Triggers the event to remove image versions. + * + * @param \Cake\ORM\Entity + * @param array + * @return void + */ + protected function _removeImage($record, $options) { + $Event = new Event('ImageVersion.removeVersion', $this->Table, [ + 'record' => $record, + 'operations' => $options + ]); + EventManager::instance()->dispatch($Event); + } + +/** + * Triggers the event to generate the new images. + * + * @param \Cake\ORM\Entity + * @param array + * @return void + */ + protected function _generateImage($record, $options) { + $Event = new Event('ImageVersion.createVersion', $this->Table, [ + 'record' => $record, + 'operations' => $options + ]); + EventManager::instance()->dispatch($Event); + } + +/** + * Gets the records for the loop. + * + * @param string $identifier + * @param integer $limit + * @param integer $offset + * @return \Cake\ORM\ResultSet + */ + public function _getRecords($identifier, $limit, $offset) { + return $this->Table + ->find() + ->where([$this->Table->alias() . '.model' => $identifier]) + ->limit($limit) + ->offset($offset) + ->all(); + } + +/** + * Gets the amount of records for an identifier in the DB. + * + * @param string $identifier + * @return integer + */ + protected function _getCount($identifier) { + $count = $this->_getCountQuery($identifier)->count(); + if ($count === 0) { + $this->out(__d('file_storage', 'No records for identifier "{0}" found.', $identifier)); + $this->_stop(); + } + return $count; + } + +/** + * Gets the query object for the count. + * + * @param string $identifier + * @return \Cake\ORM\Query + */ + protected function _getCountQuery($identifier) { + return $this->Table + ->find() + ->where([$this->Table->alias() . '.model' => $identifier]); + } + + public function getOptionParser() { + $parser = parent::getOptionParser(); + $parser->addOption('model', [ + 'short' => 'm', + 'help' => __('The model to use.'), + 'default' => 'Burzum/FileStorage.ImageStorage' + ]); + $parser->addOption('limit', [ + 'short' => 'l', + 'help' => __('The limit of records to process in a batch.'), + 'default' => 50 + ]); + $parser->addOption('versions', [ + 'short' => 's', + 'help' => __('The model to use.'), + 'default' => 'Burzum/FileStorage.ImageStorage' + ]) + ->addSubcommand('remove', [ + 'remove' => 'Remove image versions.' + ]) + ->addSubcommand('generate', [ + 'remove' => 'Generate image versions.' + ]); + $parser->addArguments([ + 'identifier' => ['help' => 'The identifier to process', 'required' => true], + 'versions' => ['help' => 'The identifier to process', 'required' => true], + ]); + return $parser; + } +} From 4385b054ef64789e9047ee1950d52d0270321444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 3 Aug 2015 10:24:23 +0200 Subject: [PATCH 056/144] Changing class / interface for entity and event. As suggested in #68. --- src/Model/Table/FileStorageTable.php | 44 +++++++++---------- src/Model/Table/ImageStorageTable.php | 36 +++++++-------- .../Storage/StorageException.php | 0 3 files changed, 38 insertions(+), 42 deletions(-) rename {tests/TestCase => src}/Storage/StorageException.php (100%) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index 7c6173a6..112f9cc4 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -2,12 +2,14 @@ namespace Burzum\FileStorage\Model\Table; use Cake\Database\Query; -use Cake\Log\LogTrait; -use Cake\ORM\Table; -use Cake\ORM\Entity; +use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Cake\Event\EventManager; use Cake\Filesystem\File; +use Cake\Log\LogTrait; +use Cake\ORM\Table; +use Cake\ORM\Entity; + use Burzum\FileStorage\Storage\StorageTrait; /** @@ -68,17 +70,16 @@ public function configureUploadValidation($options) { * beforeSave callback * * @param \Cake\Event\Event $event - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return boolean true on success */ - public function beforeSave(Event $event, Entity $entity, $options) { + public function beforeSave(Event $event, EntityInterface $entity, $options) { $this->getFileInfoFromUpload($event->data['entity']); - $Event = new Event('FileStorage.beforeSave', $this, array( + $Event = $this->dispatchEvent('FileStorage.beforeSave', array( 'record' => $entity, 'storage' => $this->storageAdapter($event->data['entity']['adapter']) )); - $this->eventManager()->dispatch($Event); if ($Event->isStopped()) { return false; } @@ -94,11 +95,11 @@ public function beforeSave(Event $event, Entity $entity, $options) { * - sets the adapter by default to local if not already set * - sets the model field to the table name if not already set * - * @param \Cake\ORM\Entity + * @param \Cake\Datasource\EntityInterface * @param string $field * @return void */ - public function getFileInfoFromUpload(&$entity, $field = 'file') { + public function getFileInfoFromUpload(EntityInterface &$entity, $field = 'file') { if (!empty($entity[$field]['tmp_name'])) { $File = new File($entity[$field]['tmp_name']); $entity['filesize'] = $File->size(); @@ -119,18 +120,17 @@ public function getFileInfoFromUpload(&$entity, $field = 'file') { /** * afterSave callback * - * @param Event $event - * @param Entity $entity + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return boolean */ - public function afterSave(Event $event, Entity $entity, $options) { - $Event = new Event('FileStorage.afterSave', $this, [ + public function afterSave(Event $event, EntityInterface $entity, $options) { + $this->dispatchEvent('FileStorage.afterSave', [ 'created' => $event->data['entity']->isNew(), 'record' => $entity, 'storage' => $this->storageAdapter($event->data['entity']['adapter']) ]); - $this->eventManager()->dispatch($Event); $this->deleteOldFileOnSave($entity); return true; } @@ -139,10 +139,10 @@ public function afterSave(Event $event, Entity $entity, $options) { * Get a copy of the actual record before we delete it to have it present in afterDelete * * @param \Cake\Event\Event $event - * @param Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @return boolean */ - public function beforeDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity) { + public function beforeDelete(Event $event, EntityInterface $entity) { $this->record = $this->find() ->contain([]) ->where([ @@ -161,16 +161,15 @@ public function beforeDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity) * afterDelete callback * * @param \Cake\Event\Event $event - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return boolean */ - public function afterDelete(\Cake\Event\Event $event, Entity $entity, $options) { - $Event = new Event('FileStorage.afterDelete', $this, [ + public function afterDelete(Event $event, EntityInterface $entity, $options) { + $this->dispatchEvent('FileStorage.afterDelete', [ 'record' => $entity, 'storage' => $this->storageAdapter($entity['adapter']) ]); - $this->eventManager()->dispatch($Event); return true; } @@ -182,10 +181,11 @@ public function afterDelete(\Cake\Event\Event $event, Entity $entity, $options) * * The old id has to be the UUID of the file_storage record that should be deleted. * - * @param string $oldIdField Name of the field in the data that holds the old id + * @param \Cake\Datasource\EntityInterface $entity + * @param string $oldIdField Name of the field in the data that holds the old id. * @return boolean Returns true if the old record was deleted */ - public function deleteOldFileOnSave(Entity $entity, $oldIdField = 'old_file_id') { + public function deleteOldFileOnSave(EntityInterface $entity, $oldIdField = 'old_file_id') { if (!empty($entity[$oldIdField]) && $entity['model']) { $oldEntity = $this->find() ->contain([]) diff --git a/src/Model/Table/ImageStorageTable.php b/src/Model/Table/ImageStorageTable.php index 56f3dca0..5b322c0c 100644 --- a/src/Model/Table/ImageStorageTable.php +++ b/src/Model/Table/ImageStorageTable.php @@ -2,6 +2,7 @@ namespace Burzum\FileStorage\Model\Table; use Cake\Core\Configure; +use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Cake\Validation\Validation; @@ -43,18 +44,17 @@ public function initialize(array $config) { * beforeSave callback * * @param \Cake\Event\Event $event - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return boolean true on success */ - public function beforeSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $options) { + public function beforeSave(Event $event, EntityInterface $entity, $options) { if (!parent::beforeSave($event, $entity, $options)) { return false; } - $Event = new Event('ImageStorage.beforeSave', $this, array( + $Event = $this->dispatchEvent('ImageStorage.beforeSave', array( 'record' => $entity )); - $this->eventManager()->dispatch($Event); if ($Event->isStopped()) { return false; } @@ -67,17 +67,16 @@ public function beforeSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $ * Does not call the parent to avoid that the regular file storage event listener saves the image already * * @param \Cake\Event\Event $event - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return boolean */ - public function afterSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $options) { + public function afterSave(\Cake\Event\Event $event, EntityInterface $entity, $options) { if ($entity->isNew()) { - $imageEvent = new Event('ImageStorage.afterSave', $this, [ + $this->dispatchEvent('ImageStorage.afterSave', [ 'storage' => $this->storageAdapter($entity['adapter']), 'record' => $entity ]); - $this->eventManager()->dispatch($imageEvent); $this->deleteOldFileOnSave($entity); } return true; @@ -87,19 +86,18 @@ public function afterSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $o * Get a copy of the actual record before we delete it to have it present in afterDelete * * @param \Cake\Event\Event $event - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @return boolean */ - public function beforeDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity) { + public function beforeDelete(\Cake\Event\Event $event, EntityInterface $entity) { if (!parent::beforeDelete($event, $entity)) { return false; } - $imageEvent = new Event('ImageStorage.beforeDelete', $this, [ + $imageEvent = $this->dispatchEvent('ImageStorage.beforeDelete', [ 'record' => $this->record, 'storage' => $this->storageAdapter($this->record['adapter']) ]); - $this->eventManager()->dispatch($imageEvent); if ($imageEvent->isStopped()) { return false; @@ -114,16 +112,15 @@ public function beforeDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity) * Note that we do not call the parent::afterDelete(), we just want to trigger the ImageStorage.afterDelete event but not the FileStorage.afterDelete at the same time! * * @param \Cake\Event\Event $event - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return boolean */ - public function afterDelete(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, $options) { - $imageEvent = new Event('ImageStorage.afterDelete', $this, [ + public function afterDelete(\Cake\Event\Event $event, EntityInterface $entity, $options) { + $this->dispatchEvent('ImageStorage.afterDelete', [ 'record' => $entity, 'storage' => $this->storageAdapter($entity['adapter']) ]); - $this->eventManager()->dispatch($imageEvent); return true; } @@ -192,24 +189,23 @@ public function validateImageSize($check, array $options = []) { * already fulfills the purpose. I might rename this event in the 3.0 version of * the plugin to a more generic one. * - * @param array $entity An ImageStorage database record + * @param \Cake\Datasource\EntityInterface $entity An ImageStorage database record * @param array $options. Options for the version. * @return array A list of versions for this image file. Key is the version, value is the path or URL to that image. */ - public function getImageVersions($entity, $options = []) { + public function getImageVersions(EntityInterface $entity, $options = []) { $versions = []; $versionData = (array)Configure::read('FileStorage.imageSizes.' . $entity['model']); $versionData['original'] = isset($options['originalVersion']) ? $options['originalVersion'] : 'original'; foreach ($versionData as $version => $data) { $hash = Configure::read('FileStorage.imageHashes.' . $entity['model'] . '.' . $version); - $Event = new Event('ImageVersion.getVersions', $this, [ + $Event = $this->dispatchEvent('ImageVersion.getVersions', [ 'hash' => $hash, 'image' => $entity, 'version' => $version, 'options' => [] ] ); - $this->eventManager()->dispatch($Event); if ($Event->isStopped()) { $versions[$version] = str_replace('\\', '/', $Event->data['path']); } diff --git a/tests/TestCase/Storage/StorageException.php b/src/Storage/StorageException.php similarity index 100% rename from tests/TestCase/Storage/StorageException.php rename to src/Storage/StorageException.php From 861bc2a47a483cdc41f61aa174e4879772feb112 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 12:35:09 +0200 Subject: [PATCH 057/144] FileStorageTable improvements. --- src/Model/Table/FileStorageTable.php | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index 112f9cc4..d747b13a 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -39,7 +39,7 @@ class FileStorageTable extends Table { * * @var array */ - public $record = array(); + public $record = []; /** * Initialize @@ -75,12 +75,12 @@ public function configureUploadValidation($options) { * @return boolean true on success */ public function beforeSave(Event $event, EntityInterface $entity, $options) { - $this->getFileInfoFromUpload($event->data['entity']); - $Event = $this->dispatchEvent('FileStorage.beforeSave', array( + $this->getFileInfoFromUpload($entity); + $storageEvent = $this->dispatchEvent('FileStorage.beforeSave', array( 'record' => $entity, - 'storage' => $this->storageAdapter($event->data['entity']['adapter']) + 'storage' => $this->storageAdapter($entity->get('adapter')) )); - if ($Event->isStopped()) { + if ($storageEvent->isStopped()) { return false; } return true; @@ -127,9 +127,9 @@ public function getFileInfoFromUpload(EntityInterface &$entity, $field = 'file') */ public function afterSave(Event $event, EntityInterface $entity, $options) { $this->dispatchEvent('FileStorage.afterSave', [ - 'created' => $event->data['entity']->isNew(), + 'created' => $entity->isNew(), 'record' => $entity, - 'storage' => $this->storageAdapter($event->data['entity']['adapter']) + 'storage' => $this->storageAdapter($entity->get('adapter')) ]); $this->deleteOldFileOnSave($entity); return true; @@ -143,10 +143,11 @@ public function afterSave(Event $event, EntityInterface $entity, $options) { * @return boolean */ public function beforeDelete(Event $event, EntityInterface $entity) { + $primaryKey = $this->primaryKey(); $this->record = $this->find() ->contain([]) ->where([ - $this->alias() . '.' . $this->primaryKey() => $entity->{$this->primaryKey()} + $this->aliasField($primaryKey) => $entity->get($primaryKey) ]) ->first(); @@ -168,7 +169,7 @@ public function beforeDelete(Event $event, EntityInterface $entity) { public function afterDelete(Event $event, EntityInterface $entity, $options) { $this->dispatchEvent('FileStorage.afterDelete', [ 'record' => $entity, - 'storage' => $this->storageAdapter($entity['adapter']) + 'storage' => $this->storageAdapter($entity->get('adapter')) ]); return true; } @@ -186,11 +187,12 @@ public function afterDelete(Event $event, EntityInterface $entity, $options) { * @return boolean Returns true if the old record was deleted */ public function deleteOldFileOnSave(EntityInterface $entity, $oldIdField = 'old_file_id') { - if (!empty($entity[$oldIdField]) && $entity['model']) { + if (!empty($entity->get($oldIdField)) && $entity->get('model')) { $oldEntity = $this->find() ->contain([]) ->where([ - $this->alias() . '.' . $this->primaryKey() => $entity[$oldIdField], 'model' => $entity['model'] + $this->aliasField($this->primaryKey()) => $entity->get($oldIdField), + 'model' => $entity->get('model') ]) ->first(); if (!empty($oldEntity)) { From e85c502dd7e6754a9307430935314955e2e6f920 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 12:38:29 +0200 Subject: [PATCH 058/144] ImageStorageTable improvements. --- src/Model/Table/ImageStorageTable.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Model/Table/ImageStorageTable.php b/src/Model/Table/ImageStorageTable.php index 5b322c0c..ca98be55 100644 --- a/src/Model/Table/ImageStorageTable.php +++ b/src/Model/Table/ImageStorageTable.php @@ -52,10 +52,10 @@ public function beforeSave(Event $event, EntityInterface $entity, $options) { if (!parent::beforeSave($event, $entity, $options)) { return false; } - $Event = $this->dispatchEvent('ImageStorage.beforeSave', array( + $imageEvent = $this->dispatchEvent('ImageStorage.beforeSave', array( 'record' => $entity )); - if ($Event->isStopped()) { + if ($imageEvent->isStopped()) { return false; } return true; @@ -71,10 +71,10 @@ public function beforeSave(Event $event, EntityInterface $entity, $options) { * @param array $options * @return boolean */ - public function afterSave(\Cake\Event\Event $event, EntityInterface $entity, $options) { + public function afterSave(Event $event, EntityInterface $entity, $options) { if ($entity->isNew()) { $this->dispatchEvent('ImageStorage.afterSave', [ - 'storage' => $this->storageAdapter($entity['adapter']), + 'storage' => $this->storageAdapter($entity->get('adapter')), 'record' => $entity ]); $this->deleteOldFileOnSave($entity); @@ -89,7 +89,7 @@ public function afterSave(\Cake\Event\Event $event, EntityInterface $entity, $op * @param \Cake\Datasource\EntityInterface $entity * @return boolean */ - public function beforeDelete(\Cake\Event\Event $event, EntityInterface $entity) { + public function beforeDelete(Event $event, EntityInterface $entity) { if (!parent::beforeDelete($event, $entity)) { return false; } @@ -116,10 +116,10 @@ public function beforeDelete(\Cake\Event\Event $event, EntityInterface $entity) * @param array $options * @return boolean */ - public function afterDelete(\Cake\Event\Event $event, EntityInterface $entity, $options) { + public function afterDelete(Event $event, EntityInterface $entity, $options) { $this->dispatchEvent('ImageStorage.afterDelete', [ 'record' => $entity, - 'storage' => $this->storageAdapter($entity['adapter']) + 'storage' => $this->storageAdapter($entity->get('adapter')) ]); return true; } @@ -190,24 +190,24 @@ public function validateImageSize($check, array $options = []) { * the plugin to a more generic one. * * @param \Cake\Datasource\EntityInterface $entity An ImageStorage database record - * @param array $options. Options for the version. + * @param array $options Options for the version. * @return array A list of versions for this image file. Key is the version, value is the path or URL to that image. */ public function getImageVersions(EntityInterface $entity, $options = []) { $versions = []; - $versionData = (array)Configure::read('FileStorage.imageSizes.' . $entity['model']); + $versionData = (array)Configure::read('FileStorage.imageSizes.' . $entity->get('model')); $versionData['original'] = isset($options['originalVersion']) ? $options['originalVersion'] : 'original'; foreach ($versionData as $version => $data) { - $hash = Configure::read('FileStorage.imageHashes.' . $entity['model'] . '.' . $version); - $Event = $this->dispatchEvent('ImageVersion.getVersions', [ + $hash = Configure::read('FileStorage.imageHashes.' . $entity->get('model') . '.' . $version); + $event = $this->dispatchEvent('ImageVersion.getVersions', [ 'hash' => $hash, 'image' => $entity, 'version' => $version, 'options' => [] ] ); - if ($Event->isStopped()) { - $versions[$version] = str_replace('\\', '/', $Event->data['path']); + if ($event->isStopped()) { + $versions[$version] = str_replace('\\', '/', $event->data['path']); } } return $versions; From 9cb16d36ef518088642831240461ccb59bc9ad77 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 12:47:56 +0200 Subject: [PATCH 059/144] PathBuilder improvements. --- src/Storage/PathBuilder/BasePathBuilder.php | 26 +++++++++---------- src/Storage/PathBuilder/LocalPathBuilder.php | 2 -- .../PathBuilder/PathBuilderInterface.php | 18 ++++++------- src/Storage/PathBuilder/S3PathBuilder.php | 11 ++++---- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index 8c0808c9..993381a6 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -7,7 +7,7 @@ namespace Burzum\FileStorage\Storage\PathBuilder; use Cake\Core\InstanceConfigTrait; -use Cake\ORM\Entity; +use Cake\Datasource\EntityInterface; use Burzum\FileStorage\Storage\StorageUtils; /** @@ -58,11 +58,11 @@ public function stripDashes($uuid) { /** * Builds the path under which the data gets stored in the storage adapter. * - * @param Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return string */ - public function path(Entity $entity, array $options = []) { + public function path(EntityInterface $entity, array $options = []) { $config = array_merge($this->config(), $options); $path = ''; if (!empty($config['pathPrefix']) && is_string($config['pathPrefix'])) { @@ -111,11 +111,11 @@ public function splitFilename($filename, $keepDot = false) { /** * Builds the filename of under which the data gets saved in the storage adapter. * - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return string */ - public function filename(Entity $entity, array $options = []) { + public function filename(EntityInterface $entity, array $options = []) { $config = array_merge($this->config(), $options); if ($config['preserveFilename'] === true) { return $this->_preserveFilename($entity, $config); @@ -132,11 +132,11 @@ public function filename(Entity $entity, array $options = []) { * * The filePrefix and fileSuffix options are also supported. * - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return string */ - protected function _buildFilename(Entity $entity, array $options = []) { + protected function _buildFilename(EntityInterface $entity, array $options = []) { $filename = $entity->id; if ($options['stripUuid'] === true) { $filename = $this->stripDashes($filename); @@ -158,11 +158,11 @@ protected function _buildFilename(Entity $entity, array $options = []) { * * This can be useful to create versions of files for example. * - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return string */ - protected function _preserveFilename(Entity $entity, array $options = []) { + protected function _preserveFilename(EntityInterface $entity, array $options = []) { $filename = $entity['filename']; if (!empty($options['filePrefix'])) { $filename = $options['filePrefix'] . $entity['filename']; @@ -177,11 +177,11 @@ protected function _preserveFilename(Entity $entity, array $options = []) { /** * Returns the path + filename. * - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return string */ - public function fullPath(Entity $entity, array $options = []) { + public function fullPath(EntityInterface $entity, array $options = []) { return $this->path($entity, $options) . $this->filename($entity, $options); } @@ -191,11 +191,11 @@ public function fullPath(Entity $entity, array $options = []) { * This is for example important for S3 and Dropbox but also the Local adapter * if you symlink a folder to your webroot and allow direct access to a file. * - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return string */ - public function url(Entity $entity, array $options = []) { + public function url(EntityInterface $entity, array $options = []) { $url = $this->path($entity) . $this->filename($entity); return str_replace('\\', '/', $url); } diff --git a/src/Storage/PathBuilder/LocalPathBuilder.php b/src/Storage/PathBuilder/LocalPathBuilder.php index b6d98934..0bf193a6 100644 --- a/src/Storage/PathBuilder/LocalPathBuilder.php +++ b/src/Storage/PathBuilder/LocalPathBuilder.php @@ -6,8 +6,6 @@ */ namespace Burzum\FileStorage\Storage\PathBuilder; -use Cake\ORM\Entity; - class LocalPathBuilder extends BasePathBuilder { } diff --git a/src/Storage/PathBuilder/PathBuilderInterface.php b/src/Storage/PathBuilder/PathBuilderInterface.php index e9e14fc1..f41cbdb7 100644 --- a/src/Storage/PathBuilder/PathBuilderInterface.php +++ b/src/Storage/PathBuilder/PathBuilderInterface.php @@ -6,36 +6,36 @@ */ namespace Burzum\FileStorage\Storage\PathBuilder; -use \Cake\ORM\Entity; +use \Cake\Datasource\EntityInterface; interface PathBuilderInterface { /** * Builds the filename of under which the data gets saved in the storage adapter. * - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return string */ - public function filename(Entity $entity, array $options = []); + public function filename(EntityInterface $entity, array $options = []); /** * Builds the path under which the data gets stored in the storage adapter. * - * @param Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return string */ - public function path(Entity $entity, array $options = []); + public function path(EntityInterface $entity, array $options = []); /** * Returns the path + filename. * - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return string */ - public function fullPath(Entity $entity, array $options = []); + public function fullPath(EntityInterface $entity, array $options = []); /** * Builds the URL under which the file is accessible. @@ -43,9 +43,9 @@ public function fullPath(Entity $entity, array $options = []); * This is for example important for S3 and Dropbox but also the Local adapter * if you symlink a folder to your webroot and allow direct access to a file. * - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return string */ - public function url(Entity $entity, array $options = []); + public function url(EntityInterface $entity, array $options = []); } diff --git a/src/Storage/PathBuilder/S3PathBuilder.php b/src/Storage/PathBuilder/S3PathBuilder.php index 3469398d..0c77e361 100644 --- a/src/Storage/PathBuilder/S3PathBuilder.php +++ b/src/Storage/PathBuilder/S3PathBuilder.php @@ -7,13 +7,14 @@ namespace Burzum\FileStorage\Storage\PathBuilder; use Burzum\FileStorage\Storage\StorageManager; -use Cake\ORM\Entity; +use Cake\Datasource\EntityInterface; class S3PathBuilder extends BasePathBuilder { public function __construct(array $config = []) { $this->_defaultConfig['https'] = false; $this->_defaultConfig['modelFolder'] = true; + $this->_defaultConfig['s3Url'] = 's3.amazonaws.com'; parent::__construct($config); } @@ -28,9 +29,9 @@ protected function _buildCloudUrl($bucket, $bucketPrefix = null, $cfDist = null) $path .= $cfDist; } else { if ($bucketPrefix) { - $path .= $bucket . '.s3.amazonaws.com'; + $path .= $bucket . '.' . $this->_config['s3Url']; } else { - $path .= 's3.amazonaws.com/' . $bucket; + $path .= $this->_config['s3Url'] . '/' . $bucket; } } return $path; @@ -42,11 +43,11 @@ protected function _buildCloudUrl($bucket, $bucketPrefix = null, $cfDist = null) * This is for example important for S3 and Dropbox but also the Local adapter * if you symlink a folder to your webroot and allow direct access to a file. * - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $options * @return string */ - public function url(Entity $entity, array $options = []) { + public function url(EntityInterface $entity, array $options = []) { $bucket = $this->_getBucket($entity->adapter); $pathPrefix = $this->_buildCloudUrl($bucket); $path = parent::path($entity); From 64f52afa7e7121275e1a89255c3db43a45505c36 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 12:55:21 +0200 Subject: [PATCH 060/144] Revert EntityInterface typehint from getFileInfoFromUpload --- src/Model/Table/FileStorageTable.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index d747b13a..8237e19c 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -95,25 +95,25 @@ public function beforeSave(Event $event, EntityInterface $entity, $options) { * - sets the adapter by default to local if not already set * - sets the model field to the table name if not already set * - * @param \Cake\Datasource\EntityInterface + * @param array|\ArrayAccess $upload * @param string $field * @return void */ - public function getFileInfoFromUpload(EntityInterface &$entity, $field = 'file') { - if (!empty($entity[$field]['tmp_name'])) { - $File = new File($entity[$field]['tmp_name']); - $entity['filesize'] = $File->size(); - $entity['mime_type'] = $File->mime(); + public function getFileInfoFromUpload(&$upload, $field = 'file') { + if (!empty($upload[$field]['tmp_name'])) { + $File = new File($upload[$field]['tmp_name']); + $upload['filesize'] = $File->size(); + $upload['mime_type'] = $File->mime(); } - if (!empty($entity[$field]['name'])) { - $entity['extension'] = pathinfo($entity[$field]['name'], PATHINFO_EXTENSION); - $entity['filename'] = $entity[$field]['name']; + if (!empty($upload[$field]['name'])) { + $upload['extension'] = pathinfo($upload[$field]['name'], PATHINFO_EXTENSION); + $upload['filename'] = $upload[$field]['name']; } - if (empty($entity['model'])) { - $entity['model'] = $this->table(); + if (empty($upload['model'])) { + $upload['model'] = $this->table(); } - if (empty($entity['adapter'])) { - $entity['adapter'] = 'Local'; + if (empty($upload['adapter'])) { + $upload['adapter'] = 'Local'; } } From 73fb042457839e862cf196530b4f21ccf0874a94 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 13:05:37 +0200 Subject: [PATCH 061/144] EntityInterface typehints clenup. --- src/Event/AbstractStorageEventListener.php | 4 +-- src/Event/S3StorageListener.php | 4 +-- .../Behavior/UploadValidatorBehavior.php | 1 - src/Model/Entity/ImageStorage.php | 2 -- src/Model/Table/FileStorageTable.php | 6 +--- src/Storage/Listener/AbstractListener.php | 6 ++-- src/Storage/Listener/ImageProcessingTrait.php | 28 +++++++++---------- src/Storage/StorageException.php | 4 +-- 8 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/Event/AbstractStorageEventListener.php b/src/Event/AbstractStorageEventListener.php index 18ad4e81..b8d51469 100644 --- a/src/Event/AbstractStorageEventListener.php +++ b/src/Event/AbstractStorageEventListener.php @@ -2,11 +2,11 @@ namespace Burzum\FileStorage\Event; use Cake\Core\InstanceConfigTrait; +use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Cake\Event\EventListenerInterface; use Cake\Log\LogTrait; use Cake\ORM\Table; -use Cake\ORM\Entity; use Cake\Utility\Text; use Cake\Filesystem\Folder; use Burzum\FileStorage\Storage\StorageManager; @@ -133,7 +133,7 @@ public function buildFilename($table, $entity) { * Builds the path under which the data gets stored in the storage adapter. * * @param Table $table - * @param Entity $entity + * @param EntityInterface $entity * @return string */ public function buildPath($table, $entity) { diff --git a/src/Event/S3StorageListener.php b/src/Event/S3StorageListener.php index 99615f1c..8605997d 100644 --- a/src/Event/S3StorageListener.php +++ b/src/Event/S3StorageListener.php @@ -2,8 +2,6 @@ namespace Burzum\FileStorage\Event; use Cake\Event\Event; -use Cake\ORM\Table; -use Cake\ORM\Entity; /** * S3StorageListener @@ -91,7 +89,7 @@ public function afterSave(Event $Event) { * Builds the storage path for this adapter. * * @param \Cake\ORM\Table $table - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @return array */ public function buildPath($table, $entity) { diff --git a/src/Model/Behavior/UploadValidatorBehavior.php b/src/Model/Behavior/UploadValidatorBehavior.php index c5bda29c..73a0f7a0 100644 --- a/src/Model/Behavior/UploadValidatorBehavior.php +++ b/src/Model/Behavior/UploadValidatorBehavior.php @@ -5,7 +5,6 @@ use Cake\Event\Event; use Cake\Event\EventManager; use Cake\ORM\Table; -use Cake\ORM\Entity; use Cake\ORM\Behavior; use Cake\Utility\File; use Cake\Utility\Number; diff --git a/src/Model/Entity/ImageStorage.php b/src/Model/Entity/ImageStorage.php index 1f4f47a5..7b91cb14 100644 --- a/src/Model/Entity/ImageStorage.php +++ b/src/Model/Entity/ImageStorage.php @@ -1,8 +1,6 @@ config('fileHash') !== false) { return $this->calculateFileHash( $entity[$fileField]['tmp_name'], diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 63d95b5c..44b286e3 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -6,10 +6,10 @@ */ namespace Burzum\FileStorage\Storage\Listener; -use \Cake\Core\Configure; -use \Cake\ORM\Entity; -use \Burzum\Imagine\Lib\ImageProcessor; -use \Burzum\FileStorage\Storage\StorageUtils; +use Burzum\FileStorage\Storage\StorageUtils; +use Burzum\Imagine\Lib\ImageProcessor; +use Cake\Core\Configure; +use Cake\Datasource\EntityInterface; /** * ImageProcessingTrait @@ -31,11 +31,11 @@ trait ImageProcessingTrait { * image versions use the other methods from this trait to implement the checks * and behavior you need. * - * @param \Cake\ORM\Entity + * @param \Cake\Datasource\EntityInterface * @param string $action `create` or `remove` * @return array */ - public function autoProcessImageVersions(Entity $entity, $action) { + public function autoProcessImageVersions(EntityInterface $entity, $action) { if (!in_array($action, ['create', 'remove'])) { throw new \InvalidArgumentException(); } @@ -108,12 +108,12 @@ protected function _checkImageVersions($identifier, array $versions) { /** * Creates the image versions of an entity. * - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array $versions $options * @param array $options * @return array */ - public function createImageVersions(Entity $entity, array $versions, array $options = []) { + public function createImageVersions(EntityInterface $entity, array $versions, array $options = []) { $this->_checkImageVersions($entity->model, $versions); $result = []; @@ -151,13 +151,13 @@ public function createImageVersions(Entity $entity, array $versions, array $opti /** * Removes image versions of an entity. * - * @param \Cake\ORM\Entity $entity + * @param \Cake\Datasource\EntityInterface $entity * @param array List of image version to remove for that entity. * @param array $versions * @param array $options * @return array */ - public function removeImageVersions(Entity $entity, array $versions, array $options = []) { + public function removeImageVersions(EntityInterface $entity, array $versions, array $options = []) { $this->_checkImageVersions($entity->model, $versions); $result = []; @@ -196,10 +196,10 @@ public function getAllVersionsKeysForModel($identifier) { /** * Convenience method to create ALL versions for an entity. * - * @param \Cake\ORM\Entity + * @param \Cake\Datasource\EntityInterface * @return array */ - public function createAllImageVersions(Entity $entity, array $options = []) { + public function createAllImageVersions(EntityInterface $entity, array $options = []) { return $this->createImageVersions( $entity, $this->getAllVersionsKeysForModel($entity->model), @@ -210,10 +210,10 @@ public function createAllImageVersions(Entity $entity, array $options = []) { /** * Convenience method to delete ALL versions for an entity. * - * @param \Cake\ORM\Entity + * @param \Cake\Datasource\EntityInterface * @return array */ - public function removeAllImageVersions(Entity $entity, array $options = []) { + public function removeAllImageVersions(EntityInterface $entity, array $options = []) { return $this->removeImageVersions( $entity, $this->getAllVersionsKeysForModel($entity->model), diff --git a/src/Storage/StorageException.php b/src/Storage/StorageException.php index 17b7c458..271db814 100644 --- a/src/Storage/StorageException.php +++ b/src/Storage/StorageException.php @@ -1,14 +1,14 @@ _entity = $entity; } From e6735b644825cf8be86b0758ba0cbb1f4cb2ef82 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 13:15:39 +0200 Subject: [PATCH 062/144] Fix write context for PHP 5.4 --- src/Model/Table/FileStorageTable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index db6fbed9..95723225 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -183,7 +183,7 @@ public function afterDelete(Event $event, EntityInterface $entity, $options) { * @return boolean Returns true if the old record was deleted */ public function deleteOldFileOnSave(EntityInterface $entity, $oldIdField = 'old_file_id') { - if (!empty($entity->get($oldIdField)) && $entity->get('model')) { + if ($entity->has($oldIdField) && $entity->has('model')) { $oldEntity = $this->find() ->contain([]) ->where([ From 3ec4ecf56a9ffd02f4605e38acdf148624601416 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 14:53:41 +0200 Subject: [PATCH 063/144] Added PathBuilderTrait to easili build and access path builders. --- src/Storage/PathBuilder/PathBuilderTrait.php | 56 ++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/Storage/PathBuilder/PathBuilderTrait.php diff --git a/src/Storage/PathBuilder/PathBuilderTrait.php b/src/Storage/PathBuilder/PathBuilderTrait.php new file mode 100644 index 00000000..5e27a31f --- /dev/null +++ b/src/Storage/PathBuilder/PathBuilderTrait.php @@ -0,0 +1,56 @@ +_pathBuilder = $pathBuilder; + } + return $this->_pathBuilder; + } +} From 1a21e79cab8a4253f92287a8e83ee56034afa176 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 14:54:06 +0200 Subject: [PATCH 064/144] Added default options for LocalPathBuilder. --- src/Storage/PathBuilder/LocalPathBuilder.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Storage/PathBuilder/LocalPathBuilder.php b/src/Storage/PathBuilder/LocalPathBuilder.php index b6d98934..100bd5b7 100644 --- a/src/Storage/PathBuilder/LocalPathBuilder.php +++ b/src/Storage/PathBuilder/LocalPathBuilder.php @@ -10,4 +10,13 @@ class LocalPathBuilder extends BasePathBuilder { +/** + * Constructor + * + * @param array $config + */ + public function __construct(array $config = array()) { + $this->_defaultConfig['modelFolder'] = true; + parent::__construct($config); + } } From c15acfb428d11798012822741bd01c50b463de5e Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 14:54:28 +0200 Subject: [PATCH 065/144] Added methods for retrieving full file paths and urls. --- src/Model/Table/FileStorageTable.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index 112f9cc4..fac376a3 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -22,6 +22,7 @@ class FileStorageTable extends Table { use LogTrait; + use PathBuilderTrait; use StorageTrait; /** @@ -199,4 +200,28 @@ public function deleteOldFileOnSave(EntityInterface $entity, $oldIdField = 'old_ } return false; } + +/** + * Returns full file path for an entity. + * + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return string + */ + public function fullFilePath(EntityInterface $entity, array $options = []) { + $pathBuilder = $this->createPathBuilder($entity['adapter']); + return $pathBuilder->fullPath($entity, $options); + } + +/** + * Returns file url for an entity. + * + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return string + */ + public function fileUrl(EntityInterface $entity, array $options = []) { + $pathBuilder = $this->createPathBuilder($entity['adapter']); + return $pathBuilder->url($entity, $options); + } } From bd84a47976982b53e46d1538216f4ae5c326db7c Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 15:04:54 +0200 Subject: [PATCH 066/144] fix use statement. --- src/Model/Table/FileStorageTable.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index fac376a3..b64873f2 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -10,6 +10,7 @@ use Cake\ORM\Table; use Cake\ORM\Entity; +use Burzum\FileStorage\Storage\PathBuilder\PathBuilderTrait; use Burzum\FileStorage\Storage\StorageTrait; /** From 98515f95d3e89d7c1dd1ad6fee0670700eff0f63 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 15:06:44 +0200 Subject: [PATCH 067/144] Revert default option. --- src/Storage/PathBuilder/LocalPathBuilder.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Storage/PathBuilder/LocalPathBuilder.php b/src/Storage/PathBuilder/LocalPathBuilder.php index 100bd5b7..b6d98934 100644 --- a/src/Storage/PathBuilder/LocalPathBuilder.php +++ b/src/Storage/PathBuilder/LocalPathBuilder.php @@ -10,13 +10,4 @@ class LocalPathBuilder extends BasePathBuilder { -/** - * Constructor - * - * @param array $config - */ - public function __construct(array $config = array()) { - $this->_defaultConfig['modelFolder'] = true; - parent::__construct($config); - } } From 876b95d0903d14f14e00b4a504bd476941ba0335 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 15:08:01 +0200 Subject: [PATCH 068/144] FIx condition. --- src/Storage/PathBuilder/PathBuilderTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storage/PathBuilder/PathBuilderTrait.php b/src/Storage/PathBuilder/PathBuilderTrait.php index 5e27a31f..7990c8d0 100644 --- a/src/Storage/PathBuilder/PathBuilderTrait.php +++ b/src/Storage/PathBuilder/PathBuilderTrait.php @@ -35,7 +35,7 @@ public function createPathBuilder($name, array $options = []) { throw new RuntimeException(sprintf('Could not find path builder "%s"!', $className)); } $pathBuilder = new $className($options); - if ($pathBuilder instanceof PathBuilderInterface) { + if (!$pathBuilder instanceof PathBuilderInterface) { throw new RuntimeException(sprintf('Path builder class "%s" does not implement the PathBuilderInterface interface!', $className)); } return $pathBuilder; From d3231beb3252528a1e2ae3dd82f0872ddc2ff4e7 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 15:15:46 +0200 Subject: [PATCH 069/144] PathBuilderTrait tests added + minor improvement for exception messages. --- src/Storage/PathBuilder/PathBuilderTrait.php | 4 +- .../PathBuilder/PathBuilderTraitTest.php | 60 +++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 tests/TestCase/Storage/PathBuilder/PathBuilderTraitTest.php diff --git a/src/Storage/PathBuilder/PathBuilderTrait.php b/src/Storage/PathBuilder/PathBuilderTrait.php index 7990c8d0..bfa0ba09 100644 --- a/src/Storage/PathBuilder/PathBuilderTrait.php +++ b/src/Storage/PathBuilder/PathBuilderTrait.php @@ -32,11 +32,11 @@ public function createPathBuilder($name, array $options = []) { $className = App::className('Burzum/FileStorage.' . $name, 'Storage/PathBuilder', 'PathBuilder'); } if (!class_exists($className)) { - throw new RuntimeException(sprintf('Could not find path builder "%s"!', $className)); + throw new RuntimeException(sprintf('Could not find path builder "%s"!', $name)); } $pathBuilder = new $className($options); if (!$pathBuilder instanceof PathBuilderInterface) { - throw new RuntimeException(sprintf('Path builder class "%s" does not implement the PathBuilderInterface interface!', $className)); + throw new RuntimeException(sprintf('Path builder class "%s" does not implement the PathBuilderInterface interface!', $name)); } return $pathBuilder; } diff --git a/tests/TestCase/Storage/PathBuilder/PathBuilderTraitTest.php b/tests/TestCase/Storage/PathBuilder/PathBuilderTraitTest.php new file mode 100644 index 00000000..331ed76d --- /dev/null +++ b/tests/TestCase/Storage/PathBuilder/PathBuilderTraitTest.php @@ -0,0 +1,60 @@ +getObjectForTrait('Burzum\FileStorage\Storage\PathBuilder\PathBuilderTrait'); + + $pathBuilder = $object->createPathBuilder('Local'); + $this->assertInstanceOf('Burzum\FileStorage\Storage\PathBuilder\PathBuilderInterface', $pathBuilder); + $this->assertInstanceOf('Burzum\FileStorage\Storage\PathBuilder\LocalPathBuilder', $pathBuilder); + } + +/** + * Test createPathBuilder() method with invalid class. + * + * @return void + * @expectedException RuntimeException + * @expectedExceptionMessage Path builder class "\stdClass" does not implement the PathBuilderInterface interface! + */ + public function testCreatePathBuilderInvalidClass() { + $object = $this->getObjectForTrait('Burzum\FileStorage\Storage\PathBuilder\PathBuilderTrait'); + + $object->createPathBuilder('\stdClass'); + } + +/** + * Test createPathBuilder() method with missing class. + * + * @return void + * @expectedException RuntimeException + * @expectedExceptionMessage Could not find path builder "Foo"! + */ + public function testCreatePathBuilderMissingClass() { + $object = $this->getObjectForTrait('Burzum\FileStorage\Storage\PathBuilder\PathBuilderTrait'); + + $object->createPathBuilder('Foo'); + } + +/** + * Test pathBuilder() method. + * + * @return void + */ + public function testPathBuilder() { + $object = $this->getObjectForTrait('Burzum\FileStorage\Storage\PathBuilder\PathBuilderTrait'); + $pathBuilder = $this->getMock('Burzum\FileStorage\Storage\PathBuilder\PathBuilderInterface'); + + $object->pathBuilder($pathBuilder); + + $this->assertSame($pathBuilder, $object->pathBuilder()); + } +} From 50a11180dbfd2e31195437876a8fe17a659866c3 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 08:21:51 +0200 Subject: [PATCH 070/144] Use PathBuilderTrait + minor improvements in AbstractListener. --- src/Storage/Listener/AbstractListener.php | 55 ++++------------------- 1 file changed, 9 insertions(+), 46 deletions(-) diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index 7421a633..70cb6872 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -6,17 +6,17 @@ */ namespace Burzum\FileStorage\Storage\Listener; +use Burzum\FileStorage\Storage\PathBuilder\PathBuilderTrait; use Burzum\FileStorage\Storage\StorageTrait; use Burzum\FileStorage\Storage\StorageUtils; use Cake\Core\InstanceConfigTrait; use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Cake\Event\EventListenerInterface; +use Cake\Filesystem\Folder; use Cake\Log\LogTrait; -use Cake\ORM\Table; use Cake\Utility\MergeVariablesTrait; use Cake\Utility\Text; -use Cake\Filesystem\Folder; /** * AbstractListener @@ -41,8 +41,9 @@ abstract class AbstractListener implements EventListenerInterface { use InstanceConfigTrait; use LogTrait; - use StorageTrait; use MergeVariablesTrait; + use PathBuilderTrait; + use StorageTrait; /** * The adapter class @@ -51,13 +52,6 @@ abstract class AbstractListener implements EventListenerInterface { */ protected $_adapterClass = null; -/** - * The class used to generate path and file names. - * - * @param string - */ - protected $_pathBuilder = null; - /** * Name of the storage table class name the event listener requires the table * instances to extend. @@ -308,47 +302,16 @@ public function createTmpFile($folder = null, $checkAndCreatePath = true) { return $folder . Text::uuid(); } -/** - * Gets the configured path builder instance. - * - * @return \Burzum\FileStorage\Storage\PathBuilder\BasePathBuilder - * @throws - */ - public function pathBuilder() { - if (!empty($this->_pathBuilder)) { - return $this->_pathBuilder; - } - throw \RuntimeException(sprintf('No path builder configured! Call _constructPathBuilder() and construct one!')); - } - /** * Constructs a path builder instance. * * @param string $class * @param array $options - * @return \Burzum\FileStorage\Storage\PathBuilder\BasePathBuilder - * @throws \RuntimeException + * @return \Burzum\FileStorage\Storage\PathBuilder\PathBuilderInterface */ - public function _constructPathBuilder($class, array $options = []) { - $classname = '\Burzum\FileStorage\Storage\PathBuilder\\' . $class . 'Builder'; - if (class_exists($classname)) { - $this->_pathBuilder = new $classname($options); - return $this->_pathBuilder; - } - $classname = '\App\Storage\PathBuilder\\' . $class . 'Builder'; - if (class_exists($classname)) { - $this->_pathBuilder = new $classname($options); - } - $classname = $class; - if (class_exists($classname)) { - $this->_pathBuilder = new $classname($options); - } - if (empty($this->_pathBuilder)) { - throw new \RuntimeException(sprintf('Could not find path builder "%s"!', $classname)); - } - if ($this->_pathBuilder instanceof \Burzum\FileStorage\Storage\PathBuilder\PathBuilderInterface) { - return $this->_pathBuilder; - } - throw new \RuntimeException(sprintf('Path builder class "%s" does not implement the PathBuilderInterface interface!')); + protected function _constructPathBuilder($class, array $options = []) { + $pathBuilder = $this->createPathBuilder($class, $options); + + return $this->pathBuilder($pathBuilder); } } From 6a88db7d54d1f3b0c335241f81ab98a6754e0843 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 08:25:24 +0200 Subject: [PATCH 071/144] Update Listeners and tests to use 'Local' path builder name instead of 'BasePath' and 'LocalPath'. Also cleaned up code a little bit. --- .../Listener/LegacyLocalFileStorageListener.php | 10 ++++------ src/Storage/Listener/LocalListener.php | 4 +--- .../TestCase/Storage/Listener/AbstractListenerTest.php | 2 +- .../Storage/Listener/ImageProcessingTraitTest.php | 2 +- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Storage/Listener/LegacyLocalFileStorageListener.php b/src/Storage/Listener/LegacyLocalFileStorageListener.php index af868514..b2e7d7b8 100644 --- a/src/Storage/Listener/LegacyLocalFileStorageListener.php +++ b/src/Storage/Listener/LegacyLocalFileStorageListener.php @@ -1,19 +1,17 @@ 'BasePath', + 'pathBuilder' => 'Local', 'pathBuilderOptions' => [ 'pathPrefix' => 'files', 'modelFolder' => false, diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index f062a350..f34d4a64 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -7,9 +7,7 @@ namespace Burzum\FileStorage\Storage\Listener; use Burzum\FileStorage\Storage\StorageException; -use Cake\Core\Configure; use Cake\Event\Event; -use Cake\Filesystem\Folder; use Psr\Log\LogLevel; /** @@ -29,7 +27,7 @@ class LocalListener extends AbstractListener { * @var array */ protected $_defaultConfig = [ - 'pathBuilder' => 'BasePath', + 'pathBuilder' => 'Local', 'pathBuilderOptions' => [ 'modelFolder' => true, ], diff --git a/tests/TestCase/Storage/Listener/AbstractListenerTest.php b/tests/TestCase/Storage/Listener/AbstractListenerTest.php index e74401e8..d3a1afa4 100644 --- a/tests/TestCase/Storage/Listener/AbstractListenerTest.php +++ b/tests/TestCase/Storage/Listener/AbstractListenerTest.php @@ -31,7 +31,7 @@ class AbstractListenerTest extends TestCase { */ public function testPathBuilder() { $Listener = new TestAbstractListener([ - 'pathBuilder' => 'LocalPath' + 'pathBuilder' => 'Local' ]); $result = $Listener->pathBuilder(); $this->assertInstanceOf('\Burzum\FileStorage\Storage\PathBuilder\BasePathBuilder', $result); diff --git a/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php b/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php index 43595fb2..684e416a 100644 --- a/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php +++ b/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php @@ -18,7 +18,7 @@ class TraitTestClass extends AbstractListener { use ImageProcessingTrait; public $_defaultConfig = [ - 'pathBuilder' => 'LocalPath', + 'pathBuilder' => 'Local', 'pathBuilderOptions' => [ 'preserveFilename' => true ] From ef45a0073286c70163e3a1986ec8632125a464fd Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 08:28:46 +0200 Subject: [PATCH 072/144] =?UTF-8?q?Fixed=20encoding=20on=20=C3=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Storage/Listener/LegacyLocalFileStorageListener.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Storage/Listener/LegacyLocalFileStorageListener.php b/src/Storage/Listener/LegacyLocalFileStorageListener.php index b2e7d7b8..59e8fdaa 100644 --- a/src/Storage/Listener/LegacyLocalFileStorageListener.php +++ b/src/Storage/Listener/LegacyLocalFileStorageListener.php @@ -1,7 +1,7 @@ Date: Tue, 4 Aug 2015 08:31:11 +0200 Subject: [PATCH 073/144] Updated authors :) --- src/Storage/PathBuilder/PathBuilderTrait.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Storage/PathBuilder/PathBuilderTrait.php b/src/Storage/PathBuilder/PathBuilderTrait.php index bfa0ba09..79d08ce8 100644 --- a/src/Storage/PathBuilder/PathBuilderTrait.php +++ b/src/Storage/PathBuilder/PathBuilderTrait.php @@ -1,6 +1,7 @@ Date: Tue, 4 Aug 2015 10:24:23 +0200 Subject: [PATCH 074/144] Changing travis to use their new container based infrastructure. http://docs.travis-ci.com/user/migrating-from-legacy/?utm_source=legacy-notice&utm_medium=banner&utm_campaign=legacy-upgrade --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 88ce6d35..2ba3fc16 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: php +sudo: false php: - 5.4 From b670465f6cb80aa6623eb274067ca50d5b4977da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Tue, 4 Aug 2015 10:42:30 +0200 Subject: [PATCH 075/144] Updating phpunit.xml.dist --- phpunit.xml.dist | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0f6c6aa9..2ae33a52 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -29,12 +29,14 @@ - - - - + + + + ./src + + ./vendor + ./tests + + + From ce89ca52fa95b9379155bbd96ea1589162a83175 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 15:39:41 +0200 Subject: [PATCH 076/144] Add listeners for image events. --- src/Storage/Listener/LocalListener.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index f34d4a64..0f6ba4a9 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -58,6 +58,8 @@ public function implementedEvents() { return [ 'FileStorage.afterSave' => 'afterSave', 'FileStorage.afterDelete' => 'afterDelete', + 'ImageStorage.afterSave' => 'afterSave', + 'ImageStorage.afterDelete' => 'afterDelete', 'ImageVersion.removeVersion' => 'removeImageVersion', 'ImageVersion.createVersion' => 'createImageVersion' ]; From 8122b2953764eed6acd9583fc3ea5ad2612ca06b Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 15:39:53 +0200 Subject: [PATCH 077/144] Temporary fix for image versions. --- src/Storage/Listener/ImageProcessingTrait.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 44b286e3..ca89f5b2 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -130,12 +130,11 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar 'hash' => $this->_imageVersionHashes[$entity->model][$version], ]; try { - $output = $this->createTmpFile(); $tmpFile = $this->_tmpFile($storage, $this->pathBuilder()->fullPath($entity)); - $this->imageProcessor()->open($tmpFile); - $this->imageProcessor()->batchProcess($output, $config, ['format' => $entity->extension]); - $storage->write($path, file_get_contents($output)); - unlink($tmpFile); + $table = TableRegistry::get($entity->source()); + $image = $table->processImage($tmpFile, null, array('format' => $entity['extension']), $config); + + $storage->write($path, $image->get($entity->extension), true); } catch (\Exception $e) { $result[$version] = [ 'status' => 'error', From 621a13b6ece776c684871c9c594996f1298ac9e2 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 15:40:12 +0200 Subject: [PATCH 078/144] Updated default bootstrap for new listeners. --- config/bootstrap.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/config/bootstrap.php b/config/bootstrap.php index e87beb5a..b2157dcd 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -1,10 +1,8 @@ on($listener); - -$listener = new LocalFileStorageListener(); +$listener = new LocalListener([ + 'processImages' => true +]); EventManager::instance()->on($listener); From b11eebf2871326a74860835483470fd17d81c1bf Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 15:46:04 +0200 Subject: [PATCH 079/144] Fixes --- config/bootstrap.php | 2 +- src/Storage/Listener/ImageProcessingTrait.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config/bootstrap.php b/config/bootstrap.php index b2157dcd..815cc5f3 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -3,6 +3,6 @@ use Cake\Event\EventManager; $listener = new LocalListener([ - 'processImages' => true + 'imageProcessing' => true ]); EventManager::instance()->on($listener); diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index ca89f5b2..61bd22c6 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -10,6 +10,7 @@ use Burzum\Imagine\Lib\ImageProcessor; use Cake\Core\Configure; use Cake\Datasource\EntityInterface; +use Cake\ORM\TableRegistry; /** * ImageProcessingTrait From 3110ac905edf0190472bd14a1519452cfbecb252 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 15:54:36 +0200 Subject: [PATCH 080/144] Fix removed unlink method. --- src/Storage/Listener/ImageProcessingTrait.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 61bd22c6..9b9c6497 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -136,6 +136,7 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar $image = $table->processImage($tmpFile, null, array('format' => $entity['extension']), $config); $storage->write($path, $image->get($entity->extension), true); + unlink($tmpFile); } catch (\Exception $e) { $result[$version] = [ 'status' => 'error', From dca72bfcc13c5459dce3ed57ec4aff481bce9ce7 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 15:58:57 +0200 Subject: [PATCH 081/144] CS fixes --- src/Storage/Listener/ImageProcessingTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 9b9c6497..3ebe6765 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -132,11 +132,11 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar ]; try { $tmpFile = $this->_tmpFile($storage, $this->pathBuilder()->fullPath($entity)); - $table = TableRegistry::get($entity->source()); + $table = TableRegistry::get($entity->source()); $image = $table->processImage($tmpFile, null, array('format' => $entity['extension']), $config); $storage->write($path, $image->get($entity->extension), true); - unlink($tmpFile); + unlink($tmpFile); } catch (\Exception $e) { $result[$version] = [ 'status' => 'error', From ab014ac051c2b0cc5598135fced5f5148368e531 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 5 Aug 2015 08:57:56 +0200 Subject: [PATCH 082/144] Revert temporary "table" solution as a bug in imagine plugin have been discovered: burzum/cakephp-imagine-plugin#36 --- src/Storage/Listener/ImageProcessingTrait.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 3ebe6765..d1f92c6e 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -131,12 +131,14 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar 'hash' => $this->_imageVersionHashes[$entity->model][$version], ]; try { + $output = $this->createTmpFile(); $tmpFile = $this->_tmpFile($storage, $this->pathBuilder()->fullPath($entity)); - $table = TableRegistry::get($entity->source()); - $image = $table->processImage($tmpFile, null, array('format' => $entity['extension']), $config); - - $storage->write($path, $image->get($entity->extension), true); + $this->imageProcessor()->open($tmpFile); + $this->imageProcessor()->batchProcess($output, $config, ['format' => $entity->extension]); + $storage->write($path, file_get_contents($output)); + unlink($tmpFile); + unlink($output); } catch (\Exception $e) { $result[$version] = [ 'status' => 'error', From 096d9c0dcc1cacaaf69c4c768cba707a08ce1a77 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 5 Aug 2015 09:01:16 +0200 Subject: [PATCH 083/144] CS fix --- src/Storage/Listener/ImageProcessingTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index d1f92c6e..bf2869c8 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -136,7 +136,7 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar $this->imageProcessor()->open($tmpFile); $this->imageProcessor()->batchProcess($output, $config, ['format' => $entity->extension]); $storage->write($path, file_get_contents($output)); - + unlink($tmpFile); unlink($output); } catch (\Exception $e) { From 7f6961aee1d59bc34e84582123ebd3b975cbcc2e Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 5 Aug 2015 11:07:56 +0200 Subject: [PATCH 084/144] Fixed encoding --- src/Shell/StorageShell.php | 4 ++-- src/Shell/Task/ImageTask.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Shell/StorageShell.php b/src/Shell/StorageShell.php index d762470c..0aff12d4 100644 --- a/src/Shell/StorageShell.php +++ b/src/Shell/StorageShell.php @@ -1,7 +1,7 @@ Date: Wed, 5 Aug 2015 11:14:40 +0200 Subject: [PATCH 085/144] Updated .gitignore to ignore netbeans nbproject directory. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 945f2a7b..59405f23 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /plugins /tmp /composer.lock +/nbproject From a7490a004c42f3e94fc755dceae889842bd379aa Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 15:39:16 +0200 Subject: [PATCH 086/144] Moved FileStorageTable methods into FileStorageBehavior. --- src/Model/Behavior/FileStorageBehavior.php | 212 ++++++++++++++++++ src/Model/Table/FileStorageTable.php | 171 +------------- .../FileStorageBehaviorTest.php} | 11 +- 3 files changed, 219 insertions(+), 175 deletions(-) create mode 100644 src/Model/Behavior/FileStorageBehavior.php rename tests/TestCase/Model/{Table/FileStorageTest.php => Behavior/FileStorageBehaviorTest.php} (76%) diff --git a/src/Model/Behavior/FileStorageBehavior.php b/src/Model/Behavior/FileStorageBehavior.php new file mode 100644 index 00000000..12d5ca9d --- /dev/null +++ b/src/Model/Behavior/FileStorageBehavior.php @@ -0,0 +1,212 @@ + [ + 'getFileInfoFromUpload' => 'getFileInfoFromUpload', + 'deleteOldFileOnSave' => 'deleteOldFileOnSave', + 'fullFilePath' => 'fullFilePath', + 'fileUrl' => 'fileUrl' + ] + ]; + +/** + * + * @param array $config + * @return void + */ + public function initialize(array $config) { + $this->_table->record = []; + $this->_eventManager = $this->_table->eventManager(); + + parent::initialize($config); + } + +/** + * Gets information about the file that is being uploaded. + * + * - gets the file size + * - gets the mime type + * - gets the extension if present + * - sets the adapter by default to local if not already set + * - sets the model field to the table name if not already set + * + * @param array|\ArrayAccess $upload + * @param string $field + * @return void + */ + public function getFileInfoFromUpload(&$upload, $field = 'file') { + if (!empty($upload[$field]['tmp_name'])) { + $File = new File($upload[$field]['tmp_name']); + $upload['filesize'] = $File->size(); + $upload['mime_type'] = $File->mime(); + } + if (!empty($upload[$field]['name'])) { + $upload['extension'] = pathinfo($upload[$field]['name'], PATHINFO_EXTENSION); + $upload['filename'] = $upload[$field]['name']; + } + if (empty($upload['model'])) { + $upload['model'] = $this->_table->table(); + } + if (empty($upload['adapter'])) { + $upload['adapter'] = 'Local'; + } + } + +/** + * beforeSave callback + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return boolean true on success + */ + public function beforeSave(Event $event, EntityInterface $entity, $options) { + $this->getFileInfoFromUpload($entity); + $storageEvent = $this->dispatchEvent('FileStorage.beforeSave', array( + 'record' => $entity, + 'storage' => $this->storageAdapter($entity->get('adapter')) + )); + if ($storageEvent->isStopped()) { + return false; + } + return true; + } + +/** + * afterSave callback + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return boolean + */ + public function afterSave(Event $event, EntityInterface $entity, $options) { + $this->dispatchEvent('FileStorage.afterSave', [ + 'created' => $entity->isNew(), + 'record' => $entity, + 'storage' => $this->storageAdapter($entity->get('adapter')) + ]); + $this->deleteOldFileOnSave($entity); + return true; + } + +/** + * Get a copy of the actual record before we delete it to have it present in afterDelete + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @return boolean + */ + public function beforeDelete(Event $event, EntityInterface $entity) { + $primaryKey = $this->_table->primaryKey(); + $this->_table->record = $this->_table->find() + ->contain([]) + ->where([ + $this->_table->aliasField($primaryKey) => $entity->get($primaryKey) + ]) + ->first(); + + if (empty($this->_table->record)) { + return false; + } + + return true; + } + +/** + * afterDelete callback + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return boolean + */ + public function afterDelete(Event $event, EntityInterface $entity, $options) { + $this->dispatchEvent('FileStorage.afterDelete', [ + 'record' => $entity, + 'storage' => $this->storageAdapter($entity->get('adapter')) + ]); + return true; + } + +/** + * Deletes an old file to replace it with the new one if an old id was passed. + * + * Thought to be called in Model::afterSave() but can be used from any other + * place as well like Model::beforeSave() as long as the field data is present. + * + * The old id has to be the UUID of the file_storage record that should be deleted. + * + * @param \Cake\Datasource\EntityInterface $entity + * @param string $oldIdField Name of the field in the data that holds the old id. + * @return boolean Returns true if the old record was deleted + */ + public function deleteOldFileOnSave(EntityInterface $entity, $oldIdField = 'old_file_id') { + if ($entity->has($oldIdField) && $entity->has('model')) { + $oldEntity = $this->_table->find() + ->contain([]) + ->where([ + $this->_table->aliasField($this->_table->primaryKey()) => $entity->get($oldIdField), + 'model' => $entity->get('model') + ]) + ->first(); + if (!empty($oldEntity)) { + return $this->_table->delete($oldEntity); + } + } + return false; + } + +/** + * Returns full file path for an entity. + * + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return string + */ + public function fullFilePath(EntityInterface $entity, array $options = []) { + $pathBuilder = $this->createPathBuilder($entity['adapter']); + return $pathBuilder->fullPath($entity, $options); + } + +/** + * Returns file url for an entity. + * + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return string + */ + public function fileUrl(EntityInterface $entity, array $options = []) { + $pathBuilder = $this->createPathBuilder($entity['adapter']); + return $pathBuilder->url($entity, $options); + } +} diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index 63faf8f6..14740e92 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -1,12 +1,6 @@ addBehavior('Burzum/FileStorage.UploadValidator'); + $this->addBehavior('Burzum/FileStorage.FileStorage'); $this->addBehavior('Timestamp'); $this->displayField('filename'); $this->table('file_storage'); @@ -63,164 +54,4 @@ public function configureUploadValidation($options) { $this->removeBehavior('Burzum/FileStorage.UploadValidator'); $this->addBehavior('Burzum/FileStorage.UploadValidator', $options); } - -/** - * beforeSave callback - * - * @param \Cake\Event\Event $event - * @param \Cake\Datasource\EntityInterface $entity - * @param array $options - * @return boolean true on success - */ - public function beforeSave(Event $event, EntityInterface $entity, $options) { - $this->getFileInfoFromUpload($entity); - $storageEvent = $this->dispatchEvent('FileStorage.beforeSave', array( - 'record' => $entity, - 'storage' => $this->storageAdapter($entity->get('adapter')) - )); - if ($storageEvent->isStopped()) { - return false; - } - return true; - } - -/** - * Gets information about the file that is being uploaded. - * - * - gets the file size - * - gets the mime type - * - gets the extension if present - * - sets the adapter by default to local if not already set - * - sets the model field to the table name if not already set - * - * @param array|\ArrayAccess $upload - * @param string $field - * @return void - */ - public function getFileInfoFromUpload(&$upload, $field = 'file') { - if (!empty($upload[$field]['tmp_name'])) { - $File = new File($upload[$field]['tmp_name']); - $upload['filesize'] = $File->size(); - $upload['mime_type'] = $File->mime(); - } - if (!empty($upload[$field]['name'])) { - $upload['extension'] = pathinfo($upload[$field]['name'], PATHINFO_EXTENSION); - $upload['filename'] = $upload[$field]['name']; - } - if (empty($upload['model'])) { - $upload['model'] = $this->table(); - } - if (empty($upload['adapter'])) { - $upload['adapter'] = 'Local'; - } - } - -/** - * afterSave callback - * - * @param \Cake\Event\Event $event - * @param \Cake\Datasource\EntityInterface $entity - * @param array $options - * @return boolean - */ - public function afterSave(Event $event, EntityInterface $entity, $options) { - $this->dispatchEvent('FileStorage.afterSave', [ - 'created' => $entity->isNew(), - 'record' => $entity, - 'storage' => $this->storageAdapter($entity->get('adapter')) - ]); - $this->deleteOldFileOnSave($entity); - return true; - } - -/** - * Get a copy of the actual record before we delete it to have it present in afterDelete - * - * @param \Cake\Event\Event $event - * @param \Cake\Datasource\EntityInterface $entity - * @return boolean - */ - public function beforeDelete(Event $event, EntityInterface $entity) { - $primaryKey = $this->primaryKey(); - $this->record = $this->find() - ->contain([]) - ->where([ - $this->aliasField($primaryKey) => $entity->get($primaryKey) - ]) - ->first(); - - if (empty($this->record)) { - return false; - } - - return true; - } - -/** - * afterDelete callback - * - * @param \Cake\Event\Event $event - * @param \Cake\Datasource\EntityInterface $entity - * @param array $options - * @return boolean - */ - public function afterDelete(Event $event, EntityInterface $entity, $options) { - $this->dispatchEvent('FileStorage.afterDelete', [ - 'record' => $entity, - 'storage' => $this->storageAdapter($entity->get('adapter')) - ]); - return true; - } - -/** - * Deletes an old file to replace it with the new one if an old id was passed. - * - * Thought to be called in Model::afterSave() but can be used from any other - * place as well like Model::beforeSave() as long as the field data is present. - * - * The old id has to be the UUID of the file_storage record that should be deleted. - * - * @param \Cake\Datasource\EntityInterface $entity - * @param string $oldIdField Name of the field in the data that holds the old id. - * @return boolean Returns true if the old record was deleted - */ - public function deleteOldFileOnSave(EntityInterface $entity, $oldIdField = 'old_file_id') { - if ($entity->has($oldIdField) && $entity->has('model')) { - $oldEntity = $this->find() - ->contain([]) - ->where([ - $this->aliasField($this->primaryKey()) => $entity->get($oldIdField), - 'model' => $entity->get('model') - ]) - ->first(); - if (!empty($oldEntity)) { - return $this->delete($oldEntity); - } - } - return false; - } - -/** - * Returns full file path for an entity. - * - * @param \Cake\Datasource\EntityInterface $entity - * @param array $options - * @return string - */ - public function fullFilePath(EntityInterface $entity, array $options = []) { - $pathBuilder = $this->createPathBuilder($entity['adapter']); - return $pathBuilder->fullPath($entity, $options); - } - -/** - * Returns file url for an entity. - * - * @param \Cake\Datasource\EntityInterface $entity - * @param array $options - * @return string - */ - public function fileUrl(EntityInterface $entity, array $options = []) { - $pathBuilder = $this->createPathBuilder($entity['adapter']); - return $pathBuilder->url($entity, $options); - } } diff --git a/tests/TestCase/Model/Table/FileStorageTest.php b/tests/TestCase/Model/Behavior/FileStorageBehaviorTest.php similarity index 76% rename from tests/TestCase/Model/Table/FileStorageTest.php rename to tests/TestCase/Model/Behavior/FileStorageBehaviorTest.php index 6804c148..243a10ae 100644 --- a/tests/TestCase/Model/Table/FileStorageTest.php +++ b/tests/TestCase/Model/Behavior/FileStorageBehaviorTest.php @@ -1,8 +1,7 @@ FileStorage = TableRegistry::get('Burzum/FileStorage.FileStorage'); + $this->FileStorageBehavior = $this->FileStorage->behaviors()->get('FileStorage'); } /** @@ -42,6 +42,7 @@ public function setUp() { public function tearDown() { parent::tearDown(); unset($this->FileStorage); + unset($this->FileStorageBehavior); TableRegistry::clear(); } @@ -53,7 +54,7 @@ public function tearDown() { public function testBeforeDelete() { $entity = $this->FileStorage->get('file-storage-1'); $event = new Event('Model.beforeDelete', $this->FileStorage); - $this->FileStorage->beforeDelete($event, $entity); + $this->FileStorageBehavior->beforeDelete($event, $entity); $this->assertEquals($this->FileStorage->record, $entity); } @@ -69,7 +70,7 @@ public function testAfterDelete() { 'record' => $entity, 'adapter' => 'Local' ]); - $result = $this->FileStorage->afterDelete($event, $entity, []); + $result = $this->FileStorageBehavior->afterDelete($event, $entity, []); $this->assertTrue($result); } } From eaa0b2df393574681578af97a4acc196dc026b58 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Aug 2015 15:39:40 +0200 Subject: [PATCH 087/144] Temporary fix for ImageStorageTable. --- src/Model/Table/ImageStorageTable.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Model/Table/ImageStorageTable.php b/src/Model/Table/ImageStorageTable.php index ca98be55..ecba0b57 100644 --- a/src/Model/Table/ImageStorageTable.php +++ b/src/Model/Table/ImageStorageTable.php @@ -49,9 +49,9 @@ public function initialize(array $config) { * @return boolean true on success */ public function beforeSave(Event $event, EntityInterface $entity, $options) { - if (!parent::beforeSave($event, $entity, $options)) { - return false; - } +// if (!parent::beforeSave($event, $entity, $options)) { +// return false; +// } $imageEvent = $this->dispatchEvent('ImageStorage.beforeSave', array( 'record' => $entity )); @@ -90,9 +90,9 @@ public function afterSave(Event $event, EntityInterface $entity, $options) { * @return boolean */ public function beforeDelete(Event $event, EntityInterface $entity) { - if (!parent::beforeDelete($event, $entity)) { - return false; - } +// if (!parent::beforeDelete($event, $entity)) { +// return false; +// } $imageEvent = $this->dispatchEvent('ImageStorage.beforeDelete', [ 'record' => $this->record, From 0c24217c2973b93658c55f90d957d146cffe09e2 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 08:36:54 +0200 Subject: [PATCH 088/144] Temporarily move traits to ImageStorageTable --- src/Model/Table/ImageStorageTable.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Model/Table/ImageStorageTable.php b/src/Model/Table/ImageStorageTable.php index ecba0b57..6d13972a 100644 --- a/src/Model/Table/ImageStorageTable.php +++ b/src/Model/Table/ImageStorageTable.php @@ -1,6 +1,9 @@ Date: Tue, 4 Aug 2015 09:30:23 +0200 Subject: [PATCH 089/144] Removed table check in AbstractListener as it seems unnessesary. Event names are defined in implementedEvents and these are unique for storage classes so there's no risk that a listener would respond to unwanted events. This change is to remove a limitation for other classes to emit storage events. Also removed abstract implementedEvents() as it was an unnsessesary duplicate of interface declaration. --- src/Storage/Listener/AbstractListener.php | 39 +---------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index 70cb6872..7f76c2d6 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -52,18 +52,6 @@ abstract class AbstractListener implements EventListenerInterface { */ protected $_adapterClass = null; -/** - * Name of the storage table class name the event listener requires the table - * instances to extend. - * - * This information is important to know when to use the event callbacks or not. - * - * Must be \FileStorage\Model\Table\FileStorageTable or \FileStorage\Model\Table\ImageStorageTable - * - * @var string - */ - public $storageTableClass = '\Burzum\FileStorage\Model\Table\FileStorageTable'; - /** * List of adapter classes the event listener can work with * @@ -125,13 +113,6 @@ protected function _mergeListenerVars() { */ public function initialize($config) {} -/** - * Implemented Events - * - * @return array - */ - abstract public function implementedEvents(); - /** * Check if the event is of a type or subject object of type model we want to * process with this listener. @@ -141,16 +122,8 @@ abstract public function implementedEvents(); * @return boolean */ protected function _checkEvent(Event $event) { - $classes = [ - '\Burzum\FileStorage\Model\Table\FileStorageTable', - '\Burzum\FileStorage\Model\Table\ImageStorageTable' - ]; - if (!in_array($this->storageTableClass, $classes)) { - throw new \InvalidArgumentException(sprintf('Invalid storage table `%s`! Table must be FileStorage or ImageStorage or extend one of both!', $this->storageTableClass)); - } return ( - $this->_checkTable($event) - && $this->getAdapterClassName($event->data['record']['adapter']) + $this->getAdapterClassName($event->data['record']['adapter']) && $this->_modelFilter($event) ); } @@ -171,16 +144,6 @@ protected function _modelFilter(Event $event) { return true; } -/** - * Checks if the events subject is a model and extending FileStorage or ImageStorage. - * - * @param Event $event - * @return boolean - */ - protected function _checkTable(Event $event) { - return ($event->subject() instanceOf $this->storageTableClass); - } - /** * Gets the adapter class name from the adapter config * From 492ba238309ec276077ebc80edb3c3d0d2a14de2 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 10:03:36 +0200 Subject: [PATCH 090/144] Created EventDispatcherTrait only for behaviors in order to feed data with a table object before Event dispatch. --- .../Behavior/Event/EventDispatcherTrait.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/Model/Behavior/Event/EventDispatcherTrait.php diff --git a/src/Model/Behavior/Event/EventDispatcherTrait.php b/src/Model/Behavior/Event/EventDispatcherTrait.php new file mode 100644 index 00000000..207d0e41 --- /dev/null +++ b/src/Model/Behavior/Event/EventDispatcherTrait.php @@ -0,0 +1,30 @@ +_table; + return $this->_dispatchEvent($name, $data, $subject); + } +} From df93b5127578916fdf3b95c5f6a491e6a70d9909 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 10:05:10 +0200 Subject: [PATCH 091/144] Use custom EventDispatcherTrait and reorder event data for consistency across events. --- src/Model/Behavior/FileStorageBehavior.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Model/Behavior/FileStorageBehavior.php b/src/Model/Behavior/FileStorageBehavior.php index 12d5ca9d..e6b79520 100644 --- a/src/Model/Behavior/FileStorageBehavior.php +++ b/src/Model/Behavior/FileStorageBehavior.php @@ -1,12 +1,12 @@ dispatchEvent('FileStorage.afterSave', [ - 'created' => $entity->isNew(), 'record' => $entity, - 'storage' => $this->storageAdapter($entity->get('adapter')) + 'storage' => $this->storageAdapter($entity->get('adapter')), + 'created' => $entity->isNew() ]); $this->deleteOldFileOnSave($entity); return true; From 1f6f74f3c332f203f4bd55acd4ad610f687884d7 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 10:07:14 +0200 Subject: [PATCH 092/144] Make sure that event has a table object in its data. --- src/Storage/Listener/AbstractListener.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Storage/Listener/AbstractListener.php b/src/Storage/Listener/AbstractListener.php index 7f76c2d6..39ad7447 100644 --- a/src/Storage/Listener/AbstractListener.php +++ b/src/Storage/Listener/AbstractListener.php @@ -15,6 +15,7 @@ use Cake\Event\EventListenerInterface; use Cake\Filesystem\Folder; use Cake\Log\LogTrait; +use Cake\ORM\Table; use Cake\Utility\MergeVariablesTrait; use Cake\Utility\Text; @@ -123,7 +124,9 @@ public function initialize($config) {} */ protected function _checkEvent(Event $event) { return ( - $this->getAdapterClassName($event->data['record']['adapter']) + isset($event->data['table']) + && $event->data['table'] instanceof Table + && $this->getAdapterClassName($event->data['record']['adapter']) && $this->_modelFilter($event) ); } From 2b5a2411ae102d1f92d97c7411e2d784247b80e7 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 10:08:13 +0200 Subject: [PATCH 093/144] Typehint events against EntityInterface --- src/Storage/Listener/LocalListener.php | 11 +++++------ tests/TestCase/Storage/Listener/LocalListenerTest.php | 5 +++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 0f6ba4a9..f2626de7 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -7,6 +7,7 @@ namespace Burzum\FileStorage\Storage\Listener; use Burzum\FileStorage\Storage\StorageException; +use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Psr\Log\LogLevel; @@ -74,9 +75,8 @@ public function implementedEvents() { * @throws \Burzum\Filestorage\Storage\StorageException * @return void */ - public function afterDelete(Event $event) { + public function afterDelete(Event $event, EntityInterface $entity) { if ($this->_checkEvent($event)) { - $entity = $event->data['record']; $path = $this->pathBuilder()->fullPath($entity); try { if ($this->storageAdapter($entity->adapter)->delete($path)) { @@ -101,9 +101,8 @@ public function afterDelete(Event $event) { * @param \Cake\Event\Event $event * @return void */ - public function afterSave(Event $event) { - if ($this->_checkEvent($event) && $event->data['record']->isNew()) { - $entity = $event->data['record']; + public function afterSave(Event $event, EntityInterface $entity) { + if ($this->_checkEvent($event) && $entity->isNew()) { $fileField = $this->config('fileField'); $entity['hash'] = $this->getFileHash($entity, $fileField); @@ -134,7 +133,7 @@ protected function _storeFile(Event $event) { $entity = $event->data['record']; $Storage = $this->storageAdapter($entity['adapter']); $Storage->write($entity['path'], file_get_contents($entity[$fileField]['tmp_name']), true); - $event->result = $event->subject()->save($entity, array( + $event->result = $event->data['table']->save($entity, array( 'checkRules' => false )); return true; diff --git a/tests/TestCase/Storage/Listener/LocalListenerTest.php b/tests/TestCase/Storage/Listener/LocalListenerTest.php index c1ecbff0..18bbb2c2 100644 --- a/tests/TestCase/Storage/Listener/LocalListenerTest.php +++ b/tests/TestCase/Storage/Listener/LocalListenerTest.php @@ -53,7 +53,8 @@ public function testAfterSave() { 'tmp_name' => $this->fileFixtures . 'titus.jpg' ]; $event = new Event('FileStorage.afterSave', $this->FileStorage, [ - 'record' => $entity + 'record' => $entity, + 'table' => $this->FileStorage ]); $this->listener->expects($this->at(0)) @@ -64,7 +65,7 @@ public function testAfterSave() { ->method('write') ->will($this->returnValue(true)); - $this->listener->afterSave($event); + $this->listener->afterSave($event, $entity); } /** From a5fd409590fa8b857bc315f9205ea8f33970cd04 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 11:50:29 +0200 Subject: [PATCH 094/144] ImageStorageTable minor improvements --- src/Model/Table/ImageStorageTable.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Model/Table/ImageStorageTable.php b/src/Model/Table/ImageStorageTable.php index 6d13972a..6affbc7a 100644 --- a/src/Model/Table/ImageStorageTable.php +++ b/src/Model/Table/ImageStorageTable.php @@ -58,9 +58,9 @@ public function beforeSave(Event $event, EntityInterface $entity, $options) { // if (!parent::beforeSave($event, $entity, $options)) { // return false; // } - $imageEvent = $this->dispatchEvent('ImageStorage.beforeSave', array( + $imageEvent = $this->dispatchEvent('ImageStorage.beforeSave', [ 'record' => $entity - )); + ]); if ($imageEvent->isStopped()) { return false; } @@ -80,8 +80,8 @@ public function beforeSave(Event $event, EntityInterface $entity, $options) { public function afterSave(Event $event, EntityInterface $entity, $options) { if ($entity->isNew()) { $this->dispatchEvent('ImageStorage.afterSave', [ - 'storage' => $this->storageAdapter($entity->get('adapter')), - 'record' => $entity + 'record' => $entity, + 'storage' => $this->storageAdapter($entity->get('adapter')) ]); $this->deleteOldFileOnSave($entity); } From f2fb52a214c0b40277a5e5f52d6367a8c953b9ea Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Aug 2015 13:12:12 +0200 Subject: [PATCH 095/144] Moved ImageStorageTable methods to ImageStorageBehavior. --- error.log | 9 + src/Event/LocalFileStorageListener.php | 2 +- .../Behavior/Event/EventDispatcherTrait.php | 3 + src/Model/Behavior/ImageStorageBehavior.php | 217 ++++++++++++++++++ src/Model/Table/ImageStorageTable.php | 184 +-------------- 5 files changed, 231 insertions(+), 184 deletions(-) create mode 100644 error.log create mode 100644 src/Model/Behavior/ImageStorageBehavior.php diff --git a/error.log b/error.log new file mode 100644 index 00000000..d236dc39 --- /dev/null +++ b/error.log @@ -0,0 +1,9 @@ +2015-08-04 10:59:40 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. +2015-08-04 10:59:56 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. +2015-08-04 11:01:06 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. +2015-08-04 11:01:21 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. +2015-08-04 11:02:10 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. +2015-08-04 11:02:15 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. +2015-08-04 11:02:16 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. +2015-08-04 11:05:03 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. +2015-08-04 11:05:12 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. diff --git a/src/Event/LocalFileStorageListener.php b/src/Event/LocalFileStorageListener.php index c86269db..bcde32a5 100644 --- a/src/Event/LocalFileStorageListener.php +++ b/src/Event/LocalFileStorageListener.php @@ -28,7 +28,7 @@ class LocalFileStorageListener extends AbstractStorageEventListener { '\Gaufrette\Adapter\Local' ); -/** + /** * Implemented Events * * @return array diff --git a/src/Model/Behavior/Event/EventDispatcherTrait.php b/src/Model/Behavior/Event/EventDispatcherTrait.php index 207d0e41..39e0d5c7 100644 --- a/src/Model/Behavior/Event/EventDispatcherTrait.php +++ b/src/Model/Behavior/Event/EventDispatcherTrait.php @@ -25,6 +25,9 @@ trait EventDispatcherTrait { */ public function dispatchEvent($name, $data = null, $subject = null) { $data['table'] = $this->_table; + if ($subject === null) { + $subject = $this->_table; + } return $this->_dispatchEvent($name, $data, $subject); } } diff --git a/src/Model/Behavior/ImageStorageBehavior.php b/src/Model/Behavior/ImageStorageBehavior.php new file mode 100644 index 00000000..029bac41 --- /dev/null +++ b/src/Model/Behavior/ImageStorageBehavior.php @@ -0,0 +1,217 @@ + [ + 'validateImageSize' => 'validateImageSize', + 'getImageVersions' => 'getImageVersions' + ] + ]; + +/** + * + * @param array $config + * @return void + */ + public function initialize(array $config) { + $this->_eventManager = $this->_table->eventManager(); + + parent::initialize($config); + } + +/** + * beforeSave callback + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return boolean true on success + */ + public function beforeSave(Event $event, EntityInterface $entity, $options) { + $imageEvent = $this->dispatchEvent('ImageStorage.beforeSave', [ + 'record' => $entity + ], $this->_table); + if ($imageEvent->isStopped()) { + return false; + } + return true; + } + +/** + * afterSave callback + * + * Does not call the parent to avoid that the regular file storage event listener saves the image already + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return boolean + */ + public function afterSave(Event $event, EntityInterface $entity, $options) { + if ($entity->isNew()) { + $this->dispatchEvent('ImageStorage.afterSave', [ + 'record' => $entity, + 'storage' => $this->storageAdapter($entity->get('adapter')) + ], $this->_table); + $this->_table->deleteOldFileOnSave($entity); + } + return true; + } + +/** + * Get a copy of the actual record before we delete it to have it present in afterDelete + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @return boolean + */ + public function beforeDelete(Event $event, EntityInterface $entity) { + $imageEvent = $this->dispatchEvent('ImageStorage.beforeDelete', [ + 'record' => $this->_table->record, + 'storage' => $this->storageAdapter($this->_table->record['adapter']) + ], $this->_table); + + if ($imageEvent->isStopped()) { + return false; + } + + return true; + } + +/** + * After the main file was deleted remove the the thumbnails + * + * Note that we do not call the parent::afterDelete(), we just want to trigger the ImageStorage.afterDelete event but not the FileStorage.afterDelete at the same time! + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return boolean + */ + public function afterDelete(Event $event, EntityInterface $entity, $options) { + $this->dispatchEvent('ImageStorage.afterDelete', [ + 'record' => $entity, + 'storage' => $this->storageAdapter($entity->get('adapter')) + ], $this->_table); + return true; + } + +/** + * Image size validation method + * + * @param mixed $check + * @param array $options is an array with key width or height and a value of array + * with two options, operator and value. For example: + * array('height' => array('==', 100)) will only be true if the image has a + * height of exactly 100px. See the CakePHP core class and method + * Validation::comparison for all operators. + * @return boolean true + * @see Validation::comparison() + * @throws \InvalidArgumentException + */ + public function validateImageSize($check, array $options = []) { + if (!isset($options['height']) && !isset($options['width'])) { + throw new \InvalidArgumentException('Missing image size validation options! You must provide a hight and / or width.'); + } + + if (is_string($check)) { + $imageFile = $check; + } else { + $check = array_values($check); + $check = $check[0]; + if (is_array($check) && isset($check['tmp_name'])) { + $imageFile = $check['tmp_name']; + } else { + $imageFile = $check; + } + } + + $imageSizes = $this->_table->getImageSize($imageFile); + + if (isset($options['height'])) { + $height = Validation::comparison($imageSizes[1], $options['height'][0], $options['height'][1]); + } else { + $height = true; + } + + if (isset($options['width'])) { + $width = Validation::comparison($imageSizes[0], $options['width'][0], $options['width'][1]); + } else { + $width = true; + } + + if ($height === false || $width === false) { + return false; + } + + return true; + } + +/** + * Gets a list of image versions for a given record. + * + * Use this method to get a list of ALL versions when needed or to cache all the + * versions somewhere. This method will return all configured versions for an + * image. For example you could store them serialized along with the file data + * by adding a "versions" field to the DB table and extend this model. + * + * Just in case you're wondering about the event name in the method code: It's + * called FileStorage.ImageHelper.imagePath there because the event is the same + * as in the helper. No need to introduce yet another event, the existing event + * already fulfills the purpose. I might rename this event in the 3.0 version of + * the plugin to a more generic one. + * + * @param \Cake\Datasource\EntityInterface $entity An ImageStorage database record + * @param array $options Options for the version. + * @return array A list of versions for this image file. Key is the version, value is the path or URL to that image. + */ + public function getImageVersions(EntityInterface $entity, $options = []) { + $versions = []; + $versionData = (array)Configure::read('FileStorage.imageSizes.' . $entity->get('model')); + $versionData['original'] = isset($options['originalVersion']) ? $options['originalVersion'] : 'original'; + foreach ($versionData as $version => $data) { + $hash = Configure::read('FileStorage.imageHashes.' . $entity->get('model') . '.' . $version); + $event = $this->dispatchEvent('ImageVersion.getVersions', [ + 'hash' => $hash, + 'image' => $entity, + 'version' => $version, + 'options' => [] + ], + $this->_table + ); + if ($event->isStopped()) { + $versions[$version] = str_replace('\\', '/', $event->data['path']); + } + } + return $versions; + } +} diff --git a/src/Model/Table/ImageStorageTable.php b/src/Model/Table/ImageStorageTable.php index 6affbc7a..716b7916 100644 --- a/src/Model/Table/ImageStorageTable.php +++ b/src/Model/Table/ImageStorageTable.php @@ -1,14 +1,6 @@ addBehavior('Burzum/FileStorage.ImageStorage'); $this->addBehavior('Burzum/Imagine.Imagine'); $this->addBehavior('Burzum/FileStorage.UploadValidator', array( 'localFile' => false, @@ -46,176 +36,4 @@ public function initialize(array $config) { )); } -/** - * beforeSave callback - * - * @param \Cake\Event\Event $event - * @param \Cake\Datasource\EntityInterface $entity - * @param array $options - * @return boolean true on success - */ - public function beforeSave(Event $event, EntityInterface $entity, $options) { -// if (!parent::beforeSave($event, $entity, $options)) { -// return false; -// } - $imageEvent = $this->dispatchEvent('ImageStorage.beforeSave', [ - 'record' => $entity - ]); - if ($imageEvent->isStopped()) { - return false; - } - return true; - } - -/** - * afterSave callback - * - * Does not call the parent to avoid that the regular file storage event listener saves the image already - * - * @param \Cake\Event\Event $event - * @param \Cake\Datasource\EntityInterface $entity - * @param array $options - * @return boolean - */ - public function afterSave(Event $event, EntityInterface $entity, $options) { - if ($entity->isNew()) { - $this->dispatchEvent('ImageStorage.afterSave', [ - 'record' => $entity, - 'storage' => $this->storageAdapter($entity->get('adapter')) - ]); - $this->deleteOldFileOnSave($entity); - } - return true; - } - -/** - * Get a copy of the actual record before we delete it to have it present in afterDelete - * - * @param \Cake\Event\Event $event - * @param \Cake\Datasource\EntityInterface $entity - * @return boolean - */ - public function beforeDelete(Event $event, EntityInterface $entity) { -// if (!parent::beforeDelete($event, $entity)) { -// return false; -// } - - $imageEvent = $this->dispatchEvent('ImageStorage.beforeDelete', [ - 'record' => $this->record, - 'storage' => $this->storageAdapter($this->record['adapter']) - ]); - - if ($imageEvent->isStopped()) { - return false; - } - - return true; - } - -/** - * After the main file was deleted remove the the thumbnails - * - * Note that we do not call the parent::afterDelete(), we just want to trigger the ImageStorage.afterDelete event but not the FileStorage.afterDelete at the same time! - * - * @param \Cake\Event\Event $event - * @param \Cake\Datasource\EntityInterface $entity - * @param array $options - * @return boolean - */ - public function afterDelete(Event $event, EntityInterface $entity, $options) { - $this->dispatchEvent('ImageStorage.afterDelete', [ - 'record' => $entity, - 'storage' => $this->storageAdapter($entity->get('adapter')) - ]); - return true; - } - -/** - * Image size validation method - * - * @param mixed $check - * @param array $options is an array with key width or height and a value of array - * with two options, operator and value. For example: - * array('height' => array('==', 100)) will only be true if the image has a - * height of exactly 100px. See the CakePHP core class and method - * Validation::comparison for all operators. - * @return boolean true - * @see Validation::comparison() - * @throws \InvalidArgumentException - */ - public function validateImageSize($check, array $options = []) { - if (!isset($options['height']) && !isset($options['width'])) { - throw new \InvalidArgumentException('Missing image size validation options! You must provide a hight and / or width.'); - } - - if (is_string($check)) { - $imageFile = $check; - } else { - $check = array_values($check); - $check = $check[0]; - if (is_array($check) && isset($check['tmp_name'])) { - $imageFile = $check['tmp_name']; - } else { - $imageFile = $check; - } - } - - $imageSizes = $this->getImageSize($imageFile); - - if (isset($options['height'])) { - $height = Validation::comparison($imageSizes[1], $options['height'][0], $options['height'][1]); - } else { - $height = true; - } - - if (isset($options['width'])) { - $width = Validation::comparison($imageSizes[0], $options['width'][0], $options['width'][1]); - } else { - $width = true; - } - - if ($height === false || $width === false) { - return false; - } - - return true; - } - -/** - * Gets a list of image versions for a given record. - * - * Use this method to get a list of ALL versions when needed or to cache all the - * versions somewhere. This method will return all configured versions for an - * image. For example you could store them serialized along with the file data - * by adding a "versions" field to the DB table and extend this model. - * - * Just in case you're wondering about the event name in the method code: It's - * called FileStorage.ImageHelper.imagePath there because the event is the same - * as in the helper. No need to introduce yet another event, the existing event - * already fulfills the purpose. I might rename this event in the 3.0 version of - * the plugin to a more generic one. - * - * @param \Cake\Datasource\EntityInterface $entity An ImageStorage database record - * @param array $options Options for the version. - * @return array A list of versions for this image file. Key is the version, value is the path or URL to that image. - */ - public function getImageVersions(EntityInterface $entity, $options = []) { - $versions = []; - $versionData = (array)Configure::read('FileStorage.imageSizes.' . $entity->get('model')); - $versionData['original'] = isset($options['originalVersion']) ? $options['originalVersion'] : 'original'; - foreach ($versionData as $version => $data) { - $hash = Configure::read('FileStorage.imageHashes.' . $entity->get('model') . '.' . $version); - $event = $this->dispatchEvent('ImageVersion.getVersions', [ - 'hash' => $hash, - 'image' => $entity, - 'version' => $version, - 'options' => [] - ] - ); - if ($event->isStopped()) { - $versions[$version] = str_replace('\\', '/', $event->data['path']); - } - } - return $versions; - } } From 228d06b21687226c905904434662c94b3bc25b09 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 5 Aug 2015 12:33:04 +0200 Subject: [PATCH 096/144] Detacth FileStorageBehavior from afterSave and afterDelete events to keep BC with old tables, where callbacks in FileStorageTable were replaced by ImageStorageTable. --- src/Model/Behavior/ImageStorageBehavior.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Model/Behavior/ImageStorageBehavior.php b/src/Model/Behavior/ImageStorageBehavior.php index 029bac41..8bd54807 100644 --- a/src/Model/Behavior/ImageStorageBehavior.php +++ b/src/Model/Behavior/ImageStorageBehavior.php @@ -46,6 +46,15 @@ public function initialize(array $config) { $this->_eventManager = $this->_table->eventManager(); parent::initialize($config); + + //remove FileStorageBehavior afterSave and afterDelete listeners to keep BC with overwrited callbacks in old tables. + $fileStorageBehavior = $this->_table->behaviors()->get('FileStorage'); + if ($fileStorageBehavior) { + $events = ['Model.afterSave', 'Model.afterDelete']; + foreach ($events as $event) { + $this->_eventManager->off($event, $fileStorageBehavior); + } + } } /** From af11dcb08a172ac33e3f5972cc5183abae60733c Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 5 Aug 2015 12:33:40 +0200 Subject: [PATCH 097/144] Move ImageStorage tests. --- .../ImageStorageBehaviorTest.php} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tests/TestCase/Model/{Table/ImageStorageTest.php => Behavior/ImageStorageBehaviorTest.php} (98%) diff --git a/tests/TestCase/Model/Table/ImageStorageTest.php b/tests/TestCase/Model/Behavior/ImageStorageBehaviorTest.php similarity index 98% rename from tests/TestCase/Model/Table/ImageStorageTest.php rename to tests/TestCase/Model/Behavior/ImageStorageBehaviorTest.php index 8dc8bbea..d90076db 100644 --- a/tests/TestCase/Model/Table/ImageStorageTest.php +++ b/tests/TestCase/Model/Behavior/ImageStorageBehaviorTest.php @@ -1,5 +1,5 @@ Date: Wed, 5 Aug 2015 13:01:45 +0200 Subject: [PATCH 098/144] Updated autor and removed accidentally added log file + tiny fixes. --- error.log | 9 --------- src/Event/LocalFileStorageListener.php | 2 +- src/Model/Behavior/Event/EventDispatcherTrait.php | 7 +++++++ src/Model/Behavior/FileStorageBehavior.php | 5 +++-- src/Model/Behavior/ImageStorageBehavior.php | 1 + 5 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 error.log diff --git a/error.log b/error.log deleted file mode 100644 index d236dc39..00000000 --- a/error.log +++ /dev/null @@ -1,9 +0,0 @@ -2015-08-04 10:59:40 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. -2015-08-04 10:59:56 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. -2015-08-04 11:01:06 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. -2015-08-04 11:01:21 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. -2015-08-04 11:02:10 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. -2015-08-04 11:02:15 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. -2015-08-04 11:02:16 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. -2015-08-04 11:05:03 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. -2015-08-04 11:05:12 Error: The file "Item/14/83/23/filestorage1/filestorage1.png" was not found. diff --git a/src/Event/LocalFileStorageListener.php b/src/Event/LocalFileStorageListener.php index bcde32a5..c86269db 100644 --- a/src/Event/LocalFileStorageListener.php +++ b/src/Event/LocalFileStorageListener.php @@ -28,7 +28,7 @@ class LocalFileStorageListener extends AbstractStorageEventListener { '\Gaufrette\Adapter\Local' ); - /** +/** * Implemented Events * * @return array diff --git a/src/Model/Behavior/Event/EventDispatcherTrait.php b/src/Model/Behavior/Event/EventDispatcherTrait.php index 39e0d5c7..39d56dee 100644 --- a/src/Model/Behavior/Event/EventDispatcherTrait.php +++ b/src/Model/Behavior/Event/EventDispatcherTrait.php @@ -3,6 +3,13 @@ use Cake\Event\EventDispatcherTrait as CakeEventDispatcherTrait; +/** + * EventDispatcherTrait + * + * @author Robert Pustułka + * @copyright 2012 - 2015 Florian Krämer + * @license MIT + */ trait EventDispatcherTrait { use CakeEventDispatcherTrait { diff --git a/src/Model/Behavior/FileStorageBehavior.php b/src/Model/Behavior/FileStorageBehavior.php index e6b79520..d365e184 100644 --- a/src/Model/Behavior/FileStorageBehavior.php +++ b/src/Model/Behavior/FileStorageBehavior.php @@ -15,6 +15,7 @@ * FileStorageTable * * @author Florian Krämer + * @author Robert Pustułka * @copyright 2012 - 2015 Florian Krämer * @license MIT */ @@ -91,10 +92,10 @@ public function getFileInfoFromUpload(&$upload, $field = 'file') { */ public function beforeSave(Event $event, EntityInterface $entity, $options) { $this->getFileInfoFromUpload($entity); - $storageEvent = $this->dispatchEvent('FileStorage.beforeSave', array( + $storageEvent = $this->dispatchEvent('FileStorage.beforeSave', [ 'record' => $entity, 'storage' => $this->storageAdapter($entity->get('adapter')) - )); + ]); if ($storageEvent->isStopped()) { return false; } diff --git a/src/Model/Behavior/ImageStorageBehavior.php b/src/Model/Behavior/ImageStorageBehavior.php index 8bd54807..156d6ff6 100644 --- a/src/Model/Behavior/ImageStorageBehavior.php +++ b/src/Model/Behavior/ImageStorageBehavior.php @@ -16,6 +16,7 @@ * FileStorageTable * * @author Florian Krämer + * @author Robert Pustułka * @copyright 2012 - 2015 Florian Krämer * @license MIT */ From 544237a609783a913a9dac411c7e5f153ad7a675 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 5 Aug 2015 13:11:31 +0200 Subject: [PATCH 099/144] CS fixes and unnesessary arguments removed. --- src/Model/Behavior/FileStorageBehavior.php | 2 +- src/Model/Behavior/ImageStorageBehavior.php | 38 ++++++++++----------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/Model/Behavior/FileStorageBehavior.php b/src/Model/Behavior/FileStorageBehavior.php index d365e184..a22808ba 100644 --- a/src/Model/Behavior/FileStorageBehavior.php +++ b/src/Model/Behavior/FileStorageBehavior.php @@ -30,7 +30,7 @@ class FileStorageBehavior extends Behavior implements EventDispatcherInterface { * * @var array */ - protected $_defaultConfig = [ + protected $_defaultConfig = [ 'implementedMethods' => [ 'getFileInfoFromUpload' => 'getFileInfoFromUpload', 'deleteOldFileOnSave' => 'deleteOldFileOnSave', diff --git a/src/Model/Behavior/ImageStorageBehavior.php b/src/Model/Behavior/ImageStorageBehavior.php index 156d6ff6..dde479c4 100644 --- a/src/Model/Behavior/ImageStorageBehavior.php +++ b/src/Model/Behavior/ImageStorageBehavior.php @@ -31,7 +31,7 @@ class ImageStorageBehavior extends Behavior implements EventDispatcherInterface * * @var array */ - protected $_defaultConfig = [ + protected $_defaultConfig = [ 'implementedMethods' => [ 'validateImageSize' => 'validateImageSize', 'getImageVersions' => 'getImageVersions' @@ -48,14 +48,14 @@ public function initialize(array $config) { parent::initialize($config); - //remove FileStorageBehavior afterSave and afterDelete listeners to keep BC with overwrited callbacks in old tables. - $fileStorageBehavior = $this->_table->behaviors()->get('FileStorage'); - if ($fileStorageBehavior) { - $events = ['Model.afterSave', 'Model.afterDelete']; - foreach ($events as $event) { - $this->_eventManager->off($event, $fileStorageBehavior); - } - } + //remove FileStorageBehavior afterSave and afterDelete listeners to keep BC with overwrited callbacks in old tables. + $fileStorageBehavior = $this->_table->behaviors()->get('FileStorage'); + if ($fileStorageBehavior) { + $events = ['Model.afterSave', 'Model.afterDelete']; + foreach ($events as $event) { + $this->_eventManager->off($event, $fileStorageBehavior); + } + } } /** @@ -69,7 +69,7 @@ public function initialize(array $config) { public function beforeSave(Event $event, EntityInterface $entity, $options) { $imageEvent = $this->dispatchEvent('ImageStorage.beforeSave', [ 'record' => $entity - ], $this->_table); + ]); if ($imageEvent->isStopped()) { return false; } @@ -91,7 +91,7 @@ public function afterSave(Event $event, EntityInterface $entity, $options) { $this->dispatchEvent('ImageStorage.afterSave', [ 'record' => $entity, 'storage' => $this->storageAdapter($entity->get('adapter')) - ], $this->_table); + ]); $this->_table->deleteOldFileOnSave($entity); } return true; @@ -108,7 +108,7 @@ public function beforeDelete(Event $event, EntityInterface $entity) { $imageEvent = $this->dispatchEvent('ImageStorage.beforeDelete', [ 'record' => $this->_table->record, 'storage' => $this->storageAdapter($this->_table->record['adapter']) - ], $this->_table); + ]); if ($imageEvent->isStopped()) { return false; @@ -131,7 +131,7 @@ public function afterDelete(Event $event, EntityInterface $entity, $options) { $this->dispatchEvent('ImageStorage.afterDelete', [ 'record' => $entity, 'storage' => $this->storageAdapter($entity->get('adapter')) - ], $this->_table); + ]); return true; } @@ -211,13 +211,11 @@ public function getImageVersions(EntityInterface $entity, $options = []) { foreach ($versionData as $version => $data) { $hash = Configure::read('FileStorage.imageHashes.' . $entity->get('model') . '.' . $version); $event = $this->dispatchEvent('ImageVersion.getVersions', [ - 'hash' => $hash, - 'image' => $entity, - 'version' => $version, - 'options' => [] - ], - $this->_table - ); + 'hash' => $hash, + 'image' => $entity, + 'version' => $version, + 'options' => [] + ]); if ($event->isStopped()) { $versions[$version] = str_replace('\\', '/', $event->data['path']); } From c76fd30bb416c8de1226c0967efe058871c48edc Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 5 Aug 2015 13:16:14 +0200 Subject: [PATCH 100/144] LegacyLocalFileStorageListener methods declaratioin updated + docblock updates. --- .../LegacyLocalFileStorageListener.php | 23 +++++++++---------- src/Storage/Listener/LocalListener.php | 2 ++ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Storage/Listener/LegacyLocalFileStorageListener.php b/src/Storage/Listener/LegacyLocalFileStorageListener.php index 59e8fdaa..8ba96c38 100644 --- a/src/Storage/Listener/LegacyLocalFileStorageListener.php +++ b/src/Storage/Listener/LegacyLocalFileStorageListener.php @@ -6,6 +6,7 @@ */ namespace Burzum\FileStorage\Storage\Listener; +use Cake\Datasource\EntityInterface; use Cake\Event\Event; /** @@ -36,23 +37,21 @@ class LegacyLocalFileStorageListener extends LocalListener { * Save the file to the storage backend after the record was created. * * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity * @return void */ - public function afterSave(Event $event) { - if ($this->_checkEvent($event) && $event->data['record']->isNew()) { - if ($this->_checkEvent($event) && $event->data['record']->isNew()) { - $entity = $event->data['record']; - $fileField = $this->config('fileField'); + public function afterSave(Event $event, EntityInterface $entity) { + if ($this->_checkEvent($event) && $entity->isNew()) { + $fileField = $this->config('fileField'); - $this->entity['hash'] = $this->getHash($entity, $fileField); - $entity['path'] = $this->pathBuilder()->path($entity); + $entity['hash'] = $this->getHash($entity, $fileField); + $entity['path'] = $this->pathBuilder()->path($entity); - if (!$this->_storeFile($entity)) { - return; - } - - $event->stopPropagation(); + if (!$this->_storeFile($entity)) { + return; } + + $event->stopPropagation(); } } } diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index f2626de7..90b82079 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -72,6 +72,7 @@ public function implementedEvents() { * No need to use an adapter here, just delete the whole folder using cakes Folder class * * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity * @throws \Burzum\Filestorage\Storage\StorageException * @return void */ @@ -99,6 +100,7 @@ public function afterDelete(Event $event, EntityInterface $entity) { * Save the file to the storage backend after the record was created. * * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity * @return void */ public function afterSave(Event $event, EntityInterface $entity) { From 47d9651e53e8bf8a45b3cbaef003334955148644 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 10 Aug 2015 12:39:20 +0200 Subject: [PATCH 101/144] Update _processImages to load images config. --- src/Storage/Listener/LocalListener.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 90b82079..8e95c8a6 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -166,9 +166,10 @@ protected function _processImages(Event $event, $method) { if ($this->config('imageProcessing') !== true) { return; } + $this->_loadImageProcessingFromConfig(); $event->result = $this->{$method}( $event->data['record'], - $event->data['operations'] + $event->data['versions'] ); } } From 5a09ff6ff2aa2293c12845a3b41f70d267f9c503 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 10 Aug 2015 12:39:47 +0200 Subject: [PATCH 102/144] Updated ImageVersioShell to use new event listeners. --- src/Shell/ImageVersionShell.php | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/Shell/ImageVersionShell.php b/src/Shell/ImageVersionShell.php index 38afd0cb..4cebc51d 100644 --- a/src/Shell/ImageVersionShell.php +++ b/src/Shell/ImageVersionShell.php @@ -7,7 +7,6 @@ use Cake\Event\EventManager; use Cake\ORM\TableRegistry; use Burzum\FileStorage\Storage\StorageManager; -use Burzum\FileStorage\Model\Table\ImageStorageTable; /** * ImageShell @@ -126,8 +125,8 @@ public function getOptionParser() { /** * @inheritDoc */ - public function initialize() { - parent::initialize(); + public function startup() { + parent::startup(); $storageTable = 'Burzum/FileStorage.ImageStorage'; if (isset($this->params['storageTable'])) { @@ -136,12 +135,6 @@ public function initialize() { $this->Table = TableRegistry::get($storageTable); - if (!$this->Table instanceOf ImageStorageTable) { - $this->out(__d('file_storage', 'Invalid Storage Table: {0}', $storageTable)); - $this->out(__d('file_storage', 'The table must be an instance of Burzum\FileStorage\Model\Table\ImageStorageTable or extend it!')); - $this->_stop(); - } - if (isset($this->params['limit'])) { if (!is_numeric($this->params['limit'])) { $this->out(__d('file_storage', '--limit must be an integer!')); @@ -163,9 +156,9 @@ public function regenerate() { $this->_stop(); } - foreach ($operations as $operation) { + foreach ($operations as $version => $operation) { try { - $this->_loop($this->command, $this->args[0], array($operation)); + $this->_loop($this->command, $this->args[0], array($version)); } catch (\Exception $e) { $this->out($e->getMessage()); $this->_stop(); @@ -188,7 +181,7 @@ public function generate($model, $version) { } try { - $this->_loop('generate', $model, array($version => $operations)); + $this->_loop('generate', $model, array($version)); } catch (\Exception $e) { $this->out($e->getMessage()); $this->_stop(); @@ -222,9 +215,9 @@ public function remove($model, $version) { * * @param string $action * @param $model - * @param array $operations + * @param array $versions */ - protected function _loop($action, $model, $operations = []) { + protected function _loop($action, $model, $versions = []) { if (!in_array($action, array('generate', 'remove', 'regenerate'))) { $this->_stop(); } @@ -236,7 +229,7 @@ protected function _loop($action, $model, $operations = []) { $this->_stop(); } - $this->out(__d('file_storage', '{0} image file(s) will be processed' . "\n", $this->totalImageCount)); + $this->out(__d('file_storage', '{0} image file(s) will be processed' . "\n", $totalImageCount)); $offset = 0; $limit = $this->limit; @@ -252,7 +245,8 @@ protected function _loop($action, $model, $operations = []) { $payload = array( 'record' => $image, 'storage' => $Storage, - 'operations' => $operations + 'versions' => $versions, + 'table' => $this->Table ); if ($action == 'generate' || $action == 'regenerate') { From c693514333e63dbec7f40aeb219bf2894668546f Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 11 Aug 2015 08:53:46 +0200 Subject: [PATCH 103/144] Keep operations event data for BC. --- src/Shell/ImageVersionShell.php | 11 ++++++----- src/Storage/Listener/LocalListener.php | 11 ++++++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Shell/ImageVersionShell.php b/src/Shell/ImageVersionShell.php index 4cebc51d..f3972a4e 100644 --- a/src/Shell/ImageVersionShell.php +++ b/src/Shell/ImageVersionShell.php @@ -158,7 +158,7 @@ public function regenerate() { foreach ($operations as $version => $operation) { try { - $this->_loop($this->command, $this->args[0], array($version)); + $this->_loop($this->command, $this->args[0], array($version => $operation)); } catch (\Exception $e) { $this->out($e->getMessage()); $this->_stop(); @@ -181,7 +181,7 @@ public function generate($model, $version) { } try { - $this->_loop('generate', $model, array($version)); + $this->_loop('generate', $model, array($version => $operations)); } catch (\Exception $e) { $this->out($e->getMessage()); $this->_stop(); @@ -215,9 +215,9 @@ public function remove($model, $version) { * * @param string $action * @param $model - * @param array $versions + * @param array $operations */ - protected function _loop($action, $model, $versions = []) { + protected function _loop($action, $model, $operations = []) { if (!in_array($action, array('generate', 'remove', 'regenerate'))) { $this->_stop(); } @@ -245,7 +245,8 @@ protected function _loop($action, $model, $versions = []) { $payload = array( 'record' => $image, 'storage' => $Storage, - 'versions' => $versions, + 'operations' => $operations, + 'versions' => array_keys($operations), 'table' => $this->Table ); diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 8e95c8a6..155752c3 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -166,10 +166,19 @@ protected function _processImages(Event $event, $method) { if ($this->config('imageProcessing') !== true) { return; } + + if (isset($event->data['versions'])) { + $versions = $event->data['versions']; + } elseif (isset($event->data['operations'])) { + $versions = array_keys($event->data['operations']); + } else { + $versions = []; + } + $this->_loadImageProcessingFromConfig(); $event->result = $this->{$method}( $event->data['record'], - $event->data['versions'] + $versions ); } } From 700247458e70c455acc2dda1ec239774d4a05abc Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 12 Aug 2015 15:37:58 +0200 Subject: [PATCH 104/144] Move version resolving to a seperate method. --- src/Storage/Listener/LocalListener.php | 28 ++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 155752c3..9739f933 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -125,7 +125,7 @@ public function afterSave(Event $event, EntityInterface $entity) { /** * Stores the file in the configured storage backend. * - * @param $event \Cake\Event\Event $event + * @param \Cake\Event\Event $event * @throws \Burzum\Filestorage\Storage\StorageException * @return boolean */ @@ -167,6 +167,26 @@ protected function _processImages(Event $event, $method) { return; } + $versions = $this->_getVersionData($event); + + $this->_loadImageProcessingFromConfig(); + $event->result = $this->{$method}( + $event->data['record'], + $versions + ); + } + +/** + * This method retrieves version names from event data. + * For backward compatibility version names are resolved from operations data keys because in old + * ImageProcessingListener operations were required in event data. ImageProcessingTrait need only + * version names so operations can be read from the config. + * + * @param \Cake\Event\Event $event + * @return array + */ + protected function _getVersionData($event) + { if (isset($event->data['versions'])) { $versions = $event->data['versions']; } elseif (isset($event->data['operations'])) { @@ -175,10 +195,6 @@ protected function _processImages(Event $event, $method) { $versions = []; } - $this->_loadImageProcessingFromConfig(); - $event->result = $this->{$method}( - $event->data['record'], - $versions - ); + return $versions; } } From f7f9a62f250e69b1c5b5cb1ea11a382f4d91bbd7 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 08:45:41 +0200 Subject: [PATCH 105/144] Allow to pass imagine save options to createImageVersions(). Default options can be set in FileStorage.saveOptions config key. --- src/Storage/Listener/ImageProcessingTrait.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index bf2869c8..1c5e0b04 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -110,16 +110,18 @@ protected function _checkImageVersions($identifier, array $versions) { * Creates the image versions of an entity. * * @param \Cake\Datasource\EntityInterface $entity - * @param array $versions $options - * @param array $options + * @param array $versions Versions array. + * @param array $options Imagine save options. * @return array */ public function createImageVersions(EntityInterface $entity, array $versions, array $options = []) { $this->_checkImageVersions($entity->model, $versions); + $options += (array)Configure::read('FileStorage.saveOptions'); + $result = []; $storage = $this->storageAdapter($entity->adapter); - foreach ($this->_imageVersions[$entity->model] as $version => $config) { + foreach ($this->_imageVersions[$entity->model] as $version => $operations) { if (!in_array($version, $versions)) { continue; } @@ -134,7 +136,7 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar $output = $this->createTmpFile(); $tmpFile = $this->_tmpFile($storage, $this->pathBuilder()->fullPath($entity)); $this->imageProcessor()->open($tmpFile); - $this->imageProcessor()->batchProcess($output, $config, ['format' => $entity->extension]); + $this->imageProcessor()->batchProcess($output, $operations, ['format' => $entity->extension] + $options); $storage->write($path, file_get_contents($output)); unlink($tmpFile); From 58d472e425fe7573fdf7faf57691a8881fd3b80c Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 09:21:17 +0200 Subject: [PATCH 106/144] Fix: allow storage adapter to overwrite versions. --- src/Storage/Listener/ImageProcessingTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 1c5e0b04..5f6b9d6c 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -137,7 +137,7 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar $tmpFile = $this->_tmpFile($storage, $this->pathBuilder()->fullPath($entity)); $this->imageProcessor()->open($tmpFile); $this->imageProcessor()->batchProcess($output, $operations, ['format' => $entity->extension] + $options); - $storage->write($path, file_get_contents($output)); + $storage->write($path, file_get_contents($output), true); unlink($tmpFile); unlink($output); From 06901fa16df0948c7b4b1caefb7a458b6e2cd81e Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 10:09:31 +0200 Subject: [PATCH 107/144] Add some control over versions overwriting process. --- src/Shell/ImageVersionShell.php | 25 ++++++++++++++++--- src/Storage/Listener/ImageProcessingTrait.php | 25 ++++++++++++------- src/Storage/Listener/LocalListener.php | 7 ++++-- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/Shell/ImageVersionShell.php b/src/Shell/ImageVersionShell.php index f3972a4e..c3a5a85f 100644 --- a/src/Shell/ImageVersionShell.php +++ b/src/Shell/ImageVersionShell.php @@ -69,6 +69,11 @@ public function getOptionParser() { 'short' => 'l', 'help' => __d('file_storage', 'Limits the amount of records to be processed in one batch'), ], + 'keep-old-versions' => [ + 'short' => 'k', + 'help' => __d('file_storage', 'Use this switch if you do not want to overwrite existing versions.'), + 'bool' => true + ] ], ], ], @@ -115,6 +120,11 @@ public function getOptionParser() { 'short' => 'l', 'help' => __d('file_storage', 'Limits the amount of records to be processed in one batch'), ], + 'keep-old-versions' => [ + 'short' => 'k', + 'help' => __d('file_storage', 'Use this switch if you do not want to overwrite existing versions.'), + 'bool' => true + ] ], ], ], @@ -150,6 +160,9 @@ public function startup() { */ public function regenerate() { $operations = Configure::read('FileStorage.imageSizes.' . $this->args[0]); + $options = [ + 'overwrite' => !$this->params['keep-old-versions'] + ]; if (empty($operations)) { $this->out(__d('file_storage', 'Invalid table or version.')); @@ -158,7 +171,7 @@ public function regenerate() { foreach ($operations as $version => $operation) { try { - $this->_loop($this->command, $this->args[0], array($version => $operation)); + $this->_loop($this->command, $this->args[0], array($version => $operation), $options); } catch (\Exception $e) { $this->out($e->getMessage()); $this->_stop(); @@ -174,6 +187,9 @@ public function regenerate() { */ public function generate($model, $version) { $operations = Configure::read('FileStorage.imageSizes.' . $model . '.' . $version); + $options = [ + 'overwrite' => !$this->params['keep-old-versions'] + ]; if (empty($operations)) { $this->out(__d('file_storage', 'Invalid table or version.')); @@ -181,7 +197,7 @@ public function generate($model, $version) { } try { - $this->_loop('generate', $model, array($version => $operations)); + $this->_loop('generate', $model, array($version => $operations), $options); } catch (\Exception $e) { $this->out($e->getMessage()); $this->_stop(); @@ -217,7 +233,7 @@ public function remove($model, $version) { * @param $model * @param array $operations */ - protected function _loop($action, $model, $operations = []) { + protected function _loop($action, $model, $operations = [], $options = []) { if (!in_array($action, array('generate', 'remove', 'regenerate'))) { $this->_stop(); } @@ -247,7 +263,8 @@ protected function _loop($action, $model, $operations = []) { 'storage' => $Storage, 'operations' => $operations, 'versions' => array_keys($operations), - 'table' => $this->Table + 'table' => $this->Table, + 'options' => $options ); if ($action == 'generate' || $action == 'regenerate') { diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 5f6b9d6c..a31bb850 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -117,7 +117,9 @@ protected function _checkImageVersions($identifier, array $versions) { public function createImageVersions(EntityInterface $entity, array $versions, array $options = []) { $this->_checkImageVersions($entity->model, $versions); - $options += (array)Configure::read('FileStorage.saveOptions'); + $options += (array)Configure::read('FileStorage.saveOptions') + [ + 'overwrite' => true + ]; $result = []; $storage = $this->storageAdapter($entity->adapter); @@ -133,14 +135,19 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar 'hash' => $this->_imageVersionHashes[$entity->model][$version], ]; try { - $output = $this->createTmpFile(); - $tmpFile = $this->_tmpFile($storage, $this->pathBuilder()->fullPath($entity)); - $this->imageProcessor()->open($tmpFile); - $this->imageProcessor()->batchProcess($output, $operations, ['format' => $entity->extension] + $options); - $storage->write($path, file_get_contents($output), true); - - unlink($tmpFile); - unlink($output); + if ($options['overwrite'] || !$storage->has($path)) { + $saveOptions = ['format' => $entity->extension] + $options; + unset($saveOptions['overwrite']); + + $output = $this->createTmpFile(); + $tmpFile = $this->_tmpFile($storage, $this->pathBuilder()->fullPath($entity)); + $this->imageProcessor()->open($tmpFile); + $this->imageProcessor()->batchProcess($output, $operations, $saveOptions); + $storage->write($path, file_get_contents($output), true); + + unlink($tmpFile); + unlink($output); + } } catch (\Exception $e) { $result[$version] = [ 'status' => 'error', diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 9739f933..33c42997 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -115,7 +115,8 @@ public function afterSave(Event $event, EntityInterface $entity) { } if ($this->_config['imageProcessing'] === true) { - $this->autoProcessImageVersions($entity, 'create'); + $options = isset($event->data['options']) ? $event->data['options'] : []; + $this->autoProcessImageVersions($entity, 'create', $options); } $event->stopPropagation(); @@ -168,11 +169,13 @@ protected function _processImages(Event $event, $method) { } $versions = $this->_getVersionData($event); + $options = isset($event->data['options']) ? $event->data['options'] : []; $this->_loadImageProcessingFromConfig(); $event->result = $this->{$method}( $event->data['record'], - $versions + $versions, + $options ); } From f03966d4d79299328e6e8dc5b9928c1dc2842530 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 10:11:22 +0200 Subject: [PATCH 108/144] Fix boolean options --- src/Shell/ImageVersionShell.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Shell/ImageVersionShell.php b/src/Shell/ImageVersionShell.php index c3a5a85f..bbfc7699 100644 --- a/src/Shell/ImageVersionShell.php +++ b/src/Shell/ImageVersionShell.php @@ -72,7 +72,7 @@ public function getOptionParser() { 'keep-old-versions' => [ 'short' => 'k', 'help' => __d('file_storage', 'Use this switch if you do not want to overwrite existing versions.'), - 'bool' => true + 'boolean' => true ] ], ], @@ -123,7 +123,7 @@ public function getOptionParser() { 'keep-old-versions' => [ 'short' => 'k', 'help' => __d('file_storage', 'Use this switch if you do not want to overwrite existing versions.'), - 'bool' => true + 'boolean' => true ] ], ], From cd74392ba6e3969da5bb74081b402bdebda15e82 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 11:15:09 +0200 Subject: [PATCH 109/144] Allow output options to be set per version. --- src/Storage/Listener/ImageProcessingTrait.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index a31bb850..2d8c1bba 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -117,7 +117,7 @@ protected function _checkImageVersions($identifier, array $versions) { public function createImageVersions(EntityInterface $entity, array $versions, array $options = []) { $this->_checkImageVersions($entity->model, $versions); - $options += (array)Configure::read('FileStorage.saveOptions') + [ + $options += (array)Configure::read('FileStorage.defaultOutput') + [ 'overwrite' => true ]; @@ -136,7 +136,11 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar ]; try { if ($options['overwrite'] || !$storage->has($path)) { - $saveOptions = ['format' => $entity->extension] + $options; + $saveOptions = $options + ['format' => $entity->extension]; + if (isset($operations['_output'])) { + $saveOptions += $operations['_output']; + unset($operations['_output']); + } unset($saveOptions['overwrite']); $output = $this->createTmpFile(); From 99579a3afb1baa57cc9ecc5a3bb40d4366242992 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 11:19:59 +0200 Subject: [PATCH 110/144] Fix save options. --- src/Storage/Listener/ImageProcessingTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 2d8c1bba..7309ee62 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -138,7 +138,7 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar if ($options['overwrite'] || !$storage->has($path)) { $saveOptions = $options + ['format' => $entity->extension]; if (isset($operations['_output'])) { - $saveOptions += $operations['_output']; + $saveOptions = $operations['_output'] + $saveOptions; unset($operations['_output']); } unset($saveOptions['overwrite']); From 8e69e5d15bf29144d5dd0693a0224317ce87fa91 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 11:51:35 +0200 Subject: [PATCH 111/144] Docblocks updated --- src/Model/Behavior/FileStorageBehavior.php | 2 +- src/Model/Behavior/ImageStorageBehavior.php | 3 ++- src/Model/Table/FileStorageTable.php | 1 + src/Model/Table/ImageStorageTable.php | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Model/Behavior/FileStorageBehavior.php b/src/Model/Behavior/FileStorageBehavior.php index a22808ba..31278ecf 100644 --- a/src/Model/Behavior/FileStorageBehavior.php +++ b/src/Model/Behavior/FileStorageBehavior.php @@ -12,7 +12,7 @@ use Cake\ORM\Behavior; /** - * FileStorageTable + * FileStorageBehavior * * @author Florian Krämer * @author Robert Pustułka diff --git a/src/Model/Behavior/ImageStorageBehavior.php b/src/Model/Behavior/ImageStorageBehavior.php index dde479c4..c18f3f55 100644 --- a/src/Model/Behavior/ImageStorageBehavior.php +++ b/src/Model/Behavior/ImageStorageBehavior.php @@ -13,7 +13,8 @@ use Cake\Validation\Validation; /** - * FileStorageTable + * ImageStorageBehavior + * Works alongside FileStorageBehavior so make sure you add both of them in your tables. * * @author Florian Krämer * @author Robert Pustułka diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index 14740e92..d791c27f 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -9,6 +9,7 @@ * @author Florian Krämer * @copyright 2012 - 2015 Florian Krämer * @license MIT + * @deprecated 3.1.0 Use ImageStorageBehavior in your tables instead. */ class FileStorageTable extends Table { diff --git a/src/Model/Table/ImageStorageTable.php b/src/Model/Table/ImageStorageTable.php index 716b7916..e8566b44 100644 --- a/src/Model/Table/ImageStorageTable.php +++ b/src/Model/Table/ImageStorageTable.php @@ -7,6 +7,7 @@ * @author Florian Krämer * @copyright 2012 - 2015 Florian Krämer * @license MIT + * @deprecated 3.1.0 Use ImageStorageBehavior in your tables instead. */ class ImageStorageTable extends FileStorageTable { From 7d52b2fe5a09124b0bc3e6716efcf40cf277e2d0 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 12:42:12 +0200 Subject: [PATCH 112/144] Allow for modification of file format. Updated path to respect different version extensions. --- src/Storage/Listener/ImageProcessingTrait.php | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 7309ee62..17d8f8e5 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -128,19 +128,19 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar continue; } $hash = $this->getImageVersionHash($entity->model, $version); - $path = $this->pathBuilder()->fullPath($entity, ['fileSuffix' => '.' . $hash]); - $result[$version] = [ - 'status' => 'success', - 'path' => $path, - 'hash' => $this->_imageVersionHashes[$entity->model][$version], - ]; + $saveOptions = $options + ['format' => $entity->extension]; + if (isset($operations['_output'])) { + $saveOptions = $operations['_output'] + $saveOptions; + unset($operations['_output']); + } + + $path = $this->pathBuilder()->fullPath($entity, [ + 'preserveExtension' => false, + 'fileSuffix' => '.' . $hash . '.' . $saveOptions['format'] + ]); + try { if ($options['overwrite'] || !$storage->has($path)) { - $saveOptions = $options + ['format' => $entity->extension]; - if (isset($operations['_output'])) { - $saveOptions = $operations['_output'] + $saveOptions; - unset($operations['_output']); - } unset($saveOptions['overwrite']); $output = $this->createTmpFile(); @@ -152,6 +152,11 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar unlink($tmpFile); unlink($output); } + $result[$version] = [ + 'status' => 'success', + 'path' => $path, + 'hash' => $hash, + ]; } catch (\Exception $e) { $result[$version] = [ 'status' => 'error', From 1586c305c8f08cc4ac778664ca0900fad67fd2f4 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 12:57:41 +0200 Subject: [PATCH 113/144] Fixed a bug where a fileSuffix couldn't be set when preserveExtension option was set to false. --- src/Storage/PathBuilder/BasePathBuilder.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index 993381a6..24885490 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -141,10 +141,10 @@ protected function _buildFilename(EntityInterface $entity, array $options = []) if ($options['stripUuid'] === true) { $filename = $this->stripDashes($filename); } + if (!empty($options['fileSuffix'])) { + $filename = $filename . $options['fileSuffix']; + } if ($options['preserveExtension'] === true) { - if (!empty($options['fileSuffix'])) { - $filename = $filename . $options['fileSuffix']; - } $filename = $filename . '.' . $entity['extension']; } if (!empty($options['filePrefix'])) { From f815fc23ea3eb222bd4b649cf35260f870a7211d Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 14:09:46 +0200 Subject: [PATCH 114/144] Added imageVersions listener. --- src/Storage/Listener/ImageProcessingTrait.php | 38 +++++++++++++++---- src/Storage/Listener/LocalListener.php | 23 ++++++++++- src/View/Helper/ImageHelper.php | 5 ++- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 17d8f8e5..816ce517 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -22,8 +22,9 @@ trait ImageProcessingTrait { protected $_imageProcessor = null; protected $_imageVersions = []; protected $_imageVersionHashes = []; + protected $_defaultOutput = []; -/** + /** * Convenience method to auto create ALL and auto remove ALL image versions for * an entity. * @@ -56,6 +57,7 @@ public function autoProcessImageVersions(EntityInterface $entity, $action) { protected function _loadImageProcessingFromConfig() { $this->_imageVersions = (array)Configure::read('FileStorage.imageSizes'); $this->_imageVersionHashes = StorageUtils::generateHashes(); + $this->_defaultOutput = (array)Configure::read('FileStorage.defaultOutput'); } /** @@ -117,7 +119,7 @@ protected function _checkImageVersions($identifier, array $versions) { public function createImageVersions(EntityInterface $entity, array $versions, array $options = []) { $this->_checkImageVersions($entity->model, $versions); - $options += (array)Configure::read('FileStorage.defaultOutput') + [ + $options += $this->_defaultOutput + [ 'overwrite' => true ]; @@ -127,17 +129,13 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar if (!in_array($version, $versions)) { continue; } - $hash = $this->getImageVersionHash($entity->model, $version); $saveOptions = $options + ['format' => $entity->extension]; if (isset($operations['_output'])) { $saveOptions = $operations['_output'] + $saveOptions; unset($operations['_output']); } - $path = $this->pathBuilder()->fullPath($entity, [ - 'preserveExtension' => false, - 'fileSuffix' => '.' . $hash . '.' . $saveOptions['format'] - ]); + $path = $this->imageVersionPath($entity, $version, $saveOptions); try { if ($options['overwrite'] || !$storage->has($path)) { @@ -155,7 +153,7 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar $result[$version] = [ 'status' => 'success', 'path' => $path, - 'hash' => $hash, + 'hash' => $this->getImageVersionHash($entity->model, $version) ]; } catch (\Exception $e) { $result[$version] = [ @@ -241,4 +239,28 @@ public function removeAllImageVersions(EntityInterface $entity, array $options = $options ); } + +/** + * Generates image version path / url / filename, etc. + * + * @param EntityInterface $entity Image entity. + * @param string $version Version name + * @param string $type Path type + * @param array $options PathBuilder options + * @return string + */ + public function imageVersionPath(EntityInterface $entity, $version, $type = 'fullPath', $options = []) { + $hash = $this->getImageVersionHash($entity->model, $version); + + $output = $this->_defaultOutput + ['format' => $entity->extension]; + $operations = $this->_imageVersions[$entity->model][$version]; + if (isset($operations['_output'])) { + $output = $operations['_output'] + $output; + } + + return $this->pathBuilder()->$type($entity, $options + [ + 'preserveExtension' => false, + 'fileSuffix' => '.' . $hash . '.' . $output['format'] + ]); + } } diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 33c42997..cb1f3f00 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -62,7 +62,9 @@ public function implementedEvents() { 'ImageStorage.afterSave' => 'afterSave', 'ImageStorage.afterDelete' => 'afterDelete', 'ImageVersion.removeVersion' => 'removeImageVersion', - 'ImageVersion.createVersion' => 'createImageVersion' + 'ImageVersion.createVersion' => 'createImageVersion', + 'ImageVersion.getVersions' => 'imagePath', + 'FileStorage.ImageHelper.imagePath' => 'imagePath' // deprecated ]; } @@ -123,6 +125,25 @@ public function afterSave(Event $event, EntityInterface $entity) { } } +/** + * Generates the path the image url / path for viewing it in a browser depending on the storage adapter + * + * @param Event $event + * @throws RuntimeException + * @return void + */ + public function imagePath(Event $event) { + $entity = $event->data['image']; + $version = $event->data['version']; + $options = $event->data['options']; + $type = isset($event->data['pathType']) ? $event->data['pathType'] : 'fullPath'; + + $this->_loadImageProcessingFromConfig(); + $event->data['path'] = $this->imageVersionPath($entity, $version, $type, $options); + + $event->stopPropagation(); + } + /** * Stores the file in the configured storage backend. * diff --git a/src/View/Helper/ImageHelper.php b/src/View/Helper/ImageHelper.php index 5adaf724..6f9f4fda 100644 --- a/src/View/Helper/ImageHelper.php +++ b/src/View/Helper/ImageHelper.php @@ -63,11 +63,12 @@ public function imageUrl($image, $version = null, $options = []) { $hash = null; } - $Event = new Event('FileStorage.ImageHelper.imagePath', $this, [ + $Event = new Event('ImageVersion.getVersions', $this, [ 'hash' => $hash, 'image' => $image, 'version' => $version, - 'options' => $options + 'options' => $options, + 'pathType' => 'url' ] ); EventManager::instance()->dispatch($Event); From 7ea6fccff2a7a7ef2ea4da7cfda2d5df5c871a7a Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 14:26:28 +0200 Subject: [PATCH 115/144] Add defaults to event data. --- src/Storage/Listener/LocalListener.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index cb1f3f00..0d1c0417 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -133,10 +133,21 @@ public function afterSave(Event $event, EntityInterface $entity) { * @return void */ public function imagePath(Event $event) { - $entity = $event->data['image']; - $version = $event->data['version']; - $options = $event->data['options']; - $type = isset($event->data['pathType']) ? $event->data['pathType'] : 'fullPath'; + $data = $event->data + [ + 'image' => null, + 'version' => null, + 'options' => [], + 'pathType' => 'fullPath' + ]; + + $entity = $data['image']; + $version = $data['version']; + $options = $data['options']; + $type = $data['pathType']; + + if (!$entity) { + throw new \InvalidArgumentException('No image entity provided.'); + } $this->_loadImageProcessingFromConfig(); $event->data['path'] = $this->imageVersionPath($entity, $version, $type, $options); From 6dd9af069ceda5afcbece66794efb51d5a47a357 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 14:30:41 +0200 Subject: [PATCH 116/144] A little clenaup --- src/Storage/Listener/ImageProcessingTrait.php | 5 ++--- src/Storage/Listener/LocalListener.php | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 816ce517..9f5db014 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -10,7 +10,6 @@ use Burzum\Imagine\Lib\ImageProcessor; use Cake\Core\Configure; use Cake\Datasource\EntityInterface; -use Cake\ORM\TableRegistry; /** * ImageProcessingTrait @@ -24,7 +23,7 @@ trait ImageProcessingTrait { protected $_imageVersionHashes = []; protected $_defaultOutput = []; - /** +/** * Convenience method to auto create ALL and auto remove ALL image versions for * an entity. * @@ -243,7 +242,7 @@ public function removeAllImageVersions(EntityInterface $entity, array $options = /** * Generates image version path / url / filename, etc. * - * @param EntityInterface $entity Image entity. + * @param \Cake\Datasource\EntityInterface $entity Image entity. * @param string $version Version name * @param string $type Path type * @param array $options PathBuilder options diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index 0d1c0417..ca86f2dc 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -128,8 +128,8 @@ public function afterSave(Event $event, EntityInterface $entity) { /** * Generates the path the image url / path for viewing it in a browser depending on the storage adapter * - * @param Event $event - * @throws RuntimeException + * @param \Cake\Event\Event $event + * @throws \InvalidArgumentException * @return void */ public function imagePath(Event $event) { From 9e0dfc3516d1236f7417b52ef3c771f9ae354414 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 14:50:52 +0200 Subject: [PATCH 117/144] Fixes for failing tests. --- src/Storage/Listener/ImageProcessingTrait.php | 4 ++-- src/Storage/PathBuilder/BasePathBuilder.php | 5 ++++- tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Storage/Listener/ImageProcessingTrait.php b/src/Storage/Listener/ImageProcessingTrait.php index 9f5db014..27cbed2b 100644 --- a/src/Storage/Listener/ImageProcessingTrait.php +++ b/src/Storage/Listener/ImageProcessingTrait.php @@ -134,7 +134,7 @@ public function createImageVersions(EntityInterface $entity, array $versions, ar unset($operations['_output']); } - $path = $this->imageVersionPath($entity, $version, $saveOptions); + $path = $this->imageVersionPath($entity, $version, 'fullPath', $saveOptions); try { if ($options['overwrite'] || !$storage->has($path)) { @@ -257,7 +257,7 @@ public function imageVersionPath(EntityInterface $entity, $version, $type = 'ful $output = $operations['_output'] + $output; } - return $this->pathBuilder()->$type($entity, $options + [ + return $this->pathBuilder()->{$type}($entity, $options + [ 'preserveExtension' => false, 'fileSuffix' => '.' . $hash . '.' . $output['format'] ]); diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index 24885490..cf8ccbc0 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -169,7 +169,10 @@ protected function _preserveFilename(EntityInterface $entity, array $options = [ } if (!empty($options['fileSuffix'])) { $split = $this->splitFilename($filename, true); - $filename = $split['filename'] . $options['fileSuffix'] . $split['extension']; + $filename = $split['filename'] . $options['fileSuffix']; + if ($options['preserveExtension'] === true) { + $filename .= $split['extension']; + } } return $filename; } diff --git a/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php b/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php index 684e416a..bda0b033 100644 --- a/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php +++ b/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php @@ -114,7 +114,7 @@ public function testCreateImageVersions() { 'hash' => '41e51a3f' ] ]; - $this->assertEquals($result, $expected); + $this->assertEquals($expected, $result); $this->assertFileExists($this->testPath . '95' . DS . '61' . DS . '80' . DS . 'filestorage3' . DS . 'titus.20876bcd.jpg'); $this->assertFileExists($this->testPath . '95' . DS . '61' . DS . '80' . DS . 'filestorage3' . DS . 'titus.41e51a3f.jpg'); From 077f3d7310dfc03f8d10d7c852e623c79885ace4 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 16:10:41 +0200 Subject: [PATCH 118/144] Revert tables --- src/Model/Table/FileStorageTable.php | 180 ++++++++++++++++- src/Model/Table/ImageStorageTable.php | 185 +++++++++++++++++- .../FileStorageTableTest.php} | 7 +- .../ImageStorageTableTest.php} | 8 +- 4 files changed, 370 insertions(+), 10 deletions(-) rename tests/TestCase/Model/{Behavior/FileStorageBehaviorTest.php => Table/FileStorageTableTest.php} (82%) rename tests/TestCase/Model/{Behavior/ImageStorageBehaviorTest.php => Table/ImageStorageTableTest.php} (98%) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index d791c27f..69664b91 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -1,6 +1,14 @@ addBehavior('Burzum/FileStorage.UploadValidator'); - $this->addBehavior('Burzum/FileStorage.FileStorage'); $this->addBehavior('Timestamp'); $this->displayField('filename'); $this->table('file_storage'); @@ -55,4 +66,171 @@ public function configureUploadValidation($options) { $this->removeBehavior('Burzum/FileStorage.UploadValidator'); $this->addBehavior('Burzum/FileStorage.UploadValidator', $options); } + +/** + * beforeSave callback + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return boolean true on success + */ + public function beforeSave(Event $event, EntityInterface $entity, $options) { + $this->getFileInfoFromUpload($event->data['entity']); + $Event = $this->dispatchEvent('FileStorage.beforeSave', array( + 'record' => $entity, + 'storage' => $this->storageAdapter($event->data['entity']['adapter']) + )); + if ($Event->isStopped()) { + return false; +} + return true; + } + +/** + * Gets information about the file that is being uploaded. + * + * - gets the file size + * - gets the mime type + * - gets the extension if present + * - sets the adapter by default to local if not already set + * - sets the model field to the table name if not already set + * + * @param \Cake\Datasource\EntityInterface + * @param string $field + * @return void + */ + public function getFileInfoFromUpload(EntityInterface &$entity, $field = 'file') { + if (!empty($entity[$field]['tmp_name'])) { + $File = new File($entity[$field]['tmp_name']); + $entity['filesize'] = $File->size(); + $entity['mime_type'] = $File->mime(); + } + if (!empty($entity[$field]['name'])) { + $entity['extension'] = pathinfo($entity[$field]['name'], PATHINFO_EXTENSION); + $entity['filename'] = $entity[$field]['name']; + } + if (empty($entity['model'])) { + $entity['model'] = $this->table(); + } + if (empty($entity['adapter'])) { + $entity['adapter'] = 'Local'; + } + } + +/** + * afterSave callback + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return boolean + */ + public function afterSave(Event $event, EntityInterface $entity, $options) { + $this->dispatchEvent('FileStorage.afterSave', [ + 'created' => $event->data['entity']->isNew(), + 'record' => $entity, + 'storage' => $this->storageAdapter($event->data['entity']['adapter']) + ]); + $this->deleteOldFileOnSave($entity); + return true; + } + +/** + * Get a copy of the actual record before we delete it to have it present in afterDelete + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @return boolean + */ + public function beforeDelete(Event $event, EntityInterface $entity) { + $this->record = $this->find() + ->contain([]) + ->where([ + $this->alias() . '.' . $this->primaryKey() => $entity->{$this->primaryKey()} + ]) + ->first(); + + if (empty($this->record)) { + return false; + } + + return true; + } + +/** + * afterDelete callback + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return boolean + */ + public function afterDelete(Event $event, EntityInterface $entity, $options) { + $this->dispatchEvent('FileStorage.afterDelete', [ + 'record' => $entity, + 'storage' => $this->storageAdapter($entity['adapter']) + ]); + return true; + } + +/** + * Deletes an old file to replace it with the new one if an old id was passed. + * + * Thought to be called in Model::afterSave() but can be used from any other + * place as well like Model::beforeSave() as long as the field data is present. + * + * The old id has to be the UUID of the file_storage record that should be deleted. + * + * @param \Cake\Datasource\EntityInterface $entity + * @param string $oldIdField Name of the field in the data that holds the old id. + * @return boolean Returns true if the old record was deleted + */ + public function deleteOldFileOnSave(EntityInterface $entity, $oldIdField = 'old_file_id') { + if (!empty($entity[$oldIdField]) && $entity['model']) { + $oldEntity = $this->find() + ->contain([]) + ->where([ + $this->alias() . '.' . $this->primaryKey() => $entity[$oldIdField], 'model' => $entity['model'] + ]) + ->first(); + if (!empty($oldEntity)) { + return $this->delete($oldEntity); + } + } + return false; + } + +/** + * Returns full file path for an entity. + * + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return string + */ + public function fullFilePath(EntityInterface $entity, array $options = []) { + $pathBuilder = $this->createPathBuilder($entity['adapter']); + return $pathBuilder->fullPath($entity, $options); + } + +/** + * Returns file url for an entity. + * + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return string + */ + public function fileUrl(EntityInterface $entity, array $options = []) { + $pathBuilder = $this->createPathBuilder($entity['adapter']); + return $pathBuilder->url($entity, $options); + } + +/** + * {@inheritDoc} + */ + public function dispatchEvent($name, $data = null, $subject = null) { + $data['table'] = $this; + + return parent::dispatchEvent($name, $data, $subject); + } } diff --git a/src/Model/Table/ImageStorageTable.php b/src/Model/Table/ImageStorageTable.php index e8566b44..53bf2d46 100644 --- a/src/Model/Table/ImageStorageTable.php +++ b/src/Model/Table/ImageStorageTable.php @@ -1,6 +1,14 @@ addBehavior('Burzum/FileStorage.ImageStorage'); $this->addBehavior('Burzum/Imagine.Imagine'); $this->addBehavior('Burzum/FileStorage.UploadValidator', array( 'localFile' => false, @@ -37,4 +48,176 @@ public function initialize(array $config) { )); } +/** + * beforeSave callback + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return boolean true on success + */ + public function beforeSave(Event $event, EntityInterface $entity, $options) { + if (!parent::beforeSave($event, $entity, $options)) { + return false; + } + $imageEvent = $this->dispatchEvent('ImageStorage.beforeSave', [ + 'record' => $entity + ]); + if ($imageEvent->isStopped()) { + return false; +} + return true; + } + +/** + * afterSave callback + * + * Does not call the parent to avoid that the regular file storage event listener saves the image already + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return boolean + */ + public function afterSave(Event $event, EntityInterface $entity, $options) { + if ($entity->isNew()) { + $this->dispatchEvent('ImageStorage.afterSave', [ + 'record' => $entity, + 'storage' => $this->storageAdapter($entity->get('adapter')) + ]); + $this->deleteOldFileOnSave($entity); + } + return true; + } + +/** + * Get a copy of the actual record before we delete it to have it present in afterDelete + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @return boolean + */ + public function beforeDelete(Event $event, EntityInterface $entity) { + if (!parent::beforeDelete($event, $entity)) { + return false; + } + + $imageEvent = $this->dispatchEvent('ImageStorage.beforeDelete', [ + 'record' => $this->record, + 'storage' => $this->storageAdapter($this->record['adapter']) + ]); + + if ($imageEvent->isStopped()) { + return false; + } + + return true; + } + +/** + * After the main file was deleted remove the the thumbnails + * + * Note that we do not call the parent::afterDelete(), we just want to trigger the ImageStorage.afterDelete event but not the FileStorage.afterDelete at the same time! + * + * @param \Cake\Event\Event $event + * @param \Cake\Datasource\EntityInterface $entity + * @param array $options + * @return boolean + */ + public function afterDelete(Event $event, EntityInterface $entity, $options) { + $this->dispatchEvent('ImageStorage.afterDelete', [ + 'record' => $entity, + 'storage' => $this->storageAdapter($entity->get('adapter')) + ]); + return true; + } + +/** + * Image size validation method + * + * @param mixed $check + * @param array $options is an array with key width or height and a value of array + * with two options, operator and value. For example: + * array('height' => array('==', 100)) will only be true if the image has a + * height of exactly 100px. See the CakePHP core class and method + * Validation::comparison for all operators. + * @return boolean true + * @see Validation::comparison() + * @throws \InvalidArgumentException + */ + public function validateImageSize($check, array $options = []) { + if (!isset($options['height']) && !isset($options['width'])) { + throw new \InvalidArgumentException('Missing image size validation options! You must provide a hight and / or width.'); + } + + if (is_string($check)) { + $imageFile = $check; + } else { + $check = array_values($check); + $check = $check[0]; + if (is_array($check) && isset($check['tmp_name'])) { + $imageFile = $check['tmp_name']; + } else { + $imageFile = $check; + } + } + + $imageSizes = $this->getImageSize($imageFile); + + if (isset($options['height'])) { + $height = Validation::comparison($imageSizes[1], $options['height'][0], $options['height'][1]); + } else { + $height = true; + } + + if (isset($options['width'])) { + $width = Validation::comparison($imageSizes[0], $options['width'][0], $options['width'][1]); + } else { + $width = true; + } + + if ($height === false || $width === false) { + return false; + } + + return true; + } + +/** + * Gets a list of image versions for a given record. + * + * Use this method to get a list of ALL versions when needed or to cache all the + * versions somewhere. This method will return all configured versions for an + * image. For example you could store them serialized along with the file data + * by adding a "versions" field to the DB table and extend this model. + * + * Just in case you're wondering about the event name in the method code: It's + * called FileStorage.ImageHelper.imagePath there because the event is the same + * as in the helper. No need to introduce yet another event, the existing event + * already fulfills the purpose. I might rename this event in the 3.0 version of + * the plugin to a more generic one. + * + * @param \Cake\Datasource\EntityInterface $entity An ImageStorage database record + * @param array $options Options for the version. + * @return array A list of versions for this image file. Key is the version, value is the path or URL to that image. + */ + public function getImageVersions(EntityInterface $entity, $options = []) { + $versions = []; + $versionData = (array)Configure::read('FileStorage.imageSizes.' . $entity->get('model')); + $versionData['original'] = isset($options['originalVersion']) ? $options['originalVersion'] : 'original'; + foreach ($versionData as $version => $data) { + $hash = Configure::read('FileStorage.imageHashes.' . $entity->get('model') . '.' . $version); + $event = $this->dispatchEvent('ImageVersion.getVersions', [ + 'hash' => $hash, + 'image' => $entity, + 'version' => $version, + 'options' => [] + ] + ); + if ($event->isStopped()) { + $versions[$version] = str_replace('\\', '/', $event->data['path']); + } + } + return $versions; + } } diff --git a/tests/TestCase/Model/Behavior/FileStorageBehaviorTest.php b/tests/TestCase/Model/Table/FileStorageTableTest.php similarity index 82% rename from tests/TestCase/Model/Behavior/FileStorageBehaviorTest.php rename to tests/TestCase/Model/Table/FileStorageTableTest.php index 243a10ae..31f4d310 100644 --- a/tests/TestCase/Model/Behavior/FileStorageBehaviorTest.php +++ b/tests/TestCase/Model/Table/FileStorageTableTest.php @@ -12,7 +12,7 @@ * @copyright 2012 - 2015 Florian Krämer * @license MIT */ -class FileStorageBehaviorTest extends FileStorageTestCase { +class FileStorageTableTest extends FileStorageTestCase { /** * Fixtures @@ -31,7 +31,6 @@ class FileStorageBehaviorTest extends FileStorageTestCase { public function setUp() { parent::setUp(); $this->FileStorage = TableRegistry::get('Burzum/FileStorage.FileStorage'); - $this->FileStorageBehavior = $this->FileStorage->behaviors()->get('FileStorage'); } /** @@ -54,7 +53,7 @@ public function tearDown() { public function testBeforeDelete() { $entity = $this->FileStorage->get('file-storage-1'); $event = new Event('Model.beforeDelete', $this->FileStorage); - $this->FileStorageBehavior->beforeDelete($event, $entity); + $this->FileStorage->beforeDelete($event, $entity); $this->assertEquals($this->FileStorage->record, $entity); } @@ -70,7 +69,7 @@ public function testAfterDelete() { 'record' => $entity, 'adapter' => 'Local' ]); - $result = $this->FileStorageBehavior->afterDelete($event, $entity, []); + $result = $this->FileStorage->afterDelete($event, $entity, []); $this->assertTrue($result); } } diff --git a/tests/TestCase/Model/Behavior/ImageStorageBehaviorTest.php b/tests/TestCase/Model/Table/ImageStorageTableTest.php similarity index 98% rename from tests/TestCase/Model/Behavior/ImageStorageBehaviorTest.php rename to tests/TestCase/Model/Table/ImageStorageTableTest.php index d90076db..7a5956f6 100644 --- a/tests/TestCase/Model/Behavior/ImageStorageBehaviorTest.php +++ b/tests/TestCase/Model/Table/ImageStorageTableTest.php @@ -1,5 +1,5 @@ Image->validateImageSize($file, ['height' => ['<', 100]]); $this->assertFalse($result); } - + /** * testDeleteOldFileOnSave * @@ -259,4 +259,4 @@ public function testDeleteOldFileOnSave() { $this->assertNotEquals($result['path'], $result2['path']); $this->assertNotEquals($result['filename'], $result2['filename']); } -} +} \ No newline at end of file From c41c5c4a2a05f68f7bdf4db92ed06391c4505985 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Aug 2015 16:16:19 +0200 Subject: [PATCH 119/144] fix --- src/Model/Table/ImageStorageTable.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Model/Table/ImageStorageTable.php b/src/Model/Table/ImageStorageTable.php index 53bf2d46..423a8a3c 100644 --- a/src/Model/Table/ImageStorageTable.php +++ b/src/Model/Table/ImageStorageTable.php @@ -19,10 +19,6 @@ */ class ImageStorageTable extends FileStorageTable { - use LogTrait; - use PathBuilderTrait; - use StorageTrait; - /** * Name * From fe4faea4e2cb24a1cf6e439a2c74e00cc3a13cc4 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 14 Aug 2015 10:58:17 +0200 Subject: [PATCH 120/144] Backport of #84 --- src/Model/Behavior/FileStorageBehavior.php | 13 ++++++- src/Model/Table/FileStorageTable.php | 45 ++++++++++++++-------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/Model/Behavior/FileStorageBehavior.php b/src/Model/Behavior/FileStorageBehavior.php index 31278ecf..954fa7eb 100644 --- a/src/Model/Behavior/FileStorageBehavior.php +++ b/src/Model/Behavior/FileStorageBehavior.php @@ -1,6 +1,7 @@ getFileInfoFromUpload($data); + } + /** * beforeSave callback * @@ -91,7 +103,6 @@ public function getFileInfoFromUpload(&$upload, $field = 'file') { * @return boolean true on success */ public function beforeSave(Event $event, EntityInterface $entity, $options) { - $this->getFileInfoFromUpload($entity); $storageEvent = $this->dispatchEvent('FileStorage.beforeSave', [ 'record' => $entity, 'storage' => $this->storageAdapter($entity->get('adapter')) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index 69664b91..f4e50fa0 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -1,6 +1,7 @@ addBehavior('Burzum/FileStorage.UploadValidator', $options); } +/** + * beforeSave callback + * + * @param \Cake\Event\Event $event + * @param \ArrayAccess $data + * @return void + */ + public function beforeMarshal(Event $event, ArrayAccess $data) { + $this->getFileInfoFromUpload($data); + } + /** * beforeSave callback * @@ -76,14 +88,13 @@ public function configureUploadValidation($options) { * @return boolean true on success */ public function beforeSave(Event $event, EntityInterface $entity, $options) { - $this->getFileInfoFromUpload($event->data['entity']); $Event = $this->dispatchEvent('FileStorage.beforeSave', array( 'record' => $entity, - 'storage' => $this->storageAdapter($event->data['entity']['adapter']) + 'storage' => $this->storageAdapter($entity['adapter']) )); if ($Event->isStopped()) { return false; -} + } return true; } @@ -96,25 +107,25 @@ public function beforeSave(Event $event, EntityInterface $entity, $options) { * - sets the adapter by default to local if not already set * - sets the model field to the table name if not already set * - * @param \Cake\Datasource\EntityInterface + * @param array|\ArrayAccess $upload * @param string $field * @return void */ - public function getFileInfoFromUpload(EntityInterface &$entity, $field = 'file') { - if (!empty($entity[$field]['tmp_name'])) { - $File = new File($entity[$field]['tmp_name']); - $entity['filesize'] = $File->size(); - $entity['mime_type'] = $File->mime(); + public function getFileInfoFromUpload(&$upload, $field = 'file') { + if (!empty($upload[$field]['tmp_name'])) { + $File = new File($upload[$field]['tmp_name']); + $upload['filesize'] = $File->size(); + $upload['mime_type'] = $File->mime(); } - if (!empty($entity[$field]['name'])) { - $entity['extension'] = pathinfo($entity[$field]['name'], PATHINFO_EXTENSION); - $entity['filename'] = $entity[$field]['name']; + if (!empty($upload[$field]['name'])) { + $upload['extension'] = pathinfo($upload[$field]['name'], PATHINFO_EXTENSION); + $upload['filename'] = $upload[$field]['name']; } - if (empty($entity['model'])) { - $entity['model'] = $this->table(); + if (empty($upload['model'])) { + $upload['model'] = $this->_table->table(); } - if (empty($entity['adapter'])) { - $entity['adapter'] = 'Local'; + if (empty($upload['adapter'])) { + $upload['adapter'] = 'Local'; } } @@ -130,7 +141,7 @@ public function afterSave(Event $event, EntityInterface $entity, $options) { $this->dispatchEvent('FileStorage.afterSave', [ 'created' => $event->data['entity']->isNew(), 'record' => $entity, - 'storage' => $this->storageAdapter($event->data['entity']['adapter']) + 'storage' => $this->storageAdapter($entity['adapter']) ]); $this->deleteOldFileOnSave($entity); return true; From 2326272a23196e1f81202ad322919a43a63ba6ec Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 14 Aug 2015 11:24:35 +0200 Subject: [PATCH 121/144] FIx + test for getFileInfoFromUpload. --- src/Model/Table/FileStorageTable.php | 2 +- .../Model/Table/FileStorageTableTest.php | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index f4e50fa0..dbff8d41 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -122,7 +122,7 @@ public function getFileInfoFromUpload(&$upload, $field = 'file') { $upload['filename'] = $upload[$field]['name']; } if (empty($upload['model'])) { - $upload['model'] = $this->_table->table(); + $upload['model'] = $this->table(); } if (empty($upload['adapter'])) { $upload['adapter'] = 'Local'; diff --git a/tests/TestCase/Model/Table/FileStorageTableTest.php b/tests/TestCase/Model/Table/FileStorageTableTest.php index 31f4d310..79e093e4 100644 --- a/tests/TestCase/Model/Table/FileStorageTableTest.php +++ b/tests/TestCase/Model/Table/FileStorageTableTest.php @@ -72,4 +72,28 @@ public function testAfterDelete() { $result = $this->FileStorage->afterDelete($event, $entity, []); $this->assertTrue($result); } + +/** + * testGetFileInfoFromUpload + * + * @return void + */ + public function testGetFileInfoFromUpload() { + $filename = \Cake\Core\Plugin::path('Burzum/FileStorage') . DS . 'tests' . DS . 'Fixture' . DS . 'File' . DS . 'titus.jpg'; + + $data = new \ArrayObject([ + 'file' => [ + 'name' => 'titus.jpg', + 'tmp_name' => $filename + ] + ]); + + $this->FileStorage->getFileInfoFromUpload($data); + + $this->assertEquals(332643, $data['filesize']); + $this->assertEquals('Local', $data['adapter']); + $this->assertEquals('image/jpeg', $data['mime_type']); + $this->assertEquals('jpg', $data['extension']); + $this->assertEquals('file_storage', $data['model']); + } } From ca6c516ec7d080cbdfe76f82e89ea7aefb323f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sat, 15 Aug 2015 12:35:19 +0200 Subject: [PATCH 122/144] Adding a test for a complete save call. --- .../Model/Table/FileStorageTableTest.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/TestCase/Model/Table/FileStorageTableTest.php b/tests/TestCase/Model/Table/FileStorageTableTest.php index 31f4d310..f88b2cf2 100644 --- a/tests/TestCase/Model/Table/FileStorageTableTest.php +++ b/tests/TestCase/Model/Table/FileStorageTableTest.php @@ -72,4 +72,32 @@ public function testAfterDelete() { $result = $this->FileStorage->afterDelete($event, $entity, []); $this->assertTrue($result); } + +/** + * Testing a complete save call + * + * @link https://github.com/burzum/cakephp-file-storage/issues/85 + * @return void + */ + public function testFileSaving() { + $entity = $this->FileStorage->newEntity([ + 'model' => 'Document', + 'adapter' => 'Local', + 'file' => [ + 'error' => UPLOAD_ERR_OK, + 'size' => filesize($this->fileFixtures . 'titus.jpg'), + 'type' => 'image/jpeg', + 'name' => 'tituts.jpg', + 'tmp_name' => $this->fileFixtures . 'titus.jpg' + ] + ]); + $this->FileStorage->configureUploadValidation([ + 'allowedExtensions' => ['jpg'], + 'validateUploadArray' => true, + 'localFile' => true, + 'validateUploadErrors' => true + ]); + $this->FileStorage->save($entity); + $this->assertEquals($entity->errors(), []); + } } From 0baac5a6a131d299d88d8ed9455d31b3d05ca61a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sat, 15 Aug 2015 13:12:20 +0200 Subject: [PATCH 123/144] #85 Working on a test for file saving --- src/Model/Table/FileStorageTable.php | 1 - src/TestSuite/FileStorageTestCase.php | 14 +++--- .../Model/Table/FileStorageTableTest.php | 48 +++++++++++-------- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index 69664b91..43c7c1ac 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -230,7 +230,6 @@ public function fileUrl(EntityInterface $entity, array $options = []) { */ public function dispatchEvent($name, $data = null, $subject = null) { $data['table'] = $this; - return parent::dispatchEvent($name, $data, $subject); } } diff --git a/src/TestSuite/FileStorageTestCase.php b/src/TestSuite/FileStorageTestCase.php index e231badf..7d9b5b7d 100644 --- a/src/TestSuite/FileStorageTestCase.php +++ b/src/TestSuite/FileStorageTestCase.php @@ -1,17 +1,18 @@ listeners['ImageProcessingListener'] = new ImageProcessingListener(); $this->listeners['LocalFileStorageListener'] = new LocalFileStorageListener(); + $this->listeners['LocalListener'] = new LocalListener(); EventManager::instance()->on($this->listeners['ImageProcessingListener']); EventManager::instance()->on($this->listeners['LocalFileStorageListener']); } diff --git a/tests/TestCase/Model/Table/FileStorageTableTest.php b/tests/TestCase/Model/Table/FileStorageTableTest.php index f88b2cf2..1256d98b 100644 --- a/tests/TestCase/Model/Table/FileStorageTableTest.php +++ b/tests/TestCase/Model/Table/FileStorageTableTest.php @@ -2,6 +2,7 @@ namespace Burzum\FileStorage\Test\TestCase\Model\Behavior; use Cake\Event\Event; +use Cake\Event\EventManager; use Cake\ORM\TableRegistry; use Burzum\FileStorage\TestSuite\FileStorageTestCase; @@ -80,24 +81,33 @@ public function testAfterDelete() { * @return void */ public function testFileSaving() { - $entity = $this->FileStorage->newEntity([ - 'model' => 'Document', - 'adapter' => 'Local', - 'file' => [ - 'error' => UPLOAD_ERR_OK, - 'size' => filesize($this->fileFixtures . 'titus.jpg'), - 'type' => 'image/jpeg', - 'name' => 'tituts.jpg', - 'tmp_name' => $this->fileFixtures . 'titus.jpg' - ] - ]); - $this->FileStorage->configureUploadValidation([ - 'allowedExtensions' => ['jpg'], - 'validateUploadArray' => true, - 'localFile' => true, - 'validateUploadErrors' => true - ]); - $this->FileStorage->save($entity); - $this->assertEquals($entity->errors(), []); + $listenersToTest = [ + 'LocalListener', + ]; + $results = []; + foreach ($listenersToTest as $listener) { + $this->_removeListeners(); + EventManager::instance()->on($this->listeners[$listener]); + $entity = $this->FileStorage->newEntity([ + 'model' => 'Document', + 'adapter' => 'Local', + 'file' => [ + 'error' => UPLOAD_ERR_OK, + 'size' => filesize($this->fileFixtures . 'titus.jpg'), + 'type' => 'image/jpeg', + 'name' => 'tituts.jpg', + 'tmp_name' => $this->fileFixtures . 'titus.jpg' + ] + ]); + $this->FileStorage->configureUploadValidation([ + 'allowedExtensions' => ['jpg'], + 'validateUploadArray' => true, + 'localFile' => true, + 'validateUploadErrors' => true + ]); + $this->FileStorage->save($entity); + $this->assertEquals($entity->errors(), []); + $results[] = $entity; + } } } From 9acb350ded32c795268bee02a1a80238ae539a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 16 Aug 2015 12:19:21 +0200 Subject: [PATCH 124/144] Fix for #85 Argument 2 passed to Burzum\FileStorage\Storage\Listener\LocalListener::afterSave() must implement interface Cake\Datasource\EntityInterface, boolean given --- composer.json | 1 - src/Event/ImageProcessingListener.php | 1 - src/Storage/Listener/LocalListener.php | 3 ++- src/TestSuite/FileStorageTestCase.php | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 55c9fcc9..688b6fdb 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,6 @@ ], "minimum-stability": "dev", "require": { - "php": ">=5.4.19", "cakephp/cakephp": "~3.0", "cakephp/plugin-installer": "*", "cakephp/migrations": "~1.0", diff --git a/src/Event/ImageProcessingListener.php b/src/Event/ImageProcessingListener.php index fa5f3a99..b280dc57 100644 --- a/src/Event/ImageProcessingListener.php +++ b/src/Event/ImageProcessingListener.php @@ -282,7 +282,6 @@ public function afterSave(Event $Event) { if (!empty($operations)) { $this->_createVersions($table, $record, $operations); } - $table->data = $data; } catch (\Exception $e) { $this->log($e->getMessage()); diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index ca86f2dc..ccca0e27 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -105,7 +105,8 @@ public function afterDelete(Event $event, EntityInterface $entity) { * @param \Cake\Datasource\EntityInterface $entity * @return void */ - public function afterSave(Event $event, EntityInterface $entity) { + public function afterSave(Event $event) { + $entity = $event->data['record']; if ($this->_checkEvent($event) && $entity->isNew()) { $fileField = $this->config('fileField'); diff --git a/src/TestSuite/FileStorageTestCase.php b/src/TestSuite/FileStorageTestCase.php index 7d9b5b7d..6f67fa57 100644 --- a/src/TestSuite/FileStorageTestCase.php +++ b/src/TestSuite/FileStorageTestCase.php @@ -47,7 +47,7 @@ public function setUp() { $this->fileFixtures = Plugin::path('Burzum/FileStorage') . 'tests' . DS . 'Fixture' . DS . 'File' . DS; if (!is_dir($this->testPath)) { - $Folder = new Folder($this->testPath, true); + mkdir($this->testPath); } Configure::write('FileStorage.basePath', $this->testPath); From dd71fa5658a8e392fd669d717efb68f6c9436148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 16 Aug 2015 14:36:18 +0200 Subject: [PATCH 125/144] Updating .scrutinizer --- .scrutinizer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 6483d8eb..17483085 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -5,7 +5,7 @@ checks: remove_php_closing_tag: true remove_trailing_whitespace: true tools: - php_code_coverage: true + php_code_coverage: false php_loc: enabled: true excluded_dirs: [vendor, tests, config, docs] From 2d3b4be456026fb6e5f89d175c4629a6254f745f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 16 Aug 2015 14:50:55 +0200 Subject: [PATCH 126/144] Proper fix for #85 --- src/Model/Table/FileStorageTable.php | 2 +- src/Storage/Listener/LocalListener.php | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Model/Table/FileStorageTable.php b/src/Model/Table/FileStorageTable.php index c5952b8d..384613df 100644 --- a/src/Model/Table/FileStorageTable.php +++ b/src/Model/Table/FileStorageTable.php @@ -139,8 +139,8 @@ public function getFileInfoFromUpload(&$upload, $field = 'file') { */ public function afterSave(Event $event, EntityInterface $entity, $options) { $this->dispatchEvent('FileStorage.afterSave', [ - 'created' => $event->data['entity']->isNew(), 'record' => $entity, + 'created' => $event->data['entity']->isNew(), 'storage' => $this->storageAdapter($entity['adapter']) ]); $this->deleteOldFileOnSave($entity); diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index ccca0e27..ca86f2dc 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -105,8 +105,7 @@ public function afterDelete(Event $event, EntityInterface $entity) { * @param \Cake\Datasource\EntityInterface $entity * @return void */ - public function afterSave(Event $event) { - $entity = $event->data['record']; + public function afterSave(Event $event, EntityInterface $entity) { if ($this->_checkEvent($event) && $entity->isNew()) { $fileField = $this->config('fileField'); From 19366d67bca164c033a8012b143b50df399de3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 16 Aug 2015 14:59:17 +0200 Subject: [PATCH 127/144] Adding a test for the StorageException --- src/Storage/StorageException.php | 7 ++++++ .../TestCase/Storage/StorageExceptionTest.php | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/TestCase/Storage/StorageExceptionTest.php diff --git a/src/Storage/StorageException.php b/src/Storage/StorageException.php index 271db814..153c3133 100644 --- a/src/Storage/StorageException.php +++ b/src/Storage/StorageException.php @@ -4,6 +4,13 @@ use \Cake\Datasource\EntityInterface; use \Exception; +/** + * Storage Exception + * + * @author Florian Krämer + * @copyright 2012 - 2015 Florian Krämer + * @license MIT + */ class StorageException extends Exception { protected $_entity = null; diff --git a/tests/TestCase/Storage/StorageExceptionTest.php b/tests/TestCase/Storage/StorageExceptionTest.php new file mode 100644 index 00000000..09a50fbf --- /dev/null +++ b/tests/TestCase/Storage/StorageExceptionTest.php @@ -0,0 +1,22 @@ +newEntity([]); + $exception = new StorageException(); + $exception->setEntity($entity); + $this->assertEquals($exception->getEntity(), $entity); + } +} From 4ed63b20e66832ffc1bc61d4a6d089860de1c98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 16 Aug 2015 15:23:29 +0200 Subject: [PATCH 128/144] Removing the empty LocalListener class. No need to keep that file. --- src/Storage/Listener/LocalListener.php | 2 +- src/Storage/PathBuilder/LocalPathBuilder.php | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) delete mode 100644 src/Storage/PathBuilder/LocalPathBuilder.php diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index ca86f2dc..ec609d41 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -28,7 +28,7 @@ class LocalListener extends AbstractListener { * @var array */ protected $_defaultConfig = [ - 'pathBuilder' => 'Local', + 'pathBuilder' => 'Base', 'pathBuilderOptions' => [ 'modelFolder' => true, ], diff --git a/src/Storage/PathBuilder/LocalPathBuilder.php b/src/Storage/PathBuilder/LocalPathBuilder.php deleted file mode 100644 index 0bf193a6..00000000 --- a/src/Storage/PathBuilder/LocalPathBuilder.php +++ /dev/null @@ -1,11 +0,0 @@ - Date: Sun, 16 Aug 2015 15:24:03 +0200 Subject: [PATCH 129/144] Removing deprecated code from the coverage report. Also the entities, no tests required so far for them but they drag the coverage down. --- phpunit.xml.dist | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 2ae33a52..0392af65 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -29,13 +29,15 @@ - + ./src ./vendor ./tests + ./src/Model/Entity + ./src/Event From f1b206e4c147c208b4477304a56f801578e4be2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 16 Aug 2015 15:26:19 +0200 Subject: [PATCH 130/144] Fixing another namespace --- phpunit.xml.dist | 1 + tests/TestCase/Lib/StorageManagerTest.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0392af65..63724b7d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -38,6 +38,7 @@ ./tests ./src/Model/Entity ./src/Event + ./src/Lib diff --git a/tests/TestCase/Lib/StorageManagerTest.php b/tests/TestCase/Lib/StorageManagerTest.php index ec48ec8f..295b485e 100644 --- a/tests/TestCase/Lib/StorageManagerTest.php +++ b/tests/TestCase/Lib/StorageManagerTest.php @@ -9,7 +9,7 @@ namespace Burzum\FileStorage\Test\TestCase\Lib; use Burzum\FileStorage\TestSuite\FileStorageTestCase; -use Burzum\FileStorage\Lib\StorageManager;; +use Burzum\FileStorage\Storage\StorageManager; class StorageManagerTest extends FileStorageTestCase { /** From 50e0458bc361babd9d3a38d2a5def5c96e186555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 16 Aug 2015 15:29:32 +0200 Subject: [PATCH 131/144] Fixing tests. --- src/Storage/Listener/LegacyLocalFileStorageListener.php | 2 +- tests/TestCase/Storage/Listener/AbstractListenerTest.php | 2 +- tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php | 2 +- tests/TestCase/Storage/PathBuilder/PathBuilderTraitTest.php | 4 ++-- tests/TestCase/{Lib => Storage}/StorageManagerTest.php | 0 .../FileStorageUtilsTest.php => Storage/StorageUtilsTest.php} | 0 6 files changed, 5 insertions(+), 5 deletions(-) rename tests/TestCase/{Lib => Storage}/StorageManagerTest.php (100%) rename tests/TestCase/{Lib/Utility/FileStorageUtilsTest.php => Storage/StorageUtilsTest.php} (100%) diff --git a/src/Storage/Listener/LegacyLocalFileStorageListener.php b/src/Storage/Listener/LegacyLocalFileStorageListener.php index 8ba96c38..2fa8659e 100644 --- a/src/Storage/Listener/LegacyLocalFileStorageListener.php +++ b/src/Storage/Listener/LegacyLocalFileStorageListener.php @@ -24,7 +24,7 @@ class LegacyLocalFileStorageListener extends LocalListener { * @var array */ protected $_defaultConfig = [ - 'pathBuilder' => 'Local', + 'pathBuilder' => 'Base', 'pathBuilderOptions' => [ 'pathPrefix' => 'files', 'modelFolder' => false, diff --git a/tests/TestCase/Storage/Listener/AbstractListenerTest.php b/tests/TestCase/Storage/Listener/AbstractListenerTest.php index d3a1afa4..13a7812e 100644 --- a/tests/TestCase/Storage/Listener/AbstractListenerTest.php +++ b/tests/TestCase/Storage/Listener/AbstractListenerTest.php @@ -31,7 +31,7 @@ class AbstractListenerTest extends TestCase { */ public function testPathBuilder() { $Listener = new TestAbstractListener([ - 'pathBuilder' => 'Local' + 'pathBuilder' => 'Base' ]); $result = $Listener->pathBuilder(); $this->assertInstanceOf('\Burzum\FileStorage\Storage\PathBuilder\BasePathBuilder', $result); diff --git a/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php b/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php index bda0b033..e1567169 100644 --- a/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php +++ b/tests/TestCase/Storage/Listener/ImageProcessingTraitTest.php @@ -18,7 +18,7 @@ class TraitTestClass extends AbstractListener { use ImageProcessingTrait; public $_defaultConfig = [ - 'pathBuilder' => 'Local', + 'pathBuilder' => 'Base', 'pathBuilderOptions' => [ 'preserveFilename' => true ] diff --git a/tests/TestCase/Storage/PathBuilder/PathBuilderTraitTest.php b/tests/TestCase/Storage/PathBuilder/PathBuilderTraitTest.php index 331ed76d..8f6d7d22 100644 --- a/tests/TestCase/Storage/PathBuilder/PathBuilderTraitTest.php +++ b/tests/TestCase/Storage/PathBuilder/PathBuilderTraitTest.php @@ -13,9 +13,9 @@ class PathBuilderTraitTest extends TestCase { public function testCreatePathBuilder() { $object = $this->getObjectForTrait('Burzum\FileStorage\Storage\PathBuilder\PathBuilderTrait'); - $pathBuilder = $object->createPathBuilder('Local'); + $pathBuilder = $object->createPathBuilder('Base'); $this->assertInstanceOf('Burzum\FileStorage\Storage\PathBuilder\PathBuilderInterface', $pathBuilder); - $this->assertInstanceOf('Burzum\FileStorage\Storage\PathBuilder\LocalPathBuilder', $pathBuilder); + $this->assertInstanceOf('Burzum\FileStorage\Storage\PathBuilder\BasePathBuilder', $pathBuilder); } /** diff --git a/tests/TestCase/Lib/StorageManagerTest.php b/tests/TestCase/Storage/StorageManagerTest.php similarity index 100% rename from tests/TestCase/Lib/StorageManagerTest.php rename to tests/TestCase/Storage/StorageManagerTest.php diff --git a/tests/TestCase/Lib/Utility/FileStorageUtilsTest.php b/tests/TestCase/Storage/StorageUtilsTest.php similarity index 100% rename from tests/TestCase/Lib/Utility/FileStorageUtilsTest.php rename to tests/TestCase/Storage/StorageUtilsTest.php From 80451f2c503024afc88a46140cd6742fd49864de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 16 Aug 2015 15:34:53 +0200 Subject: [PATCH 132/144] Updating scrutinizer --- .scrutinizer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 17483085..bf8cdafb 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -13,7 +13,7 @@ tools: enabled: true excluded_dirs: [vendor, tests, config, docs] filter: - paths: [src/Event/*, src/Lib/*] + excluded_paths: [src/Event/, src/Lib/] build: tests: override: From c6a4f9924b06c991490d33f3d3be4b69c9dff263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 16 Aug 2015 16:50:14 +0200 Subject: [PATCH 133/144] Working on the tests. --- .../LegacyLocalFileStorageListener.php | 4 +- .../LegacyLocalFileStorageListenerTest.php | 49 ++++++++++++++++++- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/Storage/Listener/LegacyLocalFileStorageListener.php b/src/Storage/Listener/LegacyLocalFileStorageListener.php index 2fa8659e..1f1f6127 100644 --- a/src/Storage/Listener/LegacyLocalFileStorageListener.php +++ b/src/Storage/Listener/LegacyLocalFileStorageListener.php @@ -44,10 +44,10 @@ public function afterSave(Event $event, EntityInterface $entity) { if ($this->_checkEvent($event) && $entity->isNew()) { $fileField = $this->config('fileField'); - $entity['hash'] = $this->getHash($entity, $fileField); + $entity['hash'] = $this->getFileHash($entity, $fileField); $entity['path'] = $this->pathBuilder()->path($entity); - if (!$this->_storeFile($entity)) { + if (!$this->_storeFile($event)) { return; } diff --git a/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php b/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php index 53836769..a83d4dd0 100644 --- a/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php +++ b/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php @@ -31,8 +31,14 @@ public function setUp() { $this->fileFixtures = Plugin::path('Burzum/FileStorage') . 'tests' . DS . 'Fixture' . DS . 'File' . DS; $this->listener = $this->getMockBuilder('Burzum\FileStorage\Storage\Listener\LegacyLocalFileStorageListener') - ->setMethods(['storageAdapter']) - ->setConstructorArgs([['models' => ['Item']]]) + ->setMethods([ + 'storageAdapter' + ]) + ->setConstructorArgs([ + [ + 'models' => ['Item'] + ] + ]) ->getMock(); $this->adapterMock = $this->getMock('\Gaufrette\Adapter\Local', [], ['']); @@ -42,6 +48,8 @@ public function setUp() { /** * Testing that the path is the same as in the old LocalFileStorageListener class. + * + * @return void */ public function testPath() { $entity = $this->FileStorage->get('file-storage-1'); @@ -49,4 +57,41 @@ public function testPath() { $expected = 'files' . DS . '00' . DS . '14' . DS . '90' . DS . 'filestorage1' . DS; $this->assertEquals($result, $expected); } + +/** + * testAfterSave + * + * @return void + */ + public function testAfterSave() { + $this->skipIf(PHP_INT_SIZE !== 4, 'This will produce a different result on a 64bit php version, skipping test.'); + + $entity = $this->FileStorage->newEntity([ + 'model' => 'Item', + 'id' => '06c0e8e2-4424-11e5-a151-feff819cdc9f', + 'filename' => 'titus.jpg', + 'extension' => 'jpg', + 'mime_type' => 'image/jpeg', + 'file' => [ + 'error' => UPLOAD_ERR_OK, + 'tmp_name' => $this->fileFixtures . 'titus.jpg' + ] + ]); + + $event = new Event('FileStorage.afterSave', $this->FileStorage, [ + 'record' => $entity, + 'table' => $this->FileStorage + ]); + + $this->listener->expects($this->at(0)) + ->method('storageAdapter') + ->will($this->returnValue($this->adapterMock)); + + $this->adapterMock->expects($this->at(0)) + ->method('write') + ->will($this->returnValue(true)); + + $this->listener->afterSave($event, $entity); + $this->assertEquals($entity->path, 'files' . DS . '05' . DS . '17' . DS . '68' . DS . '06c0e8e2442411e5a151feff819cdc9f' . DS); + } } \ No newline at end of file From f09725c44801d6a83b49a6472b65647d3148cf34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 16 Aug 2015 16:54:21 +0200 Subject: [PATCH 134/144] Working on the tests. --- phpunit.xml.dist | 1 + .../Storage/Listener/LegacyLocalFileStorageListenerTest.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 63724b7d..3f8a6e44 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -34,6 +34,7 @@ ./src + ./src/Storage/PathBuilder/PathBuilderInterface.php ./vendor ./tests ./src/Model/Entity diff --git a/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php b/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php index a83d4dd0..d3526460 100644 --- a/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php +++ b/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php @@ -64,7 +64,7 @@ public function testPath() { * @return void */ public function testAfterSave() { - $this->skipIf(PHP_INT_SIZE !== 4, 'This will produce a different result on a 64bit php version, skipping test.'); + //$this->skipIf(PHP_INT_SIZE !== 4, 'This will produce a different result on a 64bit php version, skipping test.'); $entity = $this->FileStorage->newEntity([ 'model' => 'Item', From 5104808ef69283e079f370f7c97e144a2a2d9ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 16 Aug 2015 17:06:43 +0200 Subject: [PATCH 135/144] Refactoring the randomPath building code. --- src/Storage/PathBuilder/BasePathBuilder.php | 57 ++++++++++++++++--- src/Storage/StorageUtils.php | 11 +--- .../LegacyLocalFileStorageListenerTest.php | 2 - 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/Storage/PathBuilder/BasePathBuilder.php b/src/Storage/PathBuilder/BasePathBuilder.php index cf8ccbc0..f864cf7d 100644 --- a/src/Storage/PathBuilder/BasePathBuilder.php +++ b/src/Storage/PathBuilder/BasePathBuilder.php @@ -216,18 +216,57 @@ public function url(EntityInterface $entity, array $options = []) { public function randomPath($string, $level = 3, $method = 'sha1') { // Keeping this for backward compatibility but please stop using crc32()! if ($method === 'crc32') { - return StorageUtils::randomPath($string); + return $this->_randomPathCrc32($string, $level); } if ($method === 'sha1') { - $result = sha1($string); - $randomString = ''; - $counter = 0; - for ($i = 1; $i <= $level; $i++) { - $counter = $counter + 2; - $randomString .= substr($result, $counter, 2) . DS; - } - return $randomString; + return $this->_randomPathSha1($string, $level); + } + if (method_exists($this, $method)) { + return $this->{$method}($string, $level); + } + } + +/** + * Creates a semi-random path based on a string. + * + * Please STOP USING CR32! See the huge warning on the php documentation page. + * of the crc32() function. + * + * @link http://php.net/manual/en/function.crc32.php + * @link https://www.box.com/blog/crc32-checksums-the-good-the-bad-and-the-ugly/ + * @param string $string Input string + * @param int $level Depth of the path to generate. + * @return string + */ + protected function _randomPathCrc32($string, $level) { + $string = crc32($string); + $decrement = 0; + $path = null; + for ($i = 0; $i < $level; $i++) { + $decrement = $decrement - 2; + $path .= sprintf("%02d" . DS, substr(str_pad('', 2 * $level, '0') . $string, $decrement, 2)); + } + return $path; + } + +/** + * Creates a semi-random path based on a string. + * + * Makes it possible to overload this functionality. + * + * @param string $string Input string + * @param int $level Depth of the path to generate. + * @return string + */ + protected function _randomPathSha1($string, $level) { + $result = sha1($string); + $randomString = ''; + $counter = 0; + for ($i = 1; $i <= $level; $i++) { + $counter = $counter + 2; + $randomString .= substr($result, $counter, 2) . DS; } + return $randomString; } /** diff --git a/src/Storage/StorageUtils.php b/src/Storage/StorageUtils.php index b957669f..658f61a0 100644 --- a/src/Storage/StorageUtils.php +++ b/src/Storage/StorageUtils.php @@ -1,6 +1,7 @@ randomPath($string, $level, 'crc32'); } /** diff --git a/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php b/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php index d3526460..a7749362 100644 --- a/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php +++ b/tests/TestCase/Storage/Listener/LegacyLocalFileStorageListenerTest.php @@ -64,8 +64,6 @@ public function testPath() { * @return void */ public function testAfterSave() { - //$this->skipIf(PHP_INT_SIZE !== 4, 'This will produce a different result on a 64bit php version, skipping test.'); - $entity = $this->FileStorage->newEntity([ 'model' => 'Item', 'id' => '06c0e8e2-4424-11e5-a151-feff819cdc9f', From 5e312831b624884c26a8c4795a346b14cf47c353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pustu=C5=82ka?= Date: Sun, 16 Aug 2015 20:59:28 +0200 Subject: [PATCH 136/144] Fix a typo in FileStorageTableTest --- tests/TestCase/Model/Table/FileStorageTableTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestCase/Model/Table/FileStorageTableTest.php b/tests/TestCase/Model/Table/FileStorageTableTest.php index 6db1df03..eb274a70 100644 --- a/tests/TestCase/Model/Table/FileStorageTableTest.php +++ b/tests/TestCase/Model/Table/FileStorageTableTest.php @@ -1,5 +1,5 @@ Date: Sun, 16 Aug 2015 21:01:18 +0200 Subject: [PATCH 137/144] Fix a typo in ImageStorageTableTest --- tests/TestCase/Model/Table/ImageStorageTableTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/TestCase/Model/Table/ImageStorageTableTest.php b/tests/TestCase/Model/Table/ImageStorageTableTest.php index 7a5956f6..f024542e 100644 --- a/tests/TestCase/Model/Table/ImageStorageTableTest.php +++ b/tests/TestCase/Model/Table/ImageStorageTableTest.php @@ -19,7 +19,7 @@ * @copyright 2012 - 2015 Florian Krämer * @license MIT */ -class ImageStorageTest extends FileStorageTestCase { +class ImageStorageTableTest extends FileStorageTestCase { /** * Fixtures @@ -259,4 +259,4 @@ public function testDeleteOldFileOnSave() { $this->assertNotEquals($result['path'], $result2['path']); $this->assertNotEquals($result['filename'], $result2['filename']); } -} \ No newline at end of file +} From cbcbcd4991f93a801ae701882ad3cd138af3928e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 16 Aug 2015 23:18:10 +0200 Subject: [PATCH 138/144] Adding a test for the EventDispatcherTrait.php --- .../Behavior/Event/EventDispatcherTrait.php | 3 ++ .../Event/EventDispatcherTraitTest.php | 38 +++++++++++++++++++ .../TestCase/Storage/StorageExceptionTest.php | 2 +- 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/TestCase/Model/Behavior/Event/EventDispatcherTraitTest.php diff --git a/src/Model/Behavior/Event/EventDispatcherTrait.php b/src/Model/Behavior/Event/EventDispatcherTrait.php index 39d56dee..05ea7535 100644 --- a/src/Model/Behavior/Event/EventDispatcherTrait.php +++ b/src/Model/Behavior/Event/EventDispatcherTrait.php @@ -6,6 +6,9 @@ /** * EventDispatcherTrait * + * This "overrides" the original dispatchEvent() method to inject the table object + * to keep the events backward compatible. + * * @author Robert Pustułka * @copyright 2012 - 2015 Florian Krämer * @license MIT diff --git a/tests/TestCase/Model/Behavior/Event/EventDispatcherTraitTest.php b/tests/TestCase/Model/Behavior/Event/EventDispatcherTraitTest.php new file mode 100644 index 00000000..9e483515 --- /dev/null +++ b/tests/TestCase/Model/Behavior/Event/EventDispatcherTraitTest.php @@ -0,0 +1,38 @@ +_table = $table; + } + protected function _dispatchEvent($name, $data = null, $subject = null) { + return compact(['name', 'data', 'subject']); + } +} + +class EventDispatcherTraitTest extends TestCase { + +/** + * testSetAndGetEntity + * + * @return void + */ + public function testDispatchEvent() { + $Dispatcher = new TestEventDispatcherTrait('Table goes here.'); + $result = $Dispatcher->dispatchEvent('TestEvent', null, []); + $expected = [ + 'name' => 'TestEvent', + 'data' => [ + 'table' => 'Table goes here.' + ], + 'subject' => [] + ]; + $this->assertEquals($result, $expected); + } +} diff --git a/tests/TestCase/Storage/StorageExceptionTest.php b/tests/TestCase/Storage/StorageExceptionTest.php index 09a50fbf..86907719 100644 --- a/tests/TestCase/Storage/StorageExceptionTest.php +++ b/tests/TestCase/Storage/StorageExceptionTest.php @@ -5,7 +5,7 @@ use Cake\ORM\TableRegistry; use Cake\TestSuite\TestCase; -class TestStorageException extends TestCase { +class StorageExceptionTest extends TestCase { /** * testSetAndGetEntity From 6a0b4dbc198105c12f55ce5789c7b690890432c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sun, 16 Aug 2015 23:25:03 +0200 Subject: [PATCH 139/144] Working on the LocalListener tests. --- src/Storage/Listener/LocalListener.php | 1 + .../Storage/Listener/LocalListenerTest.php | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index ec609d41..d7382426 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -121,6 +121,7 @@ public function afterSave(Event $event, EntityInterface $entity) { $this->autoProcessImageVersions($entity, 'create', $options); } + $event->result = true; $event->stopPropagation(); } } diff --git a/tests/TestCase/Storage/Listener/LocalListenerTest.php b/tests/TestCase/Storage/Listener/LocalListenerTest.php index 18bbb2c2..a8ded1df 100644 --- a/tests/TestCase/Storage/Listener/LocalListenerTest.php +++ b/tests/TestCase/Storage/Listener/LocalListenerTest.php @@ -66,6 +66,7 @@ public function testAfterSave() { ->will($this->returnValue(true)); $this->listener->afterSave($event, $entity); + $this->assertTrue($event->result); } /** @@ -74,6 +75,21 @@ public function testAfterSave() { * @return void */ public function testAfterDelete() { + $entity = $this->FileStorage->get('file-storage-3'); + $event = new Event('FileStorage.afterDelete', $this->FileStorage, [ + 'record' => $entity, + 'table' => $this->FileStorage + ]); + + $this->listener->expects($this->at(0)) + ->method('storageAdapter') + ->will($this->returnValue($this->adapterMock)); + + $this->adapterMock->expects($this->at(0)) + ->method('delete') + ->will($this->returnValue(true)); + $this->listener->afterDelete($event, $entity); + $this->assertTrue($event->result); } } From a17d83eaf18c2cb1ff34455987fb439a307a0086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Tue, 18 Aug 2015 00:12:13 +0200 Subject: [PATCH 140/144] Working around #83... ...until the framework might provide a good way to do it. --- src/Model/Behavior/UploadValidatorBehavior.php | 10 +++++----- src/Validation/UploadValidator.php | 16 ---------------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/Model/Behavior/UploadValidatorBehavior.php b/src/Model/Behavior/UploadValidatorBehavior.php index 73a0f7a0..ce948a8f 100644 --- a/src/Model/Behavior/UploadValidatorBehavior.php +++ b/src/Model/Behavior/UploadValidatorBehavior.php @@ -83,7 +83,7 @@ public function configureUploadValidation($validatorName = 'default', $config = 'imageSize', $config['validateImageSize'] ], - 'message' => __d('file_storage', 'The image dimensions are to big!') + 'message' => __d('file_storage', 'The image dimensions are to big.') ]); } if (is_int($config['validateFilesize'])) { @@ -93,7 +93,7 @@ public function configureUploadValidation($validatorName = 'default', $config = 'filesize', $config['validateFilesize'] ], - 'message' => __d('file_storage', 'The file is to big {0}!', $uploadValidator->getFilesize()) + 'message' => __d('file_storage', 'The file is to big!') ]); } if ($config['validateUploadErrors'] === true) { @@ -103,7 +103,7 @@ public function configureUploadValidation($validatorName = 'default', $config = 'uploadErrors', ['allowNoFileError' => $config['allowNoFileError']] ], - 'message' => __d('file_storage', 'The mime-type {0} is not allowed!', $uploadValidator->getMimeType()) + 'message' => __d('file_storage', 'No file was uploaded.') ]); } if ($config['validateUploadArray'] === true) { @@ -122,7 +122,7 @@ public function configureUploadValidation($validatorName = 'default', $config = 'mimeType', $config['allowedMime'] ], - 'message' => __d('file_storage', 'The mime-type {0} is not allowed!', $uploadValidator->getMimeType()) + 'message' => __d('file_storage', 'The mime-type is not allowed.') ]); } if (!empty($config['allowedExtensions'])) { @@ -132,7 +132,7 @@ public function configureUploadValidation($validatorName = 'default', $config = 'extension', $config['allowedExtensions'] ], - 'message' => __d('file_storage', 'The extension {0} is not allowed.', $uploadValidator->getExtension()) + 'message' => __d('file_storage', 'The extension is not allowed.') ]); }; if ($config['localFile'] === true) { diff --git a/src/Validation/UploadValidator.php b/src/Validation/UploadValidator.php index b195884c..8ef12440 100644 --- a/src/Validation/UploadValidator.php +++ b/src/Validation/UploadValidator.php @@ -252,20 +252,4 @@ public function uploadErrors($value, $options = array()) { $this->_uploadError = ''; return true; } - - public function getFilesize() { - return $this->_filesize; - } - - public function getExtension() { - return $this->_extension; - } - - public function getMimeType() { - return $this->_mimeType; - } - - public function getUploadError() { - return $this->_uploadError; - } } From 2eb98d82eb2d318cbf733af99289d771c7ec0fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Tue, 18 Aug 2015 00:15:29 +0200 Subject: [PATCH 141/144] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6237b0a4..dec888ea 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ FileStorage Plugin for CakePHP [![Build Status](https://img.shields.io/travis/burzum/cakephp-file-storage/3.0.svg?style=flat-square)](https://travis-ci.org/burzum/cakephp-file-storage) [![Coverage Status](https://img.shields.io/coveralls/burzum/cakephp-file-storage/3.0.svg?style=flat-square)](https://coveralls.io/r/burzum/cakephp-file-storage) +**If you're upgrading from CakePHP 2.x please read [the migration guide](docs/Documentation/Migrating-from-CakePHP-2.md).** + The **File Storage** plugin is giving you the possibility to upload and store files in virtually any kind of storage backend. This plugin is wrapping the [Gaufrette](https://github.com/KnpLabs/Gaufrette) library in a CakePHP fashion and provides a simple way to use the storage adapters through the [StorageManager](Lib/StorageManager.php) class. Storage adapters are an unified interface that allow you to store file data to your local file system, in memory, in a database or into a zip file and remote systems. There is a database table keeping track of what you stored where. You can always write your own adapter or extend and overload existing ones. -**If you're upgrading from CakePHP 2.x please read [the migration guide](docs/Documentation/Migrating-from-CakePHP-2.md).** - [Please donate if you like it!](https://pledgie.com/campaigns/29682) ------------------------------ From 2a82be571965f14b797cc4296107c7b61cf0ec22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Tue, 18 Aug 2015 01:41:19 +0200 Subject: [PATCH 142/144] Ensuring BC for #91 FileStorage.ImageHelper.imagePath was removed, but it needs to stay for BC. --- src/Storage/Listener/LocalListener.php | 3 +-- src/View/Helper/ImageHelper.php | 31 +++++++++++++++----------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Storage/Listener/LocalListener.php b/src/Storage/Listener/LocalListener.php index d7382426..f5d1f143 100644 --- a/src/Storage/Listener/LocalListener.php +++ b/src/Storage/Listener/LocalListener.php @@ -151,8 +151,7 @@ public function imagePath(Event $event) { } $this->_loadImageProcessingFromConfig(); - $event->data['path'] = $this->imageVersionPath($entity, $version, $type, $options); - + $event->data['path'] = $event->result = $this->imageVersionPath($entity, $version, $type, $options); $event->stopPropagation(); } diff --git a/src/View/Helper/ImageHelper.php b/src/View/Helper/ImageHelper.php index 6f9f4fda..d35b3d34 100644 --- a/src/View/Helper/ImageHelper.php +++ b/src/View/Helper/ImageHelper.php @@ -63,21 +63,26 @@ public function imageUrl($image, $version = null, $options = []) { $hash = null; } - $Event = new Event('ImageVersion.getVersions', $this, [ - 'hash' => $hash, - 'image' => $image, - 'version' => $version, - 'options' => $options, - 'pathType' => 'url' - ] - ); - EventManager::instance()->dispatch($Event); + $eventOptions = [ + 'hash' => $hash, + 'image' => $image, + 'version' => $version, + 'options' => $options, + 'pathType' => 'url' + ]; - if ($Event->isStopped()) { - return $this->normalizePath($Event->data['path']); - } else { - return false; + $event1 = new Event('ImageVersion.getVersions', $this, $eventOptions); + $event2 = new Event('FileStorage.ImageHelper.imagePath', $this, $eventOptions); + + EventManager::instance()->dispatch($event1); + EventManager::instance()->dispatch($event2); + + if ($event1->isStopped()) { + return $this->normalizePath($event1->data['path']); + } elseif ($event2->isStopped()) { + return $this->normalizePath($event2->data['path']); } + return false; } /** From 1ab0357d0d55b26fc4efa1ef9e6593e3c71f851e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Tue, 18 Aug 2015 11:17:03 +0200 Subject: [PATCH 143/144] Adding test skeletons for the new behaviors. --- .../Behavior/FileStorageBehaviorTest.php | 85 +++++++++++++++++++ .../Behavior/ImageStorageBehaviorTest.php | 75 ++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 tests/TestCase/Model/Behavior/FileStorageBehaviorTest.php create mode 100644 tests/TestCase/Model/Behavior/ImageStorageBehaviorTest.php diff --git a/tests/TestCase/Model/Behavior/FileStorageBehaviorTest.php b/tests/TestCase/Model/Behavior/FileStorageBehaviorTest.php new file mode 100644 index 00000000..99986d93 --- /dev/null +++ b/tests/TestCase/Model/Behavior/FileStorageBehaviorTest.php @@ -0,0 +1,85 @@ + Date: Tue, 18 Aug 2015 11:18:20 +0200 Subject: [PATCH 144/144] Use the event result as well instead of the data['path'] (BC compatible) --- src/Event/ImageProcessingListener.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Event/ImageProcessingListener.php b/src/Event/ImageProcessingListener.php index b280dc57..6ff86b41 100644 --- a/src/Event/ImageProcessingListener.php +++ b/src/Event/ImageProcessingListener.php @@ -322,7 +322,7 @@ public function imagePath(Event $Event) { protected function _buildLocalPath(Event $Event) { extract($Event->data); $path = $this->_buildPath($image, true, $hash); - $Event->data['path'] = '/' . $path; + $Event->data['path'] = $Event->result = '/' . $path; $Event->stopPropagation(); } @@ -367,7 +367,7 @@ protected function _buildAmazonS3Path(Event $Event) { $image['path'] = str_replace('\\', '/', $image['path']); $bucketPrefix = !empty($Event->data['options']['bucketPrefix']) && $Event->data['options']['bucketPrefix'] === true; - $Event->data['path'] = $this->_buildCloudFrontDistributionUrl($http, $image['path'], $bucket, $bucketPrefix, $cfDist); + $Event->data['path'] = $Event->result = $this->_buildCloudFrontDistributionUrl($http, $image['path'], $bucket, $bucketPrefix, $cfDist); $Event->stopPropagation(); }