Browse Source

Added ability to set custom html
improved session samesite implementation for older php versions
Fixes #82

Sergio Brighenti 5 years ago
parent
commit
5adb29d700

+ 3 - 0
CHANGELOG.md

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

+ 20 - 2
app/Controllers/AdminController.php

@@ -80,8 +80,8 @@ class AdminController extends Controller
     {
     {
         $config = require BASE_DIR.'config.php';
         $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 {
         } else {
             unset($config['lang']);
             unset($config['lang']);
         }
         }
@@ -92,4 +92,22 @@ class AdminController extends Controller
 
 
         return redirect($response, route('system'));
         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'));
+    }
 }
 }

+ 1 - 1
app/Database/Migrator.php

@@ -47,7 +47,7 @@ class Migrator
             $this->db->getPdo()->exec(file_get_contents($this->schemaPath.DIRECTORY_SEPARATOR.'migrations.sql'));
             $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) {
         $names = array_map(function ($path) {
             return basename($path);
             return basename($path);

+ 1 - 1
app/Middleware/AuthMiddleware.php

@@ -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) {
         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);
             $this->session->set('logged', false);
             return redirect(new Response(), route('login.show'));
             return redirect(new Response(), route('login.show'));
         }
         }

+ 26 - 0
app/Middleware/InjectMiddleware.php

@@ -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)));
+    }
+}

+ 2 - 2
app/Middleware/Middleware.php

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

+ 12 - 12
app/Web/Session.php

@@ -21,6 +21,18 @@ class Session
                 throw new Exception("The given path '{$path}' is not writable.");
                 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([
             $started = @session_start([
                 'name' => $name,
                 'name' => $name,
                 'save_path' => $path,
                 'save_path' => $path,
@@ -29,18 +41,6 @@ class Session
                 'cookie_samesite' => 'Lax' // works only for php  >= 7.3
                 '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) {
             if (!$started) {
                 throw new Exception("Cannot start the HTTP session. That the session path '{$path}' is writable and your PHP settings.");
                 throw new Exception("Cannot start the HTTP session. That the session path '{$path}' is writable and your PHP settings.");
             }
             }

+ 8 - 0
app/Web/View.php

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

+ 2 - 0
app/routes.php

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

+ 5 - 2
bootstrap/app.php

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

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

@@ -102,4 +102,9 @@ return [
     'prerelease_channel' => 'Prerelease Channel',
     'prerelease_channel' => 'Prerelease Channel',
     'no_upload_token' => 'You don\'t have a personal upload token. (Generate one and try again)',
     '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.',
     '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.',
 ];
 ];

+ 5 - 0
resources/schemas/mysql/mysql.3.sql

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

+ 4 - 0
resources/schemas/sqlite/sqlite.3.sql

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

+ 1 - 0
resources/templates/base.twig

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

+ 77 - 50
resources/templates/dashboard/system.twig

@@ -14,7 +14,7 @@
                             <i class="fas fa-users fa-3x"></i>
                             <i class="fas fa-users fa-3x"></i>
                         </div>
                         </div>
                         <h6 class="text-uppercase">{{ lang('users') }}</h6>
                         <h6 class="text-uppercase">{{ lang('users') }}</h6>
-                        <h1 class="display-4">{{ usersCount }}</h1>
+                        <h1 class="display-4 system-tile">{{ usersCount }}</h1>
                     </div>
                     </div>
                 </div>
                 </div>
             </div>
             </div>
@@ -25,7 +25,7 @@
                             <i class="fas fa-weight fa-3x"></i>
                             <i class="fas fa-weight fa-3x"></i>
                         </div>
                         </div>
                         <h6 class="text-uppercase">{{ lang('size') }}</h6>
                         <h6 class="text-uppercase">{{ lang('size') }}</h6>
-                        <h1 class="display-4">{{ totalSize }}</h1>
+                        <h1 class="display-4 system-tile">{{ totalSize }}</h1>
                     </div>
                     </div>
                 </div>
                 </div>
             </div>
             </div>
@@ -36,7 +36,7 @@
                             <i class="fas fa-upload fa-3x"></i>
                             <i class="fas fa-upload fa-3x"></i>
                         </div>
                         </div>
                         <h6 class="text-uppercase">{{ lang('files') }}</h6>
                         <h6 class="text-uppercase">{{ lang('files') }}</h6>
-                        <h1 class="display-4">{{ mediasCount }}</h1>
+                        <h1 class="display-4 system-tile">{{ mediasCount }}</h1>
                     </div>
                     </div>
                 </div>
                 </div>
             </div>
             </div>
@@ -47,60 +47,66 @@
                             <i class="fas fa-unlink fa-3x"></i>
                             <i class="fas fa-unlink fa-3x"></i>
                         </div>
                         </div>
                         <h6 class="text-uppercase">{{ lang('orphaned_files') }}</h6>
                         <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>
             </div>
         </div>
         </div>
         <div class="row">
         <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>
-                            <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 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>
-                        </form>
+                        </div>
                     </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 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>
                             </div>
-                        </form>
+                        </div>
                     </div>
                     </div>
                 </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-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="card-body">
                         <div class="row">
                         <div class="row">
@@ -125,9 +131,21 @@
                         </div>
                         </div>
                     </div>
                     </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>
-            <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-header"><i class="fas fa-cog fa-fw"></i> {{ lang('system_info') }}</div>
                     <div class="card-body">
                     <div class="card-body">
                         <strong>Max upload size:</strong>
                         <strong>Max upload size:</strong>
@@ -137,12 +155,21 @@
                         </ul>
                         </ul>
                     </div>
                     </div>
                 </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-header"><i class="fas fa-tools fa-fw"></i> {{ lang('maintenance') }}</div>
                     <div class="card-body">
                     <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>
                         <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>
                 </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>
         </div>
     </div>
     </div>

+ 4 - 0
src/css/app.css

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

+ 10 - 0
src/js/app.js

@@ -29,6 +29,16 @@ var app = {
 
 
         $('.footer').fadeIn(600);
         $('.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.');
         console.log('Application is ready.');
     },
     },
     modalDelete: function () {
     modalDelete: function () {