add userarea-layout + sidebar and topmenu

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
This commit is contained in:
Michael Kaufmann
2022-02-16 14:54:06 +01:00
parent 4fd6ebf5b7
commit 759d11d1a0
11 changed files with 189 additions and 67 deletions

View File

@@ -442,7 +442,7 @@ class Database
if (empty($sql['debug'])) { if (empty($sql['debug'])) {
$error_trace = ''; $error_trace = '';
} elseif (!is_null($stmt)) { } elseif (!is_null($stmt)) {
$error_trace .= "<br><br>" . $stmt->queryString; $error_trace .= "\n\n" . $stmt->queryString;
} }
if ($showerror && $json_response) { if ($showerror && $json_response) {

View File

@@ -152,7 +152,7 @@ class PhpHelper
return false; return false;
} }
public static function phpExceptionHandler(\Exception $exception) public static function phpExceptionHandler(\Throwable $exception)
{ {
if (! isset($_SERVER['SHELL']) || (isset($_SERVER['SHELL']) && $_SERVER['SHELL'] == '')) { if (! isset($_SERVER['SHELL']) || (isset($_SERVER['SHELL']) && $_SERVER['SHELL'] == '')) {
$err_display = '<div class="alert alert-danger my-1" role="alert">'; $err_display = '<div class="alert alert-danger my-1" role="alert">';

View File

@@ -1,4 +1,5 @@
<?php <?php
namespace Froxlor\UI; namespace Froxlor\UI;
class HTML class HTML
@@ -17,9 +18,7 @@ class HTML
*/ */
public static function buildNavigation($navigation, $userinfo) public static function buildNavigation($navigation, $userinfo)
{ {
global $theme; $returnvalue = [];
$returnvalue = '';
// sanitize user-given input (url-manipulation) // sanitize user-given input (url-manipulation)
if (isset($_GET['page']) && is_array($_GET['page'])) { if (isset($_GET['page']) && is_array($_GET['page'])) {
@@ -30,17 +29,17 @@ class HTML
} }
foreach ($navigation as $box) { foreach ($navigation as $box) {
if ((! isset($box['show_element']) || $box['show_element'] === true) && (! isset($box['required_resources']) || $box['required_resources'] == '' || (isset($userinfo[$box['required_resources']]) && ((int) $userinfo[$box['required_resources']] > 0 || $userinfo[$box['required_resources']] == '-1')))) { if ((!isset($box['show_element']) || $box['show_element'] === true) && (!isset($box['required_resources']) || $box['required_resources'] == '' || (isset($userinfo[$box['required_resources']]) && ((int) $userinfo[$box['required_resources']] > 0 || $userinfo[$box['required_resources']] == '-1')))) {
$navigation_links = ''; $navigation_links = [];
foreach ($box['elements'] as $element_id => $element) { foreach ($box['elements'] as $element_id => $element) {
if ((! isset($element['show_element']) || $element['show_element'] === true) && (! isset($element['required_resources']) || $element['required_resources'] == '' || (isset($userinfo[$element['required_resources']]) && ((int) $userinfo[$element['required_resources']] > 0 || $userinfo[$element['required_resources']] == '-1')))) { if ((!isset($element['show_element']) || $element['show_element'] === true) && (!isset($element['required_resources']) || $element['required_resources'] == '' || (isset($userinfo[$element['required_resources']]) && ((int) $userinfo[$element['required_resources']] > 0 || $userinfo[$element['required_resources']] == '-1')))) {
$target = ''; $target = '';
$active = ''; $active = false;
$navurl = '#'; $navurl = '#';
if (isset($element['url']) && trim($element['url']) != '') { if (isset($element['url']) && trim($element['url']) != '') {
// append sid only to local // append sid only to local
if (! preg_match('/^https?\:\/\//', $element['url']) && (isset($userinfo['hash']) && $userinfo['hash'] != '')) { if (!preg_match('/^https?\:\/\//', $element['url']) && (isset($userinfo['hash']) && $userinfo['hash'] != '')) {
// generate sid with ? oder & // generate sid with ? oder &
if (strpos($element['url'], '?') !== false) { if (strpos($element['url'], '?') !== false) {
@@ -55,9 +54,9 @@ class HTML
} }
if (isset($_GET['page']) && substr_count($element['url'], "page=" . $_GET['page']) > 0 && substr_count($element['url'], basename($_SERVER["SCRIPT_FILENAME"])) > 0 && isset($_GET['action']) && substr_count($element['url'], "action=" . $_GET['action']) > 0) { if (isset($_GET['page']) && substr_count($element['url'], "page=" . $_GET['page']) > 0 && substr_count($element['url'], basename($_SERVER["SCRIPT_FILENAME"])) > 0 && isset($_GET['action']) && substr_count($element['url'], "action=" . $_GET['action']) > 0) {
$active = ' active'; $active = true;
} elseif (isset($_GET['page']) && substr_count($element['url'], "page=" . $_GET['page']) > 0 && substr_count($element['url'], basename($_SERVER["SCRIPT_FILENAME"])) > 0 && substr_count($element['url'], "action=") == 0 && ! isset($_GET['action'])) { } elseif (isset($_GET['page']) && substr_count($element['url'], "page=" . $_GET['page']) > 0 && substr_count($element['url'], basename($_SERVER["SCRIPT_FILENAME"])) > 0 && substr_count($element['url'], "action=") == 0 && !isset($_GET['action'])) {
$active = ' active'; $active = true;
} }
$navurl = htmlspecialchars($element['url']); $navurl = htmlspecialchars($element['url']);
@@ -66,16 +65,21 @@ class HTML
$navlabel = $element['label']; $navlabel = $element['label'];
} }
eval("\$navigation_links .= \"" . \Froxlor\UI\Template::getTemplate("navigation_link", 1) . "\";"); $navigation_links[] = [
'url' => $navurl,
'target' => $target,
'is_active' => $active,
'label' => $navlabel
];
} }
} }
if ($navigation_links != '') { if (!empty($navigation_links)) {
$target = ''; $target = '';
if (isset($box['url']) && trim($box['url']) != '') { if (isset($box['url']) && trim($box['url']) != '') {
// append sid only to local // append sid only to local
if (! preg_match('/^https?\:\/\//', $box['url']) && (isset($userinfo['hash']) && $userinfo['hash'] != '')) { if (!preg_match('/^https?\:\/\//', $box['url']) && (isset($userinfo['hash']) && $userinfo['hash'] != '')) {
// generate sid with ? oder & // generate sid with ? oder &
if (strpos($box['url'], '?') !== false) { if (strpos($box['url'], '?') !== false) {
@@ -96,7 +100,12 @@ class HTML
$navlabel = $box['label']; $navlabel = $box['label'];
} }
eval("\$returnvalue .= \"" . \Froxlor\UI\Template::getTemplate("navigation_element", 1) . "\";"); $returnvalue[] = [
'url' => $navurl,
'target' => $target,
'label' => $navlabel,
'items' => $navigation_links
];
} }
} }
} }
@@ -134,11 +143,11 @@ class HTML
$checked = ''; $checked = '';
} }
if (! $title_trusted) { if (!$title_trusted) {
$title = htmlspecialchars($title); $title = htmlspecialchars($title);
} }
if (! $value_trusted) { if (!$value_trusted) {
$value = htmlspecialchars($value); $value = htmlspecialchars($value);
} }
@@ -181,11 +190,11 @@ class HTML
$selected .= ' disabled="disabled"'; $selected .= ' disabled="disabled"';
} }
if (! $title_trusted) { if (!$title_trusted) {
$title = htmlspecialchars($title); $title = htmlspecialchars($title);
} }
if (! $value_trusted) { if (!$value_trusted) {
$value = htmlspecialchars($value); $value = htmlspecialchars($value);
} }

View File

@@ -44,6 +44,10 @@ class FroxlorTwig extends \Twig\Extension\AbstractExtension
new \Twig\TwigFunction('lng', [ new \Twig\TwigFunction('lng', [
$this, $this,
'getLang' 'getLang'
]),
new \Twig\TwigFunction('linker', [
$this,
'getLink'
]) ])
); );
} }
@@ -91,6 +95,11 @@ class FroxlorTwig extends \Twig\Extension\AbstractExtension
return \Froxlor\UI\Panel\UI::getLng($identifier); return \Froxlor\UI\Panel\UI::getLng($identifier);
} }
public function getLink($linkopts)
{
return \Froxlor\UI\Panel\UI::getLinker()->getLink($linkopts);
}
/** /**
* *
* {@inheritdoc} * {@inheritdoc}

View File

@@ -28,6 +28,11 @@ class UI
*/ */
private static $lng = array(); private static $lng = array();
/**
* linker class object
*/
private static $linker = null;
/** /**
* default fallback theme * default fallback theme
* *
@@ -207,6 +212,16 @@ class UI
self::$twigbuf = []; self::$twigbuf = [];
} }
public static function setLinker($linker = null)
{
self::$linker = $linker;
}
public static function getLinker()
{
return self::$linker;
}
public static function setLng($lng = array()) public static function setLng($lng = array())
{ {
self::$lng = $lng; self::$lng = $lng;

View File

@@ -176,7 +176,7 @@ if (isset($s) && $s != "" && $nosession != 1) {
ini_set("url_rewriter.tags", ""); ini_set("url_rewriter.tags", "");
ini_set("session.use_cookies", false); ini_set("session.use_cookies", false);
ini_set("session.cookie_httponly", true); ini_set("session.cookie_httponly", true);
ini_set("session.cookie_secure", $is_ssl); ini_set("session.cookie_secure", UI::$SSL_REQ);
session_id($s); session_id($s);
session_start(); session_start();
$query = "SELECT `s`.*, `u`.* FROM `" . TABLE_PANEL_SESSIONS . "` `s` LEFT JOIN `"; $query = "SELECT `s`.*, `u`.* FROM `" . TABLE_PANEL_SESSIONS . "` `s` LEFT JOIN `";
@@ -305,8 +305,9 @@ include_once \Froxlor\FileDir::makeSecurePath('lng/lng_references.php');
UI::setLng($lng); UI::setLng($lng);
// Initialize our new link - class // Initialize our link - class
$linker = new \Froxlor\UI\Linker('index.php', $s); $linker = new \Froxlor\UI\Linker('index.php', $s);
UI::setLinker($linker);
/** /**
* global Theme-variable * global Theme-variable
@@ -360,6 +361,9 @@ if (Settings::Get('panel.logo_overridecustom') == 0 && file_exists($hl_path . '/
} }
} }
UI::Twig()->addGlobal('header_logo_login', $header_logo_login);
UI::Twig()->addGlobal('header_logo', $header_logo);
/** /**
* Redirects to index.php (login page) if no session exists * Redirects to index.php (login page) if no session exists
*/ */
@@ -373,10 +377,7 @@ if ($nosession == 1 && AREA != 'login') {
exit(); exit();
} }
/** UI::Twig()->addGlobal('userinfo', ($userinfo ?? []));
* Initialize Template Engine
*/
$templatecache = array();
/** /**
* Logic moved out of lng-file * Logic moved out of lng-file
@@ -390,7 +391,7 @@ if (isset($userinfo['loginname']) && $userinfo['loginname'] != '') {
/** /**
* Fills variables for navigation, header and footer * Fills variables for navigation, header and footer
*/ */
$navigation = ""; $navigation = [];
if (AREA == 'admin' || AREA == 'customer') { if (AREA == 'admin' || AREA == 'customer') {
if (\Froxlor\Froxlor::hasUpdates() || \Froxlor\Froxlor::hasDbUpdates()) { if (\Froxlor\Froxlor::hasUpdates() || \Froxlor\Froxlor::hasDbUpdates()) {
/* /*
@@ -431,8 +432,8 @@ if (AREA == 'admin' || AREA == 'customer') {
$navigation_data = \Froxlor\PhpHelper::loadConfigArrayDir('lib/navigation/'); $navigation_data = \Froxlor\PhpHelper::loadConfigArrayDir('lib/navigation/');
$navigation = \Froxlor\UI\HTML::buildNavigation($navigation_data[AREA], $userinfo); $navigation = \Froxlor\UI\HTML::buildNavigation($navigation_data[AREA], $userinfo);
} }
unset($navigation_data);
} }
UI::Twig()->addGlobal('nav_entries', $navigation);
$js = ""; $js = "";
if (is_array($_themeoptions) && array_key_exists('js', $_themeoptions['variants'][$themevariant]) && is_array($_themeoptions['variants'][$themevariant]['js'])) { if (is_array($_themeoptions) && array_key_exists('js', $_themeoptions['variants'][$themevariant]) && is_array($_themeoptions['variants'][$themevariant]['js'])) {

View File

@@ -0,0 +1,5 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block content %}
test text
{% endblock %}

View File

@@ -22,44 +22,10 @@
</title> </title>
</head> </head>
<body {% if body_class is defined %}class="{{ body_class }}"{% endif %}> <body {% if body_class is defined %}class="{{ body_class }}"{% endif %}>
<div class="container-fluid">
{% block navigation %}{% endblock %} {% block navigation %}{% endblock %}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Dropdown
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
<form class="d-flex">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
</div>
</nav>
{{ global_errors|raw }} {{ global_errors|raw }}

View File

@@ -6,7 +6,7 @@
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<form class="col-4 d-flex flex-column" method="post" enctype="application/x-www-form-urlencoded"> <form class="col-4 d-flex flex-column" method="post" enctype="application/x-www-form-urlencoded">
<img class="align-self-center mb-5" src="{{ basehref }}templates/Froxlor/assets/img/logo.png" alt="Froxlor Server Management Panel"/> <img class="align-self-center mb-5" src="{{ header_logo_login }}" alt="Froxlor Server Management Panel"/>
<div class="card shadow"> <div class="card shadow">
<div class="card-body"> <div class="card-body">

View File

@@ -0,0 +1,32 @@
<nav id="sidebar">
<ul class="nav flex-column flex-nowrap overflow-hidden w-100">
{% for idx,mitems in nav_entries %}
{% if mitems.label|upper != 'OVERVIEW' %}
{% if mitems.items is not empty %}
<li class="nav-item">
<a class="nav-link text-light {% if mitems.active == 0 %}collapsed{% endif %}" href="#sub{{ idx }}" data-bs-toggle="collapse" data-bs-target="#sub{{ idx }}">
<span class="{{ mitems.icon }}"></span>
{{ mitems.label|upper }}</a>
<div class="collapse {% if mitems.active == 1 %}show{% endif %}" id="sub{{ idx }}" aria-expanded="{% if mitems.active == 1 %}true{% else %}false{% endif %}">
<ul class="flex-column ps-3 nav">
{% for item in mitems.items %}
<li class="nav-item">
<a class="nav-link text-light pb-0 {% if item.active == 1 %}font-weight-bold{% endif %}" href="{{ item.url }}">{{ item.label }}</a>
</li>
{% endfor %}
</ul>
</div>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link text-light {% if mitems.active == 1 %}active{% endif %}" href="{% if mitems.url is not empty %}{{ mitems.url }}{% else %}#{% endif %}" {% if mitems.target is not empty %} target="{{ mitems.target }}" {% endif %}>
{% if mitems.icon is not empty %}
<span class="{{ mitems.icon }}"></span>
{% endif %}
{{ mitems.label|upper }}</a>
</li>
{% endif %}
{% endif %}
{% endfor %}
</ul>
</nav>

View File

@@ -0,0 +1,85 @@
{% extends "Froxlor/base.html.twig" %}
{% block navigation %}
<nav class="navbar navbar-expand navbar-light bg-light shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="{{ linker({'section': 'index'}) }}">
<img src="{{ header_logo }}" alt="" width="auto" height="24" class="d-inline-block align-text-top">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarTop" aria-controls="navbarTop" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-end" id="navbarTop">
<ul class="navbar-nav align-items-center">
{% if get_setting('panel.is_configured') == 0 and userinfo.adminsession == 1 and userinfo.change_serversettings == 1 %}
<div class="alert alert-warning py-0 mx-2 my-auto" role="alert">
<a href="{{ linker({'section': 'configfiles', 'page': 'configfiles'}) }}" class="alert-link">{{ lng('panel.not_configured') }}</a>
</div>
{% endif %}
<li class="nav-item text-nowrap d-block me-2">
<a class="btn btn-primary btn-sm d-block" href="{{ linker({'section': 'index'}) }}">{{ lng('panel.dashboard') }}</a>
</li>
<li class="nav-item dropdown me-2">
<a class="btn btn-info btn-sm d-block dropdown-toggle" href="#" id="navbarOpts" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ lng('panel.options') }}
</a>
<ul class="dropdown-menu" aria-labelledby="navbarOpts">
<li>
<a class="dropdown-item" href="{{ linker({'section': 'index', 'page': 'change_password'}) }}">{{ lng('login.password') }}</a>
</li>
<li>
<a class="dropdown-item" href="{{ linker({'section': 'index', 'page': 'change_language'}) }}">{{ lng('login.language') }}</a>
</li>
{% if get_setting('2fa.enabled') == 1 %}
<li>
<a class="dropdown-item" href="{{ linker({'section': 'index', 'page': '2fa'}) }}">{{ lng('2fa.2fa') }}</a>
</li>
{% endif %}
{% if get_setting('panel.allow_theme_change_admin') == '1' and userinfo.adminsession == 1 %}
<li>
<a class="dropdown-item" href="{{ linker({'section': 'index', 'page': 'change_theme'}) }}">{{ lng('panel.theme') }}</a>
</li>
{% elseif get_setting('panel.allow_theme_change_customer') == '1' and userinfo.adminsession == 0 %}
<li>
<a class="dropdown-item" href="{{ linker({'section': 'index', 'page': 'change_theme'}) }}">{{ lng('panel.theme') }}</a>
</li>
{% endif %}
{% if get_setting('api.enabled') == 1 %}
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item" href="{{ linker({'section': 'index', 'page': 'apikeys'}) }}">{{ lng('menue.main.apikeys') }}</a>
</li>
<li>
<a class="dropdown-item" href="https://docs.froxlor.org/apiguide/index.html" rel="external" target="_blank">{{ lng('menue.main.apihelp') }}</a>
</li>
{% endif %}
</ul>
</li>
<!-- if switched-user
<li class="nav-item text-nowrap d-block me-2">
<a class="btn btn-info btn-sm d-block" href="#view=suBack">
<i class="fas fa-undo"></i>
Switch back</a>
</li>
endif -->
<li class="nav-item text-nowrap d-block">
<a class="btn btn-danger btn-sm d-block" href="{{ linker({'section': 'index', 'action': 'logout'}) }}">
<i class="fas fa-power-off"></i>
{{ lng('login.logout') }}</a>
</li>
</ul>
</div>
</div>
</nav>
{% endblock %}
{% block body %}
<div class="row">
<div class="col-2 bg-secondary text-light">
{{ include('Froxlor/sidebarmenu.html.twig') }}
</div>
<main role="main" class="col-10 px-3 py-2">
{% block content %}{% endblock %}
</main>
</div>
{% endblock %}