diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b1cd41..5ffe793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ ## v.3.1 (WIP) -+ The theme is now re-applied after every system update. ++ Added registration system. ++ Added password recovery system. ++ Added ability to export all media of an account. + Added ability to choose between default and raw url on copy. + Added hide by default option. ++ Added user disk quota. ++ Fixed bug html files raws are rendered in a browser. ++ The theme is now re-applied after every system update. + Updated system settings page. + Updated translations. ++ Small fixes and improvements. ## v.3.0.2 + Fixed error with migrate command. diff --git a/app/Controllers/AdminController.php b/app/Controllers/AdminController.php index dbba393..646b609 100644 --- a/app/Controllers/AdminController.php +++ b/app/Controllers/AdminController.php @@ -2,7 +2,10 @@ namespace App\Controllers; +use App\Database\DB; +use App\Web\Media; use League\Flysystem\FileNotFoundException; +use League\Flysystem\Filesystem; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; @@ -102,29 +105,7 @@ class AdminController extends Controller */ public function recalculateUserQuota(Response $response): Response { - $uploads = $this->database->query('SELECT `id`,`user_id`, `storage_path` FROM `uploads`')->fetchAll(); - - $usersQuotas = []; - - $filesystem = $this->storage; - foreach ($uploads as $upload) { - if (!array_key_exists($upload->user_id, $usersQuotas)) { - $usersQuotas[$upload->user_id] = 0; - } - try { - $usersQuotas[$upload->user_id] += $filesystem->getSize($upload->storage_path); - } catch (FileNotFoundException $e) { - $this->database->query('DELETE FROM `uploads` WHERE `id` = ?', $upload->id); - } - } - - foreach ($usersQuotas as $userId => $quota) { - $this->database->query('UPDATE `users` SET `current_disk_quota`=? WHERE `id` = ?', [ - $quota, - $userId, - ]); - } - + Media::recalculateQuotas($this->database, $this->storage); $this->session->alert(lang('quota_recalculated')); return redirect($response, route('system')); } diff --git a/app/Controllers/Controller.php b/app/Controllers/Controller.php index f6031b6..1b27141 100644 --- a/app/Controllers/Controller.php +++ b/app/Controllers/Controller.php @@ -73,6 +73,38 @@ abstract class Controller return $totalSize; } + /** + * @param Request $request + * @param $userId + * @param $fileSize + * @param bool $dec + * @return bool + * @throws HttpNotFoundException + * @throws HttpUnauthorizedException + */ + protected function updateUserQuota(Request $request, $userId, $fileSize, $dec = false) + { + $user = $this->getUser($request, $userId); + + if ($dec) { + $tot = max($user->current_disk_quota - $fileSize, 0); + } else { + $tot = $user->current_disk_quota + $fileSize; + + $quotaEnabled = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'quota_enabled\'')->fetch()->value ?? 'off'; + if ($quotaEnabled === 'on' && $user->max_disk_quota > 0 && $user->max_disk_quota < $tot) { + return false; + } + } + + $this->database->query('UPDATE `users` SET `current_disk_quota`=? WHERE `id` = ?', [ + $tot, + $user->id + ]); + + return true; + } + /** * @param Request $request * @param $id diff --git a/app/Controllers/MediaController.php b/app/Controllers/MediaController.php index f4697b8..9476237 100644 --- a/app/Controllers/MediaController.php +++ b/app/Controllers/MediaController.php @@ -189,6 +189,7 @@ class MediaController extends Controller * @throws HttpUnauthorizedException * * @throws HttpNotFoundException + * @throws FileNotFoundException */ public function delete(Request $request, Response $response, int $id): Response { @@ -199,7 +200,8 @@ class MediaController extends Controller } if ($this->session->get('admin', false) || $media->user_id === $this->session->get('user_id')) { - $this->deleteMedia($request, $media->storage_path, $id); + $size = $this->deleteMedia($request, $media->storage_path, $id); + $this->updateUserQuota($request, $media->user_id, $size, true); $this->logger->info('User '.$this->session->get('username').' deleted a media.', [$id]); $this->session->set('used_space', humanFileSize($this->getUsedSpaceByUser($this->session->get('user_id')))); } else { @@ -248,7 +250,8 @@ class MediaController extends Controller } if ($this->session->get('admin', false) || $user->id === $media->user_id) { - $this->deleteMedia($request, $media->storage_path, $media->mediaId); + $size = $this->deleteMedia($request, $media->storage_path, $media->mediaId); + $this->updateUserQuota($request, $media->user_id, $size, true); $this->logger->info('User '.$user->username.' deleted a media via token.', [$media->mediaId]); } else { throw new HttpUnauthorizedException($request); @@ -262,12 +265,15 @@ class MediaController extends Controller * @param string $storagePath * @param int $id * + * @return bool|false|int * @throws HttpNotFoundException */ protected function deleteMedia(Request $request, string $storagePath, int $id) { try { + $size = $this->storage->getSize($storagePath); $this->storage->delete($storagePath); + return $size; } catch (FileNotFoundException $e) { throw new HttpNotFoundException($request); } finally { diff --git a/app/Controllers/UploadController.php b/app/Controllers/UploadController.php index 1e9c6dc..ca1a11c 100644 --- a/app/Controllers/UploadController.php +++ b/app/Controllers/UploadController.php @@ -39,6 +39,7 @@ class UploadController extends Controller * * @return Response * @throws FileExistsException + * @throws \Exception * */ public function upload(Request $request, Response $response): Response @@ -96,33 +97,44 @@ class UploadController extends Controller return json($response, $json, 401); } - do { - $code = humanRandomString(); - } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0); + if (!$this->updateUserQuota($request, $user->id, $file->getSize())) { + $json['message'] = 'User disk quota exceeded.'; - $published = 1; - if (($this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'hide_by_default\'')->fetch()->value ?? 'off') === 'on') { - $published = 0; + return json($response, $json, 507); } - $fileInfo = pathinfo($file->getClientFilename()); - $storagePath = "$user->user_code/$code.$fileInfo[extension]"; + try { + do { + $code = humanRandomString(); + } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0); - $this->storage->writeStream($storagePath, $file->getStream()->detach()); + $published = 1; + if (($this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'hide_by_default\'')->fetch()->value ?? 'off') === 'on') { + $published = 0; + } - $this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`, `published`) VALUES (?, ?, ?, ?, ?)', [ - $user->id, - $code, - $file->getClientFilename(), - $storagePath, - $published, - ]); + $fileInfo = pathinfo($file->getClientFilename()); + $storagePath = "$user->user_code/$code.$fileInfo[extension]"; - $json['message'] = 'OK.'; - $json['url'] = urlFor("/{$user->user_code}/{$code}.{$fileInfo['extension']}"); + $this->storage->writeStream($storagePath, $file->getStream()->detach()); - $this->logger->info("User $user->username uploaded new media.", [$this->database->getPdo()->lastInsertId()]); + $this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`, `published`) VALUES (?, ?, ?, ?, ?)', [ + $user->id, + $code, + $file->getClientFilename(), + $storagePath, + $published, + ]); - return json($response, $json, 201); + $json['message'] = 'OK.'; + $json['url'] = urlFor("/{$user->user_code}/{$code}.{$fileInfo['extension']}"); + + $this->logger->info("User $user->username uploaded new media.", [$this->database->getPdo()->lastInsertId()]); + + return json($response, $json, 201); + } catch (\Exception $e) { + $this->updateUserQuota($request, $user->id, $file->getSize(), false); + throw $e; + } } } diff --git a/app/Web/Media.php b/app/Web/Media.php new file mode 100644 index 0000000..54314a9 --- /dev/null +++ b/app/Web/Media.php @@ -0,0 +1,37 @@ +query('SELECT `id`,`user_id`, `storage_path` FROM `uploads`')->fetchAll(); + + $usersQuotas = []; + + foreach ($uploads as $upload) { + if (!array_key_exists($upload->user_id, $usersQuotas)) { + $usersQuotas[$upload->user_id] = 0; + } + try { + $usersQuotas[$upload->user_id] += $filesystem->getSize($upload->storage_path); + } catch (FileNotFoundException $e) { + $db->query('DELETE FROM `uploads` WHERE `id` = ?', $upload->id); + } + } + + foreach ($usersQuotas as $userId => $quota) { + $db->query('UPDATE `users` SET `current_disk_quota`=? WHERE `id` = ?', [ + $quota, + $userId, + ]); + } + } +} \ No newline at end of file diff --git a/install/index.php b/install/index.php index ee34bef..916a3c2 100644 --- a/install/index.php +++ b/install/index.php @@ -6,6 +6,7 @@ require __DIR__.'/../vendor/autoload.php'; use App\Database\DB; use App\Database\Migrator; use App\Factories\ViewFactory; +use App\Web\Media; use App\Web\Session; use App\Web\View; use DI\Bridge\Slim\Bridge; @@ -25,16 +26,16 @@ define('BASE_DIR', realpath(__DIR__.'/../').DIRECTORY_SEPARATOR); // default config $config = [ 'base_url' => str_replace('/install/', '', (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http')."://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"), - 'debug' => true, - 'db' => [ + 'debug' => true, + 'db' => [ 'connection' => 'sqlite', - 'dsn' => realpath(__DIR__.'/../').implode(DIRECTORY_SEPARATOR, ['resources', 'database', 'xbackbone.db']), - 'username' => null, - 'password' => null, + 'dsn' => realpath(__DIR__.'/../').implode(DIRECTORY_SEPARATOR, ['resources', 'database', 'xbackbone.db']), + 'username' => null, + 'password' => null, ], 'storage' => [ 'driver' => 'local', - 'path' => realpath(__DIR__.'/../').DIRECTORY_SEPARATOR.'storage', + 'path' => realpath(__DIR__.'/../').DIRECTORY_SEPARATOR.'storage', ], ]; @@ -45,7 +46,7 @@ if (file_exists(__DIR__.'/../config.php')) { $builder = new ContainerBuilder(); $builder->addDefinitions([ - 'config' => value($config), + 'config' => value($config), View::class => factory(function (Container $container) { return ViewFactory::createInstallerInstance($container); }), @@ -93,7 +94,7 @@ $app->get('/', function (Response $response, View $view, Session $session) use ( ]); })->setName('install'); -$app->post('/', function (Request $request, Response $response, Filesystem $storage, Session $session) use (&$config) { +$app->post('/', function (Request $request, Response $response, Filesystem $storage, Session $session, DB $db) use (&$config) { // Check if there is a previous installation, if not, setup the config file $installed = true; @@ -185,8 +186,6 @@ $app->post('/', function (Request $request, Response $response, Filesystem $stor $firstMigrate = true; } - $db = new DB(dsnFromConfig($config), $config['db']['username'], $config['db']['password']); - $migrator = new Migrator($db, __DIR__.'/../resources/schemas', $firstMigrate); $migrator->migrate(); } catch (PDOException $e) { @@ -200,6 +199,18 @@ $app->post('/', function (Request $request, Response $response, Filesystem $stor $db->query("INSERT INTO `users` (`email`, `username`, `password`, `is_admin`, `user_code`) VALUES (?, 'admin', ?, 1, ?)", [param($request, 'email'), password_hash(param($request, 'password'), PASSWORD_DEFAULT), humanRandomString(5)]); } + // re-apply the previous theme if is present + $css = $db->query('SELECT `value` FROM `settings` WHERE `key` = \'css\'')->fetch()->value; + if ($css) { + $content = file_get_contents($css); + if ($content !== false) { + file_put_contents(BASE_DIR.'static/bootstrap/css/bootstrap.min.css', $content); + } + } + + // recalculate user quota + Media::recalculateQuotas($db, $storage); + // if is upgrading and existing installation, put it out maintenance if ($installed) { unset($config['maintenance']); @@ -217,12 +228,6 @@ $app->post('/', function (Request $request, Response $response, Filesystem $stor return redirect($response, '/install'); } - // re-apply the previous theme if is present - $css = $db->query('SELECT `value` FROM `settings` WHERE `key` = \'css\'')->fetch()->value; - if ($css) { - file_put_contents(BASE_DIR.'static/bootstrap/css/bootstrap.min.css', file_get_contents($css)); - } - // post install cleanup cleanDirectory(__DIR__.'/../resources/cache'); cleanDirectory(__DIR__.'/../resources/sessions');