diff --git a/.editorconfig b/.editorconfig index 96f12c245..900affc07 100644 --- a/.editorconfig +++ b/.editorconfig @@ -112,8 +112,8 @@ ij_twig_spaces_inside_delimiters = true ij_twig_spaces_inside_variable_delimiters = true [*.vue] -indent_size = 2 -tab_width = 2 +indent_size = 4 +tab_width = 4 ij_continuation_indent_size = 4 ij_vue_indent_children_of_top_level = template ij_vue_interpolation_new_line_after_start_delimiter = true diff --git a/composer.json b/composer.json index d9baed66a..170de2f0b 100644 --- a/composer.json +++ b/composer.json @@ -61,6 +61,7 @@ "rlanvin/php-ip": "^2.0", "slim/http": "^1.1", "slim/slim": "^4.2", + "spatie/flysystem-dropbox": "^1.2", "spomky-labs/otphp": "^10.0", "studio24/rotate": "^1.0", "supervisorphp/supervisor": "^4.0", diff --git a/composer.lock b/composer.lock index da06688c3..05733b5ca 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4263a5e17fbc52e04124cf01942ac114", + "content-hash": "2b94440b6187319760f97ecc2633bfc4", "packages": [ { "name": "aws/aws-sdk-php", @@ -2422,6 +2422,69 @@ }, "time": "2019-11-13T10:30:21+00:00" }, + { + "name": "graham-campbell/guzzle-factory", + "version": "v4.2.0", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Guzzle-Factory.git", + "reference": "1f4d65962051284c4ecf6e29b710178f5d7ce75a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Guzzle-Factory/zipball/1f4d65962051284c4ecf6e29b710178f5d7ce75a", + "reference": "1f4d65962051284c4ecf6e29b710178f5d7ce75a", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^7.2", + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "graham-campbell/analyzer": "^3.0.4", + "phpunit/phpunit": "^8.5.8 || ^9.3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\GuzzleFactory\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" + } + ], + "description": "Provides A Simple Guzzle Factory With Good Defaults", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Guzzle", + "Guzzle Factory", + "Guzzle-Factory", + "http" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Guzzle-Factory/issues", + "source": "https://github.com/GrahamCampbell/Guzzle-Factory/tree/v4.2.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/guzzle-factory", + "type": "tidelift" + } + ], + "time": "2020-10-21T19:51:51+00:00" + }, { "name": "guzzlehttp/guzzle", "version": "7.2.0", @@ -5965,6 +6028,123 @@ ], "time": "2020-12-01T19:41:22+00:00" }, + { + "name": "spatie/dropbox-api", + "version": "1.16.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/dropbox-api.git", + "reference": "35e5f546bc4c29df34afa7363086dfcfa3448367" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/dropbox-api/zipball/35e5f546bc4c29df34afa7363086dfcfa3448367", + "reference": "35e5f546bc4c29df34afa7363086dfcfa3448367", + "shasum": "" + }, + "require": { + "graham-campbell/guzzle-factory": "^3.0||^4.0", + "guzzlehttp/guzzle": "^6.2||^7.0", + "php": "^7.1||^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5.15|^8.5|^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Dropbox\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex.vanderbist@gmail.com", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A minimal implementation of Dropbox API v2", + "homepage": "https://github.com/spatie/dropbox-api", + "keywords": [ + "Dropbox-API", + "api", + "dropbox", + "spatie", + "v2" + ], + "support": { + "issues": "https://github.com/spatie/dropbox-api/issues", + "source": "https://github.com/spatie/dropbox-api/tree/1.16.1" + }, + "time": "2020-11-27T14:48:37+00:00" + }, + { + "name": "spatie/flysystem-dropbox", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/flysystem-dropbox.git", + "reference": "8b6b072f217343b875316ca6a4203dd59f04207a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/flysystem-dropbox/zipball/8b6b072f217343b875316ca6a4203dd59f04207a", + "reference": "8b6b072f217343b875316ca6a4203dd59f04207a", + "shasum": "" + }, + "require": { + "league/flysystem": "^1.0.20", + "php": "^7.0 || ^8.0", + "spatie/dropbox-api": "^1.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.11 || ^9.4.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\FlysystemDropbox\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex.vanderbist@gmail.com", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Flysystem Adapter for the Dropbox v2 API", + "homepage": "https://github.com/spatie/flysystem-dropbox", + "keywords": [ + "Flysystem", + "api", + "dropbox", + "flysystem-dropbox", + "spatie", + "v2" + ], + "support": { + "issues": "https://github.com/spatie/flysystem-dropbox/issues", + "source": "https://github.com/spatie/flysystem-dropbox/tree/1.2.3" + }, + "time": "2020-11-28T22:17:09+00:00" + }, { "name": "spomky-labs/otphp", "version": "v10.0.1", diff --git a/frontend/vue/admin_storage_locations/StorageLocationEditModal.vue b/frontend/vue/admin_storage_locations/StorageLocationEditModal.vue index d452a9d3e..b0721e3b1 100644 --- a/frontend/vue/admin_storage_locations/StorageLocationEditModal.vue +++ b/frontend/vue/admin_storage_locations/StorageLocationEditModal.vue @@ -69,6 +69,18 @@ export default { validations.form.s3Version = {}; validations.form.s3Bucket = {}; validations.form.s3Endpoint = {}; + validations.form.dropboxAuthToken = {}; + break; + + case 'dropbox': + validations.form.path = {}; + validations.form.s3CredentialKey = {}; + validations.form.s3CredentialSecret = {}; + validations.form.s3Region = {}; + validations.form.s3Version = {}; + validations.form.s3Bucket = {}; + validations.form.s3Endpoint = {}; + validations.form.dropboxAuthToken = { required }; break; case 's3': @@ -79,6 +91,7 @@ export default { validations.form.s3Version = { required }; validations.form.s3Bucket = { required }; validations.form.s3Endpoint = { required }; + validations.form.dropboxAuthToken = {}; break; } @@ -95,6 +108,7 @@ export default { 's3Version': 'latest', 's3Bucket': null, 's3Endpoint': null, + 'dropboxAuthToken': null, 'storageQuota': '' }; }, @@ -126,6 +140,7 @@ export default { 's3Version': d.s3Version, 's3Bucket': d.s3Bucket, 's3Endpoint': d.s3Endpoint, + 'dropboxAuthToken': d.dropboxAuthToken, 'storageQuota': d.storageQuota }; diff --git a/frontend/vue/admin_storage_locations/form/StorageLocationForm.vue b/frontend/vue/admin_storage_locations/form/StorageLocationForm.vue index 731c0b9ce..bdf79ab9e 100644 --- a/frontend/vue/admin_storage_locations/form/StorageLocationForm.vue +++ b/frontend/vue/admin_storage_locations/form/StorageLocationForm.vue @@ -14,6 +14,9 @@ Remote: S3 Compatible + + Remote: Dropbox + @@ -133,6 +136,31 @@ + + +
+

+ Remote: Dropbox +

+
+ + + + + + + + + This field is required. + + + + + +
diff --git a/src/Entity/Migration/Version20201208185538.php b/src/Entity/Migration/Version20201208185538.php new file mode 100644 index 000000000..14efd9a7b --- /dev/null +++ b/src/Entity/Migration/Version20201208185538.php @@ -0,0 +1,30 @@ +addSql('ALTER TABLE api_keys CHANGE user_id user_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE storage_location ADD dropbox_auth_token VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE api_keys CHANGE user_id user_id INT NOT NULL'); + $this->addSql('ALTER TABLE storage_location DROP dropbox_auth_token'); + } +} diff --git a/src/Entity/StorageLocation.php b/src/Entity/StorageLocation.php index 2a500191d..74a4e3324 100644 --- a/src/Entity/StorageLocation.php +++ b/src/Entity/StorageLocation.php @@ -17,6 +17,9 @@ use League\Flysystem\Adapter\Local; use League\Flysystem\AdapterInterface; use League\Flysystem\AwsS3v3\AwsS3Adapter; use League\Flysystem\Config; +use League\Flysystem\Util; +use Spatie\Dropbox\Client; +use Spatie\FlysystemDropbox\DropboxAdapter; use Symfony\Component\Validator\Constraints as Assert; /** @@ -36,6 +39,7 @@ class StorageLocation public const ADAPTER_LOCAL = 'local'; public const ADAPTER_S3 = 's3'; + public const ADAPTER_DROPBOX = 'dropbox'; /** * @ORM\Column(name="id", type="integer") @@ -115,6 +119,13 @@ class StorageLocation */ protected $s3Endpoint = null; + /** + * @ORM\Column(name="dropbox_auth_token", type="string", length=255, nullable=true) + * + * @var string|null The optional custom S3 endpoint S3 adapters. + */ + protected $dropboxAuthToken = null; + /** * @ORM\Column(name="storage_quota", type="bigint", nullable=true) * @@ -249,6 +260,16 @@ class StorageLocation $this->s3Endpoint = $this->truncateString($s3Endpoint, 255); } + public function getDropboxAuthToken(): ?string + { + return $this->dropboxAuthToken; + } + + public function setDropboxAuthToken(?string $dropboxAuthToken): void + { + $this->dropboxAuthToken = $dropboxAuthToken; + } + public function isLocal(): bool { return self::ADAPTER_LOCAL === $this->adapter; @@ -420,6 +441,10 @@ class StorageLocation } break; + case self::ADAPTER_DROPBOX: + return 'dropbox://' . ltrim($path, '/'); + break; + case self::ADAPTER_LOCAL: default: return $path; @@ -449,6 +474,9 @@ class StorageLocation $client = $this->getS3Client(); return new AwsS3Adapter($client, $this->s3Bucket, $this->path); + case self::ADAPTER_DROPBOX: + return new DropboxAdapter($this->getDropboxClient(), $this->path); + case self::ADAPTER_LOCAL: default: return new Local($this->path); @@ -475,12 +503,26 @@ class StorageLocation return new S3Client($s3Options); } + protected function getDropboxClient(): Client + { + if (self::ADAPTER_DROPBOX !== $this->adapter) { + throw new \InvalidArgumentException('This storage location is not using the Dropbox adapter.'); + } + + return new Client($this->dropboxAuthToken); + } + /** * @param Config|array|null $config * */ public function getFilesystem($config = null): Filesystem { + $config = Util::ensureConfig($config); + if (self::ADAPTER_DROPBOX === $this->adapter) { + $config->set('case_sensitive', false); + } + return new Filesystem($this->getStorageAdapter(), $config); } diff --git a/src/Flysystem/FilesystemManager.php b/src/Flysystem/FilesystemManager.php index a118dff95..5ea68189f 100644 --- a/src/Flysystem/FilesystemManager.php +++ b/src/Flysystem/FilesystemManager.php @@ -9,7 +9,9 @@ use League\Flysystem\Adapter\AbstractAdapter; use League\Flysystem\AdapterInterface; use League\Flysystem\AwsS3v3\AwsS3Adapter; use League\Flysystem\Cached\CachedAdapter; +use League\Flysystem\Config; use Psr\Cache\CacheItemPoolInterface; +use Spatie\FlysystemDropbox\DropboxAdapter; /** * A wrapper and manager class for accessing assets on the filesystem. @@ -86,12 +88,17 @@ class FilesystemManager public function getFilesystemForAdapter(AdapterInterface $adapter, bool $cached = false): Filesystem { + $config = new Config(); + if ($adapter instanceof DropboxAdapter) { + $config->set('case_sensitive', false); + } + if ($cached) { $cachedClient = new Psr6Cache($this->cachePool, $this->getCacheKey($adapter), 3600); $adapter = new CachedAdapter($adapter, $cachedClient); } - return new Filesystem($adapter); + return new Filesystem($adapter, $config); } public function flushCacheForAdapter(AdapterInterface $adapter, bool $inMemoryOnly = false): void