Jelajahi Sumber

Completed multilang
Routes rework

Sergio Brighenti 6 tahun lalu
induk
melakukan
a950ec9af3

+ 6 - 0
CHANGELOG.md

@@ -1,3 +1,9 @@
+## v2.2
++ Added multi-language support.
++ Improved routing.
++ Fixed HTTP/2 push is resetting the current session.
++ Minor improvements and bug fixes.
+
 ## v2.1
 + Improved theme style.
 + Improved page redirecting.

+ 4 - 3
app/Controllers/DashboardController.php

@@ -23,11 +23,11 @@ class DashboardController extends Controller
 	{
 
 		if ($request->getParam('afterInstall') !== null && is_dir('install')) {
-			Session::alert('Installation completed successfully!', 'success');
+			Session::alert(lang('installed'), 'success');
 			removeDirectory('install');
 		}
 
-		return redirect($response, '/home');
+		return redirect($response, 'home');
 	}
 
 	/**
@@ -117,6 +117,7 @@ class DashboardController extends Controller
 
 		$out = [];
 
+		$out['Default - Bootstrap 4 default theme'] = 'https://bootswatch.com/_vendor/bootstrap/dist/css/bootstrap.min.css';
 		foreach ($apiJson->themes as $theme) {
 			$out["{$theme->name} - {$theme->description}"] = $theme->cssMin;
 		}
@@ -128,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 redirect($response, '/system')->withAddedHeader('Cache-Control', 'no-cache, must-revalidate');
+		return redirect($response, 'system')->withAddedHeader('Cache-Control', 'no-cache, must-revalidate');
 	}
 }

+ 9 - 9
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 redirect($response, '/home');
+			return redirect($response, 'home');
 		}
 		return $this->view->render($response, 'auth/login.twig');
 	}
@@ -35,13 +35,13 @@ class LoginController extends Controller
 		$result = DB::query('SELECT `id`, `email`, `username`, `password`,`is_admin`, `active` FROM `users` WHERE `username` = ? OR `email` = ? LIMIT 1', [$request->getParam('username'), $request->getParam('username')])->fetch();
 
 		if (!$result || !password_verify($request->getParam('password'), $result->password)) {
-			Session::alert('Wrong credentials', 'danger');
-			return redirect($response, '/login');
+			Session::alert(lang('bad_login'), 'danger');
+			return redirect($response, 'login');
 		}
 
 		if (!$result->active) {
-			Session::alert('Your account is disabled.', 'danger');
-			return redirect($response, '/login');
+			Session::alert(lang('account_disabled'), 'danger');
+			return redirect($response, 'login');
 		}
 
 		Session::set('logged', true);
@@ -50,14 +50,14 @@ class LoginController extends Controller
 		Session::set('admin', $result->is_admin);
 		Session::set('used_space', humanFileSize($this->getUsedSpaceByUser($result->id)));
 
-		Session::alert("Welcome, $result->username!", 'info');
+		Session::alert(lang('welcome', [$result->username]), 'info');
 		$this->logger->info("User $result->username logged in.");
 
 		if (Session::has('redirectTo')) {
 			return $response->withRedirect(Session::get('redirectTo'));
 		}
 
-		return redirect($response, '/home');
+		return redirect($response, 'home');
 	}
 
 	/**
@@ -69,8 +69,8 @@ class LoginController extends Controller
 	{
 		Session::clear();
 		Session::set('logged', false);
-		Session::alert('Goodbye!', 'warning');
-		return redirect($response, '/login');
+		Session::alert(lang('goodbye'), 'warning');
+		return redirect($response, 'login.show');
 	}
 
 }

+ 3 - 3
app/Controllers/UploadController.php

@@ -139,12 +139,12 @@ class UploadController extends Controller
 		$user = $this->database->query('SELECT `id`, `active` FROM `users` WHERE `token` = ? LIMIT 1', $args['token'])->fetch();
 
 		if (!$user) {
-			Session::alert('Token specified not found.', 'danger');
+			Session::alert(lang('token_not_found'), 'danger');
 			return $response->withRedirect($request->getHeaderLine('HTTP_REFERER'));
 		}
 
 		if (!$user->active) {
-			Session::alert('Account disabled.', 'danger');
+			Session::alert(lang('account_disabled'), 'danger');
 			return $response->withRedirect($request->getHeaderLine('HTTP_REFERER'));
 		}
 
@@ -163,7 +163,7 @@ class UploadController extends Controller
 			throw new UnauthorizedException();
 		}
 
-		return redirect($response, '/home');
+		return redirect($response, 'home');
 	}
 
 	/**

+ 49 - 34
app/Controllers/UserController.php

@@ -57,23 +57,28 @@ class UserController extends Controller
 	public function store(Request $request, Response $response): Response
 	{
 		if ($request->getParam('email') === null) {
-			Session::alert('The email is required.', 'danger');
-			return redirect($response, '/user/create');
+			Session::alert(lang('email_required'), 'danger');
+			return redirect($response, 'user.create');
+		}
+
+		if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ?', $request->getParam('email'))->fetch()->count > 0) {
+			Session::alert(lang('email_taken'), 'danger');
+			return redirect($response, 'user.create');
 		}
 
 		if ($request->getParam('username') === null) {
-			Session::alert('The username is required.', 'danger');
-			return redirect($response, '/user/create');
+			Session::alert(lang('username_required'), 'danger');
+			return redirect($response, 'user.create');
 		}
 
 		if ($request->getParam('password') === null) {
-			Session::alert('The password is required.', 'danger');
-			return redirect($response, '/user/create');
+			Session::alert(lang('password_required'), 'danger');
+			return redirect($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 redirect($response, '/user/create');
+			Session::alert(lang('username_taken'), 'danger');
+			return redirect($response, 'user.create');
 		}
 
 		do {
@@ -86,16 +91,16 @@ class UserController extends Controller
 			$request->getParam('email'),
 			$request->getParam('username'),
 			password_hash($request->getParam('password'), PASSWORD_DEFAULT),
-			$request->getParam('is_admin') !== null,
-			$request->getParam('is_active') !== null,
+			$request->getParam('is_admin') !== null ? 1 : 0,
+			$request->getParam('is_active') !== null ? 1 : 0,
 			$userCode,
 			$token,
 		]);
 
-		Session::alert("User '{$request->getParam('username')}' created!", 'success');
+		Session::alert(lang('user_created', [$request->getParam('username')]), 'success');
 		$this->logger->info('User ' . Session::get('username') . ' created a new user.', [array_diff($request->getParams(), ['password'])]);
 
-		return redirect($response, '/users');
+		return redirect($response, 'user.index');
 	}
 
 	/**
@@ -135,23 +140,28 @@ class UserController extends Controller
 		}
 
 		if ($request->getParam('email') === null) {
-			Session::alert('The email is required.', 'danger');
-			return redirect($response, '/user/' . $args['id'] . '/edit');
+			Session::alert(lang('email_required'), 'danger');
+			return redirect($response, 'user.edit', ['id' => $args['id']]);
+		}
+
+		if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ? AND `email` <> ?', [$request->getParam('email'), $user->email])->fetch()->count > 0) {
+			Session::alert(lang('email_taken'), 'danger');
+			return redirect($response, 'user.edit', ['id' => $args['id']]);
 		}
 
 		if ($request->getParam('username') === null) {
-			Session::alert('The username is required.', 'danger');
-			return redirect($response, '/user/' . $args['id'] . '/edit');
+			Session::alert(lang('username_required'), 'danger');
+			return redirect($response, 'user.edit', ['id' => $args['id']]);
 		}
 
 		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 redirect($response, '/user/' . $args['id'] . '/edit');
+			Session::alert(lang('username_taken'), 'danger');
+			return redirect($response, 'user.edit', ['id' => $args['id']]);
 		}
 
 		if ($user->id === Session::get('user_id') && $request->getParam('is_admin') === null) {
-			Session::alert('You cannot demote yourself.', 'danger');
-			return redirect($response, '/user/' . $args['id'] . '/edit');
+			Session::alert(lang('cannot_demote'), 'danger');
+			return redirect($response, 'user.edit', ['id' => $args['id']]);
 		}
 
 		if ($request->getParam('password') !== null && !empty($request->getParam('password'))) {
@@ -159,24 +169,24 @@ class UserController extends Controller
 				$request->getParam('email'),
 				$request->getParam('username'),
 				password_hash($request->getParam('password'), PASSWORD_DEFAULT),
-				$request->getParam('is_admin') !== null,
-				$request->getParam('is_active') !== null,
+				$request->getParam('is_admin') !== null ? 1 : 0,
+				$request->getParam('is_active') !== null ? 1 : 0,
 				$user->id,
 			]);
 		} else {
 			$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,
+				$request->getParam('is_admin') !== null ? 1 : 0,
+				$request->getParam('is_active') !== null ? 1 : 0,
 				$user->id,
 			]);
 		}
 
-		Session::alert("User '{$request->getParam('username')}' updated!", 'success');
+		Session::alert(lang('user_updated', [$request->getParam('username')]), 'success');
 		$this->logger->info('User ' . Session::get('username') . " updated $user->id.", [$user, array_diff($request->getParams(), ['password'])]);
 
-		return redirect($response, '/users');
+		return redirect($response, 'user.index');
 
 	}
 
@@ -196,16 +206,16 @@ class UserController extends Controller
 		}
 
 		if ($user->id === Session::get('user_id')) {
-			Session::alert('You cannot delete yourself.', 'danger');
-			return redirect($response, '/users');
+			Session::alert(lang('cannot_delete'), 'danger');
+			return redirect($response, 'user.index');
 		}
 
 		$this->database->query('DELETE FROM `users` WHERE `id` = ?', $user->id);
 
-		Session::alert('User deleted.', 'success');
+		Session::alert(lang('user_deleted'), 'success');
 		$this->logger->info('User ' . Session::get('username') . " deleted $user->id.");
 
-		return redirect($response, '/users');
+		return redirect($response, 'user.index');
 	}
 
 	/**
@@ -254,8 +264,13 @@ class UserController extends Controller
 		}
 
 		if ($request->getParam('email') === null) {
-			Session::alert('The email is required.', 'danger');
-			return redirect($response, '/profile');
+			Session::alert(lang('email_required'), 'danger');
+			return redirect($response, 'profile');
+		}
+
+		if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ? AND `email` <> ?', [$request->getParam('email'), $user->email])->fetch()->count > 0) {
+			Session::alert(lang('email_taken'), 'danger');
+			return redirect($response, 'profile');
 		}
 
 		if ($request->getParam('password') !== null && !empty($request->getParam('password'))) {
@@ -271,10 +286,10 @@ class UserController extends Controller
 			]);
 		}
 
-		Session::alert('Profile updated successfully!', 'success');
+		Session::alert(lang('profile_updated'), 'success');
 		$this->logger->info('User ' . Session::get('username') . " updated profile of $user->id.");
 
-		return redirect($response, '/profile');
+		return redirect($response, 'profile');
 	}
 
 	/**

+ 2 - 2
app/Middleware/AuthMiddleware.php

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

+ 35 - 2
app/helpers.php

@@ -55,12 +55,19 @@ if (!function_exists('redirect')) {
 	 * Set the redirect response
 	 * @param \Slim\Http\Response $response
 	 * @param string $path
+	 * @param array $args
 	 * @param null $status
 	 * @return \Slim\Http\Response
 	 */
-	function redirect(\Slim\Http\Response $response, string $path, $status = null)
+	function redirect(\Slim\Http\Response $response, string $path, $args = [], $status = null)
 	{
-		return $response->withRedirect(urlFor($path), $status);
+		if ($path === '/' || substr($path, 0, 1) === '/') {
+			$url = urlFor($path);
+		} else {
+			$url = route($path, $args);
+		}
+
+		return $response->withRedirect($url, $status);
 	}
 }
 
@@ -78,3 +85,29 @@ if (!function_exists('urlFor')) {
 	}
 }
 
+if (!function_exists('route')) {
+	/**
+	 * Generate the app url given a path
+	 * @param string $path
+	 * @param array $args
+	 * @return string
+	 */
+	function route(string $path, array $args = [])
+	{
+		global $app;
+		$uri = $app->getContainer()->get('router')->pathFor($path, $args);
+		return urlFor($uri);
+	}
+}
+
+if (!function_exists('lang')) {
+	/**
+	 * @param string $key
+	 * @param array $args
+	 * @return string
+	 */
+	function lang(string $key, $args = [])
+	{
+		return \App\Web\Lang::getInstance()->get($key, $args);
+	}
+}

+ 28 - 28
app/routes.php

@@ -1,41 +1,41 @@
 <?php
 // Auth routes
 $app->group('', function () {
-	$this->get('/home[/page/{page}]', \App\Controllers\DashboardController::class . ':home');
-	$this->get('/system', \App\Controllers\DashboardController::class . ':system')->add(\App\Middleware\AdminMiddleware::class);
-	$this->get('/system/themes', \App\Controllers\DashboardController::class . ':getThemes')->add(\App\Middleware\AdminMiddleware::class);
-	$this->post('/system/theme/apply', \App\Controllers\DashboardController::class . ':applyTheme')->add(\App\Middleware\AdminMiddleware::class);
+	$this->get('/home[/page/{page}]', \App\Controllers\DashboardController::class . ':home')->setName('home');
+	$this->get('/system', \App\Controllers\DashboardController::class . ':system')->add(\App\Middleware\AdminMiddleware::class)->setName('system');
+	$this->get('/system/themes', \App\Controllers\DashboardController::class . ':getThemes')->add(\App\Middleware\AdminMiddleware::class)->setName('theme');
+	$this->post('/system/theme/apply', \App\Controllers\DashboardController::class . ':applyTheme')->add(\App\Middleware\AdminMiddleware::class)->setName('theme.apply');
 
 	$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');
+		$this->get('/users[/page/{page}]', \App\Controllers\UserController::class . ':index')->setName('user.index');
+		$this->get('/user/create', \App\Controllers\UserController::class . ':create')->setName('user.create');
+		$this->post('/user/create', \App\Controllers\UserController::class . ':store')->setName('user.store');
+		$this->get('/user/{id}/edit', \App\Controllers\UserController::class . ':edit')->setName('user.edit');
+		$this->post('/user/{id}', \App\Controllers\UserController::class . ':update')->setName('user.update');
+		$this->get('/user/{id}/delete', \App\Controllers\UserController::class . ':delete')->setName('user.delete');
 	})->add(\App\Middleware\AdminMiddleware::class);
 
-	$this->get('/profile', \App\Controllers\UserController::class . ':profile');
-	$this->post('/profile/{id}', \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');
+	$this->get('/profile', \App\Controllers\UserController::class . ':profile')->setName('profile');
+	$this->post('/profile/{id}', \App\Controllers\UserController::class . ':profileEdit')->setName('profile.update');
+	$this->post('/user/{id}/refreshToken', \App\Controllers\UserController::class . ':refreshToken')->setName('refreshToken');
+	$this->get('/user/{id}/config/sharex', \App\Controllers\UserController::class . ':getShareXconfigFile')->setName('config.sharex');
 
-	$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');
+	$this->post('/upload/{id}/publish', \App\Controllers\UploadController::class . ':togglePublish')->setName('upload.publish');
+	$this->post('/upload/{id}/unpublish', \App\Controllers\UploadController::class . ':togglePublish')->setName('upload.unpublish');
+	$this->get('/upload/{id}/raw', \App\Controllers\UploadController::class . ':getRawById')->add(\App\Middleware\AdminMiddleware::class)->setName('upload.raw');
+	$this->post('/upload/{id}/delete', \App\Controllers\UploadController::class . ':delete')->setName('upload.delete');
 
 })->add(\App\Middleware\AuthMiddleware::class);
 
-$app->get('/', \App\Controllers\DashboardController::class . ':redirects');
-$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->get('/', \App\Controllers\DashboardController::class . ':redirects')->setName('root');
+$app->get('/login', \App\Controllers\LoginController::class . ':show')->setName('login.show');
+$app->post('/login', \App\Controllers\LoginController::class . ':login')->setName('login');
+$app->map(['GET', 'POST'], '/logout', \App\Controllers\LoginController::class . ':logout')->setName('logout');
 
-$app->post('/upload', \App\Controllers\UploadController::class . ':upload');
+$app->post('/upload', \App\Controllers\UploadController::class . ':upload')->setName('upload');
 
-$app->get('/{userCode}/{mediaCode}', \App\Controllers\UploadController::class . ':show');
-$app->get('/{userCode}/{mediaCode}/delete/{token}', \App\Controllers\UploadController::class . ':show');
-$app->post('/{userCode}/{mediaCode}/delete/{token}', \App\Controllers\UploadController::class . ':deleteByToken');
-$app->get('/{userCode}/{mediaCode}/raw', \App\Controllers\UploadController::class . ':showRaw');
-$app->get('/{userCode}/{mediaCode}/download', \App\Controllers\UploadController::class . ':download');
+$app->get('/{userCode}/{mediaCode}', \App\Controllers\UploadController::class . ':show')->setName('public');
+$app->get('/{userCode}/{mediaCode}/delete/{token}', \App\Controllers\UploadController::class . ':show')->setName('public.delete.show');
+$app->post('/{userCode}/{mediaCode}/delete/{token}', \App\Controllers\UploadController::class . ':deleteByToken')->setName('public.delete');
+$app->get('/{userCode}/{mediaCode}/raw', \App\Controllers\UploadController::class . ':showRaw')->setName('public.raw');
+$app->get('/{userCode}/{mediaCode}/download', \App\Controllers\UploadController::class . ':download')->setName('public.download');

+ 5 - 2
bootstrap/app.php

@@ -28,7 +28,6 @@ $config = array_replace_recursive([
 		'username' => null,
 		'password' => null,
 	],
-	'lang' => 'en',
 ], require __DIR__ . '/../config.php');
 
 if (!$config['displayErrorDetails']) {
@@ -59,7 +58,7 @@ $container['database'] = function ($container) use (&$config) {
 	return DB::getInstance();
 };
 
-Lang::build($config['lang'], __DIR__. '/../resources/lang/');
+Lang::build(substr(@$_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2), __DIR__. '/../resources/lang/');
 
 $container['lang'] = function ($container) {
 	return Lang::getInstance();
@@ -85,6 +84,10 @@ $container['view'] = function ($container) use (&$config) {
 	$view->getEnvironment()->addGlobal('session', Session::all());
 	$view->getEnvironment()->addGlobal('lang', $container->get('lang'));
 	$view->getEnvironment()->addGlobal('PLATFORM_VERSION', PLATFORM_VERSION);
+
+	$view->getEnvironment()->addFunction(new Twig_Function('route', 'route'));
+	$view->getEnvironment()->addFunction(new Twig_Function('lang', 'lang'));
+	$view->getEnvironment()->addFunction(new Twig_Function('urlFor', 'urlFor'));
 	return $view;
 };
 

+ 1 - 1
composer.json

@@ -1,6 +1,6 @@
 {
   "name": "sergix44/xbackbone",
-  "version": "2.1",
+  "version": "2.2",
   "description": "A lightweight ShareX PHP backend",
   "type": "project",
   "require": {

+ 82 - 0
resources/lang/en.lang.php

@@ -2,4 +2,86 @@
 
 return [
 
+	'lang' => 'English',
+
+	'yes' => 'Yes',
+	'no' => 'No',
+	'send' => 'Send',
+	'no_media' => 'No media found.',
+
+	'login.username' => 'Username or E-Mail',
+	'password' => 'Password',
+	'login' => 'Login',
+	'username' => 'Username',
+
+	'home' => 'Home',
+	'users' => 'Users',
+	'system' => 'System',
+	'profile' => 'Profile',
+	'logout' => 'Logout',
+
+	'pager.next' => 'Next',
+	'pager.previous' => 'Previous',
+
+	'copy_link' => 'Copy link',
+	'public.telegram' => 'Share on Telegram',
+	'public.delete_text' => 'Are you sure you want to delete this item? It will be gone forever!',
+
+	'preview' => 'Preview',
+	'filename' => 'Filename',
+	'size' => 'Size',
+	'public' => 'Public',
+	'owner' => 'Owner',
+	'date' => 'Date',
+	'raw' => 'Show raw',
+	'download' => 'Download',
+	'delete' => 'Delete',
+	'publish' => 'Publish',
+	'hide' => 'Hide',
+
+	'files' => 'Files',
+	'orphaned_files' => 'Orphaned Files',
+	'theme' => 'Theme',
+	'click_to_load' => 'Click to load...',
+	'apply' => 'Apply',
+	'save' => 'Save',
+	'used' => 'Used',
+	'system_info' => 'System Information',
+
+	'user.create' => 'Create User',
+	'user.edit' => 'Edit User',
+	'is_active' => 'Is active',
+	'is_admin' => 'Is administrator',
+	'your_profile' => 'Your Profile',
+	'token' => 'Token',
+	'copy' => 'Copy',
+	'update' => 'Update',
+	'edit' => 'Edit',
+	'client_config' => 'Client Configuration',
+	'user_code' => 'User Code',
+	'active' => 'Active',
+	'admin' => 'Admin',
+	'reg_date' => 'Registration Date',
+	'none' => 'None',
+	'open' => 'Open',
+	'confirm' => 'Confirmation',
+	'confirm_string' => 'Are you sure?',
+
+	'installed' => 'Installation completed successfully!',
+	'bad_login' => 'Wrong credentials.',
+	'account_disabled' => 'Your account is disabled.',
+	'welcome' => 'Welcome, %s!',
+	'goodbye' => 'Goodbye!',
+	'token_not_found' => 'Token specified not found.',
+	'email_required' => 'The email is required.',
+	'email_taken' => 'The email is already taken.',
+	'username_required' => 'The username is required.',
+	'username_taken' => 'The username is already taken.',
+	'password_required' => 'The password is required.',
+	'user_created' => 'User "%s" created!',
+	'user_updated' => 'User "%s" updated!',
+	'profile_updated' => 'Profile updated successfully!',
+	'user_deleted' => 'User deleted.',
+	'cannot_delete' => 'You cannot delete yourself.',
+	'cannot_demote' => 'You cannot demote yourself',
 ];

+ 87 - 0
resources/lang/it.lang.php

@@ -0,0 +1,87 @@
+<?php
+
+return [
+
+	'lang' => 'Italian',
+
+	'yes' => 'Sì',
+	'no' => 'No',
+	'send' => 'Invia',
+	'no_media' => 'Nessun media trovato.',
+
+	'login.username' => 'Username o E-Mail',
+	'password' => 'Password',
+	'login' => 'Accedi',
+	'username' => 'Username',
+
+	'home' => 'Home',
+	'users' => 'Utenti',
+	'system' => 'Sistema',
+	'profile' => 'Profilo',
+	'logout' => 'Esci',
+
+	'pager.next' => 'Avanti',
+	'pager.previous' => 'Indietro',
+
+	'copy_link' => 'Copia link',
+	'public.telegram' => 'Condividi su Telegram',
+	'public.delete_text' => 'Sei sicuro di voler eliminare questo elemento? Andrà perso per sempre!',
+
+	'preview' => 'Anteprima',
+	'filename' => 'Filename',
+	'size' => 'Dimensione',
+	'public' => 'Pubblico',
+	'owner' => 'Proprietario',
+	'date' => 'Data',
+	'raw' => 'Vedi raw',
+	'download' => 'Download',
+	'delete' => 'Elimina',
+	'publish' => 'Pubblica',
+	'hide' => 'Nascondi',
+
+	'files' => 'File',
+	'orphaned_files' => 'File orfani',
+	'theme' => 'Tema',
+	'click_to_load' => 'Clicca per caricare...',
+	'apply' => 'Applica',
+	'save' => 'Salva',
+	'used' => 'Usato',
+	'system_info' => 'Informazioni di Sistema',
+
+	'user.create' => 'Crea Utente',
+	'user.edit' => 'Modifica Utente',
+	'is_active' => 'Attivo',
+	'is_admin' => 'Amministratore',
+	'your_profile' => 'Il tuo profilo',
+	'token' => 'Token',
+	'copy' => 'Copia',
+	'update' => 'Aggiorna',
+	'edit' => 'Modifica',
+	'client_config' => 'Configurazione del Client',
+	'user_code' => 'Codice Utente',
+	'active' => 'Attivo',
+	'admin' => 'Amministratore',
+	'reg_date' => 'Data Registrazione',
+	'none' => 'Nessuno',
+	'open' => 'Apri',
+	'confirm' => 'Conferma',
+	'confirm_string' => 'Sei sicuro?',
+
+	'installed' => 'Installazione completata!',
+	'bad_login' => 'Credenziali errate.',
+	'account_disabled' => 'Il tuo account è disattivato.',
+	'welcome' => 'Benvenuto, %s!',
+	'goodbye' => 'Arrivederci!',
+	'token_not_found' => 'Il token specificato non è stato trovato.',
+	'email_required' => 'Email obbligatoria.',
+	'email_taken' => 'Email già in uso.',
+	'username_required' => 'Username obbligatorio.',
+	'username_taken' => 'Username già in uso.',
+	'password_required' => 'Password obbligatoria.',
+	'user_created' => 'L\'utente "%s" è stato creato!',
+	'user_updated' => 'L\'utente "%s" è stato aggiornato!',
+	'profile_updated' => 'Profilo aggiornato con successo!',
+	'user_deleted' => 'Utente rimosso.',
+	'cannot_delete' => 'Non puoi eliminare te stesso.',
+	'cannot_demote' => 'Non puoi degradare te stesso. ',
+];

+ 1 - 0
resources/schemas/mysql/mysql.2.sql

@@ -0,0 +1 @@
+ALTER TABLE `users` ADD UNIQUE INDEX (`email`);

+ 2 - 0
resources/schemas/sqlite/sqlite.2.sql

@@ -0,0 +1,2 @@
+CREATE UNIQUE INDEX IF NOT EXISTS `username_email`
+  ON `users` (`email`);

+ 7 - 7
resources/templates/auth/login.twig

@@ -1,6 +1,6 @@
 {% extends 'base.twig' %}
 
-{% block title %}Login{% endblock %}
+{% block title %}{{ lang('login') }}{% endblock %}
 
 {% block head %}
     <style>
@@ -27,7 +27,7 @@
 
 {% block content %}
     <div class="container-fluid">
-        <form class="form-signin text-center" method="post" action="login">
+        <form class="form-signin text-center" method="post" action="{{ route('login') }}">
             <div class="row">
                 <div class="col-md-12">
                     <h1 class="h3 mb-3 font-weight-normal">{{ config.app_name }}</h1>
@@ -36,15 +36,15 @@
             </div>
             <div class="row">
                 <div class="col-md-12">
-                    <label for="inputEmail" class="sr-only">Username or E-Mail</label>
-                    <input type="text" id="username" class="form-control" placeholder="Username or E-Mail" name="username" required autofocus>
-                    <label for="password" class="sr-only">Password</label>
-                    <input type="password" id="password" class="form-control" placeholder="Password" name="password" required>
+                    <label for="inputEmail" class="sr-only">{{ lang('login.username') }}</label>
+                    <input type="text" id="username" class="form-control" placeholder="{{ lang('login.username') }}" name="username" required autofocus>
+                    <label for="password" class="sr-only">{{ lang('login.password') }}</label>
+                    <input type="password" id="password" class="form-control" placeholder="{{ lang('password') }}" name="password" required>
                 </div>
             </div>
             <div class="row">
                 <div class="col-md-12">
-                    <button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
+                    <button class="btn btn-lg btn-primary btn-block" type="submit">{{ lang('login') }}</button>
                 </div>
             </div>
         </form>

+ 11 - 11
resources/templates/base.twig

@@ -5,17 +5,17 @@
     <meta charset="utf-8">
     <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/videojs/video-js.min.css" rel="stylesheet">
-    <link href="{{ config.base_url }}/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/videojs/video.min.js"></script>
-    <script src="{{ config.base_url }}/static/app/app.js"></script>
+    <link href="{{ urlFor('/static/bootstrap/css/bootstrap.min.css') }}" rel="stylesheet">
+    <link href="{{ urlFor('/static/highlightjs/styles/monokai.css') }}" rel="stylesheet">
+    <link href="{{ urlFor('/static/videojs/video-js.min.css') }}" rel="stylesheet">
+    <link href="{{ urlFor('/static/app/app.css') }}" rel="stylesheet">
+	<script src="{{ urlFor('/static/jquery/jquery.min.js') }}"></script>
+    <script src="{{ urlFor('/static/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
+    <script src="{{ urlFor('/static/fontawesome/js/all.min.js') }}"></script>
+    <script src="{{ urlFor('/static/highlightjs/highlight.pack.min.js') }}"></script>
+    <script src="{{ urlFor('/static/clipboardjs/clipboard.min.js') }}"></script>
+    <script src="{{ urlFor('/static/videojs/video.min.js') }}"></script>
+    <script src="{{ urlFor('/static/app/app.js') }}"></script>
     <script>hljs.initHighlightingOnLoad();</script>
     <script>window.AppConfig = {'base_url': '{{ config.base_url }}'}</script>
     {% block head %}{% endblock %}

+ 10 - 10
resources/templates/comp/navbar.twig

@@ -1,25 +1,25 @@
 <nav class="navbar navbar-dark bg-primary navbar-expand-md mb-4 box-shadow">
     <div class="container">
-        <a class="navbar-brand" href="{{ config.base_url }}">{{ config.app_name }}</a>
+        <a class="navbar-brand" href="{{ route('root') }}">{{ config.app_name }}</a>
         <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
             <span class="navbar-toggler-icon"></span>
         </button>
         <div class="collapse navbar-collapse" id="navbarCollapse">
             <ul class="navbar-nav mr-auto">
                 <li class="nav-item">
-                    <a href="{{ config.base_url }}/home" class="nav-link {{ request.uri.path starts with '/home' ? 'active' }}"><i class="fas fa-fw fa-home"></i>
-                        Home
+                    <a href="{{ route('home') }}" class="nav-link {{ request.uri.path starts with '/home' ? 'active' }}"><i class="fas fa-fw fa-home"></i>
+                        {{ lang('home') }}
                     </a>
                 </li>
                 {% if session.admin %}
                     <li class="nav-item">
-                        <a href="{{ config.base_url }}/users" class="nav-link {{ request.uri.path starts with '/user' ? 'active' }}"><i class="fas fa-fw fa-users"></i>
-                            Users
+                        <a href="{{ route('user.index') }}" class="nav-link {{ request.uri.path starts with '/user' ? 'active' }}"><i class="fas fa-fw fa-users"></i>
+                            {{ lang('users') }}
                         </a>
                     </li>
                     <li class="nav-item">
-                        <a href="{{ config.base_url }}/system" class="nav-link {{ request.uri.path starts with '/system' ? 'active' }}"><i class="fas fa-fw fa-cog"></i>
-                            System
+                        <a href="{{ route('system') }}" class="nav-link {{ request.uri.path starts with '/system' ? 'active' }}"><i class="fas fa-fw fa-cog"></i>
+                            {{ lang('system') }}
                         </a>
                     </li>
                 {% endif %}
@@ -30,10 +30,10 @@
                         <i class="fas fa-fw fa-user"></i> {{ session.username }}
                     </a>
                     <div class="dropdown-menu" aria-labelledby="userDropdown">
-                        <a class="dropdown-item disabled" href="javascript:void(0)">Used: {{ session.used_space }}</a>
+                        <a class="dropdown-item disabled" href="javascript:void(0)">{{ lang('used') }}: {{ session.used_space }}</a>
                         <div class="dropdown-divider"></div>
-                        <a class="dropdown-item" href="{{ config.base_url }}/profile"><i class="fas fa-fw fa-user"></i> Profile</a>
-                        <a class="dropdown-item" href="{{ config.base_url }}/logout"><i class="fas fa-fw fa-sign-out-alt"></i> Logout</a>
+                        <a class="dropdown-item" href="{{ route('profile') }}"><i class="fas fa-fw fa-user"></i> {{ lang('profile') }}</a>
+                        <a class="dropdown-item" href="{{ route('logout') }}"><i class="fas fa-fw fa-sign-out-alt"></i> {{ lang('logout') }}</a>
                     </div>
                 </li>
             </ul>

+ 4 - 7
resources/templates/comp/pager.twig

@@ -3,22 +3,19 @@
         <ul class="pagination justify-content-center">
             {% if previous %}
                 <li class="page-item">
-                    <a class="page-link" href="{{ config.base_url }}/{{ path }}/page/{{ current_page-1 }}"><i class="fas fa-angle-left fa-fw"></i>
-                        Previous</a>
+                    <a class="page-link" href="{{ urlFor('/' ~ path ~ '/page/' ~ (current_page-1)) }}"><i class="fas fa-angle-left fa-fw"></i> {{ lang('pager.previous') }}</a>
                 </li>
             {% else %}
                 <li class="page-item disabled">
-                    <a class="page-link" href="#"><i class="fas fa-angle-left fa-fw"></i> Previous</a></li>
+                    <a class="page-link" href="#"><i class="fas fa-angle-left fa-fw"></i> {{ lang('pager.previous') }}</a></li>
             {% endif %}
 
             {% if next %}
                 <li class="page-item">
-                    <a class="page-link" href="{{ config.base_url }}/{{ path }}/page/{{ current_page+1 }}">Next
-                        <i class="fas fa-angle-right fa-fw"></i></a>
+                    <a class="page-link" href="{{ urlFor('/' ~ path ~ '/page/' ~ (current_page+1)) }}">{{ lang('pager.next') }} <i class="fas fa-angle-right fa-fw"></i></a>
                 </li>
             {% else %}
-                <li class="page-item disabled"><a class="page-link" href="#">Next
-                        <i class="fas fa-angle-right fa-fw"></i></a></li>
+                <li class="page-item disabled"><a class="page-link" href="#">{{ lang('pager.next') }} <i class="fas fa-angle-right fa-fw"></i></a></li>
             {% endif %}
         </ul>
     </nav>

+ 16 - 15
resources/templates/dashboard/admin.twig

@@ -16,12 +16,12 @@
                                 <table class="table table-hover">
                                     <thead>
                                     <tr>
-                                        <th>Preview</th>
-                                        <th>Filename</th>
-                                        <th>Size</th>
-                                        <th>Public</th>
-                                        <th>Owner</th>
-                                        <th>Date</th>
+                                        <th>{{ lang('preview') }}</th>
+                                        <th>{{ lang('filename') }}</th>
+                                        <th>{{ lang('size') }}</th>
+                                        <th>{{ lang('public') }}</th>
+                                        <th>{{ lang('owner') }}</th>
+                                        <th>{{ lang('date') }}</th>
                                         <th></th>
                                     </tr>
                                     </thead>
@@ -31,10 +31,10 @@
                                             <td>
                                                 {% if media.mimetype starts with 'image' %}
                                                     {% if media.username is not null %}
-                                                        <img src="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ media.extension }}/raw?width=128"
+                                                        <img src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension ~ '/raw?width=128') }}"
                                                              class="img-fluid rounded admin-img">
                                                     {% else %}
-                                                        <img src="{{ config.base_url }}/upload/{{ media.id }}/raw" class="img-fluid rounded admin-img">
+                                                        <img src="{{ route('upload.raw', {'id': media.id}) }}" class="img-fluid rounded admin-img">
                                                     {% endif %}
                                                 {% else %}
                                                     <i class="far fa-file fa-2x"></i>
@@ -54,18 +54,19 @@
                                             <td class="text-right">
                                                 <div class="btn-group">
                                                     {% if media.username is not null %}
-                                                        <a href="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ media.extension }}" class="btn btn-outline-dark" data-toggle="tooltip" title="Open" target="_blank"><i class="fas fa-external-link-alt"></i></a>
-                                                        <a href="javascript:void(0)" class="btn btn-outline-success btn-clipboard" data-toggle="tooltip" title="Copy link" data-clipboard-text="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ media.extension }}"><i class="fas fa-link"></i></a>
+                                                        <a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}" class="btn btn-outline-dark" data-toggle="tooltip" title="{{ lang('open') }}" target="_blank"><i class="fas fa-external-link-alt"></i></a>
+                                                        <a href="javascript:void(0)" class="btn btn-outline-success btn-clipboard" data-toggle="tooltip" title="{{ lang('copy_link') }}" data-clipboard-text="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}"><i
+                                                                    class="fas fa-link"></i></a>
                                                     {% else %}
-                                                        <a href="{{ config.base_url }}/upload/{{ media.id }}/raw" class="btn btn-outline-dark" data-toggle="tooltip" title="Raw" target="_blank"><i class="fas fa-external-link-alt"></i></a>
+                                                        <a href="{{ route('upload.raw', {'id': media.id}) }}" class="btn btn-outline-dark" data-toggle="tooltip" title="{{ lang('raw') }}" target="_blank"><i class="fas fa-external-link-alt"></i></a>
                                                     {% endif %}
 
                                                     {% if media.published %}
-                                                        <a href="javascript:void(0)" class="btn btn-outline-warning publish-toggle" data-toggle="tooltip" title="Unpublish" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-times-circle"></i></a>
+                                                        <a href="javascript:void(0)" class="btn btn-outline-warning publish-toggle" data-toggle="tooltip" title="{{ lang('hide') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-times-circle"></i></a>
                                                     {% else %}
-                                                        <a href="javascript:void(0)" class="btn btn-outline-info publish-toggle" data-toggle="tooltip" title="Publish" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-check-circle"></i></a>
+                                                        <a href="javascript:void(0)" class="btn btn-outline-info publish-toggle" data-toggle="tooltip" title="{{ lang('publish') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-check-circle"></i></a>
                                                     {% endif %}
-                                                    <a href="javascript:void(0)" class="btn btn-outline-danger media-delete" data-link="{{ config.base_url }}/upload/{{ media.id }}/delete" data-id="{{ media.id }}" data-toggle="tooltip" title="Delete"><i class="fas fa-trash"></i></a>
+                                                    <a href="javascript:void(0)" class="btn btn-outline-danger media-delete" data-link="{{ route('upload.delete', {'id': media.id}) }}" data-id="{{ media.id }}" data-toggle="tooltip" title="{{ lang('delete') }}"><i class="fas fa-trash"></i></a>
                                                 </div>
                                             </td>
                                         </tr>
@@ -79,7 +80,7 @@
                 </div>
             </div>
         {% else %}
-            <div class="text-center text-muted"><i>No medias found.</i></div>
+            <div class="text-center text-muted"><i>{{ lang('no_media') }}</i></div>
         {% endif %}
     </div>
     {% include 'comp/footer.twig' %}

+ 37 - 39
resources/templates/dashboard/home.twig

@@ -1,56 +1,54 @@
 {% extends 'base.twig' %}
 
-{% block title %}Home{% endblock %}
+{% block title %}{{ lang('home') }}{% endblock %}
 
 {% block content %}
     {% include 'comp/navbar.twig' %}
     <div class="container">
         {% include 'comp/alert.twig' %}
         {% if medias|length > 0 %}
-            <div class="card box-shadow">
-                <div class="card-body">
-                    {% include 'comp/pager.twig' with {'path': 'home'} %}
-                    <div class="row">
-                        {% for media in medias %}
-                            <div class="col-md-4" id="media_{{ media.id }}">
-                                <div class="card mb-4 box-shadow">
-                                    {% if media.mimetype starts with 'image' %}
-                                        <a href="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ media.extension }}" target="_blank">
-                                            <img class="card-img-top user-img" src="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ media.extension }}/raw?width=348&height=192" alt="{{ media.filename }}">
-                                        </a>
-                                    {% else %}
-                                        <a href="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ media.extension }}" target="_blank">
-                                            <div class="card-header text-center"><i class="far fa-file fa-10x"></i></div>
-                                        </a>
-                                    {% endif %}
-                                    <div class="card-body">
-                                        <p class="card-text">{{ media.filename }}<small class="float-right">{{ media.size }}</small></p>
-                                        <div class="d-flex justify-content-between align-items-center">
-                                            <div class="btn-group">
-                                                <button type="button" class="btn btn-sm btn-outline-success btn-clipboard" data-toggle="tooltip" title="Copy link" data-clipboard-text="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ media.extension }}">
-                                                    <i class="fas fa-link"></i>
-                                                </button>
-                                                {% if media.published %}
-                                                    <a href="javascript:void(0)" class="btn btn-sm btn-outline-warning publish-toggle" data-toggle="tooltip" title="Unpublish" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-times-circle"></i></a>
-                                                {% else %}
-                                                    <a href="javascript:void(0)" class="btn btn-sm btn-outline-info publish-toggle" data-toggle="tooltip" title="Publish" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-check-circle"></i></a>
-                                                {% endif %}
-                                                <button type="button" class="btn btn-sm btn-outline-danger media-delete" data-link="{{ config.base_url }}/upload/{{ media.id }}/delete" data-id="{{ media.id }}" data-toggle="tooltip" title="Delete">
-                                                    <i class="fas fa-trash"></i>
-                                                </button>
-                                            </div>
-                                            <small class="text-muted">{{ media.timestamp|date("d/m/Y H:i:s") }}</small>
-                                        </div>
+            {% include 'comp/pager.twig' with {'path': 'home'} %}
+            <div class="row">
+                {% for media in medias %}
+                    <div class="col-md-4" id="media_{{ media.id }}">
+                        <div class="card mb-4 box-shadow">
+                            {% if media.mimetype starts with 'image' %}
+                                <a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}" target="_blank">
+                                    <img class="card-img-top user-img" src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension ~ '/raw?width=348&height=192') }}" alt="{{ media.filename }}">
+                                </a>
+                            {% else %}
+                                <a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}" target="_blank">
+                                    <div class="card-header text-center"><i class="far fa-file fa-10x"></i></div>
+                                </a>
+                            {% endif %}
+                            <div class="card-body">
+                                <p class="card-text">{{ media.filename }}
+                                    <small class="float-right">{{ media.size }}</small>
+                                </p>
+                                <div class="d-flex justify-content-between align-items-center">
+                                    <div class="btn-group">
+                                        <button type="button" class="btn btn-sm btn-outline-success btn-clipboard" data-toggle="tooltip" title="{{ lang('copy_link') }}" data-clipboard-text="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}">
+                                            <i class="fas fa-link"></i>
+                                        </button>
+                                        {% if media.published %}
+                                            <a href="javascript:void(0)" class="btn btn-sm btn-outline-warning publish-toggle" data-toggle="tooltip" title="{{ lang('hide') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-times-circle"></i></a>
+                                        {% else %}
+                                            <a href="javascript:void(0)" class="btn btn-sm btn-outline-info publish-toggle" data-toggle="tooltip" title="{{ lang('publish') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-check-circle"></i></a>
+                                        {% endif %}
+                                        <button type="button" class="btn btn-sm btn-outline-danger media-delete" data-link="{{ route('upload.delete', {'id': media.id}) }}" data-id="{{ media.id }}" data-toggle="tooltip" title="{{ lang('delete') }}">
+                                            <i class="fas fa-trash"></i>
+                                        </button>
                                     </div>
+                                    <small class="text-muted">{{ media.timestamp|date("d/m/Y H:i:s") }}</small>
                                 </div>
                             </div>
-                        {% endfor %}
+                        </div>
                     </div>
-                    {% include 'comp/pager.twig' with {'path': 'home'} %}
-                </div>
+                {% endfor %}
             </div>
+            {% include 'comp/pager.twig' with {'path': 'home'} %}
         {% else %}
-            <div class="text-center text-muted"><i>No medias found.</i></div>
+            <div class="text-center text-muted"><i>{{ lang('no_media') }}</i></div>
         {% endif %}
     </div>
     {% include 'comp/footer.twig' %}

+ 10 - 10
resources/templates/dashboard/system.twig

@@ -1,6 +1,6 @@
 {% extends 'base.twig' %}
 
-{% block title %}System{% endblock %}
+{% block title %}{{ lang('system') }}{% endblock %}
 
 {% block content %}
     {% include 'comp/navbar.twig' %}
@@ -12,7 +12,7 @@
                         <div class="rotate">
                             <i class="fas fa-users fa-3x"></i>
                         </div>
-                        <h6 class="text-uppercase">Users</h6>
+                        <h6 class="text-uppercase">{{ lang('users') }}</h6>
                         <h1 class="display-4">{{ usersCount }}</h1>
                     </div>
                 </div>
@@ -23,7 +23,7 @@
                         <div class="rotate">
                             <i class="fas fa-weight fa-3x"></i>
                         </div>
-                        <h6 class="text-uppercase">Size</h6>
+                        <h6 class="text-uppercase">{{ lang('size') }}</h6>
                         <h1 class="display-4">{{ totalSize }}</h1>
                     </div>
                 </div>
@@ -34,7 +34,7 @@
                         <div class="rotate">
                             <i class="fas fa-upload fa-3x"></i>
                         </div>
-                        <h6 class="text-uppercase">Files</h6>
+                        <h6 class="text-uppercase">{{ lang('files') }}</h6>
                         <h1 class="display-4">{{ mediasCount }}</h1>
                     </div>
                 </div>
@@ -45,7 +45,7 @@
                         <div class="rotate">
                             <i class="fas fa-unlink fa-3x"></i>
                         </div>
-                        <h6 class="text-uppercase">Orphaned files</h6>
+                        <h6 class="text-uppercase">{{ lang('orphaned_files') }}</h6>
                         <h1 class="display-4">{{ orphanFilesCount }}</h1>
                     </div>
                 </div>
@@ -54,20 +54,20 @@
         <div class="row">
             <div class="col-md-8">
                 <div class="card box-shadow">
-                    <div class="card-header"><i class="fas fa-paint-brush fa-fw"></i> Theme</div>
+                    <div class="card-header"><i class="fas fa-paint-brush fa-fw"></i> {{ lang('theme') }}</div>
                     <div class="card-body">
-                        <form method="post" action="{{ config.base_url }}/system/theme/apply">
+                        <form method="post" action="{{ route('theme.apply') }}">
                             <div class="form-group row">
                                 <div class="col-sm-12">
                                     <select class="form-control" id="themes" name="css">
-                                        <option id="theme-load" selected disabled hidden>Click to load...</option>
+                                        <option id="theme-load" selected disabled hidden>{{ lang('click_to_load') }}</option>
                                     </select>
                                 </div>
                             </div>
                             <div class="form-group row">
                                 <div class="col-sm-12">
                                     <button type="submit" class="btn btn-outline-success" id="themes-apply" disabled>
-                                        <i class="fas fa-save fa-fw"></i> Apply
+                                        <i class="fas fa-save fa-fw"></i> {{ lang('apply') }}
                                     </button>
                                 </div>
                             </div>
@@ -77,7 +77,7 @@
             </div>
             <div class="col-md-4">
                 <div class="card box-shadow">
-                    <div class="card-header"><i class="fas fa-cog fa-fw"></i> System Information</div>
+                    <div class="card-header"><i class="fas fa-cog fa-fw"></i> {{ lang('system_info') }}</div>
                     <div class="card-body">
                         <strong>Max upload size:</strong>
                         <ul>

+ 14 - 14
resources/templates/upload/public.twig

@@ -5,19 +5,19 @@
 {% block content %}
     <nav class="navbar navbar-dark bg-primary navbar-expand-md mb-4">
         <div class="container-fluid">
-            <a class="navbar-brand" href="{{ config.base_url }}">{{ config.app_name }}</a>
+            <a class="navbar-brand" href="{{ route('root') }}">{{ config.app_name }}</a>
             <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
                 <span class="navbar-toggler-icon"></span>
             </button>
             <div class="collapse navbar-collapse" id="navbarCollapse">
                 <div class="ml-auto">
-                    <a href="javascript:void(0)" class="btn btn-success my-2 my-sm-0 btn-clipboard" data-toggle="tooltip" title="Copy link" data-clipboard-text="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ extension }}"><i class="fas fa-link fa-lg fa-fw"></i></a>
-                    <a href="javascript:void(0)" class="btn btn-info my-2 my-sm-0" data-html="true" title="Telegram Message" data-toggle="popover" data-placement="bottom"
+                    <a href="javascript:void(0)" class="btn btn-success my-2 my-sm-0 btn-clipboard" data-toggle="tooltip" title="{{ lang('copy_link') }}" data-clipboard-text="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension) }}"><i class="fas fa-link fa-lg fa-fw"></i></a>
+                    <a href="javascript:void(0)" class="btn btn-info my-2 my-sm-0" data-html="true" title="{{ lang('public.telegram') }}" data-toggle="popover" data-placement="bottom"
                        data-content='<input type="text" class="form-control mb-2" id="telegram-share-text" onclick="this.select()" value="{{ media.filename }}">
-<button type="button" class="btn btn-info btn-block" id="telegram-share-button" onclick="app.telegramShare()" data-url="https://telegram.me/share/url?url={{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ extension }}&text="><i class="fab fa-telegram-plane fa-lg fa-fw"></i> Send</button>'><i
+<button type="button" class="btn btn-info btn-block" id="telegram-share-button" onclick="app.telegramShare()" data-url="https://telegram.me/share/url?url={{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension) }}&text="><i class="fab fa-telegram-plane fa-lg fa-fw"></i> {{ lang("send") }}</button>'><i
                                 class="fab fa-telegram-plane fa-lg fa-fw"></i></a>
-                    <a href="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ extension }}/raw" class="btn btn-light my-2 my-sm-0" data-toggle="tooltip" title="Show raw"><i class="fas fa-file-alt fa-lg fa-fw"></i></a>
-                    <a href="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ extension }}/download" class="btn btn-danger my-2 my-sm-0" data-toggle="tooltip" title="Download"><i class="fas fa-cloud-download-alt fa-lg fa-fw"></i></a>
+                    <a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/raw') }}" class="btn btn-light my-2 my-sm-0" data-toggle="tooltip" title="{{ lang('raw') }}"><i class="fas fa-file-alt fa-lg fa-fw"></i></a>
+                    <a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/download') }}" class="btn btn-danger my-2 my-sm-0" data-toggle="tooltip" title="{{ lang('download') }}"><i class="fas fa-cloud-download-alt fa-lg fa-fw"></i></a>
                 </div>
             </div>
         </div>
@@ -27,12 +27,12 @@
         <div class="row ml-auto mr-auto">
             <div class="col-md-12 justify-content-center">
                 {% if delete_token is not null %}
-                    <form method="post" action="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ extension }}/delete/{{ delete_token }}">
+                    <form method="post" action="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/delete/' ~ delete_token) }}">
                         <div class="text-center mb-4">
-                            <p>Are you sure you want to delete this item? It will be gone <b>forever</b>!</p>
+                            <p>{{ lang('public.delete_text') }}</p>
                             <div class="btn-group">
-                                <button type="submit" class="btn btn-danger"><i class="fas fa-trash"></i> Yes</button>
-                                <a href="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ extension }}" class="btn btn-secondary">No</a>
+                                <button type="submit" class="btn btn-danger"><i class="fas fa-trash"></i> {{ lang('yes') }}</button>
+                                <a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension) }}" class="btn btn-secondary">{{ lang('no') }}</a>
                             </div>
                         </div>
                     </form>
@@ -40,7 +40,7 @@
                 {% if type starts with 'image' %}
                     <div class="row mb-2">
                         <div class="col-md-12">
-                            <img src="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ extension }}/raw" class="img-thumbnail rounded mx-auto d-block" alt="{{ media.filename }}">
+                            <img src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/raw') }}" class="img-thumbnail rounded mx-auto d-block" alt="{{ media.filename }}">
                         </div>
                     </div>
                     <div class="row">
@@ -62,9 +62,9 @@
                 {% elseif type starts with 'video' %}
                     <div class="video-content">
                         <video class="video-js vjs-fluid vjs-big-play-centered" data-setup='{"controls": true, "autoplay": true, "preload": "auto"}'>
-                            <source src="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ extension }}/raw" type="{{ type }}">
+                            <source src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/raw') }}" type="{{ type }}">
                             Your browser does not support HTML5 video.
-                            <a href="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ extension }}/download" class="btn btn-dark btn-lg"><i class="fas fa-cloud-download-alt fa-fw"></i> Download</a>
+                            <a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/download') }}" class="btn btn-dark btn-lg"><i class="fas fa-cloud-download-alt fa-fw"></i> Download</a>
                         </video>
                     </div>
                 {% else %}
@@ -81,7 +81,7 @@
                         </div>
                         <div class="row">
                             <div class="col-md-12">
-                                <a href="{{ config.base_url }}/{{ media.user_code }}/{{ media.code }}.{{ extension }}/download" class="btn btn-dark btn-lg"><i class="fas fa-cloud-download-alt fa-fw"></i> Download</a>
+                                <a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/download') }}" class="btn btn-dark btn-lg"><i class="fas fa-cloud-download-alt fa-fw"></i> Download</a>
                             </div>
                         </div>
                     </div>

+ 11 - 11
resources/templates/user/create.twig

@@ -1,6 +1,6 @@
 {% extends 'base.twig' %}
 
-{% block title %}Create User{% endblock %}
+{% block title %}{{ lang('user.create') }}{% endblock %}
 
 {% block content %}
     {% include 'comp/navbar.twig' %}
@@ -9,25 +9,25 @@
         <div class="row justify-content-center">
             <div class="col-md-8">
                 <div class="card box-shadow">
-                    <div class="card-header">Create User</div>
+                    <div class="card-header">{{ lang('user.create') }}</div>
                     <div class="card-body">
-                        <form method="post" action="{{ config.base_url }}/user/create">
+                        <form method="post" action="{{ route('user.store') }}">
                             <div class="form-group row">
-                                <label for="email" class="col-sm-2 col-form-label">Email</label>
+                                <label for="email" class="col-sm-2 col-form-label">E-Mail</label>
                                 <div class="col-sm-10">
                                     <input type="email" class="form-control" id="email" placeholder="email@example.com" name="email" autocomplete="off" required>
                                 </div>
                             </div>
                             <div class="form-group row">
-                                <label for="email" class="col-sm-2 col-form-label">Username</label>
+                                <label for="email" class="col-sm-2 col-form-label">{{ lang('username') }}</label>
                                 <div class="col-sm-10">
-                                    <input type="text" class="form-control" id="username" placeholder="Username" name="username" autocomplete="off" required >
+                                    <input type="text" class="form-control" id="username" placeholder="{{ lang('username') }}" name="username" autocomplete="off" required >
                                 </div>
                             </div>
                             <div class="form-group row">
-                                <label for="password" class="col-sm-2 col-form-label">Password</label>
+                                <label for="password" class="col-sm-2 col-form-label">{{ lang('password') }}</label>
                                 <div class="col-sm-10">
-                                    <input type="password" class="form-control" id="password" placeholder="Password" name="password" autocomplete="off" required>
+                                    <input type="password" class="form-control" id="password" placeholder="{{ lang('password') }}" name="password" autocomplete="off" required>
                                 </div>
                             </div>
                             <div class="form-group row">
@@ -36,7 +36,7 @@
                                     <div class="form-check">
                                         <input class="form-check-input" type="checkbox" id="is_admin" name="is_admin">
                                         <label class="form-check-label" for="is_admin">
-                                            Is admin
+                                            {{ lang('is_admin') }}
                                         </label>
                                     </div>
                                 </div>
@@ -47,7 +47,7 @@
                                     <div class="form-check">
                                         <input class="form-check-input" type="checkbox" id="is_active" name="is_active" checked>
                                         <label class="form-check-label" for="is_active">
-                                            Is active
+                                            {{ lang('is_active') }}
                                         </label>
                                     </div>
                                 </div>
@@ -55,7 +55,7 @@
                             <div class="form-group row justify-content-md-end">
                                 <div class="col-sm-10">
                                     <button type="submit" class="btn btn-outline-success">
-                                        <i class="fas fa-save fa-fw"></i> Create
+                                        <i class="fas fa-save fa-fw"></i> {{ lang('save') }}
                                     </button>
                                 </div>
                             </div>

+ 15 - 15
resources/templates/user/edit.twig

@@ -1,6 +1,6 @@
 {% extends 'base.twig' %}
 
-{% block title %}{{ profile ? 'Your Profile' : 'Edit User' }}{% endblock %}
+{% block title %}{{ profile ? lang('your_profile') : lang('user.edit') }}{% endblock %}
 
 {% block content %}
     {% include 'comp/navbar.twig' %}
@@ -10,10 +10,10 @@
             <div class="col-md-8">
                 <div class="card box-shadow">
                     {% if not profile %}
-                        <div class="card-header">Edit User</div>
+                        <div class="card-header">{{ lang('user.edit') }}</div>
                     {% endif %}
                     <div class="card-body">
-                        <form method="post" action="{{ config.base_url }}/{{ profile ? 'profile' : 'user' }}/{{ user.id }}">
+                        <form method="post" action="{{ route( (profile ? 'profile.update' : 'user.update'), {'id': user.id}) }}">
                             <div class="form-group row">
                                 <label for="email" class="col-sm-2 col-form-label">Email</label>
                                 <div class="col-sm-10">
@@ -21,37 +21,37 @@
                                 </div>
                             </div>
                             <div class="form-group row">
-                                <label for="username" class="col-sm-2 col-form-label">Username</label>
+                                <label for="username" class="col-sm-2 col-form-label">{{ lang('username') }}</label>
                                 <div class="col-sm-10">
                                     {% if profile %}
                                         <input type="text" class="form-control disabled" id="username" value="{{ user.username }}" readonly>
                                     {% else %}
-                                        <input type="text" class="form-control" id="username" placeholder="Username" name="username" value="{{ user.username }}" autocomplete="off" required>
+                                        <input type="text" class="form-control" id="username" placeholder="{{ lang('username') }}" name="username" value="{{ user.username }}" autocomplete="off" required>
                                     {% endif %}
                                 </div>
                             </div>
                             <div class="form-group row">
-                                <label for="password" class="col-sm-2 col-form-label">Password</label>
+                                <label for="password" class="col-sm-2 col-form-label">{{ lang('password') }}</label>
                                 <div class="col-sm-10">
-                                    <input type="password" class="form-control" id="password" placeholder="Password" name="password" autocomplete="off">
+                                    <input type="password" class="form-control" id="password" placeholder="{{ lang('password') }}" name="password" autocomplete="off">
                                 </div>
                             </div>
                             <div class="form-group row">
-                                <label for="token" class="col-sm-2 col-form-label">Token</label>
+                                <label for="token" class="col-sm-2 col-form-label">{{ lang('token') }}</label>
                                 <div class="col-sm-10">
                                     <div class="input-group">
                                         <input type="text" id="token" class="form-control" value="{{ user.token }}" readonly>
                                         <div class="input-group-append">
-                                            <button class="btn btn-outline-success btn-clipboard" type="button" data-clipboard-target="#token"><i class="fas fa-fw fa-copy"></i> Copy</button>
-                                            <button class="btn btn-outline-primary refresh-token" data-id="{{ user.id }}" type="button"><i class="fas fa-fw fa-sync"></i> Update</button>
+                                            <button class="btn btn-outline-success btn-clipboard" type="button" data-clipboard-target="#token"><i class="fas fa-fw fa-copy"></i> {{ lang('copy') }}</button>
+                                            <button class="btn btn-outline-primary refresh-token" data-id="{{ user.id }}" type="button"><i class="fas fa-fw fa-sync"></i> {{ lang('update') }}</button>
                                         </div>
                                     </div>
                                 </div>
                             </div>
                             <div class="form-group row">
-                                <label class="col-sm-2 col-form-label">Client Config</label>
+                                <label class="col-sm-2 col-form-label">{{ lang('client_config') }}</label>
                                 <div class="col-sm-10">
-                                    <a href="{{ config.base_url }}/user/{{ user.id }}/config/sharex" class="btn btn-lg btn-outline-dark"><i class="fas fa-fw fa-download"></i> ShareX Config File</a>
+                                    <a href="{{ route('config.sharex', {'id': user.id}) }}" class="btn btn-lg btn-outline-dark"><i class="fas fa-fw fa-download"></i> ShareX Config File</a>
                                 </div>
                             </div>
                             {% if not profile %}
@@ -61,7 +61,7 @@
                                         <div class="form-check">
                                             <input class="form-check-input" type="checkbox" id="is_admin" name="is_admin" {{ user.is_admin ? 'checked' }}>
                                             <label class="form-check-label" for="is_admin">
-                                                Is admin
+                                                {{ lang('is_admin') }}
                                             </label>
                                         </div>
                                     </div>
@@ -72,7 +72,7 @@
                                         <div class="form-check">
                                             <input class="form-check-input" type="checkbox" id="is_active" name="is_active" {{ user.active ? 'checked' }}>
                                             <label class="form-check-label" for="is_active">
-                                                Is active
+                                                {{ lang('is_active') }}
                                             </label>
                                         </div>
                                     </div>
@@ -81,7 +81,7 @@
                             <div class="form-group row justify-content-md-end">
                                 <div class="col-sm-10">
                                     <button type="submit" class="btn btn-outline-info">
-                                        <i class="fas fa-save fa-fw"></i> Save
+                                        <i class="fas fa-save fa-fw"></i> {{ lang('save') }}
                                     </button>
                                 </div>
                             </div>

+ 16 - 16
resources/templates/user/index.twig

@@ -1,6 +1,6 @@
 {% extends 'base.twig' %}
 
-{% block title %}Users{% endblock %}
+{% block title %}{{ lang('users') }}{% endblock %}
 
 {% block content %}
     {% include 'comp/navbar.twig' %}
@@ -9,7 +9,7 @@
         <div class="card box-shadow">
             <div class="card-body">
                 <div class="text-right">
-                    <a href="{{ config.base_url }}/user/create" class="btn btn-outline-success mb-3"><i class="fas fa-plus"></i> Add User</a>
+                    <a href="{{ route('user.create') }}" class="btn btn-outline-success mb-3"><i class="fas fa-plus"></i> {{ lang('user.create') }}</a>
                 </div>
                 <div class="table-responsive">
                     <table class="table table-hover">
@@ -17,12 +17,12 @@
                         <tr>
                             <th>ID</th>
                             <th>Email</th>
-                            <th>Username</th>
-                            <th>User Code</th>
-                            <th>Token</th>
-                            <th>Active</th>
-                            <th>Admin</th>
-                            <th>Registration Date</th>
+                            <th>{{ lang('username') }}</th>
+                            <th>{{ lang('user_code') }}</th>
+                            <th>{{ lang('token') }}</th>
+                            <th>{{ lang('active') }}</th>
+                            <th>{{ lang('admin') }}</th>
+                            <th>{{ lang('reg_date') }}</th>
                             <th></th>
                         </tr>
                         </thead>
@@ -33,10 +33,10 @@
                                 <td>{{ user.email }}</td>
                                 <td>{{ user.username }}</td>
                                 <td>
-                                    <pre>{{ user.user_code|default('None') }}</pre>
+                                    <pre>{{ user.user_code|default(lang('none')) }}</pre>
                                 </td>
                                 <td>
-                                    <pre>{{ user.token|default('None') }}</pre>
+                                    <pre>{{ user.token|default(lang('none')) }}</pre>
                                 </td>
                                 <td>
                                     {% if user.active %}
@@ -57,8 +57,8 @@
                                 </td>
                                 <td class="text-right">
                                     <div class="btn-group">
-                                        <a href="{{ config.base_url }}/user/{{ user.id }}/edit" class="btn btn-outline-warning" data-toggle="tooltip" title="Edit"><i class="fas fa-pencil-alt"></i></a>
-                                        <a href="#" class="btn btn-outline-danger user-delete" data-link="{{ config.base_url }}/user/{{ user.id }}/delete" data-toggle="tooltip" title="Delete"><i class="fas fa-trash"></i></a>
+                                        <a href="{{ route('user.edit', {'id': user.id}) }}" class="btn btn-outline-warning" data-toggle="tooltip" title="{{ lang('edit') }}"><i class="fas fa-pencil-alt"></i></a>
+                                        <a href="#" class="btn btn-outline-danger user-delete" data-link="{{ route('user.delete', {'id': user.id}) }}" data-toggle="tooltip" title="{{ lang('delete') }}"><i class="fas fa-trash"></i></a>
                                     </div>
                                 </td>
                             </tr>
@@ -74,17 +74,17 @@
         <div class="modal-dialog modal-sm">
             <div class="modal-content">
                 <div class="modal-header">
-                    <h5 class="modal-title">Confirm</h5>
+                    <h5 class="modal-title">{{ lang('confirm') }}</h5>
                     <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                         <span aria-hidden="true">&times;</span>
                     </button>
                 </div>
                 <div class="modal-body">
-                    <p>Are you sure?</p>
+                    <p>{{ lang('confirm_string') }}</p>
                 </div>
                 <div class="modal-footer">
-                    <a href="#" class="btn btn-danger" id="modalDelete-link"><i class="fas fa-trash fa-fw"></i> Delete</a>
-                    <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+                    <a href="#" class="btn btn-danger" id="modalDelete-link"><i class="fas fa-trash fa-fw"></i> {{ lang('delete') }}</a>
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">{{ lang('no') }}</button>
                 </div>
             </div>
         </div>