2.0 Flight -> Slim 3

This commit is contained in:
Sergio Brighenti 2018-11-11 17:02:50 +01:00
parent 932a3763ca
commit 4a4c51996c
20 changed files with 989 additions and 655 deletions

View file

@ -3,52 +3,34 @@
namespace App\Controllers;
use App\Exceptions\AuthenticationException;
use App\Exceptions\UnauthorizedException;
use App\Web\Session;
use Flight;
use App\Database\DB;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Filesystem;
use Slim\Container;
use Slim\Http\Request;
use Slim\Http\Response;
abstract class Controller
{
/**
* Check if the current user is logged in
* @throws AuthenticationException
*/
protected function checkLogin(): void
/** @var Container */
protected $container;
public function __construct(Container $container)
{
if (!Session::get('logged', false)) {
Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
throw new AuthenticationException();
}
if (!DB::query('SELECT `id`, `active` FROM `users` WHERE `id` = ? LIMIT 1', [Session::get('user_id')])->fetch()->active) {
Session::alert('Your account is not active anymore.', 'danger');
Session::set('logged', false);
Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
throw new AuthenticationException();
}
$this->container = $container;
}
/**
* Check if the current user is an admin
* @throws AuthenticationException
* @throws UnauthorizedException
* @param $name
* @return mixed|null
* @throws \Interop\Container\Exception\ContainerException
*/
protected function checkAdmin(): void
public function __get($name)
{
$this->checkLogin();
if (!DB::query('SELECT `id`, `is_admin` FROM `users` WHERE `id` = ? LIMIT 1', [Session::get('user_id')])->fetch()->is_admin) {
Session::alert('Your account is not admin anymore.', 'danger');
Session::set('admin', false);
Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
throw new UnauthorizedException();
if ($this->container->has($name)) {
return $this->container->get($name);
}
return null;
}
@ -71,20 +53,6 @@ abstract class Controller
*/
protected function getStorage(): Filesystem
{
return new Filesystem(new Local(Flight::get('config')['storage_dir']));
}
/**
* Set http2 header for a resource if is supported
* @param string $url
* @param string $as
*/
protected function http2push(string $url, string $as = 'image'): void
{
if (Flight::request()->scheme === 'HTTP/2.0') {
$headers = isset(Flight::response()->headers()['Link']) ? Flight::response()->headers()['Link'] : [];
$headers[] = "<${url}>; rel=preload; as=${as}";
Flight::response()->header('Link', $headers);
}
return new Filesystem(new Local($this->settings['storage_dir']));
}
}

View file

@ -3,37 +3,44 @@
namespace App\Controllers;
use App\Database\DB;
use App\Traits\SingletonController;
use App\Web\Session;
use Flight;
use League\Flysystem\FileNotFoundException;
use Slim\Http\Request;
use Slim\Http\Response;
class DashboardController extends Controller
{
use SingletonController;
const PER_PAGE = 21;
const PER_PAGE_ADMIN = 50;
const PER_PAGE_ADMIN = 25;
public function redirects(): void
/**
* @param Request $request
* @param Response $response
* @return Response
*/
public function redirects(Request $request, Response $response): Response
{
$this->checkLogin();
Flight::redirect('/home');
return $response->withRedirect('/home');
}
public function home($page = 1): void
/**
* @param Request $request
* @param Response $response
* @param $args
* @return Response
*/
public function home(Request $request, Response $response, $args): Response
{
$this->checkLogin();
$page = isset($args['page']) ? (int)$args['page'] : 0;
$page = max(0, --$page);
if (Session::get('admin', false)) {
$medias = DB::query('SELECT `uploads`.*, `users`.`user_code`, `users`.`username` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id` ORDER BY `timestamp` DESC LIMIT ? OFFSET ?', [self::PER_PAGE_ADMIN, $page * self::PER_PAGE_ADMIN])->fetchAll();
$pages = DB::query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count / self::PER_PAGE_ADMIN;
$medias = $this->database->query('SELECT `uploads`.*, `users`.`user_code`, `users`.`username` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id` ORDER BY `timestamp` DESC LIMIT ? OFFSET ?', [self::PER_PAGE_ADMIN, $page * self::PER_PAGE_ADMIN])->fetchAll();
$pages = $this->database->query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count / self::PER_PAGE_ADMIN;
} else {
$medias = DB::query('SELECT `uploads`.*,`users`.`user_code`, `users`.`username` FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_id` = ? ORDER BY `timestamp` DESC LIMIT ? OFFSET ?', [Session::get('user_id'), self::PER_PAGE, $page * self::PER_PAGE])->fetchAll();
$pages = DB::query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` = ?', Session::get('user_id'))->fetch()->count / self::PER_PAGE;
$medias = $this->database->query('SELECT `uploads`.*,`users`.`user_code`, `users`.`username` FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_id` = ? ORDER BY `timestamp` DESC LIMIT ? OFFSET ?', [Session::get('user_id'), self::PER_PAGE, $page * self::PER_PAGE])->fetchAll();
$pages = $this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` = ?', Session::get('user_id'))->fetch()->count / self::PER_PAGE;
}
$filesystem = $this->getStorage();
@ -51,7 +58,8 @@ class DashboardController extends Controller
$media->size = $this->humanFilesize($size);
}
Flight::render(
return $this->view->render(
$response,
Session::get('admin', false) ? 'dashboard/admin.twig' : 'dashboard/home.twig',
[
'medias' => $medias,
@ -62,15 +70,19 @@ class DashboardController extends Controller
);
}
public function system()
/**
* @param Request $request
* @param Response $response
* @return Response
* @throws FileNotFoundException
*/
public function system(Request $request, Response $response): Response
{
$this->checkAdmin();
$usersCount = $this->database->query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count;
$mediasCount = $this->database->query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count;
$orphanFilesCount = $this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` IS NULL')->fetch()->count;
$usersCount = DB::query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count;
$mediasCount = DB::query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count;
$orphanFilesCount = DB::query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` IS NULL')->fetch()->count;
$medias = DB::query('SELECT `users`.`user_code`, `uploads`.`code`, `uploads`.`storage_path` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id`')->fetchAll();
$medias = $this->database->query('SELECT `users`.`user_code`, `uploads`.`code`, `uploads`.`storage_path` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id`')->fetchAll();
$totalSize = 0;
@ -79,7 +91,9 @@ class DashboardController extends Controller
$totalSize += $filesystem->getSize($media->storage_path);
}
Flight::render('dashboard/system.twig', [
return $this->view->render(
$response,
'dashboard/system.twig', [
'usersCount' => $usersCount,
'mediasCount' => $mediasCount,
'orphanFilesCount' => $orphanFilesCount,

View file

@ -4,40 +4,44 @@ namespace App\Controllers;
use App\Database\DB;
use App\Traits\SingletonController;
use App\Web\Log;
use App\Web\Session;
use Flight;
use Slim\Http\Request;
use Slim\Http\Response;
class LoginController extends Controller
{
use SingletonController;
public function show(): void
/**
* @param Request $request
* @param Response $response
* @return Response
*/
public function show(Request $request, Response $response): Response
{
if (Session::get('logged', false)) {
Flight::redirect('/home');
return;
return $response->withRedirect('/home');
}
Flight::render('auth/login.twig');
return $this->view->render($response, 'auth/login.twig');
}
public function login(): void
/**
* @param Request $request
* @param Response $response
* @return Response
*/
public function login(Request $request, Response $response): Response
{
$form = Flight::request()->data;
$result = DB::query('SELECT `id`,`username`, `password`,`is_admin`, `active` FROM `users` WHERE `username` = ? LIMIT 1', $form->username)->fetch();
$result = DB::query('SELECT `id`,`username`, `password`,`is_admin`, `active` FROM `users` WHERE `username` = ? LIMIT 1', $request->getParam('username'))->fetch();
if (!$result || !password_verify($form->password, $result->password)) {
Flight::redirect('login');
if (!$result || !password_verify($request->getParam('password'), $result->password)) {
Session::alert('Wrong credentials', 'danger');
return;
return $response->withRedirect('/login');
}
if (!$result->active) {
Flight::redirect('login');
Session::alert('Your account is disabled.', 'danger');
return;
return $response->withRedirect('/login');
}
Session::set('logged', true);
@ -46,23 +50,26 @@ class LoginController extends Controller
Session::set('admin', $result->is_admin);
Session::alert("Welcome, $result->username!", 'info');
Log::info("User $result->username logged in.");
$this->logger->info("User $result->username logged in.");
if (Session::has('redirectTo')) {
Flight::redirect(Session::get('redirectTo'));
return;
return $response->withRedirect(Session::get('redirectTo'));
}
Flight::redirect('/home');
return $response->withRedirect('/home');
}
public function logout(): void
/**
* @param Request $request
* @param Response $response
* @return Response
*/
public function logout(Request $request, Response $response): Response
{
$this->checkLogin();
Session::clear();
Session::set('logged', false);
Session::alert('Goodbye!', 'warning');
Flight::redirect('/login');
return $response->withRedirect('/login');
}
}

View file

@ -3,99 +3,98 @@
namespace App\Controllers;
use App\Database\DB;
use App\Exceptions\NotFoundException;
use App\Traits\SingletonController;
use App\Web\Log;
use App\Exceptions\UnauthorizedException;
use App\Web\Session;
use Flight;
use League\Flysystem\FileExistsException;
use League\Flysystem\FileNotFoundException;
use League\Flysystem\Filesystem;
use Slim\Exception\NotFoundException;
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Http\Stream;
class UploadController extends Controller
{
use SingletonController;
public function upload(): void
/**
* @param Request $request
* @param Response $response
* @return Response
* @throws FileExistsException
*/
public function upload(Request $request, Response $response): Response
{
$requestData = Flight::request()->data;
$response = [
'message' => null,
];
$json = ['message' => null];
if (!isset($requestData->token)) {
$response['message'] = 'Token not specified.';
Flight::json($response, 400);
return;
if ($request->getParam('token') === null) {
$json['message'] = 'Token not specified.';
return $response->withJson($json, 400);
}
$user = DB::query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', $requestData->token)->fetch();
$user = $this->database->query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', $request->getParam('token'))->fetch();
if (!$user) {
$response['message'] = 'Token specified not found.';
Flight::json($response, 404);
return;
$json['message'] = 'Token specified not found.';
return $response->withJson($json, 404);
}
if (!$user->active) {
$response['message'] = 'Account disabled.';
Flight::json($response, 401);
return;
$json['message'] = 'Account disabled.';
return $response->withJson($json, 401);
}
do {
$code = uniqid();
} while (DB::query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0);
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0);
$file = Flight::request()->files->current();
$fileInfo = pathinfo($file['name']);
/** @var \Psr\Http\Message\UploadedFileInterface $file */
$file = $request->getUploadedFiles()['upload'];
$fileInfo = pathinfo($file->getClientFilename());
$storagePath = "$user->user_code/$code.$fileInfo[extension]";
$stream = fopen($file['tmp_name'], 'r+');
$this->getStorage()->writeStream($storagePath, $file->getStream()->detach());
$filesystem = $this->getStorage();
try {
$filesystem->writeStream($storagePath, $stream);
} catch (FileExistsException $e) {
Flight::halt(500);
return;
} finally {
fclose($stream);
}
DB::query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`) VALUES (?, ?, ?, ?)', [
$this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`) VALUES (?, ?, ?, ?)', [
$user->id,
$code,
$file['name'],
$file->getClientFilename(),
$storagePath
]);
$base_url = Flight::get('config')['base_url'];
$base_url = $this->settings['base_url'];
$response['message'] = 'OK.';
$response['url'] = "$base_url/$user->user_code/$code.$fileInfo[extension]";
Flight::json($response, 201);
$json['message'] = 'OK.';
$json['url'] = "$base_url/$user->user_code/$code.$fileInfo[extension]";
Log::info("User $user->username uploaded new media.", [DB::raw()->lastInsertId()]);
$this->logger->info("User $user->username uploaded new media.", [$this->database->raw()->lastInsertId()]);
return $response->withJson($json, 201);
}
public function show($userCode, $mediaCode): void
/**
* @param Request $request
* @param Response $response
* @param $args
* @return Response
* @throws FileNotFoundException
* @throws NotFoundException
*/
public function show(Request $request, Response $response, $args): Response
{
$media = $this->getMedia($userCode, $mediaCode);
$media = $this->getMedia($args['userCode'], $args['mediaCode']);
if (!$media || !$media->published && Session::get('user_id') !== $media->user_id && !Session::get('admin', false)) {
Flight::error(new NotFoundException());
return;
throw new NotFoundException($request, $response);
}
$filesystem = $this->getStorage();
if (stristr(Flight::request()->user_agent, 'TelegramBot') ||
stristr(Flight::request()->user_agent, 'facebookexternalhit/') ||
stristr(Flight::request()->user_agent, 'Facebot')) {
$this->streamMedia($filesystem, $media);
if (stristr($request->getHeaderLine('User-Agent'), 'TelegramBot') ||
stristr($request->getHeaderLine('User-Agent'), 'facebookexternalhit/') ||
stristr($request->getHeaderLine('User-Agent'), 'Facebot')) {
return $this->streamMedia($request, $response, $filesystem, $media);
} else {
try {
@ -104,16 +103,15 @@ class UploadController extends Controller
$type = explode('/', $mime)[0];
if ($type === 'text') {
$media->text = $filesystem->read($media->storage_path);
} elseif (in_array($type, ['image', 'video'])) {
$this->http2push(Flight::get('config')['base_url'] . "/$userCode/$mediaCode/raw");
} elseif (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}");
}
} catch (FileNotFoundException $e) {
Flight::error($e);
return;
throw $e;
}
Flight::render('upload/public.twig', [
return $this->view->render($response, 'upload/public.twig', [
'media' => $media,
'type' => $mime,
'extension' => pathinfo($media->filename, PATHINFO_EXTENSION)
@ -121,68 +119,98 @@ class UploadController extends Controller
}
}
public function getRawById($id): void
/**
* @param Request $request
* @param Response $response
* @param $args
* @return Response
* @throws NotFoundException
* @throws FileNotFoundException
*/
public function getRawById(Request $request, Response $response, $args): Response
{
$this->checkAdmin();
$media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch();
$media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
if (!$media) {
Flight::error(new NotFoundException());
return;
throw new NotFoundException($request, $response);
}
$this->streamMedia($this->getStorage(), $media);
return $this->streamMedia($request, $response, $this->getStorage(), $media);
}
public function showRaw($userCode, $mediaCode): void
/**
* @param Request $request
* @param Response $response
* @param $args
* @return Response
* @throws NotFoundException
* @throws FileNotFoundException
*/
public function showRaw(Request $request, Response $response, $args): Response
{
$media = $this->getMedia($userCode, $mediaCode);
$media = $this->getMedia($args['userCode'], $args['mediaCode']);
if (!$media || !$media->published && Session::get('user_id') !== $media->user_id && !Session::get('admin', false)) {
Flight::error(new NotFoundException());
return;
throw new NotFoundException($request, $response);
}
$this->streamMedia($this->getStorage(), $media);
return $this->streamMedia($request, $response, $this->getStorage(), $media);
}
public function download($userCode, $mediaCode): void
/**
* @param Request $request
* @param Response $response
* @param $args
* @return Response
* @throws NotFoundException
* @throws FileNotFoundException
*/
public function download(Request $request, Response $response, $args): Response
{
$media = $this->getMedia($userCode, $mediaCode);
$media = $this->getMedia($args['userCode'], $args['mediaCode']);
if (!$media || !$media->published && Session::get('user_id') !== $media->user_id && !Session::get('admin', false)) {
Flight::error(new NotFoundException());
return;
throw new NotFoundException($request, $response);
}
$this->streamMedia($this->getStorage(), $media, 'attachment');
return $this->streamMedia($request, $response, $this->getStorage(), $media, 'attachment');
}
public function togglePublish($id): void
/**
* @param Request $request
* @param Response $response
* @param $args
* @return Response
* @throws NotFoundException
*/
public function togglePublish(Request $request, Response $response, $args): Response
{
$this->checkLogin();
if (Session::get('admin')) {
$media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch();
$media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
} else {
$media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? AND `user_id` = ? LIMIT 1', [$id, Session::get('user_id')])->fetch();
$media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? AND `user_id` = ? LIMIT 1', [$args['id'], Session::get('user_id')])->fetch();
}
if (!$media) {
Flight::halt(404);
return;
throw new NotFoundException($request, $response);
}
DB::query('UPDATE `uploads` SET `published`=? WHERE `id`=?', [!$media->published, $media->id]);
$this->database->query('UPDATE `uploads` SET `published`=? WHERE `id`=?', [!$media->published, $media->id]);
return $response->withStatus(200);
}
public function delete($id): void
/**
* @param Request $request
* @param Response $response
* @param $args
* @return Response
* @throws NotFoundException
* @throws UnauthorizedException
*/
public function delete(Request $request, Response $response, $args): Response
{
$this->checkLogin();
$media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch();
$media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
if (Session::get('admin', false) || $media->user_id === Session::get('user_id')) {
@ -190,22 +218,28 @@ class UploadController extends Controller
try {
$filesystem->delete($media->storage_path);
} catch (FileNotFoundException $e) {
Flight::halt(404);
return;
throw new NotFoundException($request, $response);
} finally {
DB::query('DELETE FROM `uploads` WHERE `id` = ?', $id);
Log::info('User ' . Session::get('username') . " deleted media $id");
$this->database->query('DELETE FROM `uploads` WHERE `id` = ?', $args['id']);
$this->logger->info('User ' . Session::get('username') . ' deleted a media.', [$args['id']]);
}
} else {
Flight::halt(403);
throw new UnauthorizedException();
}
return $response->withStatus(200);
}
/**
* @param $userCode
* @param $mediaCode
* @return mixed
*/
protected function getMedia($userCode, $mediaCode)
{
$mediaCode = pathinfo($mediaCode)['filename'];
$media = DB::query('SELECT * FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_code` = ? AND `uploads`.`code` = ? LIMIT 1', [
$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
])->fetch();
@ -213,36 +247,43 @@ class UploadController extends Controller
return $media;
}
protected function streamMedia(Filesystem $storage, $media, string $disposition = 'inline'): void
/**
* @param Request $request
* @param Response $response
* @param Filesystem $storage
* @param $media
* @param string $disposition
* @return Response
* @throws FileNotFoundException
*/
protected function streamMedia(Request $request, Response $response, Filesystem $storage, $media, string $disposition = 'inline'): Response
{
try {
$mime = $storage->getMimetype($media->storage_path);
$query = Flight::request()->query;
$mime = $storage->getMimetype($media->storage_path);
if ($query['width'] !== null && explode('/', $mime)[0] === 'image') {
Flight::response()->header('Content-Type', 'image/png');
Flight::response()->header('Content-Disposition', $disposition . ';filename="scaled-' . pathinfo($media->filename)['filename'] . '.png"');
Flight::response()->sendHeaders();
ob_clean();
if ($request->getParam('width') !== null && explode('/', $mime)[0] === 'image') {
$image = imagecreatefromstring($storage->read($media->storage_path));
$scaled = imagescale($image, $query['width'], $query['height'] !== null ? $query['height'] : -1);
$image = imagecreatefromstring($storage->read($media->storage_path));
$scaled = imagescale($image, $request->getParam('width'), $request->getParam('height') !== null ? $request->getParam('height') : -1);
imagedestroy($image);
imagedestroy($image);
ob_start();
imagepng($scaled, null, 9);
imagepng($scaled, null, 9);
imagedestroy($scaled);
} else {
Flight::response()->header('Content-Type', $mime);
Flight::response()->header('Content-Disposition', $disposition . ';filename="' . $media->filename . '"');
Flight::response()->header('Content-Length', $storage->getSize($media->storage_path));
Flight::response()->sendHeaders();
ob_end_clean();
$imagedata = ob_get_contents();
ob_end_clean();
imagedestroy($scaled);
fpassthru($storage->readStream($media->storage_path));
}
} catch (FileNotFoundException $e) {
Flight::error($e);
return $response
->withHeader('Content-Type', 'image/png')
->withHeader('Content-Disposition', $disposition . ';filename="scaled-' . pathinfo($media->filename)['filename'] . '.png"')
->write($imagedata);
} else {
ob_end_clean();
return $response
->withHeader('Content-Type', $mime)
->withHeader('Content-Disposition', $disposition . ';filename="' . $media->filename . '"')
->withHeader('Content-Length', $storage->getSize($media->storage_path))
->withBody(new Stream($storage->readStream($media->storage_path)));
}
}
}

View file

@ -3,309 +3,333 @@
namespace App\Controllers;
use App\Database\DB;
use App\Exceptions\NotFoundException;
use App\Exceptions\UnauthorizedException;
use App\Traits\SingletonController;
use App\Web\Log;
use App\Web\Session;
use Flight;
use Slim\Exception\NotFoundException;
use Slim\Http\Request;
use Slim\Http\Response;
class UserController extends Controller
{
use SingletonController;
const PER_PAGE = 15;
public function index($page = 1): void
/**
* @param Request $request
* @param Response $response
* @param $args
* @return Response
*/
public function index(Request $request, Response $response, $args): Response
{
$this->checkAdmin();
$page = isset($args['page']) ? (int)$args['page'] : 0;
$page = max(0, --$page);
$users = DB::query('SELECT * FROM `users` LIMIT ? OFFSET ?', [self::PER_PAGE, $page * self::PER_PAGE])->fetchAll();
$users = $this->database->query('SELECT * FROM `users` LIMIT ? OFFSET ?', [self::PER_PAGE, $page * self::PER_PAGE])->fetchAll();
$pages = DB::query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count / self::PER_PAGE;
$pages = $this->database->query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count / self::PER_PAGE;
Flight::render('user/index.twig', [
'users' => $users,
'next' => $page < floor($pages),
'previous' => $page >= 1,
'current_page' => ++$page,
]);
return $this->view->render($response,
'user/index.twig',
[
'users' => $users,
'next' => $page < floor($pages),
'previous' => $page >= 1,
'current_page' => ++$page,
]
);
}
public function create(): void
/**
* @param Request $request
* @param Response $response
* @return Response
*/
public function create(Request $request, Response $response): Response
{
$this->checkAdmin();
Flight::render('user/create.twig');
return $this->view->render($response, 'user/create.twig');
}
public function store(): void
/**
* @param Request $request
* @param Response $response
* @return Response
*/
public function store(Request $request, Response $response): Response
{
$this->checkAdmin();
$form = Flight::request()->data;
if (!isset($form->email) || empty($form->email)) {
if ($request->getParam('email') === null) {
Session::alert('The email is required.', 'danger');
Flight::redirectBack();
return;
return $response->withRedirect('/user/create');
}
if (!isset($form->username) || empty($form->username)) {
if ($request->getParam('username') === null) {
Session::alert('The username is required.', 'danger');
Flight::redirectBack();
return;
return $response->withRedirect('/user/create');
}
if (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', $form->username)->fetch()->count > 0) {
Session::alert('The username already taken.', 'danger');
Flight::redirectBack();
return;
}
if (!isset($form->password) || empty($form->password)) {
if ($request->getParam('password') === null) {
Session::alert('The password is required.', 'danger');
Flight::redirectBack();
return;
return $response->withRedirect('/user/create');
}
if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', $request->getParam('username'))->fetch()->count > 0) {
Session::alert('The username already taken.', 'danger');
return $response->withRedirect('/user/create');
}
do {
$userCode = substr(md5(microtime()), rand(0, 26), 5);
} while (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0);
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0);
$token = $this->generateNewToken();
DB::query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`) VALUES (?, ?, ?, ?, ?, ?, ?)', [
$form->email,
$form->username,
password_hash($form->password, PASSWORD_DEFAULT),
isset($form->is_admin),
isset($form->is_active),
$this->database->query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`) VALUES (?, ?, ?, ?, ?, ?, ?)', [
$request->getParam('email'),
$request->getParam('username'),
password_hash($request->getParam('password'), PASSWORD_DEFAULT),
$request->getParam('is_admin') !== null,
$request->getParam('is_active') !== null,
$userCode,
$token
]);
Session::alert("User '$form->username' created!", 'success');
Log::info('User ' . Session::get('username') . ' created a new user.', [array_diff($form->getData(), ['password'])]);
Session::alert("User '{$request->getParam('username')}' created!", 'success');
$this->logger->info('User ' . Session::get('username') . ' created a new user.', [array_diff($request->getParams(), ['password'])]);
Flight::redirect('/users');
return $response->withRedirect('/users');
}
public function edit($id): void
/**
* @param Request $request
* @param Response $response
* @param $args
* @return Response
* @throws NotFoundException
*/
public function edit(Request $request, Response $response, $args): Response
{
$this->checkAdmin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
throw new NotFoundException($request, $response);
}
Flight::render('user/edit.twig', [
return $this->view->render($response, 'user/edit.twig', [
'user' => $user
]);
}
public function update($id): void
/**
* @param Request $request
* @param Response $response
* @param $args
* @return Response
* @throws NotFoundException
*/
public function update(Request $request, Response $response, $args): Response
{
$this->checkAdmin();
$form = Flight::request()->data;
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
throw new NotFoundException($request, $response);
}
if (!isset($form->email) || empty($form->email)) {
if ($request->getParam('email') === null) {
Session::alert('The email is required.', 'danger');
Flight::redirectBack();
return;
return $response->withRedirect('/user/' . $args['id'] . '/edit');
}
if (!isset($form->username) || empty($form->username)) {
if ($request->getParam('username') === null) {
Session::alert('The username is required.', 'danger');
Flight::redirectBack();
return;
return $response->withRedirect('/user/' . $args['id'] . '/edit');
}
if (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ? AND `username` <> ?', [$form->username, $user->username])->fetch()->count > 0) {
if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ? AND `username` <> ?', [$request->getParam('username'), $user->username])->fetch()->count > 0) {
Session::alert('The username already taken.', 'danger');
Flight::redirectBack();
return;
return $response->withRedirect('/user/' . $args['id'] . '/edit');
}
if ($user->id === Session::get('user_id') && !isset($form->is_admin)) {
if ($user->id === Session::get('user_id') && $request->getParam('is_admin') === null) {
Session::alert('You cannot demote yourself.', 'danger');
Flight::redirectBack();
return;
return $response->withRedirect('/user/' . $args['id'] . '/edit');
}
if (isset($form->password) && !empty($form->password)) {
DB::query('UPDATE `users` SET `email`=?, `username`=?, `password`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [
$form->email,
$form->username,
password_hash($form->password, PASSWORD_DEFAULT),
isset($form->is_admin),
isset($form->is_active),
if ($request->getParam('password') !== null && !empty($request->getParam('password'))) {
$this->database->query('UPDATE `users` SET `email`=?, `username`=?, `password`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [
$request->getParam('email'),
$request->getParam('username'),
password_hash($request->getParam('password'), PASSWORD_DEFAULT),
$request->getParam('is_admin') !== null,
$request->getParam('is_active') !== null,
$user->id
]);
} else {
DB::query('UPDATE `users` SET `email`=?, `username`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [
$form->email,
$form->username,
isset($form->is_admin),
isset($form->is_active),
$this->database->query('UPDATE `users` SET `email`=?, `username`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [
$request->getParam('email'),
$request->getParam('username'),
$request->getParam('is_admin') !== null,
$request->getParam('is_active') !== null,
$user->id
]);
}
Session::alert("User '$form->username' updated!", 'success');
Log::info('User ' . Session::get('username') . " updated $user->id.", [$user, array_diff($form->getData(), ['password'])]);
Session::alert("User '{$request->getParam('username')}' updated!", 'success');
$this->logger->info('User ' . Session::get('username') . " updated $user->id.", [$user, array_diff($request->getParams(), ['password'])]);
Flight::redirect('/users');
return $response->withRedirect('/users');
}
public function delete($id): void
/**
* @param Request $request
* @param Response $response
* @param $args
* @return Response
* @throws NotFoundException
*/
public function delete(Request $request, Response $response, $args): Response
{
$this->checkAdmin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
throw new NotFoundException($request, $response);
}
if ($user->id === Session::get('user_id')) {
Session::alert('You cannot delete yourself.', 'danger');
Flight::redirectBack();
return;
return $response->withRedirect('/users');
}
DB::query('DELETE FROM `users` WHERE `id` = ?', $user->id);
$this->database->query('DELETE FROM `users` WHERE `id` = ?', $user->id);
Session::alert('User deleted.', 'success');
Log::info('User ' . Session::get('username') . " deleted $user->id.");
$this->logger->info('User ' . Session::get('username') . " deleted $user->id.");
Flight::redirect('/users');
return $response->withRedirect('/users');
}
public function profile(): void
/**
* @param Request $request
* @param Response $response
* @return Response
* @throws NotFoundException
* @throws UnauthorizedException
*/
public function profile(Request $request, Response $response): Response
{
$this->checkLogin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', Session::get('user_id'))->fetch();
$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', Session::get('user_id'))->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
throw new NotFoundException($request, $response);
}
if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
Flight::error(new UnauthorizedException());
return;
throw new UnauthorizedException();
}
Flight::render('user/profile.twig', [
return $this->view->render($response, 'user/profile.twig', [
'user' => $user
]);
}
public function profileEdit($id): void
/**
* @param Request $request
* @param Response $response
* @param $args
* @return Response
* @throws NotFoundException
* @throws UnauthorizedException
*/
public function profileEdit(Request $request, Response $response, $args): Response
{
$this->checkLogin();
$form = Flight::request()->data;
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
throw new NotFoundException($request, $response);
}
if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
Flight::error(new UnauthorizedException());
return;
throw new UnauthorizedException();
}
if (!isset($form->email) || empty($form->email)) {
if ($request->getParam('email') === null) {
Session::alert('The email is required.', 'danger');
Flight::redirectBack();
return;
return $response->withRedirect('/profile');
}
if (isset($form->password) && !empty($form->password)) {
DB::query('UPDATE `users` SET `email`=?, `password`=? WHERE `id` = ?', [
$form->email,
password_hash($form->password, PASSWORD_DEFAULT),
if ($request->getParam('password') !== null && !empty($request->getParam('password'))) {
$this->database->query('UPDATE `users` SET `email`=?, `password`=? WHERE `id` = ?', [
$request->getParam('email'),
password_hash($request->getParam('password'), PASSWORD_DEFAULT),
$user->id
]);
} else {
DB::query('UPDATE `users` SET `email`=? WHERE `id` = ?', [
$form->email,
$this->database->query('UPDATE `users` SET `email`=? WHERE `id` = ?', [
$request->getParam('email'),
$user->id
]);
}
Session::alert('Profile updated successfully!', 'success');
Log::info('User ' . Session::get('username') . " updated profile of $user->id.");
$this->logger->info('User ' . Session::get('username') . " updated profile of $user->id.");
Flight::redirectBack();
return $response->withRedirect('/profile');
}
public function refreshToken($id): void
/**
* @param Request $request
* @param Response $response
* @param $args
* @return Response
* @throws NotFoundException
* @throws UnauthorizedException
*/
public function refreshToken(Request $request, Response $response, $args): Response
{
$this->checkLogin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
if (!$user) {
Flight::halt(404);
return;
throw new NotFoundException($request, $response);
}
if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
Flight::halt(403);
return;
throw new UnauthorizedException();
}
$token = $this->generateNewToken();
DB::query('UPDATE `users` SET `token`=? WHERE `id` = ?', [
$this->database->query('UPDATE `users` SET `token`=? WHERE `id` = ?', [
$token,
$user->id
]);
Log::info('User ' . Session::get('username') . " refreshed token of user $user->id.");
$this->logger->info('User ' . Session::get('username') . " refreshed token of user $user->id.");
echo $token;
$response->getBody()->write($token);
return $response;
}
public function getShareXconfigFile($id): void
/**
* @param Request $request
* @param Response $response
* @param $args
* @return Response
* @throws NotFoundException
* @throws UnauthorizedException
*/
public function getShareXconfigFile(Request $request, Response $response, $args): Response
{
$this->checkLogin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
if (!$user) {
Flight::halt(404);
return;
throw new NotFoundException($request, $response);
}
if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
Flight::halt(403);
return;
throw new UnauthorizedException();
}
$base_url = Flight::get('config')['base_url'];
$base_url = $this->settings['base_url'];
$json = [
'DestinationType' => 'ImageUploader, TextUploader, FileUploader',
'RequestURL' => "$base_url/upload",
@ -319,18 +343,19 @@ class UserController extends Controller
'ThumbnailURL' => '$json:url$/raw',
];
Flight::response()->header('Content-Type', 'application/json');
Flight::response()->header('Content-Disposition', 'attachment;filename="' . $user->username . '-ShareX.sxcu"');
Flight::response()->sendHeaders();
echo json_encode($json, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
return $response
->withHeader('Content-Disposition', 'attachment;filename="' . $user->username . '-ShareX.sxcu"')
->withJson($json, 200, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
}
/**
* @return string
*/
protected function generateNewToken(): string
{
do {
$token = 'token_' . md5(uniqid('', true));
} while (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `token` = ?', $token)->fetch()->count > 0);
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `token` = ?', $token)->fetch()->count > 0);
return $token;
}

View file

@ -21,7 +21,7 @@ class DB
protected $pdo;
/** @var string */
protected static $dsn = 'database.db';
protected static $dsn = 'sqlite:database.db';
/** @var string */
protected $currentDriver;
@ -67,6 +67,15 @@ class DB
return $this->currentDriver;
}
public static function getInstance(): DB
{
if (self::$instance === null) {
self::$instance = new self(self::$dsn, self::$username, self::$password);
}
return self::$instance;
}
/**
* Perform a query
* @param string $query
@ -76,11 +85,7 @@ class DB
public static function query(string $query, $parameters = [])
{
if (self::$instance === null) {
self::$instance = new self(self::$dsn, self::$username, self::$password);
}
return self::$instance->doQuery($query, $parameters);
return self::getInstance()->doQuery($query, $parameters);
}
/**
@ -90,11 +95,7 @@ class DB
public static function driver(): string
{
if (self::$instance === null) {
self::$instance = new self(self::$dsn, self::$username, self::$password);
}
return self::$instance->getCurrentDriver();
return self::getInstance()->getCurrentDriver();
}
/**
@ -103,11 +104,8 @@ class DB
*/
public static function raw(): PDO
{
if (self::$instance === null) {
self::$instance = new self(self::$dsn, self::$username, self::$password);
}
return self::$instance->getPdo();
return self::getInstance()->getPdo();
}
/**

View file

@ -1,15 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
use Throwable;
class AuthenticationException extends Exception
{
public function __construct(string $message = 'Not Authorized', int $code = 401, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View file

@ -1,15 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
use Throwable;
class NotFoundException extends Exception
{
public function __construct(string $message = 'Not Found', int $code = 404, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace App\Middleware;
use App\Exceptions\AuthenticationException;
use App\Exceptions\UnauthorizedException;
use App\Web\Session;
use Slim\Http\Request;
use Slim\Http\Response;
class AdminMiddleware
{
/** @var \Slim\Container */
private $container;
public function __construct($container)
{
$this->container = $container;
}
/**
* @param Request $request
* @param Response $response
* @param callable $next
* @return Response
* @throws UnauthorizedException
*/
public function __invoke(Request $request, Response $response, callable $next)
{
if (!$this->container->database->query('SELECT `id`, `is_admin` FROM `users` WHERE `id` = ? LIMIT 1', [Session::get('user_id')])->fetch()->is_admin) {
Session::alert('Your account is not admin anymore.', 'danger');
Session::set('admin', false);
Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
throw new UnauthorizedException();
}
return $next($request, $response);
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace App\Middleware;
use App\Exceptions\AuthenticationException;
use App\Web\Session;
use Slim\Http\Request;
use Slim\Http\Response;
class AuthMiddleware
{
/** @var \Slim\Container */
private $container;
public function __construct($container)
{
$this->container = $container;
}
/**
* @param Request $request
* @param Response $response
* @param callable $next
* @return Response
*/
public function __invoke(Request $request, Response $response, callable $next)
{
if (!Session::get('logged', false)) {
Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? 'https' : 'http') . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
return $response->withRedirect('/login');
}
if (!$this->container->database->query('SELECT `id`, `active` FROM `users` WHERE `id` = ? LIMIT 1', [Session::get('user_id')])->fetch()->active) {
Session::alert('Your account is not active anymore.', 'danger');
Session::set('logged', false);
Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? 'https' : 'http') . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
return $response->withRedirect('/login');
}
return $next($request, $response);
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace App\Traits;
use App\Controllers\Controller;
trait SingletonController
{
protected static $instance;
/**
* Return the controller instance
* @return Controller
*/
public static function instance(): Controller
{
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
}

View file

@ -1,68 +0,0 @@
<?php
namespace App\Web;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
class Log
{
/** @var Logger */
protected $logger;
/** @var Log */
protected static $instance;
/**
* @return Log
* @throws \Exception
*/
public static function instance(): Log
{
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
/**
* Log constructor.
* @throws \Exception
*/
public function __construct()
{
$this->logger = new Logger(self::class);
$streamHandler = new RotatingFileHandler(__DIR__ . '/../../logs/log.txt', 10, Logger::DEBUG);
$streamHandler->setFormatter(new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n", "Y-m-d H:i:s", true));
$this->logger->pushHandler($streamHandler);
}
public static function debug(string $message, array $context = [])
{
self::instance()->logger->debug($message, $context);
}
public static function info(string $message, array $context = [])
{
self::instance()->logger->info($message, $context);
}
public static function warning(string $message, array $context = [])
{
self::instance()->logger->warning($message, $context);
}
public static function error(string $message, array $context = [])
{
self::instance()->logger->error($message, $context);
}
public static function critical(string $message, array $context = [])
{
self::instance()->logger->critical($message, $context);
}
}

View file

@ -1,29 +1,37 @@
<?php
// Auth routes
$app->group('', function () {
$this->get('/', \App\Controllers\DashboardController::class . ':redirects');
$this->get('/home[/page/{page}]', \App\Controllers\DashboardController::class . ':home');
$this->get('/system', \App\Controllers\DashboardController::class . ':system')->add(\App\Middleware\AdminMiddleware::class);
Flight::route('GET /login', [\App\Controllers\LoginController::instance(), 'show']);
Flight::route('POST /login', [\App\Controllers\LoginController::instance(), 'login']);
Flight::route('GET|POST /logout', [\App\Controllers\LoginController::instance(), 'logout']);
$this->group('', function () {
$this->get('/users[/page/{page}]', \App\Controllers\UserController::class . ':index');
$this->get('/user/create', \App\Controllers\UserController::class . ':create');
$this->post('/user/create', \App\Controllers\UserController::class . ':store');
$this->get('/user/{id}/edit', \App\Controllers\UserController::class . ':edit');
$this->post('/user/{id}', \App\Controllers\UserController::class . ':update');
$this->get('/user/{id}/delete', \App\Controllers\UserController::class . ':delete');
})->add(\App\Middleware\AdminMiddleware::class);
Flight::route('GET /', [\App\Controllers\DashboardController::instance(), 'redirects']);
Flight::route('GET /home(/page/@page)', [\App\Controllers\DashboardController::instance(), 'home']);
Flight::route('GET /system', [\App\Controllers\DashboardController::instance(), 'system']);
$this->get('/profile', \App\Controllers\UserController::class . ':profile');
$this->post('/profile/{id}/edit', \App\Controllers\UserController::class . ':profileEdit');
$this->post('/user/{id}/refreshToken', \App\Controllers\UserController::class . ':refreshToken');
$this->get('/user/{id}/config/sharex', \App\Controllers\UserController::class . ':getShareXconfigFile');
Flight::route('GET /users(/page/@page)', [\App\Controllers\UserController::instance(), 'index']);
Flight::route('GET /user/add', [\App\Controllers\UserController::instance(), 'create']);
Flight::route('POST /user/add', [\App\Controllers\UserController::instance(), 'store']);
Flight::route('GET /user/@id/edit', [\App\Controllers\UserController::instance(), 'edit']);
Flight::route('POST /user/@id', [\App\Controllers\UserController::instance(), 'update']);
Flight::route('GET /user/@id/delete', [\App\Controllers\UserController::instance(), 'delete']);
Flight::route('GET /profile', [\App\Controllers\UserController::instance(), 'profile']);
Flight::route('POST /profile/@id/edit', [\App\Controllers\UserController::instance(), 'profileEdit']);
Flight::route('POST /user/@id/refreshToken', [\App\Controllers\UserController::instance(), 'refreshToken']);
Flight::route('GET /user/@id/config/sharex', [\App\Controllers\UserController::instance(), 'getShareXconfigFile']);
$this->post('/upload/{id}/publish', \App\Controllers\UploadController::class . ':togglePublish');
$this->post('/upload/{id}/unpublish', \App\Controllers\UploadController::class . ':togglePublish');
$this->get('/upload/{id}/raw', \App\Controllers\UploadController::class . ':getRawById')->add(\App\Middleware\AdminMiddleware::class);
$this->post('/upload/{id}/delete', \App\Controllers\UploadController::class . ':delete');
Flight::route('POST /upload', [\App\Controllers\UploadController::instance(), 'upload']);
Flight::route('POST /upload/@id/publish', [\App\Controllers\UploadController::instance(), 'togglePublish']);
Flight::route('POST /upload/@id/unpublish', [\App\Controllers\UploadController::instance(), 'togglePublish']);
Flight::route('GET /upload/@id/raw', [\App\Controllers\UploadController::instance(), 'getRawById']);
Flight::route('POST /upload/@id/delete', [\App\Controllers\UploadController::instance(), 'delete']);
Flight::route('GET /@userCode/@filename', [\App\Controllers\UploadController::instance(), 'show']);
Flight::route('GET /@userCode/@filename/raw', [\App\Controllers\UploadController::instance(), 'showRaw']);
Flight::route('GET /@userCode/@filename/download', [\App\Controllers\UploadController::instance(), 'download']);
})->add(\App\Middleware\AuthMiddleware::class);
$app->get('/login', \App\Controllers\LoginController::class . ':show');
$app->post('/login', \App\Controllers\LoginController::class . ':login');
$app->map(['GET', 'POST'], '/logout', \App\Controllers\LoginController::class . ':logout');
$app->post('/upload', \App\Controllers\UploadController::class . ':upload');
$app->get('/{userCode}/{mediaCode}', \App\Controllers\UploadController::class . ':show');
$app->get('/{userCode}/{mediaCode}/raw', \App\Controllers\UploadController::class . ':showRaw');
$app->get('/{userCode}/{mediaCode}/download', \App\Controllers\UploadController::class . ':download');

View file

@ -1,13 +1,19 @@
<?php
use App\Database\DB;
use App\Web\Session;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use Slim\App;
use Slim\Container;
// Load the config
$config = array_replace_recursive([
'app_name' => 'XBackBone',
'base_url' => isset($_SERVER['HTTPS']) ? 'https://' . $_SERVER['HTTP_HOST'] : 'http://' . $_SERVER['HTTP_HOST'],
'storage_dir' => 'storage',
'debug' => false,
'displayErrorDetails' => false,
'db' => [
'connection' => 'sqlite',
'dsn' => 'resources/database/xbackbone.db',
@ -16,68 +22,68 @@ $config = array_replace_recursive([
],
], require 'config.php');
// Set flight parameters
Flight::set('flight.base_url', $config['base_url']);
Flight::set('flight.log_errors', false);
Flight::set('config', $config);
$container = new Container(['settings' => $config]);
$container['logger'] = function ($container) {
$logger = new Logger('app');
$streamHandler = new RotatingFileHandler(__DIR__ . '/../logs/log.txt', 10, Logger::DEBUG);
$streamHandler->setFormatter(new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n", "Y-m-d H:i:s", true));
$logger->pushHandler($streamHandler);
return $logger;
};
// Set the database dsn
DB::setDsn($config['db']['connection'] . ':' . $config['db']['dsn'], $config['db']['username'], $config['db']['password']);
// Register the Twig instance
Flight::register('view', 'Twig_Environment',
[new Twig_Loader_Filesystem('resources/templates'),
[
'cache' => 'resources/cache',
'autoescape' => 'html',
'debug' => $config['debug'],
'auto_reload' => $config['debug'],
]
]
);
$container['database'] = function ($container) use (&$config) {
return DB::getInstance();
};
// Redirect back helper
Flight::register('redirectBack', function () {
Flight::redirect(Flight::request()->referrer);
});
// Map the render call to the Twig view instance
Flight::map('render', function (string $template, array $data = []) use (&$config) {
Flight::view()->addGlobal('config', $config);
Flight::view()->addGlobal('request', Flight::request());
Flight::view()->addGlobal('alerts', App\Web\Session::getAlert());
Flight::view()->addGlobal('session', App\Web\Session::all());
Flight::view()->addGlobal('PLATFORM_VERSION', PLATFORM_VERSION);
Flight::view()->display($template, $data);
});
$container['view'] = function ($container) use (&$config) {
$view = new \Slim\Views\Twig('resources/templates', [
'cache' => 'resources/cache',
'autoescape' => 'html',
'debug' => $config['displayErrorDetails'],
'auto_reload' => $config['displayErrorDetails'],
]);
// The application error handler
Flight::map('error', function (Exception $exception) {
if ($exception instanceof \App\Exceptions\AuthenticationException) {
Flight::redirect('/login');
return;
}
// Instantiate and add Slim specific extension
$router = $container->get('router');
$uri = \Slim\Http\Uri::createFromEnvironment(new \Slim\Http\Environment($_SERVER));
$view->addExtension(new Slim\Views\TwigExtension($router, $uri));
if ($exception instanceof \App\Exceptions\UnauthorizedException) {
Flight::response()->status(403);
Flight::render('errors/403.twig');
return;
}
$view->getEnvironment()->addGlobal('config', $config);
$view->getEnvironment()->addGlobal('request', $container->get('request'));
$view->getEnvironment()->addGlobal('alerts', Session::getAlert());
$view->getEnvironment()->addGlobal('session', Session::all());
$view->getEnvironment()->addGlobal('PLATFORM_VERSION', PLATFORM_VERSION);
return $view;
};
if ($exception instanceof \App\Exceptions\NotFoundException) {
Flight::response()->status(404);
Flight::render('errors/404.twig');
return;
}
$container['errorHandler'] = function ($container) {
return function (\Slim\Http\Request $request, \Slim\Http\Response $response, $exception) use (&$container) {
Flight::response()->status(500);
\App\Web\Log::critical('Fatal error during app execution', [$exception->getTraceAsString()]);
Flight::render('errors/500.twig', ['exception' => $exception]);
});
if ($exception instanceof \App\Exceptions\UnauthorizedException) {
return $container->view->render($response->withStatus(403), 'errors/403.twig');
}
Flight::map('notFound', function () {
Flight::render('errors/404.twig');
});
$container->logger->critical('Fatal error during app execution', [$exception, $exception->getTraceAsString()]);
return $container->view->render($response->withStatus(500), 'errors/500.twig', ['exception' => $exception]);
};
};
$container['notFoundHandler'] = function ($container) {
return function (\Slim\Http\Request $request, \Slim\Http\Response $response) use (&$container) {
$response->withStatus(404)->withHeader('Content-Type', 'text/html');
return $container->view->render($response, 'errors/404.twig');
};
};
$app = new App($container);
// Load the application routes
require 'app/routes.php';

View file

@ -1,14 +1,14 @@
{
"name": "sergix44/xbackbone",
"version": "1.3",
"version": "2.0",
"description": "A lightweight ShareX PHP backend",
"type": "project",
"require": {
"mikecao/flight": "^1.3",
"league/flysystem": "^1.0.45",
"twig/twig": "~2.0",
"monolog/monolog": "^1.23",
"php": ">=7.1",
"slim/slim": "^3.0",
"slim/twig-view": "^2.4",
"league/flysystem": "^1.0.45",
"monolog/monolog": "^1.23",
"ext-json": "*",
"ext-gd": "*",
"ext-pdo": "*"

429
composer.lock generated
View file

@ -4,20 +4,51 @@
"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": "b2d12fd3c7627ea2da10cbb0bc3cc35d",
"content-hash": "c1434c69fd6bdbfc70c7ca0b185ffdc9",
"packages": [
{
"name": "league/flysystem",
"version": "1.0.47",
"name": "container-interop/container-interop",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
"reference": "a11e4a75f256bdacf99d20780ce42d3b8272975c"
"url": "https://github.com/container-interop/container-interop.git",
"reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a11e4a75f256bdacf99d20780ce42d3b8272975c",
"reference": "a11e4a75f256bdacf99d20780ce42d3b8272975c",
"url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8",
"reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8",
"shasum": ""
},
"require": {
"psr/container": "^1.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Interop\\Container\\": "src/Interop/Container/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Promoting the interoperability of container objects (DIC, SL, etc.)",
"homepage": "https://github.com/container-interop/container-interop",
"time": "2017-02-14T19:40:03+00:00"
},
{
"name": "league/flysystem",
"version": "1.0.48",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
"reference": "a6ded5b2f6055e2db97b4b859fdfca2b952b78aa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a6ded5b2f6055e2db97b4b859fdfca2b952b78aa",
"reference": "a6ded5b2f6055e2db97b4b859fdfca2b952b78aa",
"shasum": ""
},
"require": {
@ -88,63 +119,20 @@
"sftp",
"storage"
],
"time": "2018-09-14T15:30:29+00:00"
},
{
"name": "mikecao/flight",
"version": "v1.3.5",
"source": {
"type": "git",
"url": "https://github.com/mikecao/flight.git",
"reference": "e146b8c0ddbc6384c5a66f23dea71743fc282289"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mikecao/flight/zipball/e146b8c0ddbc6384c5a66f23dea71743fc282289",
"reference": "e146b8c0ddbc6384c5a66f23dea71743fc282289",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "~4.6"
},
"type": "library",
"autoload": {
"files": [
"flight/autoload.php",
"flight/Flight.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike Cao",
"email": "mike@mikecao.com",
"homepage": "http://www.mikecao.com/",
"role": "Original Developer"
}
],
"description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications.",
"homepage": "http://flightphp.com",
"time": "2017-10-20T01:46:56+00:00"
"time": "2018-10-15T13:53:10+00:00"
},
{
"name": "monolog/monolog",
"version": "1.23.0",
"version": "1.24.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4"
"reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
"reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
"reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
"shasum": ""
},
"require": {
@ -209,7 +197,202 @@
"logging",
"psr-3"
],
"time": "2017-06-19T01:22:40+00:00"
"time": "2018-11-05T09:00:11+00:00"
},
{
"name": "nikic/fast-route",
"version": "v1.3.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/FastRoute.git",
"reference": "181d480e08d9476e61381e04a71b34dc0432e812"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812",
"reference": "181d480e08d9476e61381e04a71b34dc0432e812",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35|~5.7"
},
"type": "library",
"autoload": {
"psr-4": {
"FastRoute\\": "src/"
},
"files": [
"src/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Nikita Popov",
"email": "nikic@php.net"
}
],
"description": "Fast request router for PHP",
"keywords": [
"router",
"routing"
],
"time": "2018-02-13T20:26:39+00:00"
},
{
"name": "pimple/pimple",
"version": "v3.2.3",
"source": {
"type": "git",
"url": "https://github.com/silexphp/Pimple.git",
"reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32",
"reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"psr/container": "^1.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^3.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2.x-dev"
}
},
"autoload": {
"psr-0": {
"Pimple": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"description": "Pimple, a simple Dependency Injection Container",
"homepage": "http://pimple.sensiolabs.org",
"keywords": [
"container",
"dependency injection"
],
"time": "2018-01-21T07:42:36+00:00"
},
{
"name": "psr/container",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common Container Interface (PHP FIG PSR-11)",
"homepage": "https://github.com/php-fig/container",
"keywords": [
"PSR-11",
"container",
"container-interface",
"container-interop",
"psr"
],
"time": "2017-02-14T16:28:37+00:00"
},
{
"name": "psr/http-message",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "psr/log",
@ -258,9 +441,131 @@
],
"time": "2016-10-10T12:19:37+00:00"
},
{
"name": "slim/slim",
"version": "3.11.0",
"source": {
"type": "git",
"url": "https://github.com/slimphp/Slim.git",
"reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
"reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
"shasum": ""
},
"require": {
"container-interop/container-interop": "^1.2",
"nikic/fast-route": "^1.0",
"php": ">=5.5.0",
"pimple/pimple": "^3.0",
"psr/container": "^1.0",
"psr/http-message": "^1.0"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"phpunit/phpunit": "^4.0",
"squizlabs/php_codesniffer": "^2.5"
},
"type": "library",
"autoload": {
"psr-4": {
"Slim\\": "Slim"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Rob Allen",
"email": "rob@akrabat.com",
"homepage": "http://akrabat.com"
},
{
"name": "Josh Lockhart",
"email": "hello@joshlockhart.com",
"homepage": "https://joshlockhart.com"
},
{
"name": "Gabriel Manricks",
"email": "gmanricks@me.com",
"homepage": "http://gabrielmanricks.com"
},
{
"name": "Andrew Smith",
"email": "a.smith@silentworks.co.uk",
"homepage": "http://silentworks.co.uk"
}
],
"description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs",
"homepage": "https://slimframework.com",
"keywords": [
"api",
"framework",
"micro",
"router"
],
"time": "2018-09-16T10:54:21+00:00"
},
{
"name": "slim/twig-view",
"version": "2.4.0",
"source": {
"type": "git",
"url": "https://github.com/slimphp/Twig-View.git",
"reference": "78386c01a97f7870462b38fff759dad649da9efc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/slimphp/Twig-View/zipball/78386c01a97f7870462b38fff759dad649da9efc",
"reference": "78386c01a97f7870462b38fff759dad649da9efc",
"shasum": ""
},
"require": {
"php": ">=5.5.0",
"psr/http-message": "^1.0",
"twig/twig": "^1.18|^2.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8|^5.7",
"slim/slim": "^3.10"
},
"type": "library",
"autoload": {
"psr-4": {
"Slim\\Views\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Josh Lockhart",
"email": "hello@joshlockhart.com",
"homepage": "http://joshlockhart.com"
}
],
"description": "Slim Framework 3 view helper built on top of the Twig 2 templating component",
"homepage": "http://slimframework.com",
"keywords": [
"framework",
"slim",
"template",
"twig",
"view"
],
"time": "2018-05-07T10:54:29+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.9.0",
"version": "v1.10.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
@ -318,16 +623,16 @@
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.9.0",
"version": "v1.10.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8"
"reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8",
"reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
"reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
"shasum": ""
},
"require": {
@ -373,7 +678,7 @@
"portable",
"shim"
],
"time": "2018-08-06T14:22:27+00:00"
"time": "2018-09-21T13:07:52+00:00"
},
{
"name": "twig/twig",

View file

@ -5,4 +5,4 @@ define('PLATFORM_VERSION', json_decode(file_get_contents('composer.json'))->vers
require 'bootstrap/app.php';
Flight::start();
$app->run();

View file

@ -12,7 +12,7 @@
</div>
</div>
</div>
{% if config.debug %}
{% if config.displayErrorDetails %}
<div class="row">
<div class="col-md-12">

View file

@ -12,7 +12,7 @@
<div class="card">
<div class="card-header">Create User</div>
<div class="card-body">
<form method="post" action="{{ config.base_url }}/user/add">
<form method="post" action="{{ config.base_url }}/user/create">
<div class="form-group row">
<label for="email" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">

View file

@ -7,7 +7,7 @@
<div class="container">
{% include 'comp/alert.twig' %}
<div class="text-right">
<a href="{{ config.base_url }}/user/add" class="btn btn-outline-success mb-3"><i class="fas fa-plus"></i>
<a href="{{ config.base_url }}/user/create" class="btn btn-outline-success mb-3"><i class="fas fa-plus"></i>
Add User</a>
</div>
<div class="table-responsive">