Objectify locale negotiator and template files, add documentation

This commit is contained in:
Vojtěch Sajdl 2018-01-07 20:39:10 +01:00
parent 9820686776
commit 61f563c9b0
No known key found for this signature in database
GPG Key ID: 5D4EB1361A272390
20 changed files with 440 additions and 230 deletions

View File

@ -1,12 +1,11 @@
<?php
require("config.php");
require("header.php");
require("footer.php");
render_header("Page not found");
require("template.php");
Template::render_header("Page not found");
?>
<div class="text-center">
<h1><?php echo _("Page Not Found");?></h1>
<p><?php echo _("Sorry, but the page you were trying to view does not exist.");?></p>
</div>
<?php
render_footer();
Template::render_footer();

View File

@ -19,7 +19,7 @@ if (isset($_GET['delete']))
Incident::delete($_GET['delete']);
}
render_header(_("Dashboard"), true);
Template::render_header(_("Dashboard"), true);
?>
<div class="text-center">

View File

@ -2,8 +2,7 @@
session_start();
require("../config.php");
require("../classes/constellation.php");
require("../header.php");
require("../footer.php");
require("../template.php");
if(isset($_COOKIE['user'])&&!isset($_SESSION['user']))
{
@ -66,5 +65,5 @@ else
break;
}
render_footer(true);
Template::render_footer(true);
}

View File

@ -1,5 +1,5 @@
<?php
render_header(_("Login"));
Template::render_header(_("Login"));
?>
<div class="text-center">
<h1><?php echo _("Login");?></h1>
@ -29,4 +29,4 @@ render_header(_("Login"));
</form>
</div>
<?php
render_footer();
Template::render_footer();

View File

@ -1,5 +1,5 @@
<?php
render_header(_("Lost password"));
Template::render_header(_("Lost password"));
?>
<div class="text-center">
<h1><?php echo _("Lost password");?></h1>
@ -69,4 +69,4 @@ render_header(_("Lost password"));
<?php }?>
</div>
<?php
render_footer();
Template::render_footer();

View File

@ -4,7 +4,7 @@ if (isset($_GET['new']))
User::add();
}
render_header(_("New user"), true); ?>
Template::render_header(_("New user"), true); ?>
<div class="text-center">
<h2>Add new user</h2>
</div>

View File

@ -9,7 +9,7 @@ if (isset($_GET['delete']))
Service::delete();
}
render_header(_("Settings"), true);
Template::render_header(_("Settings"), true);
?>
<div class="text-center">
<h2>Settings</h2>

View File

@ -32,7 +32,7 @@ if (isset($_GET['what']) && $_GET['what']=='toggle')
$displayed_user->toggle();
}
render_header(_("User"), true);
Template::render_header(_("User"), true);
?>
<div class="text-center">

View File

@ -10,6 +10,13 @@ require(__DIR__ . "/token.php");
class Constellation
{
/**
* Renders incidents matching specified constraints.
* @param Boolean $future - specifies whether to render old or upcoming incidents
* @param int $offset - specifies offset - used for pagination
* @param int $limit - limits the number of incidents rendered
* @param Boolean $admin - specifies whether to render admin controls
*/
public function render_incidents($future=false, $offset=0, $limit = 5, $admin = 0){
global $mysqli;
if ($offset<0)
@ -57,6 +64,11 @@ class Constellation
}
}
/**
* Renders service status - in admin page it returns array so it can be processed further.
* @param boolean $admin
* @return array of services
*/
public function render_status($admin = 0){
global $mysqli;

View File

@ -12,8 +12,13 @@ class Incident
private $title;
private $username;
/**
* Constructs service from its data.
* @param array $data incident data
*/
function __construct($data)
{
//TODO: Maybe get data from id?
$this->id = $data['status_id'];
$this->date = new DateTime("@".$data['time']);
$this->date = $this->date->format('Y-m-d H:i:sP');
@ -27,7 +32,12 @@ class Incident
$this->username = $data['username'];
}
/**
* Deletes incident by ID.
* @param int ID
*/
public static function delete($id){
//TODO: This should check whether it's admin or their own post...
global $mysqli, $message;
$stmt = $mysqli->prepare("DELETE FROM services_status WHERE status_id = ?");
@ -42,6 +52,12 @@ class Incident
header("Location: /admin");
}
/**
* Processes submitted form and adds incident unless problem is encountered,
* calling this is possible only for admin or higher rank. Also checks requirements
* for char limits.
* @return void
*/
public static function add()
{
global $mysqli, $message;
@ -120,6 +136,12 @@ class Incident
}
}
/**
* Renders incident
* @param Boolean $admin - decides whether admin controls should be rendered
* @return void
*/
public function render($admin=0){
global $icons;
global $classes, $user;

View File

@ -0,0 +1,105 @@
<?php
/**
* This class is used to negotiate language displayed to user.
* Reads browser preferences and chooses the best language from list
*/
class LocaleNegotiator
{
private $accepted_langs = [];
private $default_language;
/**
* This method scans for languages and creates a list of language and its name (localized ofc.)
* @param String $default_language language displayed to user in case no suitable lang is found
*/
function __construct($default_language)
{
$tmp = glob(__DIR__ . '/locale/*' , GLOB_ONLYDIR);
$this->default_language = $default_language;
//Works only if the server supports the locale
//This basically means $accepted_langs[<lang_code>] = "<lang name>";
foreach ($accepted_langs as $key => $value) {
$this->accepted_langs[basename($value)] = self::mb_ucfirst(locale_get_display_language($lang, $lang));
}
}
/**
* Returns list of accepted langs so it can be reused for rendering language list for switching...
*/
public function get_accepted_langs(){
return $this->accepted_langs;
}
/**
* This methid does ucfirst() on multibyte encodings like UTF-8 - good for edge cases when locale starts with Č or similar.
* @param String $string string
* @return String string with first char uppercase
*/
private static function mb_ucfirst($string)
{
return mb_strtoupper(mb_substr($string, 0, 1)).mb_strtolower(mb_substr($string, 1));
}
/**
* This method does the actual negotiation. It has override parameter in case user wants to switch
* languages.
* @param String $override adds language to list of preffered languages with highest priority
* @return String language code that matched best with browser preferences
*/
public function negotiate($override = null){
$langs = [];
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
if (count($lang_parse[1])) {
$langs = array_combine($lang_parse[1], $lang_parse[4]);
foreach ($langs as $lang => $val) {
//If browser didn't send quality of language, it is 1 by default
if ($val === '') $langs[$lang] = 1;
}
if (isset($override))
{
//More important than the best lang of browser
$langs[$override] = 2;
}
arsort($langs, SORT_NUMERIC);
}
}
//So we have lang code as value
$langs = array_flip($langs);
//False unless we set it, so we know to set default locale
$best_match = false;
//So we have also lang code as value
$accepted_langs = array_flip($this->accepted_langs);
foreach ($langs as $lang) {
if (strlen($lang)>2){
if (in_array($lang, $accepted_langs)){
$best_match = $lang;
break;
}
}else{
$possible = array_filter($accepted_langs, function($key) {
global $lang;
return strpos($key, $lang) === 0;
});
if (count($possible)){
$best_match = $possible[0];
break;
}
}
}
if ($best_match === false){
$best_match = $this->default_language;
}
return $best_match;
}
}

View File

@ -1,6 +1,6 @@
<?php
/**
* Class for creating and rendering an incident
* Class for managing services
*/
class Service
{
@ -8,28 +8,53 @@ class Service
private $name;
private $status;
/**
* Constructs service from its data.
* @param int $id service ID
* @param String $name service name
* @param int $status current service status
*/
function __construct($id, $name, $status=3)
{
//TODO: Maybe get data from ID?
$this->id = $id;
$this->name = $name;
$this->status = $status;
}
/**
* Returns status of this service
* @return int status
*/
public function get_status()
{
return $this->status;
}
/**
* Returns id of this service
* @return int id
*/
public function get_id()
{
return $this->id;
}
/**
* Returns name of this service
* @return String name
*/
public function get_name()
{
return $this->name;
}
/**
* Processes submitted form and adds service unless problem is encountered,
* calling this is possible only for admin or higher rank. Also checks requirements
* for char limits.
* @return void
*/
public static function add()
{
global $user, $message;
@ -57,6 +82,10 @@ class Service
}
}
/**
* Deletes this service - first checks if user has permission to do that.
* @return void
*/
public static function delete()
{
global $user;
@ -95,6 +124,11 @@ class Service
}
}
/**
* Renders current status for services from passed array of services.
* @param Service[] $array array of services
* @return void
*/
public static function current_status($array){
global $all, $some, $classes;
$statuses = array(0,0,0,0);
@ -118,6 +152,10 @@ class Service
echo '</div>';
}
/**
* Renders this service.
* @return void
*/
public function render(){
global $statuses;
global $classes;

View File

@ -4,6 +4,13 @@
*/
class Token
{
/**
* Generates a new token from user id and randomly generated salt.
* @param int $user ID
* @param String $data associated with token that are important
* @param timestamp $expire expiration time
* @return String token
*/
public static function new($id, $data, $expire)
{
global $mysqli;
@ -16,6 +23,13 @@ class Token
return $token;
}
/**
* Checks whether token exists in the database and has not expired.
* @param String $token
* @param int $id user ID
* @param String $data
* @return int count of results in database
*/
public static function validate_token($token, $id, $data)
{
global $mysqli;
@ -27,6 +41,11 @@ class Token
return $query->fetch_assoc()['count'];
}
/**
* Deletes token.
* @param String $token
* @return void
*/
public static function delete($token)
{
global $mysqli;

View File

@ -1,6 +1,6 @@
<?php
/**
* Class for creating and rendering an incident
* Class that encapsulates everything that can be done with a user
*/
class User
{
@ -12,6 +12,10 @@ class User
private $rank;
private $active;
/**
* Gets user data from database and creates the class
* @param int $id user ID
*/
function __construct($id)
{
global $mysqli;
@ -36,26 +40,47 @@ class User
$this->rank = $result['permission'];
}
/**
* Returns username of this user
* @return String username
*/
public function get_username()
{
return $this->username;
}
/**
* Returns whether this user is active
* @return Boolean user active status
*/
public function is_active()
{
return $this->active;
}
/**
* Returns rank of this user
* @return int rank
*/
public function get_rank()
{
return $this->rank;
}
/**
* Returns full name of this user
* @return String name in "Name Surname" format
*/
public function get_name()
{
return $this->name . " " . $this->surname;
}
/**
* Toggles active status of this user. First checks if the user
* making the change has permission to do that.
* @return void
*/
public function toggle()
{
global $mysqli, $message, $user;
@ -78,6 +103,13 @@ class User
}
}
/**
* Processes submitted form and adds user unless problem is encountered,
* calling this is possible only for Superadmin (other ranks cannot add users)
* or when the installation script is being run. Also checks requirements
* for username and email being unique and char limits.
* @return void
*/
public static function add()
{
global $user, $message, $mysqli;
@ -146,6 +178,13 @@ class User
}
}
/**
* Processes submitted form and logs user in, unless the user is deactivated or wrong
* password or email has been submitted. The script doesn't let anyone know which
* field was wrong as it is not possible to verify email address from outside admin panel,
* so this actually helps with security :)
* @return void
*/
public static function login()
{
global $message, $mysqli;
@ -200,6 +239,12 @@ class User
}
}
/**
* Checks whether token is valid (this means is in database and associated
* with the user) and sets session data if it is, so user remains logged in.
* The script deletes the token either way.
* @return void
*/
public static function restore_session()
{
global $mysqli, $message;
@ -225,7 +270,10 @@ class User
Token::delete($token);
}
/**
* Renders settings for this user so it can be displayed in admin panel.
* @return void
*/
public function render_user_settings()
{
global $permissions, $user;
@ -307,7 +355,12 @@ class User
}
/**
* Changes user password and deletes all remember tokens so all other sessions
* won't stay logged in without knowing new pass. Uses token when reseting password.
* @param String $token
* @return void
*/
public function change_password($token = false)
{
global $mysqli, $user, $message;
@ -344,6 +397,10 @@ class User
$stmt->bind_param("si", $hash, $id);
$stmt->execute();
$stmt->close();
$stmt = $mysqli->prepare("DELETE FROM tokens WHERE user = ? AND data = 'remember'");
$stmt->bind_param("d", $id);
$stmt->execute();
$query = $stmt->get_result();
User::logout();
}
else{
@ -366,6 +423,10 @@ class User
$stmt->bind_param("si", $hash,$id);
$stmt->execute();
$stmt->close();
$stmt = $mysqli->prepare("DELETE FROM tokens WHERE user = ? AND data = 'remember'");
$stmt->bind_param("d", $id);
$stmt->execute();
$query = $stmt->get_result();
}
else
{
@ -377,6 +438,10 @@ class User
}
}
/**
* Sends email with link for password reset, link is token protected and valid only once.
* @return void
*/
public static function password_link()
{
global $mysqli;
@ -405,6 +470,10 @@ class User
mail($to, $subject, $msg, $headers);
}
/**
* Sends email with link for email change confirmation (security reasons), link is token protected and valid only once.
* @return void
*/
public function email_link(){
global $mysqli;
$email = $_POST['email'];
@ -426,9 +495,12 @@ class User
mail($to, $subject, $msg, $headers);
}
/**
* Changes email.
* @return void
*/
public function change_email()
{
//TODO: Get message from this somehow
global $mysqli, $message;
$time = time();
$token = $_GET['token'];
@ -456,6 +528,10 @@ class User
}
/**
* Logs current user out.
* @return void
*/
public static function logout(){
global $mysqli;
session_unset();
@ -469,6 +545,10 @@ class User
header("Location: /admin");
}
/**
* Changes permissions of current user - only super admin can do this, so it checks permission first.
* @return void
*/
public function change_permission(){
global $mysqli, $message, $user;
if ($user->get_rank()==0)

View File

@ -9,13 +9,27 @@ define("MAILER_ADDRESS", "##mailer_email##"); //Mailer address
define("INSTALL_OVERRIDE", false);
define("DEFAULT_LANGUAGE", "en_GB");
require("locale.php");
require("classes/locale-negotiator.php");
if (!isset($_SESSION['locale'])||isset($_GET['lang']))
{
$override = ((isset($_GET['lang']))?$_GET['lang']:null);
$negotiator = new LocaleNegotiator(DEFAULT_LANGUAGE);
$best_match = $negotiator->negotiate($override);
$_SESSION['locale'] = $best_match;
setlocale(LC_ALL, $_SESSION['locale'].".UTF-8");
bindtextdomain("server-status", __DIR__ . "/locale/");
bind_textdomain_codeset($_SESSION['locale'], "utf-8");
textdomain("server-status");
}
//Database connection
$mysqli = new mysqli("##server##","##user##","##password##","##database##");
if ($mysqli->connect_errno) {
printf("Connection failed: %s\n", $mysqli->connect_error);
printf(_("Connection failed: %s\n"), $mysqli->connect_error);
exit();
}

View File

@ -1,45 +0,0 @@
<?php
function render_footer($admin = false)
{
global $lang_names;
?>
</div>
<div id="footerwrap">
<div class="container">
<div class="row centered">
<div class="col-md-4 text-left">Copyright © <?php echo date("Y");?> Vojtěch Sajdl</div>
<div class="col-md-4 text-center">
<div class="btn-group dropup">
<button type="button" class="btn btn-primary"><?php echo '<img src="'.WEB_URL.'/locale/'.$_SESSION['locale'].'/flag.png" alt="'.$lang_names[$_SESSION['locale']].'">'.$lang_names[$_SESSION['locale']];?></button>
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only"><?php echo _("Toggle Dropdown");?></span>
</button>
<ul class="dropdown-menu">
<?php
foreach ($lang_names as $key => $value) {
echo '<a href="?lang='.$key.'"><img src="'.WEB_URL.'/locale/'.$key.'/flag.png" alt="'.$value.'">'.$value.'</a>';
}
?>
<hr role="separator" class="divider">
<a href="https://poeditor.com/join/project/37SpmJtyOm"><?php echo _("Help with translation!");?></a>
</ul>
</div>
</div>
<div class="col-md-4 text-right"><a href="https://github.com/Pryx/server-status/" target="_blank"><i class="fa fa-github" aria-hidden="true"></i></a></div>
</div><!--/row -->
</div><!--/container -->
</div>
<script src="/js/vendor/jquery-1.11.2.min.js"></script>
<script src="/js/vendor/jquery.timeago.js"></script>
<?php if ($admin){?>
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script src="/js/admin.js"></script>
<script src="/js/vendor/jquery.growl.js"></script>
<? }?>
<script src="/js/vendor/bootstrap.min.js"></script>
<script src="/js/main.js"></script>
</body>
</html>
<?
}

View File

@ -1,79 +0,0 @@
<?php
function render_header($page_name, $admin = false){
if (!$admin)
{
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?php echo $page_name." - ".NAME ?></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Current service status for <?php echo NAME;?> can be found here as well as incident history.">
<link rel="shortcut icon" href="/favicon.ico" type="image/png">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/main.css" media="screen">
<link rel="stylesheet" href="/css/print.css" media="print">
<link href="/css/font-awesome.min.css" rel="stylesheet">
</head>
<body>
<div class="navbar navbar-default" role="navigation">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="<?php echo WEB_URL;?>"><img src="/img/logo_white.png" alt="logo" class="menu-logo" width="50" height="50"></a>
</div>
<div class="navbar-left hidden-xs">
<ul class="nav navbar-nav">
<li><a href="<?php echo WEB_URL;?>/"><h1><?php echo _("Service Status");?></h1></a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
<div id="wrapper" class="center">
<?php
}
else{
global $user;
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?php echo $page_name." - ".NAME ?></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="/favicon.ico" type="image/png">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/main.css">
<link href="/css/font-awesome.min.css" rel="stylesheet">
<link href="/css/jquery.growl.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
</head>
<body class="admin">
<div class="navbar navbar-default" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only"><?php echo _("Toggle navigation");?></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="<?php echo WEB_URL;?>/admin"><img src="/img/logo_white.png" alt="logo" class="menu-logo" width="50" height="50"></a>
</div>
<div class="navbar-collapse collapse navbar-right navbar-admin">
<ul class="nav navbar-nav">
<li><a href="<?php echo WEB_URL;?>/admin/"><?php echo _("Dashboard");?></a></li>
<li><a href="<?php echo WEB_URL;?>/admin/?do=user"><?php printf(_("User (%s)"), $user->get_username());?></a></li>
<li><a href="<?php echo WEB_URL;?>/admin/?do=settings"><?php echo _("Settings");?></a></li>
<li><a href="<?php echo WEB_URL;?>/admin/?do=logout"><?php echo _("Logout");?></a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
<div id="wrapper" class="center admin">
<?php
}
}

View File

@ -1,6 +1,5 @@
<?php
require("header.php");
require("footer.php");
require("template.php");
if (!file_exists("config.php"))
{
@ -22,7 +21,7 @@ if (isset($_GET['ajax']))
$offset = $_GET['offset'];
}
render_header("Status");
Template::render_header("Status");
?>
<div class="text-center">
<h2><?php echo _("Current status");?></h2>
@ -47,5 +46,5 @@ render_header("Status");
</div>
<?php }
render_footer();
Template::render_footer();
}

View File

@ -1,77 +0,0 @@
<?php
function mb_ucfirst($string)
{
return mb_strtoupper(mb_substr($string, 0, 1)).mb_strtolower(mb_substr($string, 1));
}
$accepted_langs = glob(__DIR__ . '/locale/*' , GLOB_ONLYDIR);
$lang_names = array();
foreach ($accepted_langs as $key => $value) {
$accepted_langs[$key] = basename($value);
}
foreach ($accepted_langs as $lang) {
$lang_names[$lang] = mb_ucfirst(locale_get_display_language($lang, $lang));
}
if (!isset($_SESSION['locale'])||isset($_GET['lang']))
{
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
if (count($lang_parse[1])) {
$langs = array_combine($lang_parse[1], $lang_parse[4]);
foreach ($langs as $lang => $val) {
if ($val === '') $langs[$lang] = 1;
}
if (isset($_GET['lang']))
{
$langs[$_GET['lang']] = 2;
}
arsort($langs, SORT_NUMERIC);
}
}
$langs = array_flip($langs);
$best_match = false;
foreach ($langs as $lang) {
if (strlen($lang)>2){
if (in_array($lang, $accepted_langs)){
$best_match = $lang;
break;
}
}else{
$possible = array_filter($accepted_langs, function($key) {
global $lang;
return strpos($key, $lang) === 0;
});
if (count($possible)){
$best_match = $possible[0];
break;
}
}
}
if ($best_match === false){
$best_match = DEFAULT_LANGUAGE;
}
$_SESSION['locale'] = $best_match;
}
setlocale(LC_ALL, $_SESSION['locale'].".UTF-8");
bindtextdomain("server-status", __DIR__ . "/locale/");
bind_textdomain_codeset($_SESSION['locale'], "utf-8");
textdomain("server-status");

124
template.php Normal file
View File

@ -0,0 +1,124 @@
<?php
class Template(){
public static function render_header($page_name, $admin = false){
if (!$admin)
{
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?php echo $page_name." - ".NAME ?></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Current service status for <?php echo NAME;?> can be found here as well as incident history.">
<link rel="shortcut icon" href="/favicon.ico" type="image/png">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/main.css" media="screen">
<link rel="stylesheet" href="/css/print.css" media="print">
<link href="/css/font-awesome.min.css" rel="stylesheet">
</head>
<body>
<div class="navbar navbar-default" role="navigation">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="<?php echo WEB_URL;?>"><img src="/img/logo_white.png" alt="logo" class="menu-logo" width="50" height="50"></a>
</div>
<div class="navbar-left hidden-xs">
<ul class="nav navbar-nav">
<li><a href="<?php echo WEB_URL;?>/"><h1><?php echo _("Service Status");?></h1></a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
<div id="wrapper" class="center">
<?php
}else{
global $user;
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?php echo $page_name." - ".NAME ?></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="/favicon.ico" type="image/png">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/main.css">
<link href="/css/font-awesome.min.css" rel="stylesheet">
<link href="/css/jquery.growl.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
</head>
<body class="admin">
<div class="navbar navbar-default" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only"><?php echo _("Toggle navigation");?></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="<?php echo WEB_URL;?>/admin"><img src="/img/logo_white.png" alt="logo" class="menu-logo" width="50" height="50"></a>
</div>
<div class="navbar-collapse collapse navbar-right navbar-admin">
<ul class="nav navbar-nav">
<li><a href="<?php echo WEB_URL;?>/admin/"><?php echo _("Dashboard");?></a></li>
<li><a href="<?php echo WEB_URL;?>/admin/?do=user"><?php printf(_("User (%s)"), $user->get_username());?></a></li>
<li><a href="<?php echo WEB_URL;?>/admin/?do=settings"><?php echo _("Settings");?></a></li>
<li><a href="<?php echo WEB_URL;?>/admin/?do=logout"><?php echo _("Logout");?></a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
<div id="wrapper" class="center admin">
<?php
}
}
public static function render_footer($admin = false)
{
global $lang_names;
?>
</div>
<div id="footerwrap">
<div class="container">
<div class="row centered">
<div class="col-md-4 text-left">Copyright © <?php echo date("Y");?> Vojtěch Sajdl</div>
<div class="col-md-4 text-center">
<div class="btn-group dropup">
<button type="button" class="btn btn-primary"><?php echo '<img src="'.WEB_URL.'/locale/'.$_SESSION['locale'].'/flag.png" alt="'.$lang_names[$_SESSION['locale']].'">'.$lang_names[$_SESSION['locale']];?></button>
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only"><?php echo _("Toggle Dropdown");?></span>
</button>
<ul class="dropdown-menu">
<?php
foreach ($lang_names as $key => $value) {
echo '<a href="?lang='.$key.'"><img src="'.WEB_URL.'/locale/'.$key.'/flag.png" alt="'.$value.'">'.$value.'</a>';
}
?>
<hr role="separator" class="divider">
<a href="https://poeditor.com/join/project/37SpmJtyOm"><?php echo _("Help with translation!");?></a>
</ul>
</div>
</div>
<div class="col-md-4 text-right"><a href="https://github.com/Pryx/server-status/" target="_blank"><i class="fa fa-github" aria-hidden="true"></i></a></div>
</div><!--/row -->
</div><!--/container -->
</div>
<script src="/js/vendor/jquery-1.11.2.min.js"></script>
<script src="/js/vendor/jquery.timeago.js"></script>
<?php if ($admin){?>
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script src="/js/admin.js"></script>
<script src="/js/vendor/jquery.growl.js"></script>
<? }?>
<script src="/js/vendor/bootstrap.min.js"></script>
<script src="/js/main.js"></script>
</body>
</html>
<?
}
}