Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements after testing the plugin with an example application #1

Merged
merged 3 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,25 @@ The recommended way to install composer packages is:

`composer require cakedc/cakephp-uppy`

Then, ensure the plugin is added to your application, in `config/plugins.php` add

```php
'CakeDC/Uppy'
```

Important: By default, the plugin will add a number of routes to your application `/uppy/files` in order to
upload and view the uploaded files. You should protect these endpoints in case your application requires
authentication to upload or manage files. Failing to do so would allow an authenticated user to *upload* files
to your application.

This plugin uses `uppy_files` as a table to store filedata as filename, filesize, path in S3, ...

To create table run in console `bin/cake migrations migrate`
Run the Plugin migrations to create a table to hold the uploaded files details

`bin/cake migrations migrate -p CakeDC/Uppy`

You must configure the connection parameters with S3 in `config/uppy.php`
Then copy the file `vendor/cakedc/cakephp-uppy/config/uppy.php` into `config/uppy.php`. Tweak this file to
provide the required configuration for your S3 compatible connection.

```php
<?php
Expand Down Expand Up @@ -72,8 +86,8 @@ return [
- lifeTimeGetObject = life time generated link to access file in S3
- lifeTimePutObject = life time generated link to post file in S3
- region = configured region S3
- endpoint = endpoint server to PUT/POST/GET S3 files
- key = S3 account key
- endpoint = endpoint server to PUT/POST/GET S3 files
- key = S3 account key
- secret = S3 account secret
- bucket = bucket name

Expand Down
4 changes: 0 additions & 4 deletions config/Migrations/20220412110508_CreateUppyFiles.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,11 @@ public function change(): void
'default' => null,
'limit' => null,
'null' => true,
'precision' => 6,
'scale' => 6,
])
->addColumn('modified', 'timestamp', [
'default' => null,
'limit' => null,
'null' => true,
'precision' => 6,
'scale' => 6,
])
->addColumn('metadata', 'text', [
'default' => null,
Expand Down
1 change: 1 addition & 0 deletions config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
['path' => '/uppy'],
function (RouteBuilder $routes): void {
$routes->setRouteClass(DashedRoute::class);
$routes->fallbacks();
}
);
};
2 changes: 2 additions & 0 deletions config/uppy.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
return [
'Uppy' => [
'Props' => [
// the Table Alias to identify the user who uploaded the file
'usersAliasModel' => 'Users',
// the Table className to identify the user who uploaded the file
'usersModel' => 'Users',
'deleteFileS3' => true,
'tableFiles' => 'uppy_files',
Expand Down
29 changes: 17 additions & 12 deletions src/Controller/FilesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
public function initialize(): void
{
parent::initialize();
if (!$this->components()->has('FormProtection')) {
$this->loadComponent('FormProtection');
}
$this->FormProtection->setConfig('unlockedActions', ['sign','save']);
}

Expand Down Expand Up @@ -77,13 +80,15 @@
public function save(): void
{
$this->getRequest()->allowMethod('post');
$this->viewBuilder()->setClassName('Json');

$items = $this->getRequest()->getData('items');

$files = [];
$result = [];
foreach ($items as $item) {
if (!isset($item['model'])) {
$tableAlias = $item['model'] ?? null;
if (!$tableAlias) {
$result['error'] = true;
$result['message'] = __('model is required');
$this->set('result', $result);
Expand All @@ -92,16 +97,17 @@
return;
}
try {
$relationTable = $this->fetchTable($item['model']);
} catch (MissingTableClassException) {
$relationTable = $this->fetchTable($tableAlias);
} catch (MissingTableClassException | UnexpectedValueException) {

Check failure on line 101 in src/Controller/FilesController.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

Caught class CakeDC\Uppy\Controller\UnexpectedValueException not found.

Check failure on line 101 in src/Controller/FilesController.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

UndefinedClass

src/Controller/FilesController.php:101:51: UndefinedClass: Class, interface or enum named CakeDC\Uppy\Controller\UnexpectedValueException does not exist (see https://psalm.dev/019)
$result['error'] = true;
$result['message'] = __('there is no table {0} to associate the file', $item['model']);
$result['message'] = __('there is no table {0} to associate the file', $tableAlias);
$this->set('result', $result);
$this->viewBuilder()->setOption('serialize', ['result']);

return;
}
if (!isset($item['foreign_key'])) {
$foreignKey = $item['foreign_key'] ?? null;
if (!$foreignKey) {
$result['error'] = true;
$result['message'] = __('foreign key is required');
$this->set('result', $result);
Expand All @@ -110,10 +116,10 @@
return;
}
try {
$register = $relationTable->get($item['foreign_key']);
$register = $relationTable->get($foreignKey);
} catch (RecordNotFoundException) {
$result['error'] = true;
$result['message'] = __('there is no record {0} to associate the file', $item['foreign_key']);
$result['message'] = __('there is no record with id {0} to associate the file', $foreignKey);
$this->set('result', $result);
$this->viewBuilder()->setOption('serialize', ['result']);

Expand All @@ -123,10 +129,10 @@
$file->filename = $item['filename'];
$file->filesize = $item['filesize'];
$file->extension = $item['extension'];
$model = Configure::read('Uppy.Props.usersModel');
$model = Configure::readOrFail('Uppy.Props.usersModel');
$relation_key = Inflector::singularize(mb_strtolower($model)) . '_id';
$file->user_id = $register->{$relation_key};
$file->model = $item['model'];
$file->model = $tableAlias;
$files[] = $file;
}

Expand All @@ -138,7 +144,6 @@
$result['message'] = __('The association to file could not be saved');
}

$this->viewBuilder()->setClassName('Json');
$this->set('result', $result);
$this->viewBuilder()->setOption('serialize', ['result']);
}
Expand Down Expand Up @@ -190,8 +195,8 @@
$filename = Text::uuid() . '-' . Text::slug($this->getRequest()->getData('filename'));

$contentType = $this->getRequest()->getData('contentType');
if (!in_array($contentType, Configure::read('Uppy.AcceptedContentTypes'))) {
throw new PageOutOfBoundsException(__('contenType {0} is not valid', $contentType));
if (!in_array($contentType, Configure::read('Uppy.AcceptedContentTypes', []))) {
throw new PageOutOfBoundsException(__('contenType {0} is not valid', h($contentType)));

Check failure on line 199 in src/Controller/FilesController.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

Function h not found.

Check failure on line 199 in src/Controller/FilesController.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

UndefinedFunction

src/Controller/FilesController.php:199:82: UndefinedFunction: Function CakeDC\Uppy\Controller\h does not exist (see https://psalm.dev/021)
}

$presignedRequest = $this->createPresignedRequest($filename, $contentType);
Expand Down
14 changes: 7 additions & 7 deletions src/Model/Table/FilesTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ public function initialize(array $config): void
{
parent::initialize($config);

$this->setTable(Configure::read('Uppy.Props.tableFiles'));
$this->setTable(Configure::readOrFail('Uppy.Props.tableFiles'));
$this->setDisplayField('id');
$this->setPrimaryKey('id');

$this->addBehavior('Timestamp');

$this->belongsTo(Configure::read('Uppy.Props.usersAliasModel'), [
$this->belongsTo(Configure::readOrFail('Uppy.Props.usersAliasModel'), [
'foreignKey' => 'user_id',
'className' => Configure::read('Uppy.Props.usersModel'),
'className' => Configure::readOrFail('Uppy.Props.usersModel'),
]);
}

Expand Down Expand Up @@ -100,13 +100,13 @@ public function validationDefault(Validator $validator): Validator

$validator
->scalar('mime_type')
->inList('mime_type', Configure::read('Uppy.AcceptedContentTypes'))
->inList('mime_type', Configure::read('Uppy.AcceptedContentTypes', []))
->maxLength('mime_type', 128)
->allowEmptyString('mime_type');

$validator
->scalar('extension')
->inList('extension', Configure::read('Uppy.AcceptedExtensions'))
->inList('extension', Configure::read('Uppy.AcceptedExtensions', []))
->maxLength('extension', 32)
->allowEmptyString('extension');

Expand Down Expand Up @@ -149,7 +149,7 @@ public function buildRules(RulesChecker $rules): RulesChecker
$rules->add(
$rules->existsIn(
'user_id',
Configure::read('Uppy.Props.usersAliasModel')
Configure::readOrFail('Uppy.Props.usersAliasModel')
),
['errorField' => 'user_id']
);
Expand All @@ -167,7 +167,7 @@ public function buildRules(RulesChecker $rules): RulesChecker
*/
public function afterDelete(EventInterface $event, File $entity, ArrayObject $options): void
{
if (Configure::read('Uppy.Props.deleteFileS3')) {
if (Configure::read('Uppy.Props.deleteFileS3', false)) {
$this->deleteObject($entity->path, $entity->filename);
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/UppyPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,19 @@
namespace CakeDC\Uppy;

use Cake\Core\BasePlugin;
use Cake\Core\Configure;
use Cake\Core\PluginApplicationInterface;

/**
* Plugin for UppyManager
*/
class UppyPlugin extends BasePlugin
{
public function bootstrap(PluginApplicationInterface $app): void

Check failure on line 24 in src/UppyPlugin.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

Missing doc comment for function bootstrap()
{
parent::bootstrap($app);
if (file_exists(CONFIG . 'uppy.php')) {

Check failure on line 27 in src/UppyPlugin.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

Constant CONFIG not found.

Check failure on line 27 in src/UppyPlugin.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

UndefinedConstant

src/UppyPlugin.php:27:25: UndefinedConstant: Const CONFIG is not defined (see https://psalm.dev/020)
Configure::load('uppy');
}
}
}
40 changes: 20 additions & 20 deletions src/Util/S3Trait.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@
{
$path = $path ?? '';
if (Configure::read('Uppy.S3.config.connection') !== 'dummy') {
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$exist = $s3Client->doesObjectExist(Configure::read('Uppy.S3.bucket'), $path);
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
$exist = $s3Client->doesObjectExist(Configure::readOrFail('Uppy.S3.bucket'), $path);
if ($exist) {
$s3Client->deleteObject([
'Bucket' => Configure::read('Uppy.S3.bucket'),
'Bucket' => Configure::readOrFail('Uppy.S3.bucket'),
'Key' => $path,
]);
if ($s3Client->doesObjectExist(Configure::read('Uppy.S3.bucket'), $path)) {
if ($s3Client->doesObjectExist(Configure::readOrFail('Uppy.S3.bucket'), $path)) {
return false;
}
}
Expand All @@ -67,9 +67,9 @@
return 'https://example.com';
}

$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
$cmd = $s3Client->getCommand('GetObject', [
'Bucket' => Configure::read('Uppy.S3.bucket'),
'Bucket' => Configure::readOrFail('Uppy.S3.bucket'),
'Key' => $path,
]);
$request = $s3Client->createPresignedRequest($cmd, Configure::read('Uppy.S3.contants.lifeTimeGetObject'));
Expand All @@ -91,15 +91,15 @@
return (new Request())
->withUri(new Uri('https://example.com'));
} else {
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
$command = $s3Client->getCommand('putObject', [
'Bucket' => Configure::read('Uppy.S3.bucket'),
'Bucket' => Configure::readOrFail('Uppy.S3.bucket'),
'Key' => $path,
'ContentType' => $contentType,
'Body' => '',
]);

return $s3Client->createPresignedRequest($command, Configure::read('Uppy.S3.contants.lifeTimePutObject'));
return $s3Client->createPresignedRequest($command, Configure::readOrFail('Uppy.S3.contants.lifeTimePutObject'));

Check warning on line 102 in src/Util/S3Trait.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

Line exceeds 120 characters; contains 124 characters
}
}

Expand All @@ -113,8 +113,8 @@
*/
public function uploadDir(string $source, string $target): void
{
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$dest = 's3://' . Configure::read('Uppy.S3.bucket') . DS . $target;
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
$dest = 's3://' . Configure::readOrFail('Uppy.S3.bucket') . DS . $target;
$manager = new Transfer($s3Client, $source, $dest);
$manager->transfer();

Expand All @@ -140,9 +140,9 @@
*/
public function uploadFile(string $sourceFilePath, string $destinationS3Path): void
{
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
$s3Options = [
'Bucket' => Configure::read('Uppy.S3.bucket'),
'Bucket' => Configure::readOrFail('Uppy.S3.bucket'),
'Key' => $destinationS3Path,
'SourceFile' => $sourceFilePath,
];
Expand All @@ -168,7 +168,7 @@
*/
public function deleteFile(string $fileS3Path): void
{
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
$s3Options = [
'Bucket' => Configure::read('Uppy.S3.bucket'),
'Key' => $fileS3Path,
Expand All @@ -186,9 +186,9 @@
*/
public function deleteDir(string $fileS3Path): void
{
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
if ($this->folderExists($fileS3Path)) {
$s3Client->deleteMatchingObjects(Configure::read('Uppy.S3.bucket'), $fileS3Path);
$s3Client->deleteMatchingObjects(Configure::readOrFail('Uppy.S3.bucket'), $fileS3Path);
} else {
throw new Exception('File doesn\'t exist. Please try again.');
}
Expand All @@ -202,9 +202,9 @@
*/
public function fileExists(string $filename): bool
{
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));

return $s3Client->doesObjectExist(Configure::read('Uppy.S3.bucket'), $filename);
return $s3Client->doesObjectExist(Configure::readOrFail('Uppy.S3.bucket'), $filename);
}

/**
Expand All @@ -216,9 +216,9 @@
*/
public function folderExists(string $filename): bool
{
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
$list = $s3Client->listObjectsV2([
'Bucket' => Configure::read('Uppy.S3.bucket'),
'Bucket' => Configure::readOrFail('Uppy.S3.bucket'),
'Prefix' => $filename,
]);
if ($list['Contents'] > 0) {
Expand Down
6 changes: 3 additions & 3 deletions templates/element/assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@
sprintf('let file_not_saved = "%s";', __('The file could not be saved. Please, try again.'))
);
?>
<?= $this->Html->css('CakeDC/Uppy.uppy.min.css', ['block' => true]) ?>
<?= $this->Html->script('CakeDC/Uppy.uppy.min.js', ['block' => true]) ?>
<?= $this->Html->script('CakeDC/Uppy.add.js', ['block' => 'bottom_script']);
<?= $this->Html->css('CakeDC/Uppy.uppy.min.css') ?>
<?= $this->Html->script('CakeDC/Uppy.uppy.min.js') ?>
<?= $this->Html->script('CakeDC/Uppy.add.js');
Loading