Upload improvements
This commit is contained in:
parent
038fb156c1
commit
ac4429fe0c
7 changed files with 210 additions and 89 deletions
|
@ -71,6 +71,7 @@ abstract class Controller
|
|||
if ($this->getSetting('quota_enabled', 'off') === 'on') {
|
||||
if ($max < 0) {
|
||||
$this->session->set('max_disk_quota', '∞');
|
||||
$this->session->set('percent_disk_quota', null);
|
||||
} else {
|
||||
$this->session->set('max_disk_quota', humanFileSize($max));
|
||||
$this->session->set('percent_disk_quota', round(($current * 100) / $max));
|
||||
|
|
|
@ -2,35 +2,73 @@
|
|||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Exceptions\ValidationException;
|
||||
use Exception;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
|
||||
class UploadController extends Controller
|
||||
{
|
||||
private $json = [
|
||||
'message' => null,
|
||||
'version' => PLATFORM_VERSION,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
*
|
||||
* @return Response
|
||||
* @throws \Twig\Error\LoaderError
|
||||
* @throws \Twig\Error\RuntimeError
|
||||
* @throws \Twig\Error\SyntaxError
|
||||
*
|
||||
* @throws \Twig\Error\LoaderError
|
||||
*/
|
||||
public function webUpload(Request $request, Response $response): Response
|
||||
public function uploadWebPage(Response $response): Response
|
||||
{
|
||||
$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $this->session->get('user_id'))->fetch();
|
||||
return view()->render($response, 'upload/web.twig');
|
||||
}
|
||||
|
||||
if ($user->token === null || $user->token === '') {
|
||||
$this->session->alert(lang('no_upload_token'), 'danger');
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return Response
|
||||
* @throws \Slim\Exception\HttpNotFoundException
|
||||
* @throws \Slim\Exception\HttpUnauthorizedException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function uploadWeb(Request $request, Response $response): Response
|
||||
{
|
||||
if ($this->config['maintenance']) {
|
||||
$this->json['message'] = 'Endpoint under maintenance.';
|
||||
|
||||
return redirect($response, $request->getHeaderLine('Referer'));
|
||||
return json($response, $this->json, 503);
|
||||
}
|
||||
|
||||
return view()->render($response, 'upload/web.twig', [
|
||||
'user' => $user,
|
||||
]);
|
||||
try {
|
||||
$file = $this->validateFile($request, $response);
|
||||
|
||||
$user = $this->getUser($request, $this->session->get('user_id'));
|
||||
|
||||
$this->validateUser($request, $response, $file, $user);
|
||||
} catch (ValidationException $e) {
|
||||
return $e->response();
|
||||
}
|
||||
|
||||
if (!$this->updateUserQuota($request, $user->id, $file->getSize())) {
|
||||
$this->json['message'] = 'User disk quota exceeded.';
|
||||
|
||||
return json($response, $this->json, 507);
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->saveMedia($response, $file, $user);
|
||||
$this->setSessionQuotaInfo($user->current_disk_quota + $file->getSize(), $user->max_disk_quota);
|
||||
} catch (Exception $e) {
|
||||
$this->updateUserQuota($request, $user->id, $file->getSize(), true);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,100 +78,145 @@ class UploadController extends Controller
|
|||
* @return Response
|
||||
* @throws \Slim\Exception\HttpNotFoundException
|
||||
* @throws \Slim\Exception\HttpUnauthorizedException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function upload(Request $request, Response $response): Response
|
||||
public function uploadEndpoint(Request $request, Response $response): Response
|
||||
{
|
||||
$json = [
|
||||
'message' => null,
|
||||
'version' => PLATFORM_VERSION,
|
||||
];
|
||||
|
||||
if ($this->config['maintenance']) {
|
||||
$json['message'] = 'Endpoint under maintenance.';
|
||||
$this->json['message'] = 'Endpoint under maintenance.';
|
||||
|
||||
return json($response, $json, 503);
|
||||
return json($response, $this->json, 503);
|
||||
}
|
||||
|
||||
if ($request->getServerParams()['CONTENT_LENGTH'] > stringToBytes(ini_get('post_max_size'))) {
|
||||
$json['message'] = 'File too large (post_max_size too low?).';
|
||||
|
||||
return json($response, $json, 400);
|
||||
}
|
||||
|
||||
$file = array_values($request->getUploadedFiles());
|
||||
/** @var \Psr\Http\Message\UploadedFileInterface|null $file */
|
||||
$file = $file[0] ?? null;
|
||||
|
||||
if ($file === null) {
|
||||
$json['message'] = 'Request without file attached.';
|
||||
|
||||
return json($response, $json, 400);
|
||||
}
|
||||
|
||||
if ($file->getError() === UPLOAD_ERR_INI_SIZE) {
|
||||
$json['message'] = 'File too large (upload_max_filesize too low?).';
|
||||
|
||||
return json($response, $json, 400);
|
||||
try {
|
||||
$file = $this->validateFile($request, $response);
|
||||
} catch (ValidationException $e) {
|
||||
return $e->response();
|
||||
}
|
||||
|
||||
if (param($request, 'token') === null) {
|
||||
$json['message'] = 'Token not specified.';
|
||||
$this->json['message'] = 'Token not specified.';
|
||||
|
||||
return json($response, $json, 400);
|
||||
return json($response, $this->json, 400);
|
||||
}
|
||||
|
||||
$user = $this->database->query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', param($request, 'token'))->fetch();
|
||||
|
||||
if (!$user) {
|
||||
$json['message'] = 'Token specified not found.';
|
||||
$this->json['message'] = 'Token specified not found.';
|
||||
|
||||
return json($response, $json, 404);
|
||||
}
|
||||
|
||||
if (!$user->active) {
|
||||
$json['message'] = 'Account disabled.';
|
||||
|
||||
return json($response, $json, 401);
|
||||
}
|
||||
|
||||
if (!$this->updateUserQuota($request, $user->id, $file->getSize())) {
|
||||
$json['message'] = 'User disk quota exceeded.';
|
||||
|
||||
return json($response, $json, 507);
|
||||
return json($response, $this->json, 404);
|
||||
}
|
||||
|
||||
try {
|
||||
do {
|
||||
$code = humanRandomString();
|
||||
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0);
|
||||
$this->validateUser($request, $response, $file, $user);
|
||||
} catch (ValidationException $e) {
|
||||
return $e->response();
|
||||
}
|
||||
|
||||
$published = 1;
|
||||
if ($this->getSetting('hide_by_default') === 'on') {
|
||||
$published = 0;
|
||||
}
|
||||
if (!$this->updateUserQuota($request, $user->id, $file->getSize())) {
|
||||
$this->json['message'] = 'User disk quota exceeded.';
|
||||
|
||||
$fileInfo = pathinfo($file->getClientFilename());
|
||||
$storagePath = "$user->user_code/$code.$fileInfo[extension]";
|
||||
return json($response, $this->json, 507);
|
||||
}
|
||||
|
||||
$this->storage->writeStream($storagePath, $file->getStream()->detach());
|
||||
|
||||
$this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`, `published`) VALUES (?, ?, ?, ?, ?)', [
|
||||
$user->id,
|
||||
$code,
|
||||
$file->getClientFilename(),
|
||||
$storagePath,
|
||||
$published,
|
||||
]);
|
||||
|
||||
$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);
|
||||
try {
|
||||
$response = $this->saveMedia($response, $file, $user);
|
||||
} catch (Exception $e) {
|
||||
$this->updateUserQuota($request, $user->id, $file->getSize(), true);
|
||||
throw $e;
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return UploadedFileInterface
|
||||
* @throws ValidationException
|
||||
*/
|
||||
protected function validateFile(Request $request, Response $response)
|
||||
{
|
||||
if ($request->getServerParams()['CONTENT_LENGTH'] > stringToBytes(ini_get('post_max_size'))) {
|
||||
$this->json['message'] = 'File too large (post_max_size too low?).';
|
||||
|
||||
throw new ValidationException(json($response, $this->json, 400));
|
||||
}
|
||||
|
||||
$file = array_values($request->getUploadedFiles());
|
||||
/** @var UploadedFileInterface|null $file */
|
||||
$file = $file[0] ?? null;
|
||||
|
||||
if ($file === null) {
|
||||
$this->json['message'] = 'Request without file attached.';
|
||||
|
||||
throw new ValidationException(json($response, $this->json, 400));
|
||||
}
|
||||
|
||||
if ($file->getError() === UPLOAD_ERR_INI_SIZE) {
|
||||
$this->json['message'] = 'File too large (upload_max_filesize too low?).';
|
||||
|
||||
throw new ValidationException(json($response, $this->json, 400));
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param UploadedFileInterface $file
|
||||
* @param $user
|
||||
* @return void
|
||||
* @throws ValidationException
|
||||
*/
|
||||
protected function validateUser(Request $request, Response $response, UploadedFileInterface $file, $user)
|
||||
{
|
||||
if (!$user->active) {
|
||||
$this->json['message'] = 'Account disabled.';
|
||||
|
||||
throw new ValidationException(json($response, $this->json, 401));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param UploadedFileInterface $file
|
||||
* @param $user
|
||||
* @return Response
|
||||
* @throws \League\Flysystem\FileExistsException
|
||||
*/
|
||||
protected function saveMedia(Response $response, UploadedFileInterface $file, $user)
|
||||
{
|
||||
do {
|
||||
$code = humanRandomString();
|
||||
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0);
|
||||
|
||||
$published = 1;
|
||||
if ($this->getSetting('hide_by_default') === 'on') {
|
||||
$published = 0;
|
||||
}
|
||||
|
||||
$fileInfo = pathinfo($file->getClientFilename());
|
||||
$storagePath = "$user->user_code/$code.$fileInfo[extension]";
|
||||
|
||||
$this->storage->writeStream($storagePath, $file->getStream()->detach());
|
||||
|
||||
$this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`, `published`) VALUES (?, ?, ?, ?, ?)', [
|
||||
$user->id,
|
||||
$code,
|
||||
$file->getClientFilename(),
|
||||
$storagePath,
|
||||
$published,
|
||||
]);
|
||||
|
||||
$this->json['message'] = 'OK';
|
||||
$this->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, $this->json, 201);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,14 +98,14 @@ class UserController extends Controller
|
|||
|
||||
$maxUserQuota = -1;
|
||||
if ($this->getSetting('quota_enabled') === 'on') {
|
||||
$maxUserQuota = param($request, 'max_user_quota', humanFileSize($this->getSetting('default_user_quota'), 0, true));
|
||||
if (!preg_match('/([0-9]+[K|M|G|T])|(\-1)/i', $maxUserQuota)) {
|
||||
$maxUserQuotaStr = param($request, 'max_user_quota', humanFileSize($this->getSetting('default_user_quota'), 0, true));
|
||||
if (!preg_match('/([0-9]+[K|M|G|T])|(\-1)/i', $maxUserQuotaStr)) {
|
||||
$this->session->alert(lang('invalid_quota', 'danger'));
|
||||
return redirect($response, route('user.create'));
|
||||
}
|
||||
|
||||
if ($maxUserQuota !== '-1') {
|
||||
$maxUserQuota = stringToBytes($maxUserQuota);
|
||||
if ($maxUserQuotaStr !== '-1') {
|
||||
$maxUserQuota = stringToBytes($maxUserQuotaStr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,6 +201,7 @@ class UserController extends Controller
|
|||
return redirect($response, route('user.edit', ['id' => $id]));
|
||||
}
|
||||
|
||||
$user->max_disk_quota = -1;
|
||||
if ($this->getSetting('quota_enabled') === 'on') {
|
||||
$maxUserQuota = param($request, 'max_user_quota', humanFileSize($this->getSetting('default_user_quota'), 0, true));
|
||||
if (!preg_match('/([0-9]+[K|M|G|T])|(\-1)/i', $maxUserQuota)) {
|
||||
|
@ -234,6 +235,10 @@ class UserController extends Controller
|
|||
]);
|
||||
}
|
||||
|
||||
if ($user->id === $this->session->get('user_id')) {
|
||||
$this->setSessionQuotaInfo($user->current_disk_quota, $user->max_disk_quota);
|
||||
}
|
||||
|
||||
$this->session->alert(lang('user_updated', [param($request, 'username')]), 'success');
|
||||
$this->logger->info('User '.$this->session->get('username')." updated $user->id.", [
|
||||
array_diff_key((array) $user, array_flip(['password'])),
|
||||
|
|
32
app/Exceptions/ValidationException.php
Normal file
32
app/Exceptions/ValidationException.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
|
||||
use Exception;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Throwable;
|
||||
|
||||
class ValidationException extends Exception
|
||||
{
|
||||
/**
|
||||
* @var Response
|
||||
*/
|
||||
private $response;
|
||||
|
||||
public function __construct(Response $response, $message = "", $code = 0, Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $response->getStatusCode(), $previous);
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Response
|
||||
*/
|
||||
public function response(): Response
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
}
|
|
@ -22,7 +22,8 @@ use Slim\Routing\RouteCollectorProxy;
|
|||
global $app;
|
||||
$app->group('', function (RouteCollectorProxy $group) {
|
||||
$group->get('/home[/page/{page}]', [DashboardController::class, 'home'])->setName('home');
|
||||
$group->get('/upload', [UploadController::class, 'webUpload'])->setName('upload.web');
|
||||
$group->get('/upload', [UploadController::class, 'uploadWebPage'])->setName('upload.web.show');
|
||||
$group->post('/upload/web', [UploadController::class, 'uploadWeb'])->setName('upload.web');
|
||||
|
||||
$group->group('', function (RouteCollectorProxy $group) {
|
||||
$group->get('/home/switchView', [DashboardController::class, 'switchView'])->setName('switchView');
|
||||
|
@ -76,7 +77,7 @@ $app->get('/login', [LoginController::class, 'show'])->setName('login.show');
|
|||
$app->post('/login', [LoginController::class, 'login'])->setName('login');
|
||||
$app->map(['GET', 'POST'], '/logout', [LoginController::class, 'logout'])->setName('logout');
|
||||
|
||||
$app->post('/upload', [UploadController::class, 'upload'])->setName('upload');
|
||||
$app->post('/upload', [UploadController::class, 'uploadEndpoint'])->setName('upload');
|
||||
|
||||
$app->get('/{userCode}/{mediaCode}', [MediaController::class, 'show'])->setName('public');
|
||||
$app->get('/{userCode}/{mediaCode}/delete/{token}', [MediaController::class, 'show'])->setName('public.delete.show')->add(CheckForMaintenanceMiddleware::class);
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ route('upload') }}" class="nav-link {{ inPath(request.uri.path, '/upload') ? 'active' }}"><i class="fas fa-fw fa-upload"></i>
|
||||
<a href="{{ route('upload.web.show') }}" class="nav-link {{ inPath(request.uri.path, '/upload') ? 'active' }}"><i class="fas fa-fw fa-upload"></i>
|
||||
{{ lang('upload') }}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -20,11 +20,10 @@
|
|||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<form action="{{ route('upload') }}?web=1" method="post" id="upload-dropzone" class="dropzone">
|
||||
<form action="{{ route('upload.web') }}" method="post" id="upload-dropzone" class="dropzone">
|
||||
<div class="fallback">
|
||||
<input name="file" type="file" multiple>
|
||||
</div>
|
||||
<input type="hidden" name="token" value="{{ user.token }}">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Reference in a new issue