Initial commit.

This commit is contained in:
Stefan Rohde-Enslin 2018-06-12 07:53:27 +02:00
commit 227c91963e
16 changed files with 1264 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/data

68
edit/inc/functions.php Normal file
View File

@ -0,0 +1,68 @@
<?PHP
/**
* This file contains all the main functions for the backend.
*
* @file
*
* @author Joshua Ramon Enslin <joshua@jrenslin.de>
*/
// Include functions from frontend.
require_once __DIR__ . '/../../inc/functions.php';
require_once __DIR__ . '/standardHTML.php';
/**
* Function for ensuring everything is in order in the backend.
*
* @return void
*/
function ensureBackendEnv() {
if (session_status() != PHP_SESSION_ACTIVE) {
session_start();
}
include_once __DIR__ . "/../password_protect.php";
}
/**
* Function that checks all the indexes in the input array for their availability in $_GET or $_POST.
* If they exist in either, they are written to global variables.
*
* @param string[] $vars Input array containing indices, that may be contained in $_GET or $_POST.
*
* @return void
*/
function loadHttpToGlobals(array $vars) {
foreach ($vars as $var) {
if (isset($_GET[$var])) $GLOBALS[$var] = $_GET[$var];
else if (isset($_POST[$var])) $GLOBALS[$var] = $_POST[$var];
}
}
/**
* Function for loading the language.
*
* @return string[]
*/
function loadLanguage():array {
if (isset($_GET['lan'])) $_SESSION['lan'] = $lan = $_GET['lan'];
else if (isset($_SESSION['lan'])) $lan = $_SESSION['lan'];
// Default to English
if (!isset($lan) or !file_exists(__DIR__ . "/translations/$lan.php")) {
$lan = "en";
}
include __DIR__ . "/../translations/$lan.php";
return $translations;
}
?>

163
edit/inc/standardHTML.php Normal file
View File

@ -0,0 +1,163 @@
<?PHP
/**
* Functions for forming the HTML output.
*/
/**
* Prints the head element of an HTML page
*
* @param string $page Name / ID of the current page.
* @param string $title Title of the page.
*
* @return string
*/
function printBackendHead(string $page = "home", string $title = "Home"):string {
$output = '<!DOCTYPE html>
<html lang="en" id="' . $page . '">
<head>
<title>' . $title . '</title>
<link rel="stylesheet" type="text/css" href="themes/imports.css">
<link rel="stylesheet" type="text/css" href="themes/default/default.css">
<meta http-equiv="content-type" content="text/html;charset=utf-8">';
$output .= '
<meta name="robots" content="none" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script type="text/javascript" src="./js/newToolTip.js"></script>
</head>
<body>
';
if (isset($_SESSION['editHistory'])) {
$output .= "<p class='editLine ".$_SESSION['editHistory'][0]."'>".$_SESSION['editHistory'][1]."</p>";
unset($_SESSION['editHistory']);
}
return $output;
}
/**
* Prints the header element of an HTML page.
*
* @param string $title Title of the page.
* @param string $helpText Additional help text for the page. Optional.
*
* @return string
*/
function printBackendHeader(string $title = "Home", string $helpText = ""):string {
$output = '
<header id="mainHeader">
<span id="toggleNavigation"></span>
<h1>' . $title . '</h1>
<span>
<span id="toggleTextBlocks"></span>';
if ($helpText) $output .= '
<span class="newToolTipTag" data-for="pageHelp" id="helpText">
<span>?</span>
<div class="newToolTip" id="tooltip_pageHelp" data-title="' . $title . '">
' . $helpText . '
</div>
</span>';
$output .= '
<span id="uploadFile"></span>
</span>
</header>
';
return $output;
}
/**
* Returns HTML code for a help icon and its attached tooltip.
*
* @param string $tooltipName Name / ID of the tooltip to generate.
* @param string $title Title to print in the tooltip.
* @param string $helpText Text to print into the tooltip.
*
* @return string
*/
function generateHelpToolTip(string $tooltipName, string $title, string $helpText):string {
$output = '
<span class="newToolTipTag helpToolTip" data-for="' . $tooltipName . '">
<div class="newToolTip" id="tooltip_' . $tooltipName . '" data-title="' . $title . '">
' . $helpText . '
</div>
</span>';
return $output;
}
/**
* Prints the navigation for the backend.
*
* @param string[] $translations Translation variable.
* @param integer[] $numbers Count of the given values.
*
* @return string
*/
function printBackendNav(array $translations, $numbers = []):string {
$output = '
<nav id="mainNav">
<div>
<span>' . $translations['edit'] . '</span>
<div>
<a href=".">' . $translations['start'] . '</a>
</div>
<div>
<a href="pages.php">' . $translations['pages'] . '</a>
<a href="page.php"> + </a>';
if (isset($numbers['pages'])) $output .= '
<a href="users.php#listUsers">' . $numbers['pages'] . '</a>';
$output .= '
</div>
<div>
<a href="fileUpload.php">' . $translations['fileUpload'] . '</a>
</div>
</div>
<div>
<span>' . $translations['administration'] . '</span>
<div>
<a href="users.php#listUsers">' . $translations['users'] . '</a>
<a href="users.php#addUser"> + </a>';
if (isset($numbers['users'])) $output .= '
<a href="users.php#listUsers">' . $numbers['users'] . '</a>';
$output .= '
</div>
</div>
</nav>
';
return $output;
}
/**
* Prints the finishing elements of an HTML page.
*
* @return string
*/
function printBackendEnd():string {
$output = '
</body>
</html>';
return $output;
}
?>

47
edit/index.php Normal file
View File

@ -0,0 +1,47 @@
<?PHP
/**
* Start page of the backend.
* Offers a dashboard.
*
* @author Joshua Ramon Enslin <joshua@jrenslin.de>
*/
/*
* Require files and ensure environment.
*/
require_once __DIR__ . "/inc/functions.php";
ensureEnvironment(); // Ensure existence of system files.
$translations = loadLanguage(); // Load translations.
ensureBackendEnv(); // Ensure session is started etc.
/*
* Load data.
*/
/*
* Output
*/
echo printBackendHead($translations['start']);
echo printBackendHeader($translations['start'], $translations['helpStart']);
echo '
<div id="mainWrapper">
';
echo printBackendNav($translations);
echo '
<main>';
echo '
</main>
</div>';
echo printBackendEnd();
?>

69
edit/js/newToolTip.js Normal file
View File

@ -0,0 +1,69 @@
/**
* A simple implementation of a tooltip.
*
* @author Joshua Ramon Enslin <joshua@jrenslin.de>
*/
document.addEventListener("DOMContentLoaded", function () {
/**
* Function for setting the alignment of an element.
*
* @param {Event} e Event triggering the execution of this function.
* @param {DOMElement} elem Dom element to position.
*
* @return {void}
*/
function getDirection(e, elem) {
if (window.innerHeight < e.clientY + elem.clientHeight) {
elem.style.top = "";
elem.style.bottom = (window.innerHeight - e.clientY) + "px";
}
else {
elem.style.bottom = "";
elem.style.top = (e.clientY + 2) + "px";
}
if (window.innerWidth < e.clientX + elem.clientWidth) {
elem.style.left = "";
elem.style.right = (window.innerWidth - e.clientX) + "px";
} else {
elem.style.right = "";
elem.style.left = (e.clientX + 2) + "px";
}
}
let triggers = document.getElementsByClassName("newToolTipTag");
for (let i = 0, max = triggers.length; i < max; i++) {
let trigger = triggers[i];
let dataTarget = trigger.getAttribute("data-for");
let target = document.getElementById("tooltip_" + dataTarget);
trigger.addEventListener("mouseover", function(e) {
let newMain = document.getElementById("newToolTipMain");
if (newMain !== null) return;
newMain = target.cloneNode(true);
newMain.id = "newToolTipMain";
document.getElementsByTagName("body")[0].appendChild(newMain);
newMain.classList.add("visible");
getDirection(e, newMain);
});
trigger.addEventListener("mousemove", function(e) {
let newMain = document.getElementById("newToolTipMain");
getDirection(e, newMain);
});
trigger.addEventListener("mouseout", function(e) {
let newMain = document.getElementById("newToolTipMain");
if (newMain.classList.contains("sticked")) return;
newMain.classList.remove("visible");
document.getElementsByTagName("body")[0].removeChild(newMain);
});
trigger.addEventListener("click", function(e) {
document.getElementById("newToolTipMain").classList.toggle("sticked");
});
}
});

40
edit/page.php Normal file
View File

@ -0,0 +1,40 @@
<?PHP
/**
* This page offers the opportunity to edit static pages.
*/
/*
* Require files and ensure environment.
*/
require_once __DIR__ . "/inc/functions.php";
ensureEnvironment(); // Ensure existence of system files.
$translations = loadLanguage(); // Load translations.
ensureBackendEnv(); // Ensure session is started etc.
/*
* Output
*/
echo printBackendHead($translations['start']);
echo printBackendHeader($translations['start'], $translations['helpStart']);
echo '
<div id="mainWrapper">
';
echo printBackendNav($translations);
echo '
<main>';
echo '
</main>
</div>';
echo printBackendEnd();
?>

9
edit/pages.php Normal file
View File

@ -0,0 +1,9 @@
<?PHP
/**
* This page provides an overview over all static pages in the system.
*
* @author Joshua Ramon Enslin <joshua@jrenslin.de>
*/
?>

179
edit/password_protect.php Normal file
View File

@ -0,0 +1,179 @@
<?PHP
/**
* Login script
*
* @author Joshua Ramon Enslin <joshua@jrenslin.de>
*/
// Load settings
// Demand HTTPS
if (!isset($_SERVER['HTTPS']) or $_SERVER['HTTPS'] != 'on') header("Location: ../");
// Get available login information
$loginInformation = json_decode(file_get_contents(__DIR__ . '/../data/users.json'), True);
define("loginLogFile", "/../data/logins.csv");
/**
* Function for printing the login page
*
* @param string $error_msg Error message to print. Optional.
* @param string $cssFile CSS file.
*
* @return void
*/
function showLoginPasswordProtect($error_msg = "", $cssFile = "themes/default/default.css") {
echo '<!DOCTYPE html>
<html id="loginPage">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta charset="UTF-8">
<title>Log In</title>
<link rel="stylesheet" type="text/css" href="' . $cssFile . '">
</head>
<body>
<h1>Log In</h1>';
if ($error_msg) echo '<p id="errorMsg">'.$error_msg.'</p>';
echo '
<form action="" method="POST">
<input type="text" name="username" placeholder="Username" required autofocus />
<input type="password" name="password" placeholder="Password" required />
<button type="submit">Submit</button>
</form>
</body>
</html>
';
die();
}
/**
* Logout function: Unsets all relevant session variables.
*
* @return void
*/
function logout() {
$loginVariables = array("username", "userlevel", "userLastLogin", "userLang");
foreach ($loginVariables as $var) {
if (isset($_SESSION[$var])) unset($_SESSION[$var]);
}
header("Location: ../");
}
/**
* Function for Logging logins.
*
* @return void
*/
function logLogin() {
file_put_contents(loginLogFile, $_SESSION['username'].'|'.date("Y-m-d H:i:s").PHP_EOL, FILE_APPEND | LOCK_EX);
}
/**
* Prevent brute force attacks
*
* @return void
*/
function preventBruteForce() {
if (!isset($_SESSION['loginattempt'])) $_SESSION['loginattempt'] = array();
$_SESSION['loginattempt'][] = date("Y-m-d H:i:s");
if (count($_SESSION['loginattempt']) > 3) {
$secondtolastattempt = strtotime($_SESSION['loginattempt'][count($_SESSION['loginattempt']) - 3]);
$lastattempt = strtotime($_SESSION['loginattempt'][count($_SESSION['loginattempt']) - 2]);
$logindelay = (intval(count($_SESSION['loginattempt'])) * 20);
$sincelastlogin = strtotime(date("Y-m-d H:i:s")) - $lastattempt;
if ($sincelastlogin < $logindelay) showLoginPasswordProtect("You failed logging in too often, try again in $logindelay seconds.");
}
}
/**
* Check for validity of username / password combination.
*
* @param mixed[] $loginInformation Array containing all login information.
* @param string $username Username to check.
*
* @return void
*/
function checkLoginValidity(array $loginInformation, $username) {
if (!isset($loginInformation[$username])) showLoginPasswordProtect("Incorrect username");
}
// Main part of the script
if (isset($_GET['logout'])) logout(); // Log out if so requested
// Check for expiry
else if (isset($_SESSION['username'])) {
checkLoginValidity($loginInformation, $_SESSION["username"]);
// Check for expiry of login
if (isset($_SESSION["userLastLogin"]) and strtotime(date("Y-m-d H:i:s")) - strtotime($_SESSION["userLastLogin"]) > 3600) logout();
// Renew last login time
$_SESSION["userLastLogin"] = date("Y-m-d H:i:s");
}
// If the corresponding POST vars are set, check for
else if (isset($_SERVER['PHP_AUTH_USER']) and isset($_SERVER['PHP_AUTH_PW'])) {
preventBruteForce();
checkLoginValidity($loginInformation, $_SERVER['PHP_AUTH_USER']);
if (password_verify($loginInformation[$_SERVER['PHP_AUTH_USER']]['password'], $_SERVER['PHP_AUTH_PW'])) showLoginPasswordProtect("Incorrect password");
// Set username
$_SESSION["username"] = $_SERVER['PHP_AUTH_USER'];
// Set last access time
$_SESSION["userLastLogin"] = date("Y-m-d H:i:s");
if (isset($_SESSION['loginattempt'])) unset($_SESSION['loginattempt']);
// Log login time
logLogin();
}
else if (isset($_POST['username']) and isset($_POST['password'])) {
preventBruteForce();
checkLoginValidity($loginInformation, $_POST['username']);
if (password_verify($loginInformation[$_POST['username']]['password'], $_POST['username'])) showLoginPasswordProtect("Incorrect password");
// Set username
$_SESSION["username"] = $_POST['username'];
// Set last access time
$_SESSION["userLastLogin"] = date("Y-m-d H:i:s");
if (isset($_SESSION['loginattempt'])) unset($_SESSION['loginattempt']);
// Log login time
logLogin();
}
// If nothing happens, offer login page
else showLoginPasswordProtect();
// End script
unset($loginInformation); // Unset array containing login information
?>

View File

@ -0,0 +1,186 @@
/**
* Default theme for the backend of md:cms.
*/
/****************************
* Load fonts
*/
@font-face {
font-family: sourceSansPro;
src: local('Source-Sans-Pro'), local('Source Sans Pro'),
url(./fonts/SourceSansPro-Regular.woff2) format('woff2'),
url(./fonts/SourceSansPro-Regular.ttf) format('truetype');
}
/****************************
* General
*/
html { margin: 0; padding: 0; }
body { margin: 0; padding: 0; font-family: sourceSansPro; font-size: 1.15em; line-height: 1.5; }
* { box-sizing: border-box; z-index: 1; }
a { color: inherit; text-decoration: none; }
a.buttonLike { display: inline-block; }
a.buttonLike,
select,
button,
textarea,
input { padding: .6em .8em; background: #FAFAFA; border: 1px solid #CFD8DC; font-family: roboto; font-size: inherit;
border-radius: .3em; transition: background .1s, box-shadow .1s; box-sizing: border-box; }
button { padding: .6em; }
select { padding: .3em .8em; }
textarea { line-height: 1.4em; }
a.buttonLike:hover,
select:hover,
button:hover,
textarea:hover,
input:hover { background: #FFF; box-shadow: 1px 1px 3px #CFD8DC; }
a.buttonLike:focus,
select:focus,
button:focus,
textarea:focus,
input:focus { background: #FFF; box-shadow: 1px 1px 3px #607D8B; }
input[type="range"] { background: inherit; border: 0; }
table { width: 100%; border-collapse: collapse; }
th { text-align: left; }
/************
* Tables for providing an overview.
*/
table.overviewtable { height: auto; }
table.overviewtable thead { position: sticky; top: 0px; border-bottom: 3px solid #D6D6D6; }
table.overviewtable th,
table.overviewtable td { padding: .35em; }
table.overviewtable tr th:first-child,
table.overviewtable tr td:first-child { padding-left: .7em; }
table.overviewtable tr th:last-child,
table.overviewtable tr td:last-child { padding-right: .7em; text-align: right; }
table.overviewtable tr { transition: background .4s; }
table.secondRowShaded tr:nth-child(2n):hover,
table.overviewtable tr:hover { background: #E0E0E0; }
table.overviewtable tr:nth-child(2n) { background: #F2F2F2; }
table.fullwidth { width: auto; table-layout: fixed; }
/************
* Tables for adding something.
*/
table.obj_cha_maintable { width: 100%; margin: 2em 0; }
.withAside .obj_cha_maintable { width: calc(100%); margin: 0px; }
table.obj_cha_maintable th { width: 100px; padding-top: .5em; padding-right: .8em; color: #424242; vertical-align: top; font-weight: normal; text-align: left; }
table.obj_cha_maintable td > label { padding-top: .5em; }
table.obj_cha_maintable td { vertical-align: top; }
table.obj_cha_maintable td:last-child { text-align: center; padding-top: .5em; }
table.obj_cha_maintable td.twoInputs input { width: 50%; }
table.obj_cha_maintable select:only-child,
table.obj_cha_maintable button:only-child,
table.obj_cha_maintable input:only-child { width: 100%; }
table.obj_cha_maintable textarea:only-child { width: 100%; height: 14em; }
@media screen and (min-width:65em) {
table.obj_cha_maintable th { width: 16em; }
}
table.obj_cha_maintable td.shortInputLong input { width: 8em; }
table.obj_cha_maintable td.shortInputLong input:nth-child(2) { width: calc(100% - 9em); float: right; }
.obj_cha_maintable td:last-child { width: 3em; }
.helpToolTip { display: inline-block; padding: .1em .5em;
background: #DDD; border-radius: .2em; transition: background .4s, color .4s; }
.helpToolTip:before { content: "?"; }
.helpToolTip:hover { background: #C0CA33; color: #FFF; }
/************
* Edit line
*/
p.editLine { animation: fade-in-and-vanish 2.4s; position: fixed; left: 0; top: 0;
display: block; width: 100%;
margin: 0; text-align: center; font-weight: bold; opacity: 0;
box-sizing: border-box; box-shadow: 0 1.5px 2px rgba(0,0,0,.06),0 1.5px 1.5px rgba(0,0,0,.06); z-index: 0; }
p.editLine.massiveEditLine { animation: fade-in-and-vanish-massive 2.4s; }
p.changesStored { background: #7CB342; color: #FFF; }
p.changesDeleted { background: #E53935; color: #FFF; }
p.changesAborted { background: #C62828; color: #FFF; }
/****************************
* Main Structuring Elements
*/
/************
* Main Header
* Displays current position and offers access
*/
#mainHeader { display: table; width: 100%; border-bottom: 1px solid #D6D6D6; }
#mainHeader > * { display: table-cell; padding: .5rem; color: #666; vertical-align: middle; }
#mainHeader h1 { color: #333; }
#mainHeader > *:last-child { text-align: right; }
#mainHeader > *:last-child > * { display: inline-block; margin: 0 .5em; padding: .5rem 1rem;
background: #1976D2; color: #FFF; border-radius: .2em; transition: background .4s; }
#mainHeader #toggleNavigation { width: 3em; text-align: center; }
#mainHeader #toggleNavigation:before { content:'\2630'; }
#mainHeader #uploadFile:before { content:'\2630'; }
/************
* Main wrapper
* Contains the navigation and the main part.
*/
div#mainWrapper { display: block; }
@media screen and (min-width: 75em) {
div#mainWrapper { display: table; width: 100%; }
}
/************
* Main navigation
*/
#mainNav { display: block; border: 1px solid #D6D6D6; border-width: 0; }
#mainNav > * { display: block; border-bottom: 1px solid #D6D6D6; }
#mainNav > * > span { display: block; padding: .7em 1em; font-weight: bold; transition: background .2s; }
#mainNav > * > span:hover { background: #F2F2F2; }
#mainNav > * > div { position: relative; }
#mainNav > * > div > *:first-child { position: relative; display: block; padding: .7em 1em; font-weight: bold; transition: background .2s; }
#mainNav > * > div > *:first-child:hover { background: #F2F2F2; }
#mainNav > * > div > *:not(:first-child) { position: absolute; right: 1em; top: 50%; transform: translate(0, -50%);
display: block; padding: .1em .5em; background: #EEE; color: #888; border-radius: 100%;
transition: background .4s, color .4s; }
#mainNav > * > div > *:not(:first-child):hover { background: #AAA; color: #000; }
@media screen and (min-width: 75em) {
#mainNav { display: table-cell; width: 250px; border-width: 0 1px 0 0; }
}
/************
* Main part of the page.
*/
main { padding: .5em 5em 3em 3em; }

Binary file not shown.

Binary file not shown.

37
edit/themes/imports.css Normal file
View File

@ -0,0 +1,37 @@
/* ========
| Notifications
|= ======== */
p.notifLine { animation: fade-in-and-vanish 2.4s; position: fixed; left: 0; top: 0; display: block; width: 100%;
margin: 0; background: rgba(0,0,0,.8); color: #FFF; text-align: center; font-weight: bold; opacity: 0;
box-sizing: border-box; box-shadow: 0 1.5px 2px rgba(0,0,0,.06),0 1.5px 1.5px rgba(0,0,0,.06); z-index: 0; }
/* ========
| New tooltip
|= ======== */
.newToolTip { position: fixed; display: none !important; min-width: 300px !important; max-width: 600px;
background: #FFF !important; text-align: left; font-size: .95rem;
border-radius: .2em; box-shadow: 1px 1px 4px #646464; z-index: 3000; white-space: initial !important; }
#newToolTipMain:before { content: attr(data-title); display: block; padding: .5em 1em;
background: #37474F; color: #FFF; }
#newToolTipMain > * { padding: .5rem 1rem !important; }
#newToolTipMain > table td { padding: .5rem 1em; vertical-align: top; }
#newToolTipMain.visible { display: block !important; }
#newToolTipMain img { max-width: 200px; max-height: 300px; }
/* ========
| Animations
|= ======== */
@keyframes fade-in-and-vanish {
0% { opacity: 0; z-index: 1000; }
5% { opacity: .4; }
15% { opacity: 1; padding: .8em; }
75% { opacity: 1; padding: .8em; }
85% { opacity: .4; transform: translateY(0px);}
99% { opacity: 0; transform: translateY(-30px);}
100% { opacity: 0; z-index: 0; }
}

34
edit/translations/en.php Normal file
View File

@ -0,0 +1,34 @@
<?PHP
$translations = [
"submit" => "Submit",
"options" => "Options",
"start" => "Start",
"fileUpload" => "File Uploads",
"users" => "Users",
"pages" => "Pages",
"edit" => "Edit",
"administration" => "Administration",
"usersOverview" => "Overview of All Users",
"listUsers" => "List users",
"addUser" => "Add user",
"userAdded" => "Successfully added new user: ",
"username" => "Username",
"helpUsername" => "<p>Username of the user. The user logs in with his or her username.</p><p><b>Required.</b></p>",
"email" => "Email Address",
"helpEmail" => "<p>Email address of the user. This needs to be saved, to be able to contact the user later on.</p><p><b>Required.</b></p>",
"password" => "Password",
"passwordVerify" => "Password (Verification)",
"helpPassword" => "<p>Password of the user. The password needs to be at least 8 characters long.</p><p><b>Required.</b></p>",
"realName" => "Real name",
"helpRealName" => "<p>Full, real name of the user. The full name is displayed next to entries by that person.</p><p><b>Required.</b></p>",
"helpUsers" => "<p>On this page, you can see access an overview of all users. You can also add new users here.</p>",
"helpStart" => "<p>This is the start page of md:cms.</p>",
"requiredValueMissing" => "A required value is missing.",
"passwordsDoNotMatch" => "The passwords do not match.",
"passwordTooShort" => "The passwords is too short.",
];
?>

192
edit/users.php Normal file
View File

@ -0,0 +1,192 @@
<?PHP
/**
* Start page of the backend.
* Offers a dashboard.
*
* @author Joshua Ramon Enslin <joshua@jrenslin.de>
*/
/*
* Require files and ensure environment.
*/
require_once __DIR__ . "/inc/functions.php";
ensureEnvironment(); // Ensure existence of system files.
$translations = loadLanguage(); // Load translations.
ensureBackendEnv(); // Ensure session is started etc.
/*
* Load data.
*/
// Check for vars.
loadHttpToGlobals(["task", "username", "realName", "email", "password", "passwordVerify"]);
if (!isset($users)) {
$users = json_decode(file_get_contents(__DIR__ . "/../data/users.json"), true);
}
if (isset($task) and $task == "insert") { // Adding new users.
$redirectURL = "./users.php?" . write_common_vars(["username", "realName", "email"]) . "#addUser";
// Ensure all required values are set.
foreach (["username", "realName", "email", "password", "passwordVerify"] as $var) {
if (isset($$var)) continue;
$_SESSION["editHistory"] = ["changesAborted", $translations['requiredValueMissing']];
header('Location: ' . $redirectURL);
return;
}
// Check if the passwords match.
if ($password != $passwordVerify) {
$_SESSION["editHistory"] = ["changesAborted", $translations['passwordsDoNotMatch']];
header('Location: ' . $redirectURL);
return;
}
// Check if passwords is too short.
if (strlen($password) < 8) {
$_SESSION["editHistory"] = ["changesAborted", $translations['passwordTooShort']];
header('Location: ' . $redirectURL);
return;
}
// Options for hashing.
$newUser = array(
"username" => $username,
"realName" => $realName,
"email" => $email,
"password" => password_hash("$password", PASSWORD_BCRYPT, ['cost' => 12]),
"created" => date("Y-m-d H:i:s"),
);
$users[$username] = $newUser;
// Store the users array.
file_put_contents(__DIR__ . "/../data/users.json", json_encode($users), LOCK_EX);
$_SESSION["editHistory"] = ["changesStored", $translations['userAdded'] . " $username"];
header('Location: ./users.php#addUser');
return;
}
/*
* Output
*/
echo printBackendHead($translations['start']);
echo printBackendHeader($translations['usersOverview'], $translations['helpUsers']);
echo '
<div id="mainWrapper">
';
echo printBackendNav($translations);
echo '
<main>
<p>
<a href="#listUsers" class="buttonLike">' . $translations['listUsers'] . '</a>
<a href="#addUser" class="buttonLike">' . $translations['addUser'] . '</a>
</p>
<section id="listUsers">
<form action="" method="POST">
<table class="obj_cha_maintable">
<tr>
<th><label for="username">' . $translations['username'] . '</label></th>
<td><input type="text" id="username" name="username" placeholder="' . $translations['username']. '"';
if (isset($username)) echo " value='$username'";
echo ' required /></td>
<td>' . generateHelpToolTip("helpUsername", $translations['username'], $translations['helpUsername']) . '</td>
</tr>
<tr>
<th><label for="realName">' . $translations['realName'] . '</label></th>
<td><input type="text" id="realName" name="realName" placeholder="' . $translations['realName']. '"';
if (isset($realName)) echo " value='$realName'";
echo ' required /></td>
<td>' . generateHelpToolTip("helpRealName", $translations['realName'], $translations['helpRealName']) . '</td>
</tr>
<tr>
<th><label for="userEmail">' . $translations['email'] . '</label></th>
<td><input type="email" id="userEmail" name="email" placeholder="' . $translations['email']. '"';
if (isset($email)) echo " value='$email'";
echo ' required /></td>
<td>' . generateHelpToolTip("helpEmail", $translations['email'], $translations['helpEmail']) . '</td>
</tr>
<tr>
<th><label for="password">' . $translations['password'] . '</label></th>
<td><input type="password" id="password" name="password" placeholder="' . $translations['password']. '" required /></td>
<td>' . generateHelpToolTip("helpPassword", $translations['password'], $translations['helpPassword']) . '</td>
</tr>
<tr>
<th><label for="passwordVerify">' . $translations['passwordVerify'] . '</label></th>
<td><input type="password" id="passwordVerify" name="passwordVerify" placeholder="' . $translations['passwordVerify']. '" required /></td>
<td></td>
</tr>
<tr>
<th></th>
<td><button type="submit">' . $translations['submit'] . '</button></td>
<td>
' . printHiddenInputs(['task' => 'insert'], 16) . '
</td>
</tr>
</table>
</form>
</section>
<section>
<table class="overviewtable">
<thead>
<tr>
<th>' . $translations['username'] . '</th>
<th>' . $translations['realName'] . '</th>
<th>' . $translations['email'] . '</th>
<th>' . $translations['options'] . '</th>
</tr>
</thead>
<tbody>
';
foreach ($users as $user) {
echo '
<tr>
<td><a href="user.php?t=' . urlencode($user['username']) . '">' . $user['username'] . '</a></td>
<td>' . $user['realName'] . '</td>
<td>' . $user['email'] . '</td>
<td></td>
</tr>
';
}
echo '
</tbody>
</table>
</section>
</main>
</div>';
echo printBackendEnd();
?>

221
inc/functions.php Normal file
View File

@ -0,0 +1,221 @@
<?PHP
/**
* Main file for functions.
*
* @file
*
* @author Joshua Ramon Enslin <joshua@jrenslin.de>
*/
/**
* Function ensureDir checks if a directory exists, and creates it if it does not yet.
*
* @param string $filepath File path.
*
* @return void
*/
function ensureDir(string $filepath) {
if (!is_dir($filepath)) mkdir($filepath);
}
/**
* Function ensureJson checks that a JSON file exists. If not, it creates an empty one at the specified location.
*
* @param string $filepath File path to the JSON file.
*
* @return void
*/
function ensureJson(string $filepath) {
if (!file_exists($filepath) or filesize($filepath) < 2) {
file_put_contents($filepath, "[]");
}
}
/**
* Function for ensuring the existence of an appropriate environment.
*
* @return void
*/
function ensureEnvironment() {
// Enable error reporting
error_reporting(E_ALL);
ini_set("display_errors", 1);
// Ensure existence of directories
foreach ([__DIR__ . "/../data", __DIR__ . "/../data/static", __DIR__ . "/../data/caches", __DIR__ . "/../js"] as $folder) {
ensureDir($folder);
}
// Ensure existence of settings files
foreach ([
__DIR__ . "/../data/settings.json",
__DIR__ . "/../data/caches/pages.json",
__DIR__ . "/../data/users.json",
] as $jsonFile) {
ensureJson($jsonFile);
}
}
/**
* Function scanDirConts is a wrapper around scandir(), which removes [".", ".."].
*
* @param string $folder Folder to scan.
*
* @return string[]
*/
function scanDirConts(string $folder):array {
return array_values(array_diff(scandir($folder), [".", "..", ".git"]));
}
/**
* Function printHiddenInputs takes an array, in which each entry stands for a new hidden input field.
* The key to the entry stands for the name attribute, the value for the value attribute.
*
* @param array $array Associative array containing all the variable names and corresponding values to print
* @param int $indent Integer describing how far each line in the returned string should be indented.
*
* @return string
*/
function printHiddenInputs(array $array, int $indent = 0):string {
$output = '';
$indentString = PHP_EOL;
for ($i = 0; $i < $indent; $i++) $indentString .= ' ';
foreach ($array as $name => $value) {
$output .= $indentString . '<input type="hidden" name="'.$name.'" value="'.$value.'" />';
}
return $output;
}
/**
* Function printHiddenInputsFromGlobalsSimple takes an array, in which each entry stands for a new hidden input field.
* The value stands for the name attribute of the input field and the key in $GLOBALS.
*
* @param array $array Input array.
* @param int $indent Number of spaces for indenting the output input fields in HTML. Optional.
*
* @return string
*/
function printHiddenInputsFromGlobalsSimple(array $array, int $indent = 0):string {
$output = '';
$indentString = PHP_EOL;
for ($i = 0; $i < $indent; $i++) $indentString .= ' ';
foreach ($array as $value) {
if (!isset($GLOBALS[$value])) continue;
$output .= $indentString . '<input type="hidden" name="'.$value.'" value="'.$GLOBALS[$value].'" />';
}
return $output;
}
/**
* Function write_get_vars prints checks for GET variables specified in the input array and returns them as a single string.
* Useful for avoiding long blocks of links working to write meaningful links.
*
* @param string[] $input Input array: both simple and associative arrays are accepted.
*
* @return string
*/
function write_get_vars(array $input):string {
// Check if keys have been specified in the array (in Python terms, if it is a dict or a list).
// If keys are not specified, write new working variable $vars with keys equaling the value.
// $str is the string that will eventually be returned.
$vars = array();
$str = '';
if (isset($input[0])) {
foreach ($input as $value) $vars[$value] = $value;
}
else $vars = $input;
// For each of the variables specified in $vars, check if a corresponding GET variable is set.
// If so, add that to the return string.
// The key is used in place of the original GET variable's name ($value),
// because some pages may have the same GET variables carry different names.
foreach ($vars as $key => $value) {
if (isset($GLOBALS['_GET'][$value])) $str .= '&'.$key.'='.$GLOBALS['_GET'][$value];
}
return ($str);
}
/**
* Function write_post_vars prints checks for POST variables specified in the input
* array and returns them as a single string.
* Useful for avoiding long blocks of links working to write meaningful links.
*
* @param string[] $input Input array: both simple and associative arrays are accepted.
*
* @return string
*/
function write_post_vars(array $input):string {
// Check if keys have been specified in the array (in Python terms, if it is a dict or a list).
// If keys are not specified, write new working variable $vars with keys equaling the value.
// $str is the string that will eventually be returned.
$vars = array();
$str = '';
if (isset($input[0])) {
foreach ($input as $value) $vars[$value] = $value;
}
else $vars = $input;
// For each of the variables specified in $vars, check if a corresponding GET variable is set.
// If so, add that to the return string. The key is used in place of the original POST variable's name ($value),
// because some pages may have the same GET variables carry different names.
foreach ($vars as $key => $value) {
if (isset($GLOBALS['_POST'][$value])) $str .= '&'.$key.'='.$GLOBALS['_POST'][$value];
}
return ($str);
}
/**
* Function write_common_vars prints checks for global variables specified in the input
* array and returns them as a single string.
* Useful for avoiding long blocks of links working to write meaningful links.
*
* @param string[] $input Input array: both simple and associative arrays are accepted.
*
* @return string
*/
function write_common_vars(array $input):string {
// Check if keys have been specified in the array (in Python terms, if it is a dict or a list).
// If keys are not specified, write new working variable $vars with keys equaling the value.
// $str is the string that will eventually be returned.
$vars = array();
$str = '';
if (isset($input[0])) {
foreach ($input as $value) $vars[$value] = $value;
}
else $vars = $input;
// For each of the variables specified in $vars, check if a corresponding GET variable is set.
// If so, add that to the return string. The key is used in place of the original GET variable's name ($value),
// because some pages may have the same GET variables carry different names.
foreach ($vars as $key => $value) {
if (isset($GLOBALS[$value])) $str .= '&'.$key.'='.$GLOBALS[$value];
}
return ($str);
}
?>

17
index.php Normal file
View File

@ -0,0 +1,17 @@
<?PHP
/**
* The main display file for standalone pages.
*
* @author Joshua Ramon Enslin <joshua@jrenslin.de>
*/
// Include functions and settings.
require __DIR__ . "/inc/functions.php";
ensureEnvironment();
echo "hi";
?>