From f6a6be7312cebff04f6ac83b0e9630d659109547 Mon Sep 17 00:00:00 2001 From: Sergio Brighenti Date: Sun, 19 May 2019 15:39:42 +0200 Subject: [PATCH] Start adding support to use AWS S3, Google Cloud Storage, Dropbox and FTP(s) accounts as storage location --- CHANGELOG.md | 4 + app/Controllers/AdminController.php | 4 +- app/Controllers/Controller.php | 4 +- app/Controllers/DashboardController.php | 2 +- app/Controllers/UploadController.php | 14 +- app/Database/Queries/MediaQuery.php | 22 +- app/helpers.php | 18 +- bootstrap/app.php | 54 +- composer.json | 7 +- composer.lock | 757 +++++++++++++++++++++++- install/index.php | 122 +++- 11 files changed, 953 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fc5d92..38f43ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v2.6 ++ Added support to use AWS S3, Google Cloud Storage, Dropbox and FTP(s) accounts as storage location. ++ Fixed missing icon. + ## v2.5.3 + Fixed bad css loading on Firefox (#35). + Fixed wrong style for publish/unpublish button. diff --git a/app/Controllers/AdminController.php b/app/Controllers/AdminController.php index bba7329..51a68f7 100644 --- a/app/Controllers/AdminController.php +++ b/app/Controllers/AdminController.php @@ -26,7 +26,7 @@ class AdminController extends Controller $totalSize = 0; - $filesystem = storage(); + $filesystem = $this->storage; foreach ($medias as $media) { $totalSize += $filesystem->getSize($media->storage_path); } @@ -50,7 +50,7 @@ class AdminController extends Controller { $orphans = $this->database->query('SELECT * FROM `uploads` WHERE `user_id` IS NULL')->fetchAll(); - $filesystem = storage(); + $filesystem = $this->storage; $deleted = 0; foreach ($orphans as $orphan) { diff --git a/app/Controllers/Controller.php b/app/Controllers/Controller.php index 42942e8..761ee09 100644 --- a/app/Controllers/Controller.php +++ b/app/Controllers/Controller.php @@ -5,6 +5,7 @@ namespace App\Controllers; use App\Database\DB; use App\Web\Session; use League\Flysystem\FileNotFoundException; +use League\Flysystem\Filesystem; use Monolog\Logger; use Slim\Container; @@ -13,6 +14,7 @@ use Slim\Container; * @property mixed|null view * @property DB|null database * @property Logger|null logger + * @property Filesystem|null storage */ abstract class Controller { @@ -48,7 +50,7 @@ abstract class Controller $totalSize = 0; - $filesystem = storage(); + $filesystem = $this->storage; foreach ($medias as $media) { try { $totalSize += $filesystem->getSize($media->storage_path); diff --git a/app/Controllers/DashboardController.php b/app/Controllers/DashboardController.php index 55cdb48..266dbe1 100644 --- a/app/Controllers/DashboardController.php +++ b/app/Controllers/DashboardController.php @@ -34,7 +34,7 @@ class DashboardController extends Controller $page = isset($args['page']) ? (int)$args['page'] : 0; $page = max(0, --$page); - $query = new MediaQuery($this->database, $this->session->get('admin', false)); + $query = new MediaQuery($this->database, $this->session->get('admin', false), $this->storage); switch ($request->getParam('sort', 'time')) { case 'size': diff --git a/app/Controllers/UploadController.php b/app/Controllers/UploadController.php index cc715e4..b284068 100644 --- a/app/Controllers/UploadController.php +++ b/app/Controllers/UploadController.php @@ -71,7 +71,7 @@ class UploadController extends Controller $fileInfo = pathinfo($file->getClientFilename()); $storagePath = "$user->user_code/$code.$fileInfo[extension]"; - storage()->writeStream($storagePath, $file->getStream()->detach()); + $this->storage->writeStream($storagePath, $file->getStream()->detach()); $this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`) VALUES (?, ?, ?, ?)', [ $user->id, @@ -104,7 +104,7 @@ class UploadController extends Controller throw new NotFoundException($request, $response); } - $filesystem = storage(); + $filesystem = $this->storage; if (isBot($request->getHeaderLine('User-Agent'))) { return $this->streamMedia($request, $response, $filesystem, $media); @@ -168,7 +168,7 @@ class UploadController extends Controller if ($this->session->get('admin', false) || $user->id === $media->user_id) { try { - storage()->delete($media->storage_path); + $this->storage->delete($media->storage_path); } catch (FileNotFoundException $e) { throw new NotFoundException($request, $response); } finally { @@ -199,7 +199,7 @@ class UploadController extends Controller throw new NotFoundException($request, $response); } - return $this->streamMedia($request, $response, storage(), $media); + return $this->streamMedia($request, $response, $this->storage, $media); } /** @@ -217,7 +217,7 @@ class UploadController extends Controller if (!$media || !$media->published && $this->session->get('user_id') !== $media->user_id && !$this->session->get('admin', false)) { throw new NotFoundException($request, $response); } - return $this->streamMedia($request, $response, storage(), $media); + return $this->streamMedia($request, $response, $this->storage, $media); } @@ -236,7 +236,7 @@ class UploadController extends Controller if (!$media || !$media->published && $this->session->get('user_id') !== $media->user_id && !$this->session->get('admin', false)) { throw new NotFoundException($request, $response); } - return $this->streamMedia($request, $response, storage(), $media, 'attachment'); + return $this->streamMedia($request, $response, $this->storage, $media, 'attachment'); } /** @@ -282,7 +282,7 @@ class UploadController extends Controller if ($this->session->get('admin', false) || $media->user_id === $this->session->get('user_id')) { try { - storage()->delete($media->storage_path); + $this->storage->delete($media->storage_path); } catch (FileNotFoundException $e) { throw new NotFoundException($request, $response); } finally { diff --git a/app/Database/Queries/MediaQuery.php b/app/Database/Queries/MediaQuery.php index ead1db3..444da5f 100644 --- a/app/Database/Queries/MediaQuery.php +++ b/app/Database/Queries/MediaQuery.php @@ -5,6 +5,7 @@ namespace App\Database\Queries; use App\Database\DB; use League\Flysystem\FileNotFoundException; +use League\Flysystem\Filesystem; use League\Flysystem\Plugin\ListFiles; class MediaQuery @@ -34,6 +35,9 @@ class MediaQuery /** @var string */ protected $text; + /** @var Filesystem */ + protected $storage; + private $pages; private $media; @@ -41,11 +45,13 @@ class MediaQuery * MediaQuery constructor. * @param DB $db * @param bool $isAdmin + * @param Filesystem $storage */ - public function __construct(DB $db, bool $isAdmin) + public function __construct(DB $db, bool $isAdmin, Filesystem $storage) { $this->db = $db; $this->isAdmin = $isAdmin; + $this->storage = $storage; } /** @@ -128,12 +134,10 @@ class MediaQuery $this->pages = $this->db->query($queryPages, array_merge([$this->userId], $params))->fetch()->count / self::PER_PAGE; } - $filesystem = storage(); - foreach ($this->media as $media) { try { - $media->size = humanFileSize($filesystem->getSize($media->storage_path)); - $media->mimetype = $filesystem->getMimetype($media->storage_path); + $media->size = humanFileSize($this->storage->getSize($media->storage_path)); + $media->mimetype = $this->storage->getMimetype($media->storage_path); } catch (FileNotFoundException $e) { $media->size = null; $media->mimetype = null; @@ -148,17 +152,17 @@ class MediaQuery */ private function runWithOrderBySize(int $page) { - $filesystem = storage()->addPlugin(new ListFiles()); + $this->storage->addPlugin(new ListFiles()); if ($this->isAdmin) { - $files = $filesystem->listFiles('/', true); + $files = $this->storage->listFiles('/', true); $this->pages = count($files) / self::PER_PAGE_ADMIN; $offset = $page * self::PER_PAGE_ADMIN; $limit = self::PER_PAGE_ADMIN; } else { $userCode = $this->db->query('SELECT `user_code` FROM `users` WHERE `id` = ?', [$this->userId])->fetch()->user_code; - $files = $filesystem->listFiles($userCode); + $files = $this->storage->listFiles($userCode); $this->pages = count($files) / self::PER_PAGE; $offset = $page * self::PER_PAGE; @@ -195,7 +199,7 @@ class MediaQuery } $media->size = humanFileSize($file['size']); try { - $media->mimetype = $filesystem->getMimetype($file['path']); + $media->mimetype = $this->storage->getMimetype($file['path']); } catch (FileNotFoundException $e) { $media->mimetype = null; } diff --git a/app/helpers.php b/app/helpers.php index 4eb5ef0..45dff36 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -5,20 +5,6 @@ use League\Flysystem\Filesystem; require __DIR__ . '/../vendor/autoload.php'; -if (!function_exists('storage')) { - /** - * Get a filesystem instance given a path - * @param string $root - * @return Filesystem - */ - function storage($root = null): Filesystem - { - global $app; - $storagePath = $app->getContainer()->get('settings')['storage_dir']; - return new Filesystem(new Local($root !== null ? $root : $storagePath)); - } -} - if (!function_exists('humanFileSize')) { /** * Generate a human readable file size @@ -224,13 +210,13 @@ if (!function_exists('mime2font')) { return $class; } } - return 'fa-file-download'; + return 'fa-file'; } } if (!function_exists('dd')) { /** - * Dumps all the giver vars and halt the execution. + * Dumps all the given vars and halt the execution. */ function dd() { diff --git a/bootstrap/app.php b/bootstrap/app.php index b50fde4..34ba2ba 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -5,6 +5,12 @@ use App\Exceptions\MaintenanceException; use App\Exceptions\UnauthorizedException; use App\Web\Lang; use App\Web\Session; +use Aws\S3\S3Client; +use Google\Cloud\Storage\StorageClient; +use League\Flysystem\Adapter\Ftp as FtpAdapter; +use League\Flysystem\Adapter\Local; +use League\Flysystem\AwsS3v3\AwsS3Adapter; +use League\Flysystem\Filesystem; use Monolog\Formatter\LineFormatter; use Monolog\Handler\RotatingFileHandler; use Monolog\Logger; @@ -15,6 +21,9 @@ use Slim\Http\Request; use Slim\Http\Response; use Slim\Http\Uri; use Slim\Views\Twig; +use Spatie\Dropbox\Client as DropboxClient; +use Spatie\FlysystemDropbox\DropboxAdapter; +use Superbalist\Flysystem\GoogleStorage\GoogleStorageAdapter; use Twig\TwigFunction; if (!file_exists('config.php') && is_dir('install/')) { @@ -28,7 +37,6 @@ if (!file_exists('config.php') && is_dir('install/')) { $config = array_replace_recursive([ 'app_name' => 'XBackBone', 'base_url' => isset($_SERVER['HTTPS']) ? 'https://' . $_SERVER['HTTP_HOST'] : 'http://' . $_SERVER['HTTP_HOST'], - 'storage_dir' => 'storage', 'displayErrorDetails' => false, 'maintenance' => false, 'db' => [ @@ -37,6 +45,9 @@ $config = array_replace_recursive([ 'username' => null, 'password' => null, ], + 'storage' => [ + 'driver' => 'local', + ], ], require BASE_DIR . 'config.php'); if (!$config['displayErrorDetails']) { @@ -73,6 +84,47 @@ $container['database'] = function ($container) use (&$config) { return new DB($config['db']['connection'] . ':' . $dsn, $config['db']['username'], $config['db']['password']); }; +$container['storage'] = function ($container) use (&$config) { + + switch ($config['storage']['driver']) { + case 'local': + return new Filesystem(new Local($config['storage']['path'])); + case 's3': + $client = new S3Client([ + 'credentials' => [ + 'key' => $config['storage']['key'], + 'secret' => $config['storage']['secret'], + ], + 'region' => $config['storage']['region'], + 'version' => 'latest', + ]); + + return new Filesystem(new AwsS3Adapter($client, $config['storage']['bucket'], $config['storage']['path'])); + case 'dropbox': + $client = new DropboxClient($config['storage']['token']); + return new Filesystem(new DropboxAdapter($client), ['case_sensitive' => false]); + case 'ftp': + return new Filesystem(new FtpAdapter([ + 'host' => $config['storage']['host'], + 'username' => $config['storage']['username'], + 'password' => $config['storage']['password'], + 'port' => $config['storage']['port'], + 'root' => $config['storage']['path'], + 'passive' => $config['storage']['passive'], + 'ssl' => $config['storage']['ssl'], + 'timeout' => 30, + ])); + case 'google-cloud': + $client = new StorageClient([ + 'projectId' => $config['storage']['project_id'], + 'keyFilePath' => $config['storage']['key_path'], + ]); + return new Filesystem(new GoogleStorageAdapter($client, $client->bucket($config['storage']['bucket']))); + default: + throw new InvalidArgumentException('The driver specified is not supported.'); + } +}; + $container['lang'] = function ($container) { return Lang::build(Lang::recognize(), BASE_DIR . 'resources/lang/'); }; diff --git a/composer.json b/composer.json index 4258d51..f08554b 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "sergix44/xbackbone", - "version": "2.5.3", + "version": "2.6", "description": "A lightweight ShareX PHP backend", "type": "project", "require": { @@ -13,7 +13,10 @@ "ext-json": "*", "ext-gd": "*", "ext-pdo": "*", - "ext-zip": "*" + "ext-zip": "*", + "league/flysystem-aws-s3-v3": "^1.0", + "spatie/flysystem-dropbox": "^1.0", + "superbalist/flysystem-google-storage": "^7.2" }, "autoload": { "files": [ diff --git a/composer.lock b/composer.lock index 806bd5b..a0df95b 100644 --- a/composer.lock +++ b/composer.lock @@ -1,11 +1,94 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "120224d788960ba61ae3d0e49e173ad3", + "content-hash": "d2c8e7bde7729d78cef484491535c639", "packages": [ + { + "name": "aws/aws-sdk-php", + "version": "3.93.12", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "60f5c4bd261e19844d4263c3a8f370e88e038282" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/60f5c4bd261e19844d4263c3a8f370e88e038282", + "reference": "60f5c4bd261e19844d4263c3a8f370e88e038282", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^5.3.3|^6.2.1", + "guzzlehttp/promises": "~1.0", + "guzzlehttp/psr7": "^1.4.1", + "mtdowling/jmespath.php": "~2.2", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "phpunit/phpunit": "^4.8.35|^5.4.3", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Aws\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "time": "2019-05-17T18:26:46+00:00" + }, { "name": "container-interop/container-interop", "version": "1.2.0", @@ -37,6 +120,329 @@ "homepage": "https://github.com/container-interop/container-interop", "time": "2017-02-14T19:40:03+00:00" }, + { + "name": "firebase/php-jwt", + "version": "v5.0.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/9984a4d3a32ae7673d6971ea00bae9d0a1abba0e", + "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": " 4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "time": "2017-06-27T22:17:23+00:00" + }, + { + "name": "google/auth", + "version": "v1.5.1", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-auth-library-php.git", + "reference": "0f75e20e7392e863f5550ed2c2d3d50af21710fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/0f75e20e7392e863f5550ed2c2d3d50af21710fb", + "reference": "0f75e20e7392e863f5550ed2c2d3d50af21710fb", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "~2.0|~3.0|~4.0|~5.0", + "guzzlehttp/guzzle": "~5.3.1|~6.0", + "guzzlehttp/psr7": "^1.2", + "php": ">=5.4", + "psr/cache": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^1.11", + "guzzlehttp/promises": "0.1.1|^1.3", + "phpseclib/phpseclib": "^2", + "phpunit/phpunit": "^4.8.36|^5.7", + "sebastian/comparator": ">=1.2.3" + }, + "suggest": { + "phpseclib/phpseclib": "May be used in place of OpenSSL for signing strings. Please require version ^2." + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Auth\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Google Auth Library for PHP", + "homepage": "http://github.com/google/google-auth-library-php", + "keywords": [ + "Authentication", + "google", + "oauth2" + ], + "time": "2019-04-16T18:48:28+00:00" + }, + { + "name": "google/cloud-core", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-cloud-php-core.git", + "reference": "d32db261499d28268f9ed97834e7aca829df5b53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-cloud-php-core/zipball/d32db261499d28268f9ed97834e7aca829df5b53", + "reference": "d32db261499d28268f9ed97834e7aca829df5b53", + "shasum": "" + }, + "require": { + "google/auth": "^1.5.1", + "guzzlehttp/guzzle": "^5.3|^6.0", + "guzzlehttp/promises": "^1.3", + "guzzlehttp/psr7": "^1.2", + "monolog/monolog": "~1", + "php": ">=5.5", + "psr/http-message": "1.0.*", + "rize/uri-template": "~0.3" + }, + "require-dev": { + "erusev/parsedown": "^1.6", + "google/gax": "^1.0", + "opis/closure": "^3", + "phpdocumentor/reflection": "^3.0", + "phpunit/phpunit": "^4.8|^5.0", + "squizlabs/php_codesniffer": "2.*" + }, + "suggest": { + "opis/closure": "May be used to serialize closures to process jobs in the batch daemon. Please require version ^3.", + "symfony/lock": "Required for the Spanner cached based session pool. Please require the following commit: 3.3.x-dev#1ba6ac9" + }, + "bin": [ + "bin/google-cloud-batch" + ], + "type": "library", + "extra": { + "component": { + "id": "cloud-core", + "target": "googleapis/google-cloud-php-core.git", + "path": "Core", + "entry": "src/ServiceBuilder.php" + } + }, + "autoload": { + "psr-4": { + "Google\\Cloud\\Core\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Google Cloud PHP shared dependency, providing functionality useful to all components.", + "time": "2019-04-16T20:44:33+00:00" + }, + { + "name": "google/cloud-storage", + "version": "v1.12.1", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-cloud-php-storage.git", + "reference": "a252e2012ea875e873b6d146159e54edb8fa4544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-cloud-php-storage/zipball/a252e2012ea875e873b6d146159e54edb8fa4544", + "reference": "a252e2012ea875e873b6d146159e54edb8fa4544", + "shasum": "" + }, + "require": { + "google/cloud-core": "^1.28" + }, + "require-dev": { + "erusev/parsedown": "^1.6", + "google/cloud-pubsub": "^1.0", + "phpdocumentor/reflection": "^3.0", + "phpseclib/phpseclib": "^2", + "phpunit/phpunit": "^4.8|^5.0", + "squizlabs/php_codesniffer": "2.*" + }, + "suggest": { + "google/cloud-pubsub": "May be used to register a topic to receive bucket notifications.", + "phpseclib/phpseclib": "May be used in place of OpenSSL for creating signed Cloud Storage URLs. Please require version ^2." + }, + "type": "library", + "extra": { + "component": { + "id": "cloud-storage", + "target": "googleapis/google-cloud-php-storage.git", + "path": "Storage", + "entry": "src/StorageClient.php" + } + }, + "autoload": { + "psr-4": { + "Google\\Cloud\\Storage\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Cloud Storage Client for PHP", + "time": "2019-05-02T16:40:15+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.3.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.3-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2018-04-22T15:46:56+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, { "name": "guzzlehttp/psr7", "version": "1.5.2", @@ -258,6 +664,53 @@ ], "time": "2019-03-30T13:22:34+00:00" }, + { + "name": "league/flysystem-aws-s3-v3", + "version": "1.0.22", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "883b02c80ca9cd68cf58a9b4b2185beef24b836b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/883b02c80ca9cd68cf58a9b4b2185beef24b836b", + "reference": "883b02c80ca9cd68cf58a9b4b2185beef24b836b", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.0.0", + "league/flysystem": "^1.0.40", + "php": ">=5.5.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "~1.0.1", + "phpspec/phpspec": "^2.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3v3\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Flysystem adapter for the AWS S3 SDK v3.x", + "time": "2019-01-31T15:07:25+00:00" + }, { "name": "monolog/monolog", "version": "1.24.0", @@ -336,6 +789,61 @@ ], "time": "2018-11-05T09:00:11+00:00" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/adcc9531682cf87dfda21e1fd5d0e7a41d292fac", + "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "time": "2016-12-03T22:08:25+00:00" + }, { "name": "nikic/fast-route", "version": "v1.3.0", @@ -432,6 +940,52 @@ ], "time": "2018-01-21T07:42:36+00:00" }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "time": "2016-08-06T20:24:11+00:00" + }, { "name": "psr/container", "version": "1.0.0", @@ -618,6 +1172,50 @@ "description": "A polyfill for getallheaders.", "time": "2016-02-11T07:05:27+00:00" }, + { + "name": "rize/uri-template", + "version": "0.3.2", + "source": { + "type": "git", + "url": "https://github.com/rize/UriTemplate.git", + "reference": "9e5fdd5c47147aa5adf7f760002ee591ed37b9ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rize/UriTemplate/zipball/9e5fdd5c47147aa5adf7f760002ee591ed37b9ca", + "reference": "9e5fdd5c47147aa5adf7f760002ee591ed37b9ca", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Rize\\UriTemplate": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marut K", + "homepage": "http://twitter.com/rezigned" + } + ], + "description": "PHP URI Template (RFC 6570) supports both expansion & extraction", + "keywords": [ + "RFC 6570", + "template", + "uri" + ], + "time": "2017-06-14T03:57:53+00:00" + }, { "name": "slim/slim", "version": "3.12.1", @@ -740,6 +1338,161 @@ ], "time": "2019-04-06T16:34:38+00:00" }, + { + "name": "spatie/dropbox-api", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/dropbox-api.git", + "reference": "dc930cde4fdb802d20d0aeef5a98c3805fdcf9c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/dropbox-api/zipball/dc930cde4fdb802d20d0aeef5a98c3805fdcf9c2", + "reference": "dc930cde4fdb802d20d0aeef5a98c3805fdcf9c2", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.2", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Dropbox\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Alex Vanderbist", + "email": "alex.vanderbist@gmail.com", + "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" + ], + "time": "2019-04-13T10:52:11+00:00" + }, + { + "name": "spatie/flysystem-dropbox", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/spatie/flysystem-dropbox.git", + "reference": "cff7dba925282736fff271fc41781a76fbe40799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/flysystem-dropbox/zipball/cff7dba925282736fff271fc41781a76fbe40799", + "reference": "cff7dba925282736fff271fc41781a76fbe40799", + "shasum": "" + }, + "require": { + "league/flysystem": "^1.0", + "php": "^7.0", + "spatie/dropbox-api": "^1.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "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" + ], + "time": "2017-11-18T21:56:03+00:00" + }, + { + "name": "superbalist/flysystem-google-storage", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/Superbalist/flysystem-google-cloud-storage.git", + "reference": "97cf8a5c9a9d368260b2791ec130690a1bec0cbd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Superbalist/flysystem-google-cloud-storage/zipball/97cf8a5c9a9d368260b2791ec130690a1bec0cbd", + "reference": "97cf8a5c9a9d368260b2791ec130690a1bec0cbd", + "shasum": "" + }, + "require": { + "google/cloud-storage": "~1.0", + "league/flysystem": "~1.0", + "php": ">=5.5.0" + }, + "require-dev": { + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Superbalist\\Flysystem\\GoogleStorage\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Superbalist.com a division of Takealot Online (Pty) Ltd", + "email": "info@superbalist.com" + } + ], + "description": "Flysystem adapter for Google Cloud Storage", + "time": "2019-02-21T08:07:24+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.11.0", diff --git a/install/index.php b/install/index.php index a3bb5cf..7b6cafc 100644 --- a/install/index.php +++ b/install/index.php @@ -4,6 +4,13 @@ require __DIR__ . '/../vendor/autoload.php'; use App\Database\DB; use App\Web\Session; +use Aws\S3\S3Client; +use Google\Cloud\Storage\StorageClient; +use League\Flysystem\Adapter\Local; +use League\Flysystem\AwsS3v3\AwsS3Adapter; +use League\Flysystem\Adapter\Ftp as FtpAdapter; +use Spatie\Dropbox\Client as DropboxClient; +use League\Flysystem\Filesystem; use Slim\App; use Slim\Container; use Slim\Http\Environment; @@ -11,21 +18,28 @@ use Slim\Http\Request; use Slim\Http\Response; use Slim\Http\Uri; use Slim\Views\Twig; +use Spatie\FlysystemDropbox\DropboxAdapter; +use Superbalist\Flysystem\GoogleStorage\GoogleStorageAdapter; define('PLATFORM_VERSION', json_decode(file_get_contents(__DIR__ . '/../composer.json'))->version); $config = [ 'base_url' => isset($_SERVER['HTTPS']) ? 'https://' . $_SERVER['HTTP_HOST'] : 'http://' . $_SERVER['HTTP_HOST'], - 'storage_dir' => 'storage', - 'displayErrorDetails' => true, 'db' => [ 'connection' => 'sqlite', 'dsn' => 'resources/database/xbackbone.db', 'username' => null, 'password' => null, ], + 'storage' => [ + 'driver' => 'local', + ], ]; +if (file_exists(__DIR__ . '/../config.php')) { + $config = array_replace_recursive($config, require __DIR__ . '/../config.php'); +} + $container = new Container(['settings' => $config]); $container['session'] = function ($container) { @@ -53,6 +67,47 @@ $container['view'] = function ($container) use (&$config) { return $view; }; +$container['storage'] = function ($container) use (&$config) { + + switch ($config['storage']['driver']) { + case 'local': + return new Filesystem(new Local($config['storage']['path'])); + case 's3': + $client = new S3Client([ + 'credentials' => [ + 'key' => $config['storage']['key'], + 'secret' => $config['storage']['secret'], + ], + 'region' => $config['storage']['region'], + 'version' => 'latest', + ]); + + return new Filesystem(new AwsS3Adapter($client, $config['storage']['bucket'], $config['storage']['path'])); + case 'dropbox': + $client = new DropboxClient($config['storage']['token']); + return new Filesystem(new DropboxAdapter($client), ['case_sensitive' => false]); + case 'ftp': + return new Filesystem(new FtpAdapter([ + 'host' => $config['storage']['host'], + 'username' => $config['storage']['username'], + 'password' => $config['storage']['password'], + 'port' => $config['storage']['port'], + 'root' => $config['storage']['path'], + 'passive' => $config['storage']['passive'], + 'ssl' => $config['storage']['ssl'], + 'timeout' => 30, + ])); + case 'google-cloud': + $client = new StorageClient([ + 'projectId' => $config['storage']['project_id'], + 'keyFilePath' => $config['storage']['key_path'], + ]); + return new Filesystem(new GoogleStorageAdapter($client, $client->bucket($config['storage']['bucket']))); + default: + throw new InvalidArgumentException('The driver specified is not supported.'); + } +}; + function migrate($config) { $firstMigrate = false; @@ -137,28 +192,64 @@ $app->get('/', function (Request $request, Response $response) { }); $app->post('/', function (Request $request, Response $response) use (&$config) { + + // Check if there is a previous installation, if not, setup the config file $installed = true; if (!file_exists(__DIR__ . '/../config.php')) { $installed = false; + // config file setup $config['base_url'] = $request->getParam('base_url'); - $config['storage_dir'] = $request->getParam('storage_dir'); + $config['storage']['driver'] = $request->getParam('storage_driver'); unset($config['displayErrorDetails']); $config['db']['connection'] = $request->getParam('connection'); $config['db']['dsn'] = $request->getParam('dsn'); $config['db']['username'] = $request->getParam('db_user'); $config['db']['password'] = $request->getParam('db_password'); + + // setup storage configuration + switch ($config['storage']['driver']) { + case 's3': + $config['storage']['key'] = $request->getParam('storage_key'); + $config['storage']['secret'] = $request->getParam('storage_secret'); + $config['storage']['region'] = $request->getParam('storage_region'); + $config['storage']['bucket'] = $request->getParam('storage_bucket'); + $config['storage']['path'] = $request->getParam('storage_path'); + break; + case 'dropbox': + $config['storage']['token'] = $request->getParam('storage_token'); + break; + case 'ftp': + $config['storage']['host'] = $request->getParam('storage_host'); + $config['storage']['username'] = $request->getParam('storage_username'); + $config['storage']['password'] = $request->getParam('storage_password'); + $config['storage']['port'] = $request->getParam('storage_port'); + $config['storage']['path'] = $request->getParam('storage_path'); + $config['storage']['passive'] = $request->getParam('storage_passive'); + $config['storage']['ssl'] = $request->getParam('storage_ssl'); + break; + case 'google-cloud': + $config['storage']['project_id'] = $request->getParam('storage_project_id'); + $config['storage']['key_path'] = $request->getParam('storage_key_path'); + $config['storage']['bucket'] = $request->getParam('storage_bucket'); + break; + case 'local': + default: + $config['storage']['path'] = $request->getParam('storage_path'); + break; + } + + // check if the storage is valid try { - storage($config['storage_dir']); - } catch (LogicException $exception) { - $this->session->alert('The storage folder is not readable (' . $config['storage_dir'] . ')', 'danger'); - return redirect($response, './'); - } finally { - if (!is_writable($config['storage_dir'])) { - $this->session->alert('The storage folder is not writable (' . $config['storage_dir'] . ')', 'danger'); - return redirect($response, './'); + $success = $this->storage->write('test.install.txt', ''); + $this->storage->readAndDelete('test.install.txt'); + if (!$success) { + throw new Exception('The storage is not writable.'); } + } catch (Exception $e) { + $this->session->alert("Storage setup error: {$e->getMessage()} [{$e->getTraceAsString()}]", 'danger'); + return redirect($response, './'); } $ret = file_put_contents(__DIR__ . '/../config.php', 'post('/', function (Request $request, Response $response) use (&$config) { $this->session->alert('The config folder is not writable (' . __DIR__ . '/../config.php' . ')', 'danger'); return redirect($response, './'); } - } else { - $config = require __DIR__ . '/../config.php'; } - $dsn = $config['db']['connection'] === 'sqlite' ? __DIR__ . '/../' . $config['db']['dsn'] : $config['db']['dsn']; + // Build the dns string and run the migrations try { + + $dsn = $config['db']['connection'] === 'sqlite' ? __DIR__ . '/../' . $config['db']['dsn'] : $config['db']['dsn']; DB::setDsn($config['db']['connection'] . ':' . $dsn, $config['db']['username'], $config['db']['password']); migrate($config); @@ -181,15 +272,18 @@ $app->post('/', function (Request $request, Response $response) use (&$config) { return redirect($response, './'); } + // if not installed, create the default admin account if (!$installed) { DB::doQuery("INSERT INTO `users` (`email`, `username`, `password`, `is_admin`, `user_code`) VALUES (?, 'admin', ?, 1, ?)", [$request->getParam('email'), password_hash($request->getParam('password'), PASSWORD_DEFAULT), substr(md5(microtime()), rand(0, 26), 5)]); } + // post install cleanup cleanDirectory(__DIR__ . '/../resources/cache'); cleanDirectory(__DIR__ . '/../resources/sessions'); removeDirectory(__DIR__ . '/../install'); + // if is upgrading and existing installation, put it out maintenance if ($installed) { unset($config['maintenance']);