diff --git a/CHANGELOG.md b/CHANGELOG.md index 391d95e..bbbee2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ -## v2.0 [WIP] +## v2.0 + Migrated from Flight to Slim 3 framework. + Added install wizard (using the CLI is no longer required). + Allow discord bot to display the preview. ++ Theme switcher on the web UI. ++ Added used space indicator per user. ++ MySQL support. + Improvements under the hood. ## v1.3 diff --git a/app/Controllers/Controller.php b/app/Controllers/Controller.php index 1df56e0..9f07e97 100644 --- a/app/Controllers/Controller.php +++ b/app/Controllers/Controller.php @@ -4,6 +4,7 @@ namespace App\Controllers; use League\Flysystem\Adapter\Local; +use League\Flysystem\FileNotFoundException; use League\Flysystem\Filesystem; use Slim\Container; use Slim\Http\Request; @@ -59,7 +60,7 @@ abstract class Controller /** * @param $path */ - public function removeDirectory($path) + protected function removeDirectory($path) { $files = glob($path . '/*'); foreach ($files as $file) { @@ -68,4 +69,25 @@ abstract class Controller rmdir($path); return; } + + /** + * @param $id + * @return int + */ + protected function getUsedSpaceByUser($id): int + { + $medias = $this->database->query('SELECT `uploads`.`storage_path` FROM `uploads` WHERE `user_id` = ?', $id)->fetchAll(); + + $totalSize = 0; + + $filesystem = $this->getStorage(); + foreach ($medias as $media) { + try { + $totalSize += $filesystem->getSize($media->storage_path); + } catch (FileNotFoundException $e) { + } + } + + return $totalSize; + } } \ No newline at end of file diff --git a/app/Controllers/DashboardController.php b/app/Controllers/DashboardController.php index f6f7410..d9a7cc4 100644 --- a/app/Controllers/DashboardController.php +++ b/app/Controllers/DashboardController.php @@ -23,6 +23,7 @@ class DashboardController extends Controller { if ($request->getParam('afterInstall') !== null && is_dir('install')) { + Session::alert('Installation completed successfully!', 'success'); $this->removeDirectory('install'); } @@ -101,7 +102,33 @@ class DashboardController extends Controller 'mediasCount' => $mediasCount, 'orphanFilesCount' => $orphanFilesCount, 'totalSize' => $this->humanFilesize($totalSize), - 'max_filesize' => ini_get('post_max_size') . '/' . ini_get('upload_max_filesize'), + 'post_max_size' => ini_get('post_max_size'), + 'upload_max_filesize' => ini_get('upload_max_filesize'), ]); } + + /** + * @param Request $request + * @param Response $response + * @return Response + */ + public function getThemes(Request $request, Response $response): Response + { + $apiJson = json_decode(file_get_contents('https://bootswatch.com/api/4.json')); + + $out = []; + + foreach ($apiJson->themes as $theme) { + $out["{$theme->name} - {$theme->description}"] = $theme->cssMin; + } + + return $response->withJson($out); + } + + + public function applyTheme(Request $request, Response $response): Response + { + file_put_contents('static/bootstrap/css/bootstrap.min.css', file_get_contents($request->getParam('css'))); + return $response->withRedirect('/system')->withAddedHeader('Cache-Control', 'no-cache, must-revalidate'); + } } \ No newline at end of file diff --git a/app/Controllers/LoginController.php b/app/Controllers/LoginController.php index 1596094..2234bb6 100644 --- a/app/Controllers/LoginController.php +++ b/app/Controllers/LoginController.php @@ -48,6 +48,7 @@ class LoginController extends Controller Session::set('user_id', $result->id); Session::set('username', $result->username); Session::set('admin', $result->is_admin); + Session::set('used_space', $this->humanFilesize($this->getUsedSpaceByUser($result->id))); Session::alert("Welcome, $result->username!", 'info'); $this->logger->info("User $result->username logged in."); diff --git a/app/Controllers/UploadController.php b/app/Controllers/UploadController.php index 1dad526..f109fc7 100644 --- a/app/Controllers/UploadController.php +++ b/app/Controllers/UploadController.php @@ -60,7 +60,7 @@ class UploadController extends Controller $user->id, $code, $file->getClientFilename(), - $storagePath + $storagePath, ]); $base_url = $this->settings['base_url']; @@ -104,7 +104,7 @@ class UploadController extends Controller $type = explode('/', $mime)[0]; if ($type === 'text') { $media->text = $filesystem->read($media->storage_path); - } elseif (in_array($type, ['image', 'video']) && $request->getHeaderLine('Scheme') === 'HTTP/2.0') { + } else if (in_array($type, ['image', 'video']) && $request->getHeaderLine('Scheme') === 'HTTP/2.0') { $response = $response->withHeader('Link', "<{$this->settings['base_url']}/$args[userCode]/$args[mediaCode]/raw>; rel=preload; as={$type}"); } @@ -115,7 +115,7 @@ class UploadController extends Controller return $this->view->render($response, 'upload/public.twig', [ 'media' => $media, 'type' => $mime, - 'extension' => pathinfo($media->filename, PATHINFO_EXTENSION) + 'extension' => pathinfo($media->filename, PATHINFO_EXTENSION), ]); } } @@ -196,7 +196,7 @@ class UploadController extends Controller throw new NotFoundException($request, $response); } - $this->database->query('UPDATE `uploads` SET `published`=? WHERE `id`=?', [!$media->published, $media->id]); + $this->database->query('UPDATE `uploads` SET `published`=? WHERE `id`=?', [$media->published ? 0 : 1, $media->id]); return $response->withStatus(200); } @@ -223,6 +223,7 @@ class UploadController extends Controller } finally { $this->database->query('DELETE FROM `uploads` WHERE `id` = ?', $args['id']); $this->logger->info('User ' . Session::get('username') . ' deleted a media.', [$args['id']]); + Session::set('used_space', $this->humanFilesize($this->getUsedSpaceByUser(Session::get('user_id')))); } } else { throw new UnauthorizedException(); @@ -242,7 +243,7 @@ class UploadController extends Controller $media = $this->database->query('SELECT * FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_code` = ? AND `uploads`.`code` = ? LIMIT 1', [ $userCode, - $mediaCode + $mediaCode, ])->fetch(); return $media; diff --git a/app/Database/DB.php b/app/Database/DB.php index e199b3b..5483e8f 100644 --- a/app/Database/DB.php +++ b/app/Database/DB.php @@ -45,7 +45,12 @@ class DB $parameters = [$parameters]; } $query = $this->pdo->prepare($query); - $query->execute($parameters); + + foreach ($parameters as $index => $parameter) { + $query->bindValue($index + 1, $parameter, is_int($parameter) ? PDO::PARAM_INT : PDO::PARAM_STR); + } + + $query->execute(); return $query; } diff --git a/app/routes.php b/app/routes.php index c640c66..f3f1d72 100644 --- a/app/routes.php +++ b/app/routes.php @@ -3,6 +3,8 @@ $app->group('', function () { $this->get('/home[/page/{page}]', \App\Controllers\DashboardController::class . ':home'); $this->get('/system', \App\Controllers\DashboardController::class . ':system')->add(\App\Middleware\AdminMiddleware::class); + $this->get('/system/themes', \App\Controllers\DashboardController::class . ':getThemes')->add(\App\Middleware\AdminMiddleware::class); + $this->post('/system/theme/apply', \App\Controllers\DashboardController::class . ':applyTheme')->add(\App\Middleware\AdminMiddleware::class); $this->group('', function () { $this->get('/users[/page/{page}]', \App\Controllers\UserController::class . ':index'); diff --git a/bin/clean b/bin/clean index 2bb9974..97ed9b8 100644 --- a/bin/clean +++ b/bin/clean @@ -1,7 +1,7 @@ #!/usr/bin/env php 'XBackBone', diff --git a/install/index.php b/install/index.php index b11b397..fd84139 100644 --- a/install/index.php +++ b/install/index.php @@ -60,8 +60,8 @@ $app->post('/', function (Request $request, Response $response) use (&$config) { $config['displayErrorDetails'] = false; $config['db']['connection'] = $request->getParam('connection'); $config['db']['dsn'] = $request->getParam('dsn'); - $config['db']['username'] = null; - $config['db']['password'] = null; + $config['db']['username'] = $request->getParam('db_user'); + $config['db']['password'] = $request->getParam('db_password'); file_put_contents(__DIR__ . '/../config.php', 'post('/', function (Request $request, Response $response) use (&$config) { DB::query("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)]); - Session::alert('Installation completed successfully!', 'success'); return $response->withRedirect('../?afterInstall=true'); }); diff --git a/install/templates/install.twig b/install/templates/install.twig index ca6685e..38689ef 100644 --- a/install/templates/install.twig +++ b/install/templates/install.twig @@ -51,7 +51,8 @@
- +

@@ -60,6 +61,7 @@
@@ -67,7 +69,8 @@
- +
@@ -81,28 +84,33 @@
- +

- + + Must be a writable directory

- +
- +
@@ -119,6 +127,24 @@ + {% include 'footer.twig' %} diff --git a/resources/schemas/mysql/mysql.1.sql b/resources/schemas/mysql/mysql.1.sql new file mode 100644 index 0000000..791e36b --- /dev/null +++ b/resources/schemas/mysql/mysql.1.sql @@ -0,0 +1,26 @@ +CREATE TABLE IF NOT EXISTS `users` ( + `id` INTEGER PRIMARY KEY AUTO_INCREMENT, + `email` VARCHAR(30) NOT NULL, + `username` VARCHAR(30) NOT NULL, + `password` VARCHAR(256) NOT NULL, + `user_code` VARCHAR(5), + `token` VARCHAR(256), + `active` BOOLEAN NOT NULL DEFAULT 1, + `is_admin` BOOLEAN NOT NULL DEFAULT 0, + `registration_date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + INDEX (`username`, `user_code`, `token`) +); + +CREATE TABLE IF NOT EXISTS `uploads` ( + `id` INTEGER PRIMARY KEY AUTO_INCREMENT, + `user_id` INTEGER(20), + `code` VARCHAR(64) NOT NULL, + `filename` VARCHAR(128) NOT NULL, + `storage_path` VARCHAR(256) NOT NULL, + `published` BOOLEAN NOT NULL DEFAULT 1, + `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + INDEX (`code`), + FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) + ON UPDATE CASCADE + ON DELETE SET NULL +); \ No newline at end of file diff --git a/resources/schemas/sqlite/sqlite.1.sql b/resources/schemas/sqlite/sqlite.1.sql index 56d2484..fadd84e 100644 --- a/resources/schemas/sqlite/sqlite.1.sql +++ b/resources/schemas/sqlite/sqlite.1.sql @@ -1,13 +1,13 @@ CREATE TABLE IF NOT EXISTS `users` ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT, - `email` VARCHAR(30) NOT NULL, - `username` VARCHAR(30) NOT NULL, - `password` VARCHAR(256) NOT NULL, + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `email` VARCHAR(30) NOT NULL, + `username` VARCHAR(30) NOT NULL, + `password` VARCHAR(256) NOT NULL, `user_code` VARCHAR(5), `token` VARCHAR(256), - `active` BOOLEAN NOT NULL DEFAULT 1, - `is_admin` BOOLEAN NOT NULL DEFAULT 0, - `registration_date` NOT NULL DEFAULT CURRENT_TIMESTAMP + `active` BOOLEAN NOT NULL DEFAULT 1, + `is_admin` BOOLEAN NOT NULL DEFAULT 0, + `registration_date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS `uploads` ( @@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS `uploads` ( `filename` VARCHAR(128) NOT NULL, `storage_path` VARCHAR(256) NOT NULL, `published` BOOLEAN NOT NULL DEFAULT 1, - `timestamp` NOT NULL DEFAULT CURRENT_TIMESTAMP, + `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE SET NULL diff --git a/resources/templates/comp/navbar.twig b/resources/templates/comp/navbar.twig index bcb1ff6..0e683e4 100644 --- a/resources/templates/comp/navbar.twig +++ b/resources/templates/comp/navbar.twig @@ -30,6 +30,8 @@ {{ session.username }} diff --git a/resources/templates/dashboard/system.twig b/resources/templates/dashboard/system.twig index 74d2546..f7c1e2d 100644 --- a/resources/templates/dashboard/system.twig +++ b/resources/templates/dashboard/system.twig @@ -54,14 +54,38 @@
-
+
+
+
Theme
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
System Information
-

- Max upload size (max_post_size/upload_max_filesize): - {{ max_filesize }} -

+ Max upload size: +
    +
  • post_max_size: {{ post_max_size }}
  • +
  • upload_max_filesize: {{ upload_max_filesize }}
  • +
diff --git a/src/js/app.js b/src/js/app.js index 66b95b5..39037bf 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -7,6 +7,7 @@ var app = { $('.media-delete').click(app.mediaDelete); $('.publish-toggle').click(app.publishToggle); $('.refresh-token').click(app.refreshToken); + $('#themes').mousedown(app.loadThemes); $('.alert').fadeTo(2000, 500).slideUp(500, function () { $('.alert').slideUp(500); @@ -64,6 +65,21 @@ var app = { $.post(window.AppConfig.base_url + '/user/' + id + '/refreshToken', function (data) { $('#token').val(data); }); + }, + loadThemes: function (e) { + e.preventDefault(); + var $themes = $('#themes'); + $.get(window.AppConfig.base_url + '/system/themes', function (data) { + $themes.empty(); + Object.keys(data).forEach(function (key) { + var opt = document.createElement('option'); + opt.value = data[key]; + opt.innerHTML = key; + $themes.append(opt); + }); + $('#themes-apply').prop('disabled', false); + }); + $themes.unbind('mousedown'); } };