Skip to content
This repository has been archived by the owner on Nov 16, 2022. It is now read-only.

Commit

Permalink
Merge pull request #173 from davidyell/develop
Browse files Browse the repository at this point in the history
Merge for release
  • Loading branch information
davidyell committed Mar 3, 2016
2 parents e0cad2e + df84b53 commit 9e39c95
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 32 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@ app/[Cc]onfig/database.php

/vendor/
composer.lock
.idea/*
10 changes: 6 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "davidyell/proffer",
"description": "An upload plugin for CakePHP 3",
"type": "cakephp-plugin",
"keywords": ["cakephp", "cakephp3", "upload", "file", "image"],
"keywords": ["cakephp", "cakephp3", "upload", "file", "image", "orm"],
"homepage": "https://github.com/davidyell/CakePHP3-Proffer",
"license": "MIT",
"authors": [
Expand All @@ -18,11 +18,13 @@
},
"require": {
"php": ">=5.4.16",
"imagine/imagine": "0.6.2",
"cakephp/cakephp": "~3.0"
"imagine/imagine": "^0.6",
"cakephp/orm": "3.*"
},
"require-dev": {
"phpunit/phpunit": "*"
"phpunit/phpunit": "*",
"cakephp/cakephp": "~3.0",
"cakephp/cakephp-codesniffer": "^2.0"
},
"autoload": {
"psr-4": {
Expand Down
9 changes: 8 additions & 1 deletion docs/customisation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
This manual page deals with customising the behaviour of the Proffer plugin. How to change the upload location and changing
file names. It also cover how you can use the Proffer events to change the way the plugin behaves.

##Customising upload file names and paths using an event listener
##Customising using an event listener

###Customising upload file names and paths
Using the `Proffer.afterPath` event you can hook into all the details about the file upload before it is processed. Using
this event you can change the name of the file and the upload path to match whatever convention you want. I have created
an example listener which is [available as an example](examples/UploadFilenameListener.md).
Expand All @@ -23,6 +25,11 @@ file and also attach this listener to multiple tables, if you wanted the same na
:warning: The listener will overwrite any settings that are configured in the path class. This includes if you are using
your own path class.

###Customising behavior of file creation/deletion
Proffer’s image creation can be hooked by using `Proffer.afterCreateImage` event, and by using `Proffer.beforeDeleteImage` event, Proffer’s image deletion can be hooked.
These events can be used to copy files to external services (e.g. Amazon S3), or deleting files from external services at the same time of Proffer creating/deleting images.
I have created an example listener which is [available as an example](examples/UploadAndDeleteImageListener.md).

##Advanced customisation
If you want more control over how the plugin is handling paths or creating thumbnails you can replace these components
with your own by creating a class using the provided interfaces and injecting them into the plugin.
Expand Down
30 changes: 30 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ echo $this->Html->image('../files/<table>/<field>/' . $data->get('image_dir') .
## Example event listener
Here are some basic event listener example classes
* [Customize the upload folder and filename](examples/UploadFilenameListener.md)
* [Customize behavior of file creation/deletion](examples/UploadAndDeleteImageListener.md)

##Uploading multiple related images
This example will show you how to upload many images which are related to your
Expand Down Expand Up @@ -73,4 +74,33 @@ field names, so that your request data is formatted correctly.
How you deal with the display of existing images, deletion of existing images,
and adding of new upload fields is up to you, and outside the scope of this example.

###Deleting images but preserving data
If you need to delete an upload and remove it's associated data from your data store, you can achieve this in your controller.

The easiest way is to add a checkbox to your form and then look for it when processing your post data.

An example form might look like. It's important to note that I've disabled the `hiddenField` option here.

```php
echo $this->Form->input('cover', ['type' => 'file']);
if (!empty($league->cover)) {
echo $this->Form->input('delete_cover', ['type' => 'checkbox', 'hiddenField' => false, 'label' => 'Remove my cover photo']);
}
```

Then in your controller, check for the field before using `patchEntity`

```php
// Deleting the upload?
if (isset($this->request->data['delete_cover'])) {
$this->request->data['image_dir'] = null;
$this->request->data['cover'] = null;

$path = new \Proffer\Lib\ProfferPath($this->Leagues, $league, 'cover', $this->Leagues->behaviors()->Proffer->config('cover'));
$path->deleteFiles($path->getFolder(), true);
}

// patchEntity etc
```

[< Shell tasks](shell.md) | [FAQ >](faq.md)
48 changes: 48 additions & 0 deletions docs/examples/UploadAndDeleteImageListener.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
##Customizing behavior of file creation/deletion using event listener

You can hook Proffer's image creation/deletion as below.

###Create src/Event/UploadAndDeleteImageListener.php

```php
<?php

namespace App\Event;

use Cake\Event\Event;
use Cake\Event\EventListenerInterface;
use Cake\Log\Log;
use Proffer\Lib\ProfferPath;

class UploadAndDeleteImageListener implements EventListenerInterface {

public function implementedEvents()
{
return [
'Proffer.afterCreateImage' => 'createImage',
'Proffer.beforeDeleteImage' => 'deleteImage',
];
}

public function createImage(Event $event, ProfferPath $path, $imagePath)
{
Log::write('debug', 'hook event of createImage path: ' . $imagePath);

// copy file to external service (e.g. Amazon S3)
// delete locale file
}

public function deleteImage(Event $event, ProfferPath $path)
{
Log::write('debug', 'hook event of deleteImage folder: ' . $path->getFolder());

// delete file from external service (e.g. Amazon S3)
}
}
```

###Register listener to EventManager in config/bootstrap.php

```php
Cake\Event\EventManager::instance()->on(new \App\Event\UploadAndDeleteImageListener());
```
2 changes: 1 addition & 1 deletion docs/examples/UploadFilenameListener.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class UploadFilenameListener implements EventListenerInterface

// Change the filename in both the path to be saved, and in the entity data for saving to the db
$path->setFilename($newFilename);
$event->subject()['image']['name'] = $newFilename;
$event->subject('image')['name'] = $newFilename;

// Must return the modified path instance, so that things are saved in the right place
return $path;
Expand Down
12 changes: 8 additions & 4 deletions src/Lib/ImageTransform.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,13 @@ protected function setImagine($engine = 'gd')
* Take an upload fields configuration and create all the thumbnails
*
* @param array $config The upload fields configuration
* @return void
* @return array
*/
public function processThumbnails(array $config)
{
$thumbnailPaths = [];
if (!isset($config['thumbnailSizes'])) {
return;
return $thumbnailPaths;
}

foreach ($config['thumbnailSizes'] as $prefix => $thumbnailConfig) {
Expand All @@ -99,8 +100,10 @@ public function processThumbnails(array $config)
$method = $config['thumbnailMethod'];
}

$this->makeThumbnail($prefix, $thumbnailConfig, $method);
$thumbnailPath = $this->makeThumbnail($prefix, $thumbnailConfig, $method);
$thumbnailPaths[] = $thumbnailPath;
}
return $thumbnailPaths;
}

/**
Expand All @@ -109,7 +112,7 @@ public function processThumbnails(array $config)
* @param string $prefix The thumbnail prefix
* @param array $config Array of thumbnail config
* @param string $thumbnailMethod Which engine to use to make thumbnails
* @return void
* @return string
*/
public function makeThumbnail($prefix, array $config, $thumbnailMethod = 'gd')
{
Expand All @@ -130,6 +133,7 @@ public function makeThumbnail($prefix, array $config, $thumbnailMethod = 'gd')
unset($config['crop'], $config['w'], $config['h']);

$image->save($this->Path->fullPath($prefix), $config);
return $this->Path->fullPath($prefix);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/Lib/ImageTransformInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface ImageTransformInterface
* Take an upload fields configuration and process each configured thumbnail
*
* @param array $config The upload fields configuration
* @return void
* @return array
*/
public function processThumbnails(array $config);

Expand All @@ -27,7 +27,7 @@ public function processThumbnails(array $config);
* @param string $prefix The prefix name for the thumbnail
* @param array $dimensions The thumbnail dimensions
* @param string $thumbnailMethod Which method to use to create the thumbnail
* @return void
* @return string
*/
public function makeThumbnail($prefix, array $dimensions, $thumbnailMethod = 'gd');
}
49 changes: 36 additions & 13 deletions src/Model/Behavior/ProfferBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

use ArrayObject;
use Cake\Database\Type;
use Cake\Datasource\EntityInterface;
use Cake\Event\Event;
use Cake\ORM\Behavior;
use Cake\ORM\Entity;
Expand All @@ -27,6 +28,7 @@ class ProfferBehavior extends Behavior
* Build the behaviour
*
* @param array $config Passed configuration
*
* @return void
*/
public function initialize(array $config)
Expand All @@ -42,9 +44,12 @@ public function initialize(array $config)
/**
* beforeMarshal event
*
* @param Event $event Event instance
* If a field is allowed to be empty as defined in the validation it should be unset to prevent processing
*
* @param \Cake\Event\Event $event Event instance
* @param ArrayObject $data Data to process
* @param ArrayObject $options Array of options for event
*
* @return void
*/
public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options)
Expand All @@ -61,18 +66,23 @@ public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $opti
/**
* beforeSave method
*
* @param Event $event The event
* @param Entity $entity The entity
* Process any uploaded files, generate paths, move the files and kick of thumbnail generation if it's an image
*
* @param \Cake\Event\Event $event The event
* @param \Cake\Datasource\EntityInterface $entity The entity
* @param ArrayObject $options Array of options
* @param ProfferPathInterface $path Inject an instance of ProfferPath
* @param \Proffer\Lib\ProfferPathInterface $path Inject an instance of ProfferPath
*
* @return true
* @throws Exception
*
* @throws \Exception
*/
public function beforeSave(Event $event, Entity $entity, ArrayObject $options, ProfferPathInterface $path = null)
public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options, ProfferPathInterface $path = null)
{
foreach ($this->config() as $field => $settings) {
if ($entity->has($field) && is_array($entity->get($field)) &&
$entity->get($field)['error'] === UPLOAD_ERR_OK) {

// Allow path to be injected or set in config
if (!empty($settings['pathClass'])) {
$path = new $settings['pathClass']($this->_table, $entity, $field, $settings);
Expand All @@ -89,6 +99,8 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options, P
$path->createPathFolder();

if ($this->moveUploadedFile($entity->get($field)['tmp_name'], $path->fullPath())) {
$imagePaths = [$path->fullPath()];

$entity->set($field, $path->getFilename());
$entity->set($settings['dir'], $path->getSeed());

Expand All @@ -101,7 +113,12 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options, P
$imageTransform = new ImageTransform($this->_table, $path);
}

$imageTransform->processThumbnails($settings);
$thumbnailPaths = $imageTransform->processThumbnails($settings);
$imagePaths = array_merge($imagePaths, $thumbnailPaths);

$eventData = ['path' => $path, 'images' => $imagePaths];
$event = new Event('Proffer.afterCreateImage', $entity, $eventData);
$this->_table->eventManager()->dispatch($event);
}
} else {
throw new Exception('Cannot upload file');
Expand All @@ -118,22 +135,27 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options, P
*
* Remove images from records which have been deleted, if they exist
*
* @param Event $event The passed event
* @param Entity $entity The entity
* @param \Cake\Event\Event $event The passed event
* @param \Cake\Datasource\EntityInterface $entity The entity
* @param ArrayObject $options Array of options
* @param ProfferPathInterface $path Inject and instance of ProfferPath
* @return bool
* @param \Proffer\Lib\ProfferPathInterface $path Inject an instance of ProfferPath
*
* @return true
*/
public function afterDelete(Event $event, Entity $entity, ArrayObject $options, ProfferPathInterface $path = null)
public function afterDelete(Event $event, EntityInterface $entity, ArrayObject $options, ProfferPathInterface $path = null)
{
foreach ($this->config() as $field => $settings) {
$dir = $entity->get($settings['dir']);

if (!empty($entity) && !empty($dir)) {
if (!$path) {
if (!empty($settings['pathClass'])) {
$path = new $settings['pathClass']($this->_table, $entity, $field, $settings);
} elseif (!isset($path)) {
$path = new ProfferPath($this->_table, $entity, $field, $settings);
}

$event = new Event('Proffer.beforeDeleteFolder', $entity, ['path' => $path]);
$this->_table->eventManager()->dispatch($event);
$path->deleteFiles($path->getFolder(), true);
}

Expand All @@ -149,6 +171,7 @@ public function afterDelete(Event $event, Entity $entity, ArrayObject $options,
*
* @param string $file Path to the uploaded file
* @param string $destination The destination file name
*
* @return bool
*/
protected function moveUploadedFile($file, $destination)
Expand Down
22 changes: 20 additions & 2 deletions src/Shell/ProfferShell.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ public function getOptionParser()
'image-class' => [
'short' => 'i',
'help' => __('Fully name spaced custom image transform class, you must use double backslash.')
]
],
'remove-behaviors' => [
'help' => __('The behaviors to remove before generate.'),
],
]
]
]);
Expand All @@ -58,7 +61,10 @@ public function getOptionParser()
'short' => 'd',
'help' => __('Do a dry run and don\'t delete any files.'),
'boolean' => true
]
],
'remove-behaviors' => [
'help' => __('The behaviors to remove before cleanup.'),
],
]
],
]);
Expand Down Expand Up @@ -163,12 +169,17 @@ public function cleanup($table)
// Loop through each upload field configured for this table (field)
foreach ($uploadFieldFolders as $fieldFolder) {
// Loop through each instance of an upload for this field (seed)
$pathFieldName = pathinfo($fieldFolder, PATHINFO_BASENAME);
$uploadFolders = glob($fieldFolder . DS . '*');
foreach ($uploadFolders as $seedFolder) {
// Does the seed exist in the db?
$seed = pathinfo($seedFolder, PATHINFO_BASENAME);

foreach ($config as $field => $settings) {
if ($pathFieldName != $field) {
continue;
}

$targets = [];

$record = $this->{$this->Table->alias()}->find()
Expand Down Expand Up @@ -294,5 +305,12 @@ protected function checkTable($table)
}

}

if ($this->param('remove-behaviors')) {
$removeBehaviors = explode(',', (string)$this->param('remove-behaviors'));
foreach ($removeBehaviors as $removeBehavior) {
$this->Table->removeBehavior($removeBehavior);
}
}
}
}
Loading

0 comments on commit 9e39c95

Please sign in to comment.