Bläddra i källkod

Bug fixes
added route caching
added helpers
improved redirecting

Sergio Brighenti 6 år sedan
förälder
incheckning
5c362d7e26

+ 10 - 28
app/Controllers/Controller.php

@@ -7,7 +7,6 @@ use League\Flysystem\Adapter\Local;
 use League\Flysystem\FileNotFoundException;
 use League\Flysystem\Filesystem;
 use Slim\Container;
-use Slim\Http\Request;
 use Slim\Http\Response;
 
 abstract class Controller
@@ -34,20 +33,6 @@ abstract class Controller
 		return null;
 	}
 
-
-	/**
-	 * Generate a human readable file size
-	 * @param $size
-	 * @param int $precision
-	 * @return string
-	 */
-	protected function humanFilesize($size, $precision = 2): string
-	{
-		for ($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {
-		}
-		return round($size, $precision) . ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i];
-	}
-
 	/**
 	 * Get a filesystem instance
 	 * @return Filesystem
@@ -57,19 +42,6 @@ abstract class Controller
 		return new Filesystem(new Local($this->settings['storage_dir']));
 	}
 
-	/**
-	 * @param $path
-	 */
-	protected function removeDirectory($path)
-	{
-		$files = glob($path . '/*');
-		foreach ($files as $file) {
-			is_dir($file) ? $this->removeDirectory($file) : unlink($file);
-		}
-		rmdir($path);
-		return;
-	}
-
 	/**
 	 * @param $id
 	 * @return int
@@ -90,4 +62,14 @@ abstract class Controller
 
 		return $totalSize;
 	}
+
+	/**
+	 * @param Response $response
+	 * @param string $path
+	 * @return Response
+	 */
+	function redirectTo(Response $response, string $path): Response
+	{
+		return $response->withRedirect($this->settings['base_url'] . $path);
+	}
 }

+ 5 - 5
app/Controllers/DashboardController.php

@@ -24,10 +24,10 @@ class DashboardController extends Controller
 
 		if ($request->getParam('afterInstall') !== null && is_dir('install')) {
 			Session::alert('Installation completed successfully!', 'success');
-			$this->removeDirectory('install');
+			removeDirectory('install');
 		}
 
-		return $response->withRedirect('/home');
+		return $this->redirectTo($response,'/home');
 	}
 
 	/**
@@ -61,7 +61,7 @@ class DashboardController extends Controller
 			}
 			$media->mimetype = $mime;
 			$media->extension = $extension;
-			$media->size = $this->humanFilesize($size);
+			$media->size = humanFileSize($size);
 		}
 
 		return $this->view->render(
@@ -101,7 +101,7 @@ class DashboardController extends Controller
 			'usersCount' => $usersCount,
 			'mediasCount' => $mediasCount,
 			'orphanFilesCount' => $orphanFilesCount,
-			'totalSize' => $this->humanFilesize($totalSize),
+			'totalSize' => humanFileSize($totalSize),
 			'post_max_size' => ini_get('post_max_size'),
 			'upload_max_filesize' => ini_get('upload_max_filesize'),
 		]);
@@ -129,6 +129,6 @@ class DashboardController extends Controller
 	public function applyTheme(Request $request, Response $response): Response
 	{
 		file_put_contents('static/bootstrap/css/bootstrap.min.css', file_get_contents($request->getParam('css')));
-		return $response->withRedirect('/system')->withAddedHeader('Cache-Control', 'no-cache, must-revalidate');
+		return $this->redirectTo($response,'/system')->withAddedHeader('Cache-Control', 'no-cache, must-revalidate');
 	}
 }

+ 6 - 6
app/Controllers/LoginController.php

@@ -19,7 +19,7 @@ class LoginController extends Controller
 	public function show(Request $request, Response $response): Response
 	{
 		if (Session::get('logged', false)) {
-			return $response->withRedirect('/home');
+			return $this->redirectTo($response, '/home');
 		}
 		return $this->view->render($response, 'auth/login.twig');
 	}
@@ -36,19 +36,19 @@ class LoginController extends Controller
 
 		if (!$result || !password_verify($request->getParam('password'), $result->password)) {
 			Session::alert('Wrong credentials', 'danger');
-			return $response->withRedirect('/login');
+			return $this->redirectTo($response, '/login');
 		}
 
 		if (!$result->active) {
 			Session::alert('Your account is disabled.', 'danger');
-			return $response->withRedirect('/login');
+			return $this->redirectTo($response, '/login');
 		}
 
 		Session::set('logged', true);
 		Session::set('user_id', $result->id);
 		Session::set('username', $result->username);
 		Session::set('admin', $result->is_admin);
-		Session::set('used_space', $this->humanFilesize($this->getUsedSpaceByUser($result->id)));
+		Session::set('used_space', humanFileSize($this->getUsedSpaceByUser($result->id)));
 
 		Session::alert("Welcome, $result->username!", 'info');
 		$this->logger->info("User $result->username logged in.");
@@ -57,7 +57,7 @@ class LoginController extends Controller
 			return $response->withRedirect(Session::get('redirectTo'));
 		}
 
-		return $response->withRedirect('/home');
+		return $this->redirectTo($response,'/home');
 	}
 
 	/**
@@ -70,7 +70,7 @@ class LoginController extends Controller
 		Session::clear();
 		Session::set('logged', false);
 		Session::alert('Goodbye!', 'warning');
-		return $response->withRedirect('/login');
+		return $this->redirectTo($response,'/login');
 	}
 
 }

+ 1 - 1
app/Controllers/UploadController.php

@@ -223,7 +223,7 @@ class UploadController extends Controller
 			} finally {
 				$this->database->query('DELETE FROM `uploads` WHERE `id` = ?', $args['id']);
 				$this->logger->info('User ' . Session::get('username') . ' deleted a media.', [$args['id']]);
-				Session::set('used_space', $this->humanFilesize($this->getUsedSpaceByUser(Session::get('user_id'))));
+				Session::set('used_space', humanFileSize($this->getUsedSpaceByUser(Session::get('user_id'))));
 			}
 		} else {
 			throw new UnauthorizedException();

+ 14 - 14
app/Controllers/UserController.php

@@ -58,22 +58,22 @@ class UserController extends Controller
 	{
 		if ($request->getParam('email') === null) {
 			Session::alert('The email is required.', 'danger');
-			return $response->withRedirect('/user/create');
+			return $this->redirectTo($response,'/user/create');
 		}
 
 		if ($request->getParam('username') === null) {
 			Session::alert('The username is required.', 'danger');
-			return $response->withRedirect('/user/create');
+			return $this->redirectTo($response,'/user/create');
 		}
 
 		if ($request->getParam('password') === null) {
 			Session::alert('The password is required.', 'danger');
-			return $response->withRedirect('/user/create');
+			return $this->redirectTo($response,'/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');
+			return $this->redirectTo($response,'/user/create');
 		}
 
 		do {
@@ -95,7 +95,7 @@ class UserController extends Controller
 		Session::alert("User '{$request->getParam('username')}' created!", 'success');
 		$this->logger->info('User ' . Session::get('username') . ' created a new user.', [array_diff($request->getParams(), ['password'])]);
 
-		return $response->withRedirect('/users');
+		return $this->redirectTo($response,'/users');
 	}
 
 	/**
@@ -135,22 +135,22 @@ class UserController extends Controller
 
 		if ($request->getParam('email') === null) {
 			Session::alert('The email is required.', 'danger');
-			return $response->withRedirect('/user/' . $args['id'] . '/edit');
+			return $this->redirectTo($response,'/user/' . $args['id'] . '/edit');
 		}
 
 		if ($request->getParam('username') === null) {
 			Session::alert('The username is required.', 'danger');
-			return $response->withRedirect('/user/' . $args['id'] . '/edit');
+			return $this->redirectTo($response,'/user/' . $args['id'] . '/edit');
 		}
 
 		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');
-			return $response->withRedirect('/user/' . $args['id'] . '/edit');
+			return $this->redirectTo($response,'/user/' . $args['id'] . '/edit');
 		}
 
 		if ($user->id === Session::get('user_id') && $request->getParam('is_admin') === null) {
 			Session::alert('You cannot demote yourself.', 'danger');
-			return $response->withRedirect('/user/' . $args['id'] . '/edit');
+			return $this->redirectTo($response,'/user/' . $args['id'] . '/edit');
 		}
 
 		if ($request->getParam('password') !== null && !empty($request->getParam('password'))) {
@@ -175,7 +175,7 @@ class UserController extends Controller
 		Session::alert("User '{$request->getParam('username')}' updated!", 'success');
 		$this->logger->info('User ' . Session::get('username') . " updated $user->id.", [$user, array_diff($request->getParams(), ['password'])]);
 
-		return $response->withRedirect('/users');
+		return $this->redirectTo($response,'/users');
 
 	}
 
@@ -196,7 +196,7 @@ class UserController extends Controller
 
 		if ($user->id === Session::get('user_id')) {
 			Session::alert('You cannot delete yourself.', 'danger');
-			return $response->withRedirect('/users');
+			return $this->redirectTo($response,'/users');
 		}
 
 		$this->database->query('DELETE FROM `users` WHERE `id` = ?', $user->id);
@@ -204,7 +204,7 @@ class UserController extends Controller
 		Session::alert('User deleted.', 'success');
 		$this->logger->info('User ' . Session::get('username') . " deleted $user->id.");
 
-		return $response->withRedirect('/users');
+		return $this->redirectTo($response,'/users');
 	}
 
 	/**
@@ -253,7 +253,7 @@ class UserController extends Controller
 
 		if ($request->getParam('email') === null) {
 			Session::alert('The email is required.', 'danger');
-			return $response->withRedirect('/profile');
+			return $this->redirectTo($response,'/profile');
 		}
 
 		if ($request->getParam('password') !== null && !empty($request->getParam('password'))) {
@@ -272,7 +272,7 @@ class UserController extends Controller
 		Session::alert('Profile updated successfully!', 'success');
 		$this->logger->info('User ' . Session::get('username') . " updated profile of $user->id.");
 
-		return $response->withRedirect('/profile');
+		return $this->redirectTo($response,'/profile');
 	}
 
 	/**

+ 2 - 2
app/Middleware/AuthMiddleware.php

@@ -28,14 +28,14 @@ class AuthMiddleware
 	{
 		if (!Session::get('logged', false)) {
 			Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? 'https' : 'http') . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
-			return $response->withRedirect('/login');
+			return $response->withRedirect($this->container->settings['base_url'] . '/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 $response->withRedirect($this->container->settings['base_url'] . '/login');
 		}
 
 		return $next($request, $response);

+ 51 - 0
app/helpers.php

@@ -0,0 +1,51 @@
+<?php
+
+require __DIR__ . '/../vendor/autoload.php';
+
+if (!function_exists('humanFileSize')) {
+	/**
+	 * Generate a human readable file size
+	 * @param $size
+	 * @param int $precision
+	 * @return string
+	 */
+	function humanFileSize($size, $precision = 2): string
+	{
+		for ($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {
+		}
+		return round($size, $precision) . ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i];
+	}
+}
+
+if (!function_exists('removeDirectory')) {
+	/**
+	 * Remove a directory and it's content
+	 * @param $path
+	 */
+	function removeDirectory($path)
+	{
+		$files = glob($path . '/*');
+		foreach ($files as $file) {
+			is_dir($file) ? removeDirectory($file) : unlink($file);
+		}
+		rmdir($path);
+		return;
+	}
+}
+
+if (!function_exists('cleanDirectory')) {
+	/**
+	 * Removes all directory contents
+	 * @param $path
+	 */
+	function cleanDirectory($path)
+	{
+		$directoryIterator = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
+		$iteratorIterator = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::CHILD_FIRST);
+		foreach ($iteratorIterator as $file) {
+			if ($file->getFilename() !== '.gitkeep') {
+				$file->isDir() ? rmdir($file) : unlink($file);
+			}
+		}
+	}
+}

+ 4 - 15
bin/clean

@@ -7,29 +7,18 @@ if (php_sapi_name() !== 'cli') {
 	die();
 }
 
-function cleanDir($path)
-{
-	$directoryIterator = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
-	$iteratorIterator = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::CHILD_FIRST);
-	foreach ($iteratorIterator as $file) {
-		if ($file->getFilename() !== '.gitkeep') {
-			$file->isDir() ? rmdir($file) : unlink($file);
-		}
-	}
-}
-
 $action = isset($argv[1]) ? $argv[1] : 'all';
 
 switch ($action) {
 	case 'cache':
-		cleanDir(__DIR__ . '/../resources/cache');
+		cleanDirectory(__DIR__ . '/../resources/cache');
 		break;
 	case 'sessions':
-		cleanDir(__DIR__ . '/../resources/sessions');
+		cleanDirectory(__DIR__ . '/../resources/sessions');
 		break;
 	case 'all':
-		cleanDir(__DIR__ . '/../resources/cache');
-		cleanDir(__DIR__ . '/../resources/sessions');
+		cleanDirectory(__DIR__ . '/../resources/cache');
+		cleanDirectory(__DIR__ . '/../resources/sessions');
 		break;
 	case 'help':
 	default:

+ 27 - 7
bootstrap/app.php

@@ -9,7 +9,7 @@ use Slim\App;
 use Slim\Container;
 
 if (!file_exists('config.php') && is_dir('install/')) {
-	header('Location: /install/');
+	header('Location: ./install/');
 	exit();
 } else if (!file_exists('config.php') && !is_dir('install/')) {
 	die('Cannot find the config file.');
@@ -23,11 +23,12 @@ $config = array_replace_recursive([
 	'displayErrorDetails' => false,
 	'db' => [
 		'connection' => 'sqlite',
-		'dsn' => 'resources/database/xbackbone.db',
+		'dsn' => __DIR__ . '/../resources/database/xbackbone.db',
 		'username' => null,
 		'password' => null,
 	],
-], require 'config.php');
+	'routerCacheFile' => __DIR__ . '/../resources/cache/routes.cache.php',
+], require __DIR__ . '/../config.php');
 
 $container = new Container(['settings' => $config]);
 
@@ -43,10 +44,11 @@ $container['logger'] = function ($container) {
 };
 
 // Session init
-Session::init('xbackbone_session', 'resources/sessions');
+Session::init('xbackbone_session', __DIR__ . '/../resources/sessions');
 
 // Set the database dsn
-DB::setDsn($config['db']['connection'] . ':' . $config['db']['dsn'], $config['db']['username'], $config['db']['password']);
+$dsn = $config['db']['connection'] === 'sqlite' ? __DIR__ . '/../' . $config['db']['dsn'] : $config['db']['dsn'];
+DB::setDsn($config['db']['connection'] . ':' . $dsn, $config['db']['username'], $config['db']['password']);
 
 $container['database'] = function ($container) use (&$config) {
 	return DB::getInstance();
@@ -54,8 +56,8 @@ $container['database'] = function ($container) use (&$config) {
 
 
 $container['view'] = function ($container) use (&$config) {
-	$view = new \Slim\Views\Twig('resources/templates', [
-		'cache' => 'resources/cache',
+	$view = new \Slim\Views\Twig(__DIR__ . '/../resources/templates', [
+		'cache' => __DIR__ . '/../resources/cache',
 		'autoescape' => 'html',
 		'debug' => $config['displayErrorDetails'],
 		'auto_reload' => $config['displayErrorDetails'],
@@ -95,5 +97,23 @@ $container['notFoundHandler'] = function ($container) {
 
 $app = new App($container);
 
+// Permanently redirect paths with a trailing slash to their non-trailing counterpart
+$app->add(function (\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next) {
+	$uri = $request->getUri();
+	$path = $uri->getPath();
+
+	if ($path !== '/' && substr($path, -1) === '/') {
+		$uri = $uri->withPath(substr($path, 0, -1));
+
+		if ($request->getMethod() === 'GET') {
+			return $response->withRedirect((string)$uri, 301);
+		} else {
+			return $next($request->withUri($uri), $response);
+		}
+	}
+
+	return $next($request, $response);
+});
+
 // Load the application routes
 require 'app/routes.php';

+ 3 - 0
composer.json

@@ -14,6 +14,9 @@
     "ext-pdo": "*"
   },
   "autoload": {
+    "files": [
+      "app/helpers.php"
+    ],
     "psr-4": {
       "App\\": "app/"
     }

+ 13 - 18
install/index.php

@@ -11,7 +11,6 @@ use Slim\Http\Response;
 define('PLATFORM_VERSION', json_decode(file_get_contents(__DIR__ . '/../composer.json'))->version);
 
 $config = [
-	'app_name' => 'XBackBone',
 	'base_url' => isset($_SERVER['HTTPS']) ? 'https://' . $_SERVER['HTTP_HOST'] : 'http://' . $_SERVER['HTTP_HOST'],
 	'storage_dir' => 'storage',
 	'displayErrorDetails' => true,
@@ -28,7 +27,7 @@ $container = new Container(['settings' => $config]);
 Session::init('xbackbone_session');
 
 $container['view'] = function ($container) use (&$config) {
-	$view = new \Slim\Views\Twig('templates', [
+	$view = new \Slim\Views\Twig(__DIR__ . '/templates', [
 		'cache' => false,
 		'autoescape' => 'html',
 		'debug' => $config['displayErrorDetails'],
@@ -51,7 +50,7 @@ $container['view'] = function ($container) use (&$config) {
 function migrate($config)
 {
 	$firstMigrate = false;
-	if (!file_exists(__DIR__ . '/../' . $config['db']['dsn']) && DB::driver() === 'sqlite') {
+	if ($config['db']['connection'] === 'sqlite' && !file_exists(__DIR__ . '/../' . $config['db']['dsn'])) {
 		touch(__DIR__ . '/../' . $config['db']['dsn']);
 		$firstMigrate = true;
 	}
@@ -110,17 +109,6 @@ function migrate($config)
 	}
 }
 
-function cleanDir($path)
-{
-	$directoryIterator = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
-	$iteratorIterator = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::CHILD_FIRST);
-	foreach ($iteratorIterator as $file) {
-		if ($file->getFilename() !== '.gitkeep') {
-			$file->isDir() ? rmdir($file) : unlink($file);
-		}
-	}
-}
-
 $app = new App($container);
 
 $app->get('/', function (Request $request, Response $response) {
@@ -131,7 +119,10 @@ $app->get('/', function (Request $request, Response $response) {
 });
 
 $app->post('/', function (Request $request, Response $response) use (&$config) {
+	$installed = true;
 	if (!file_exists(__DIR__ . '/../config.php')) {
+		$installed = false;
+
 		$config['base_url'] = $request->getParam('base_url');
 		$config['storage_dir'] = $request->getParam('storage_dir');
 		$config['displayErrorDetails'] = false;
@@ -144,14 +135,18 @@ $app->post('/', function (Request $request, Response $response) use (&$config) {
 		file_put_contents(__DIR__ . '/../config.php', '<?php' . PHP_EOL . 'return ' . var_export($config, true) . ';');
 	}
 
-	DB::setDsn($config['db']['connection'] . ':' . __DIR__ . '/../' . $config['db']['dsn'], $config['db']['username'], $config['db']['password']);
+	$dsn = $config['db']['connection'] === 'sqlite' ? __DIR__ . '/../' . $config['db']['dsn'] : $config['db']['dsn'];
+
+	DB::setDsn($config['db']['connection'] . ':' . $dsn, $config['db']['username'], $config['db']['password']);
 
 	migrate($config);
 
-	DB::query("INSERT INTO `users` (`email`, `username`, `password`, `is_admin`, `user_code`) VALUES (?, 'admin', ?, 1, ?)", [$request->getParam('email'), password_hash($request->getParam('password'), PASSWORD_DEFAULT), substr(md5(microtime()), rand(0, 26), 5)]);
+	if (!$installed) {
+		DB::query("INSERT INTO `users` (`email`, `username`, `password`, `is_admin`, `user_code`) VALUES (?, 'admin', ?, 1, ?)", [$request->getParam('email'), password_hash($request->getParam('password'), PASSWORD_DEFAULT), substr(md5(microtime()), rand(0, 26), 5)]);
+	}
 
-	cleanDir(__DIR__ . '/../resources/cache');
-	cleanDir(__DIR__ . '/../resources/sessions');
+	cleanDirectory(__DIR__ . '/../resources/cache');
+	cleanDirectory(__DIR__ . '/../resources/sessions');
 
 	return $response->withRedirect('../?afterInstall=true');
 });

+ 9 - 9
install/templates/install.twig

@@ -6,16 +6,16 @@
     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
     <meta name="description" content="A lightweight PHP backend for ShareX">
 
-    <link href="{{ config.base_url }}/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
-    <link href="{{ config.base_url }}/static/highlightjs/styles/monokai.css" rel="stylesheet">
-    <link href="{{ config.base_url }}/static/app/app.css" rel="stylesheet">
+    <link href="{{ request.uri }}../static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
+    <link href="{{ request.uri }}../static/highlightjs/styles/monokai.css" rel="stylesheet">
+    <link href="{{ request.uri }}../static/app/app.css" rel="stylesheet">
 
-    <script src="{{ config.base_url }}/static/jquery/jquery.min.js"></script>
-    <script src="{{ config.base_url }}/static/bootstrap/js/bootstrap.bundle.min.js"></script>
-    <script src="{{ config.base_url }}/static/fontawesome/js/all.min.js"></script>
-    <script src="{{ config.base_url }}/static/highlightjs/highlight.pack.min.js"></script>
-    <script src="{{ config.base_url }}/static/clipboardjs/clipboard.min.js"></script>
-    <script src="{{ config.base_url }}/static/app/app.js"></script>
+    <script src="{{ request.uri }}../static/jquery/jquery.min.js"></script>
+    <script src="{{ request.uri }}../static/bootstrap/js/bootstrap.bundle.min.js"></script>
+    <script src="{{ request.uri }}../static/fontawesome/js/all.min.js"></script>
+    <script src="{{ request.uri }}../static/highlightjs/highlight.pack.min.js"></script>
+    <script src="{{ request.uri }}../static/clipboardjs/clipboard.min.js"></script>
+    <script src="{{ request.uri }}../static/app/app.js"></script>
     <style>
         html,
         body {