Added ability to set custom html

improved session samesite implementation for older php versions
Fixes #82
This commit is contained in:
Sergio Brighenti 2019-11-15 00:56:25 +01:00
parent 56c58d50b3
commit 5adb29d700
17 changed files with 187 additions and 71 deletions

View file

@ -3,7 +3,10 @@
+ Added web upload.
+ Raw URL now accept file extensions.
+ Improved installer.
+ Improved thumbnail generation.
+ Replaced videojs player with Plyr.
+ Implemented SameSite XSS protection.
+ Added ability to add custom HTML in <head> tag.
+ Small fixes and improvements.
## v.2.6.6

View file

@ -80,8 +80,8 @@ class AdminController extends Controller
{
$config = require BASE_DIR.'config.php';
if (param($request,'lang') !== 'auto') {
$config['lang'] = param($request,'lang');
if (param($request, 'lang') !== 'auto') {
$config['lang'] = param($request, 'lang');
} else {
unset($config['lang']);
}
@ -92,4 +92,22 @@ class AdminController extends Controller
return redirect($response, route('system'));
}
/**
* @param Request $request
* @param Response $response
* @return Response
*/
public function applyCustomHead(Request $request, Response $response): Response
{
if ($request->getAttribute('custom_head_key_present')) {
$this->database->query('UPDATE `settings` SET `value`=? WHERE `key` = \'custom_head\'', param($request, 'custom_head'));
} else {
$this->database->query('INSERT INTO `settings`(`key`, `value`) VALUES (\'custom_head\', ?)', param($request, 'custom_head'));
}
$this->session->alert(lang('custom_head_set'));
return redirect($response, route('system'));
}
}

View file

@ -47,7 +47,7 @@ class Migrator
$this->db->getPdo()->exec(file_get_contents($this->schemaPath.DIRECTORY_SEPARATOR.'migrations.sql'));
}
$files = glob($this->schemaPath.DIRECTORY_SEPARATOR.$this->db->getCurrentDriver().'/*.sql');
$files = glob($this->schemaPath.'/'.$this->db->getCurrentDriver().'/*.sql');
$names = array_map(function ($path) {
return basename($path);

View file

@ -24,7 +24,7 @@ class AuthMiddleware extends Middleware
}
if (!$this->database->query('SELECT `id`, `active` FROM `users` WHERE `id` = ? LIMIT 1', [$this->session->get('user_id')])->fetch()->active) {
$this->session->alert('Your account is not active anymore.', 'danger');
$this->session->alert(lang('account_disabled'), 'danger');
$this->session->set('logged', false);
return redirect(new Response(), route('login.show'));
}

View file

@ -0,0 +1,26 @@
<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class InjectMiddleware extends Middleware
{
/**
* @param Request $request
* @param RequestHandler $handler
* @return Response
*/
public function __invoke(Request $request, RequestHandler $handler)
{
$head = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'custom_head\'')->fetch();
$this->view->getTwig()->addGlobal('customHead', $head->value ?? null);
return $handler->handle($request->withAttribute('custom_head_key_present', isset($head->value)));
}
}

View file

@ -6,17 +6,17 @@ namespace App\Middleware;
use App\Database\DB;
use App\Web\Lang;
use App\Web\Session;
use App\Web\View;
use DI\Container;
use League\Flysystem\Filesystem;
use Monolog\Logger;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Twig\Environment;
/**
* @property Session|null session
* @property Environment view
* @property View view
* @property DB|null database
* @property Logger|null logger
* @property Filesystem|null storage

View file

@ -21,6 +21,18 @@ class Session
throw new Exception("The given path '{$path}' is not writable.");
}
// Workaround for php <= 7.3
if (PHP_VERSION_ID < 70300) {
$params = session_get_cookie_params();
session_set_cookie_params(
$params['lifetime'],
$params['path'].'; SameSite=Lax',
$params['domain'],
$params['secure'],
$params['httponly']
);
}
$started = @session_start([
'name' => $name,
'save_path' => $path,
@ -29,18 +41,6 @@ class Session
'cookie_samesite' => 'Lax' // works only for php >= 7.3
]);
// Workaround for php <= 7.3
if (PHP_VERSION_ID < 70300) {
$sessionParams = session_get_cookie_params();
setcookie(
$name,
$this->getId(),
$sessionParams['filetime'],
$sessionParams['path'],
$sessionParams['domain'].'; SameSite=Lax'
);
}
if (!$started) {
throw new Exception("Cannot start the HTTP session. That the session path '{$path}' is writable and your PHP settings.");
}

View file

@ -60,4 +60,12 @@ class View
return $this->twig->render($view, $parameters);
}
/**
* @return Environment
*/
public function getTwig(): Environment
{
return $this->twig;
}
}

View file

@ -27,6 +27,8 @@ $app->group('', function (RouteCollectorProxy $group) {
$group->post('/system/lang/apply', [AdminController::class, 'applyLang'])->setName('lang.apply');
$group->post('/system/customHead', [AdminController::class, 'applyCustomHead'])->setName('customHead.apply');
$group->post('/system/upgrade', [UpgradeController::class, 'upgrade'])->setName('system.upgrade');
$group->get('/system/checkForUpdates', [UpgradeController::class, 'checkForUpdates'])->setName('system.checkForUpdates');

View file

@ -4,6 +4,7 @@ use App\Database\DB;
use App\Exception\Handlers\AppErrorHandler;
use App\Exception\Handlers\Renderers\HtmlErrorRenderer;
use App\Factories\ViewFactory;
use App\Middleware\InjectMiddleware;
use App\Web\Lang;
use App\Web\Session;
use Aws\S3\S3Client;
@ -172,8 +173,7 @@ $app->add(function (Request $request, RequestHandler $handler) {
return $handler->handle($request);
});
// Load the application routes
require BASE_DIR.'app/routes.php';
$app->add(InjectMiddleware::class);
// Configure the error handler
$errorHandler = new AppErrorHandler($app->getCallableResolver(), $app->getResponseFactory());
@ -183,4 +183,7 @@ $errorHandler->registerErrorRenderer('text/html', HtmlErrorRenderer::class);
$errorMiddleware = $app->addErrorMiddleware($config['debug'], true, true);
$errorMiddleware->setDefaultErrorHandler($errorHandler);
// Load the application routes
require BASE_DIR.'app/routes.php';
return $app;

View file

@ -102,4 +102,9 @@ return [
'prerelease_channel' => 'Prerelease Channel',
'no_upload_token' => 'You don\'t have a personal upload token. (Generate one and try again)',
'drop_to_upload' => 'Click or drop your files here to upload.',
'donation' => 'Donation',
'donate_text' => 'If you like XBackBone, consider a donation to support development!',
'custom_head_html' => 'Custom HTML Head content',
'custom_head_html_hint' => 'This content will be added at the <head> tag on every page.',
'custom_head_set' => 'Custom Head HTML applied successfully.',
];

View file

@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS `settings` (
`key` VARCHAR(32) PRIMARY KEY,
`value` TEXT
);

View file

@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS `settings` (
`key` VARCHAR(32) PRIMARY KEY,
`value` TEXT
);

View file

@ -27,6 +27,7 @@
}
</script>
{% block head %}{% endblock %}
{{ customHead|raw }}
</head>
<body class="bg-light">
{% block content %}{% endblock %}

View file

@ -14,7 +14,7 @@
<i class="fas fa-users fa-3x"></i>
</div>
<h6 class="text-uppercase">{{ lang('users') }}</h6>
<h1 class="display-4">{{ usersCount }}</h1>
<h1 class="display-4 system-tile">{{ usersCount }}</h1>
</div>
</div>
</div>
@ -25,7 +25,7 @@
<i class="fas fa-weight fa-3x"></i>
</div>
<h6 class="text-uppercase">{{ lang('size') }}</h6>
<h1 class="display-4">{{ totalSize }}</h1>
<h1 class="display-4 system-tile">{{ totalSize }}</h1>
</div>
</div>
</div>
@ -36,7 +36,7 @@
<i class="fas fa-upload fa-3x"></i>
</div>
<h6 class="text-uppercase">{{ lang('files') }}</h6>
<h1 class="display-4">{{ mediasCount }}</h1>
<h1 class="display-4 system-tile">{{ mediasCount }}</h1>
</div>
</div>
</div>
@ -47,60 +47,66 @@
<i class="fas fa-unlink fa-3x"></i>
</div>
<h6 class="text-uppercase">{{ lang('orphaned_files') }}</h6>
<h1 class="display-4">{{ orphanFilesCount }}</h1>
<h1 class="display-4 system-tile">{{ orphanFilesCount }}</h1>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8 mb-3">
<div class="card shadow-sm mb-3">
<div class="card-header"><i class="fas fa-paint-brush fa-fw"></i> {{ lang('theme') }}</div>
<div class="card-body">
<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>{{ lang('click_to_load') }}</option>
</select>
</div>
<div class="col-md-8 mt-3">
<div class="row same-height-container">
<div class="col-md-6">
<div class="card shadow-sm same-height">
<div class="card-header"><i class="fas fa-paint-brush fa-fw"></i> {{ lang('theme') }}</div>
<div class="card-body">
<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>{{ 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 float-right" id="themes-apply" disabled>
<i class="fas fa-save fa-fw"></i> {{ lang('apply') }}
</button>
</div>
</div>
</form>
</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> {{ lang('apply') }}
</button>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card shadow-sm same-height">
<div class="card-header"><i class="fas fa-language fa-fw"></i> {{ lang('enforce_language') }}</div>
<div class="card-body">
<form method="post" action="{{ route('lang.apply') }}">
<div class="form-group row">
<div class="col-sm-12">
<select class="form-control" id="lang" name="lang">
<option value="auto" selected>({{ lang('auto_set') }})</option>
{% for lang, name in installed_lang %}
<option value="{{ lang }}">{{ name }}</option>
{% endfor %}
</select>
<small>{{ lang('default_lang_behavior') }}</small>
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<button type="submit" class="btn btn-outline-success float-right" id="lang-apply">
<i class="fas fa-save fa-fw"></i> {{ lang('apply') }}
</button>
</div>
</div>
</form>
</div>
</form>
</div>
</div>
</div>
<div class="card shadow-sm mb-3">
<div class="card-header"><i class="fas fa-language fa-fw"></i> {{ lang('enforce_language') }}</div>
<div class="card-body">
<form method="post" action="{{ route('lang.apply') }}">
<div class="form-group row">
<div class="col-sm-12">
<select class="form-control" id="lang" name="lang">
<option value="auto" selected>({{ lang('auto_set') }})</option>
{% for lang, name in installed_lang %}
<option value="{{ lang }}">{{ name }}</option>
{% endfor %}
</select>
<small>{{ lang('default_lang_behavior') }}</small>
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<button type="submit" class="btn btn-outline-success" id="lang-apply">
<i class="fas fa-save fa-fw"></i> {{ lang('apply') }}
</button>
</div>
</div>
</form>
</div>
</div>
<div class="card shadow-sm">
<div class="card shadow-sm mt-3">
<div class="card-header"><i class="fas fa-cloud-download-alt fa-fw"></i> {{ lang('updates') }} <span class="float-right">v{{ PLATFORM_VERSION }}</span></div>
<div class="card-body">
<div class="row">
@ -125,9 +131,21 @@
</div>
</div>
</div>
<div class="card shadow-sm mt-3">
<div class="card-header"><i class="fas fa-code fa-fw"></i> {{ lang('custom_head_html') }}</div>
<div class="card-body">
<form method="post" action="{{ route('customHead.apply') }}">
<textarea name="custom_head" class="form-control text-monospace">{{ customHead|raw }}</textarea>
<small>{{ lang('custom_head_html_hint') }}</small>
<button type="submit" class="btn btn-outline-success float-right mt-3">
<i class="fas fa-save fa-fw"></i> {{ lang('apply') }}
</button>
</form>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card shadow-sm mb-3">
<div class="col-md-4 mt-3">
<div class="card shadow-sm">
<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>
@ -137,12 +155,21 @@
</ul>
</div>
</div>
<div class="card shadow-sm mb-3">
<div class="card shadow-sm mt-3">
<div class="card-header"><i class="fas fa-tools fa-fw"></i> {{ lang('maintenance') }}</div>
<div class="card-body">
<a href="{{ route('system.deleteOrphanFiles') }}" class="btn btn-outline-dark btn-block"><i class="fas fa-broom fa-fw"></i> {{ lang('clean_orphaned_uploads') }}</a>
</div>
</div>
<div class="card shadow-sm mt-3">
<div class="card-header"><i class="fas fa-donate fa-fw"></i> {{ lang('donation') }}</div>
<div class="card-body">
<p>{{ lang('donate_text') }}</p>
<div class="text-center">
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=6RXF8ZGCZBL68&item_name=Support+the+XBackBone+Development&currency_code=EUR&source=url" target="_blank" class="text-warning"><i class="fab fa-cc-paypal fa-3x"></i></a>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -111,4 +111,8 @@ body {
.dropzone .dz-image {
border-radius: .25rem !important;
}
.system-tile {
font-size: 2.5rem;
}

View file

@ -29,6 +29,16 @@ var app = {
$('.footer').fadeIn(600);
$('.same-height-container').each(function () {
var highestBox = 0;
$('.same-height', this).each(function () {
if ($(this).height() > highestBox) {
highestBox = $(this).height();
}
});
$('.same-height', this).height(highestBox);
});
console.log('Application is ready.');
},
modalDelete: function () {