Compare commits

...
29 Commits
Author SHA1 Message Date
marc-go 205f2e2f73 Enhance API error handling, add lastError tracking, and implement admin home template 2026-10-11 14:37:56 +02:00
admin ce0777bb33 Update file permissions and enhance error handling in API responses 2026-10-10 21:26:38 +02:00
marc-go eb67458ab1 Enhance API error handling and response structure; update app name retrieval method 2026-10-10 19:23:11 +02:00
marc-go 95f8e22f11 Remove debug_print_backtrace() call from API main library 2026-10-10 15:09:13 +02:00
marc-go b4621fc127 Refactor API files: replace require with require_once for improved file inclusion 2026-10-10 15:06:29 +02:00
marc-go 9cb50d7d28 Refactor API structure: update internal handling, enhance input retrieval, and improve template loading 2026-10-10 15:05:24 +02:00
marc-go a33de15f0c Refactor API structure: add internal handling, update templates, and remove deprecated files 2026-10-10 13:43:18 +02:00
admin 313ffec92c Update file permissions and enhance error handling in login template and setup scripts 2026-10-09 21:21:44 +02:00
marc-go c94122c9fd Fix variable name in token validation and add login template 2026-10-09 19:36:56 +02:00
marc-go 6b5a299079 Refactor authentication: streamline cookie handling and enhance username retrieval in responses 2026-10-09 19:20:53 +02:00
admin a1bbae383a Add cookie token lifetime configuration and implement base64 encoding/decoding methods 2026-10-08 16:22:47 +00:00
marc-go bf552e87cc Enhance token generation: include expiration in cookie token and improve authentication checks 2026-10-08 14:50:59 +02:00
marc-go fd1b8d9a0b Refactor entry handling: update date format, enhance entry retrieval, and improve HTML structure 2026-10-07 19:22:40 +02:00
hacker_marc ff2afb4441 Change generateToken to generateCookieToken 2026-10-06 18:24:59 +00:00
marc-go 1a2ee0c176 Refactor API and database interactions: enhance token management, improve entry handling, and add application name retrieval 2026-10-06 19:23:15 +02:00
marc-go 9316818753 Enhance API functionality: add token generation methods, implement entry management, and improve setup process with settings storage 2026-10-06 14:46:25 +02:00
root 8ef5f7a941 Fix Bugs 2026-10-06 12:59:22 +02:00
marc-go 0c73ad14f5 Implement entry page with dynamic content and update template structure 2026-10-05 19:25:13 +02:00
root 1a2e1cc33f Fix Bugs 2026-10-05 18:48:47 +02:00
admin ae54fabd6b Add PASSWORD_DEFAULT to password_hash in api/setup/save.php 2026-10-05 09:43:12 +00:00
admin 0cb0d97529 Add debug settings to setup.html 2026-10-05 09:13:15 +00:00
admin e53e53146c Fix Bugs 2026-10-05 09:12:00 +00:00
admin 3f4e859153 Fix Bugs 2026-10-05 09:09:22 +00:00
admin f2bd887783 Fix Bugs and finish setup.php 2026-10-05 09:09:07 +00:00
root 3ad5f5bdd6 Add Database structure 2026-10-05 10:52:31 +02:00
hacker_marc f5f42b73ee Fix type error in api.php 2026-10-04 18:53:42 +00:00
marc-go c50c1bd122 Implement user authentication and registration; enhance error handling and database interaction in API 2026-10-04 19:21:38 +02:00
admin 2ac799d0a4 Enhance gbAPI with configuration loading, token generation, and response formatting; update file permissions for API and template files 2026-10-03 22:03:11 +02:00
marc-go 06a4f9a6c0 Add setup and API files with enhanced user interface and error handling 2026-10-03 19:31:34 +02:00
27 changed files with 1365 additions and 91 deletions

No files matched your search

Executable
+4
View File
@@ -0,0 +1,4 @@
COOKIE_TOKEN_SIGNATURE=a11a9ea5fd839e616bd08ebc432f641a24ef1f4d74ca478f904e7a3122fc7a05
COOKIE_TOKEN_LIFETIME=86400
API_TOKEN_SIGNATURE=38bcd8a8e9ac3be83ddfcf4c12c19ccb3be70df00e48d43d4f63f54f014d1946
DB_FILE=database.db
Executable
+292
View File
@@ -0,0 +1,292 @@
<?php
$debug = true;
if ($debug) {
ini_set("display_errors", 1);
ini_set("display_startup_errors", 1);
error_reporting(E_ALL);
}
// API Main Library
class gbAPI {
public $username;
public $input;
private $db;
private $config;
private $internal;
private $response;
private $lastError;
private function b64url_encode($data) {
return rtrim(strtr(base64_encode($data), "+/", "-_"), "=");
}
private function b64url_decode($data) {
return base64_decode(strtr($data, "-_", "+/"));
}
function __construct($authRequired, $internal) {
if (!defined("APP_PATH")) {
define("APP_PATH", __DIR__ . "/../");
}
$this->internal = $internal;
if ($authRequired && !$internal) {
if (!$this->checkAuth()) {
$status = 401;
$data = [
"error" => "unauthorized",
"error_text" => "Authentication failed."
];
$this->lastError = $data;
$this->answer($status, $data, true);
}
}
}
function getInput() {
if (isset($this->input)) {
return $this->input;
}elseif ($_SERVER["REQUEST_METHOD"] == "POST") {
try {
$input = json_decode(file_get_contents("php://input"), true);
} catch (JsonException $error) {
$error_payload = [
"error" => "invalid_body",
"error_text" => "Can not read request body: " . $error
];
$this->lastError = $error_payload;
$this->answer(400, $error_payload, true);
}
return $input;
}else{
$error_payload = [
"error" => "false_request_method",
"error_text" => "Only POST allowed"
];
$this->lastError = $error_payload;
$this->answer(400, $error_payload, true);
}
}
function getConfig() {
if (isset($this->config)) {
return $config;
}else{
return parse_ini_file(APP_PATH . ".env");
}
}
function generateCookieToken($username) {
$config = $this->getConfig();
// Generate Token
$payload = [
"user" => $username,
"auth" => true,
"exp" => time() + $config["COOKIE_TOKEN_LIFETIME"]
];
$string = $this->b64url_encode(json_encode($payload));
$secret = $config["COOKIE_TOKEN_SIGNATURE"];
$sig = hash_hmac("sha256", $string, $secret);
return $string . "." . $sig;
}
function generateAPIToken($username) {
$config = $this->getConfig();
// Generate Token
$payload = [
"user" => $username,
"auth" => true
];
$string = $this->b64url_encode(json_encode($payload));
$secret = $config["API_TOKEN_SIGNATURE"];
$sig = hash_hmac("sha256", $string, $secret);
return $string . "." . $sig;
}
function checkAuth() {
if (isset($_COOKIE["auth_token"])) {
// Get Config
$config = $this->getConfig();
// Check Token
$given = $_COOKIE["auth_token"];
if (!is_string($given)) {
return false;
}
$given_string = explode(".", $given);
if (count($given_string) !== 2) {
return false;
}
$sig_expected = hash_hmac("sha256", $given_string[0], $config["COOKIE_TOKEN_SIGNATURE"]);
@$result = hash_equals($sig_expected, $given_string[1]);
if ($result) {
$body = json_decode($this->b64url_decode($given_string[0]), true);
if ($body["exp"] > time()) {
$this->username = $body["user"];
return true;
}else{
return false;
}
}else{
return false;
}
}elseif (isset($_SERVER["HTTP_X_API_KEY"])) {
// Get config
$config = $this->getConfig();
// Decode Token
$given = $_SERVER["HTTP_X_API_KEY"];
if (!is_string($given)) {
return false;
}
$given_string = explode(".", $given);
if (count($given_string) !== 2) {
return false;
}
$sig_expected = hash_hmac("sha256", $given_string[0], $config["API_TOKEN_SIGNATURE"]);
@$result = hash_equals($sig_expected, $given_string[1]);
}
return false;
}
function answer($status, $data = [], $error = false) {
if ($error) {
$return = [];
$return["status"] = $status;
foreach ($data as $key => $value) {
$return[$key] = $value;
}
}else{
$return = [
"status" => $status,
"data" => $data
];
}
// Check if request is internal or external
if ($this->internal) {
$this->response = $return;
return;
}else{
http_response_code($status);
header("Content-Type: application/json; charset=UTF-8");
$return = json_encode($return);
die($return);
}
}
function returnLastError() {
if (isset($this->lastError)) {
return $this->lastError;
}else{
return NULL;
}
}
function getAnswer() {
if (!isset($this->response)) {
$error = [
"status" => 500,
"error" => "empty_return",
"error_text" => "API Returned nothing."
];
$this->lastError = $error;
return $error;
}
return $this->response;
}
function getResponse() {
return $this->response;
}
function dbCommand($command, $expectResult, $stmtArgsRequired = false, $stmtArgs = []) {
if (isset($this->db)) {
$db = $this->db;
}else{
$config = $this->getConfig();
$db_name = $config["DB_FILE"];
$db_path = APP_PATH . $db_name;
if (!is_file($db_path)) {
$error_payload = [
"error" => "database_not_found",
"error_text" => "The Database was not found."
];
$this->lastError = $error_payload;
$api->answer(500, $error_payload, true);
}
if (!is_writeable($db_path)) {
$error_payload = [
"error" => "database_not_writeable",
"error_text" => "The Database is not writeable."
];
$this->lastError = $error_payload;
$this->answer(500, $error_payload, true);
return;
}
$db = new PDO("sqlite:" . $db_path);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
$stmt = $db->prepare($command);
$stmt->execute($stmtArgs);
if ($expectResult) {
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $result;
}
}
function getSetting($key) {
$command = "SELECT name, value FROM settings WHERE name = :name";
$value = $this->dbCommand($command, true, true, [":name" => $key]);
if (isset($value[0]["value"])) {
return $value[0]["value"];
}else{
$this->answer(500, [
"error" => "query_returned_nothing",
"error_text" => "The SQL Query Returned nothing usable. Check Database Permissions."
]);
}
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
// Set internal to false so that the answer goes to the client
if (!isset($internal)) {
$internal = false;
define("API_DIR", "../");
}
// Load Main Library
require_once API_DIR . "api.php";
$api = new gbAPI(true, $internal);
$auth = $api->checkAuth();
$lastError = $api->returnLastError();
if ($lastError == NULL) {
if ($auth) {
$api->answer(200, [
"login" => true,
"username" => $api->username
]);
}else{
$api->answer(401, [
"login" => false,
"username" => ""
]);
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
// Set internal to false so that the answer goes to the client
if (!isset($internal)) {
$internal = false;
define("API_DIR", "../");
}
// Load API Library
require_once API_DIR . "api.php";
$api = new gbAPI(false, $internal);
// Check Request Body
$input = $api->getInput();
if (!isset($input["username"]) || !isset($input["password"])) {
$api->answer(400, [
"error" => "missing_fields",
"error_text" => "Some fields are missing"
], true);
return;
}
$username = $input["username"];
$password = $input["password"];
// Prepare SQL Command
$command = "SELECT username, password FROM users WHERE username = :username";
$args = [
":username" => $username
];
// Execute SQL Command
$result = $api->dbCommand($command, true, true, $args);
// Check Result
if ($result && password_verify($password, $result[0]["password"])) {
// Login successful. Generate Auth Token
$token = $api->generateCookieToken($username);
// Set cookies
setcookie("auth_token", $token, time() + 86400, "/");
$api->answer(200);
}else{
$api->answer(401, [
"error" => "unauthorized",
"error_text" => "A user with this password does not exists."
], true);
}
+52
View File
@@ -0,0 +1,52 @@
<?php
// Set internal to false so that the answer goes to the client
if (!isset($internal)) {
$internal = false;
define("API_DIR", "../");
}
// Load Main Library
require_once API_DIR . "api.php";
$api = new gbAPI(false, $internal);
$input = $api->getInput();
if (!isset($input["name"]) || !isset($input["text"])) {
$api->answer(400, [
"error" => "missing_fields",
"error_text" => "Some fields are missing"
], true);
return;
}
$html_allowed = $api->getSetting("allow_html");
if ($html_allowed == "true") {
$name = $input["name"];
$text = $input["text"];
}else{
$name = htmlspecialchars($input["name"]);
$text = htmlspecialchars($input["text"]);
}
$date = date("Y-m-d H:i:s");
$approve = $api->getSetting("approve_entrys") == "true" ? true : false;
if ($approve) {
$status = 0;
}else{
$status = 1;
}
// Insert Into Database
$command = "INSERT INTO entrys (name, text, date, status) VALUES (:name, :text, :date, :status)";
$api->dbCommand($command, false, true, [
":name" => $name,
":text" => $text,
":date" => $date,
":status" => $status
]);
$api->answer(200);
+47
View File
@@ -0,0 +1,47 @@
<?php
// Set internal to false so that the answer goes to the client
if (!isset($internal)) {
$internal = false;
define("API_DIR", "../");
}
// Load Main Library
require_once API_DIR . "api.php";
$api = new gbAPI(false, $internal);
// Get Entrys
$command = "SELECT * FROM entrys WHERE status = :status ORDER BY date DESC";
$result = $api->dbCommand($command, true, true, [":status" => 1]);
$entrys = [];
if (!$result) {
$api->answer(200, [
"entrys" => [],
"empty" => true
]);
return;
}
foreach($result as $row) {
$entry_name = $row["name"];
$entry_text = $row["text"];
$date = new DateTime($row["date"]);
$entry_date = $date->format("d.m.Y H:i:s");
$entry = [
"name" => $entry_name,
"text" => $entry_text,
"date" => $entry_date
];
$entrys[] = $entry;
}
$json = json_encode($entrys);
$api->answer(200, [
"entrys" => $json,
"empty" => false
]);
+24
View File
@@ -0,0 +1,24 @@
<?php
// Set internal to false so that the answer goes to the client
if (!isset($internal)) {
$internal = false;
define("API_DIR", "../");
}
// Load Main Library
require_once API_DIR . "api.php";
$api = new gbAPI(false, $internal);
// Get App Name
$result = $api->getSetting("application_name");
if ($result !== NULL) {
$api->answer(200, [
"application_name" => $result
]);
}else{
$api->answer(500, [
"error" => "empty_query",
"error_text" => "Database query returned nothing."
]);
}
+111
View File
@@ -0,0 +1,111 @@
<?php
// Set internal to false so that the answer goes to the client
if (!isset($internal)) {
$internal = false;
define("API_DIR", "../");
}
// Load Main Libary
require_once API_DIR . "api.php";
$api = new gbAPI(false, $internal);
// Check Request body
$input = $api->getInput();
if (!isset($input["username"]) || !isset($input["password"]) || !isset($input["password_repeat"])) {
$api->answer(400, [
"error" => "missing_fields",
"error_text" => "Some fields are missing"
]);
return;
}
// Check if application already configurated
if (is_file(APP_PATH . "database.db")) {
$api->answer(503, [
"error" => "already_configurated",
"error_text" => "The application is already configurated."
]);
return;
}
// Check PDO drivers
if (!extension_loaded("pdo_sqlite") || !extension_loaded("sqlite3")) {
$api->answer(500, [
"error" => "missing_drives",
"error_text" => "Please install the extensions pdo_sqlite and sqlite3."
]);
return;
}
$username = $input["username"];
$password = $input["password"];
$password_repeat = $input["password_repeat"];
// Check Passwords
if ($password !== $password_repeat) {
$api->answer(400, [
"error" => "passwords_dont_match",
"error_text" => "The passwords does not match."
]);
return;
}
// Hash Password
$hash = password_hash($password, PASSWORD_DEFAULT);
// CREATE DATABASE STRUCTURE
$sql_file = file_get_contents(APP_PATH . "db_structure.sql");
$db_path = APP_PATH . "database.db";
$db = new PDO("sqlite:" . $db_path);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec($sql_file);
// INSERT USER
$command = "INSERT INTO users (username, password, owner) VALUES (:username, :password, :owner)";
$stmt = $db->prepare($command);
$stmt->execute([":username" => $username, ":password" => $hash, ":owner" => 1]);
// INSERT SETTINGS
$command = "INSERT INTO settings (name, value) VALUES (:name, :value)";
$settings = [
"application_name" => "Guestbook",
"allow_html" => "false",
"approve_entrys" => "true",
"allow_new_entrys" => "true",
"template" => "standard"
];
foreach ($settings as $key => $value) {
$stmt = $db->prepare($command);
$stmt->execute([":name" => $key, ":value" => $value]);
}
// WRITE .env AND GENERATE SECRETS
$env = "";
$cookie_token = bin2hex(random_bytes(32));
$env .= "COOKIE_TOKEN_SIGNATURE=" . $cookie_token . "\n";
$env .= "COOKIE_TOKEN_LIFETIME=86400\n";
$api_token = bin2hex(random_bytes(32));
$env .= "API_TOKEN_SIGNATURE=" . $api_token . "\n";
$env .= "DB_FILE=database.db";
file_put_contents(APP_PATH . ".env", $env);
// RETURN SUCCESS MESSAGE
$api->answer(200);
+17
View File
@@ -0,0 +1,17 @@
<?php
// Set internal to false so that the answer goes to the client
if (!isset($internal)) {
$internal = false;
define("API_DIR", "../");
}
// Load Main Library
require_once API_DIR . "api.php";
$api = new gbAPI(false, $internal);
// Get Template
$template = $api->getSetting("template");
if ($template !== NULL) {
$api->answer(200, [
"template_name" => $template
]);
}
Executable → Regular
BIN
View File
Binary file not shown.
+20
View File
@@ -0,0 +1,20 @@
CREATE TABLE IF NOT EXISTS "users" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"username" TEXT NOT NULL UNIQUE,
"password" TEXT NOT NULL,
"owner" INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS "entrys" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"text" TEXT NOT NULL,
"date" TEXT NOT NULL,
"status" INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS "settings" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"value" TEXT NOT NULL
);
+25
View File
@@ -0,0 +1,25 @@
<?php
// Set debug to true
$debug = true;
// Load Main Library
require "../main.php";
// Init Main Class
$gb = new gb($debug);
// Load and init HTML Loader Library
require FRONTEND_PATH . "html_loader.php";
$htmlloader = new HTMLLoader();
// Check Login
$call = new APICall("auth/isauth.php");
$response = $call->response;
if (!$response["data"]["login"]) {
$htmlloader->setPage("login");
$htmlloader->show([], true);
}else{
$htmlloader->setPage("admin_home");
$htmlloader->show([], true);
}
+75 -23
View File
@@ -1,46 +1,77 @@
<?php <?php
class HTMLLoader { class HTMLLoader {
private $template; private $template;
private $page;
private $templates;
private $template_conf;
function __construct() { function __construct() {
// Check if Paths variables exists // Check if Paths variables exists
if (!defined("TEMPLATE_PATH") || !defined("TEMPLATE_CONF") || !defined("APP_DOMAIN")) { if (!defined("TEMPLATE_PATH") || !defined("TEMPLATE_CONF") || !defined("APP_DOMAIN")) {
die("TEMPLATE_PATH or TEMPLATE_CONF are not defined at " . __FILE__); die("TEMPLATE_PATH or TEMPLATE_CONF are not defined at " . __FILE__);
} }
$this->templates = json_decode(file_get_contents(TEMPLATE_CONF), true);
$this->getTemplate();
} }
function setTemplate($template) { function setTemplate($template) {
// Check if template exists // Check if template exists
$file = file_get_contents(TEMPLATE_CONF); if (!array_key_exists($template, $this->templates)) {
$json = json_decode($file, true); die("Template " . $template . " not found.");
if (!array_key_exists($template, $json) && array_key_exists("error", $json)) {
$this->showError('Template "' . $template . '" was not found.');
exit;
}elseif (!array_key_exists("error", $json)){
die("Error Template was not found.");
} }
if (!is_dir(TEMPLATE_PATH . $this->templates[$template]["dir_name"])) {
die("Template " . $template . " not found.");
}
$this->template = $template;
$this->template_conf = file_get_contents(TEMPLATE_PATH . $this->templates[$template]["dir_name"] . "/template.json");
}
function getTemplate() {
// Call API
$call = new APICall("template/get.php");
$response = $call->response;
if ($response["status"] !== 200) {
$this->setTemplate("standard");
$this->showError("Template API Returned an error: " . $response["error_text"]);
}
$template = $response["data"]["template_name"];
$this->template_conf = json_decode(file_get_contents(TEMPLATE_PATH . $this->templates[$template]["dir_name"] . "/template.json"), true);
$this->template = $template; $this->template = $template;
} }
function showError($error) { function setPage($page) {
$file = file_get_contents(TEMPLATE_CONF); if (!array_key_exists($page, $this->template_conf)) {
$json = json_decode($file, true); $this->showError("Page " . $page . " for template " . $this->template . " not found.");
}else{
$this->page = $page;
}
}
$page = $json["error"]; function showError($error) {
$html = file_get_contents(TEMPLATE_PATH . $page["dir_name"] . "/" . $page["html_file"]); $template_dir = $this->templates[$this->template]["dir_name"];
$template_conf = json_decode(file_get_contents(TEMPLATE_PATH . $template_dir . "/template.json"), true);
$page = $template_conf["error"];
$html = file_get_contents(TEMPLATE_PATH . $template_dir . "/" . $page["dir_name"] . "/" . $page["html_file"]);
$css = ""; $css = "";
// Get CSS // Get CSS
foreach ($page["css_files"] as $file) { foreach ($page["css_files"] as $file) {
// Check if File exists // Check if File exists
if (!is_file(TEMPLATE_PATH . $page["dir_name"] . "/" . $file)) { if (!is_file(TEMPLATE_PATH . $template_dir . "/" . $page["dir_name"] . "/" . $file)) {
$this->showError('CSS File <span class="code">' . $file . '</span> for Template <span class="code">error</span> in <span class="code">' . __FILE__ . "</span> not found."); die("CSS File " . $file . " not found.");
} }
// Add CSS Files to HTML // Add CSS Files to HTML
$css .= '<link rel="stylesheet" type="text/css" href="http://' . APP_DOMAIN . "/templates/" . $page["dir_name"] . "/" . $file . '">'; $css .= '<link rel="stylesheet" type="text/css" href="http://' . APP_DOMAIN . "/templates/" . $template_dir . "/" . $page["dir_name"] . "/" . $file . '">';
} }
$html = str_replace("%css%", $css, $html); $html = str_replace("%css%", $css, $html);
@@ -51,30 +82,51 @@ class HTMLLoader {
die($replace); die($replace);
} }
function show() { function show($args = [], $showAppName = true) {
if (!isset($this->template)) {
$this->getTemplate();
}
$template = $this->template; $template = $this->template;
$tempalte_conf = $this->template_conf;
$template_dir = $this->templates[$template]["dir_name"];
$file = file_get_contents(TEMPLATE_CONF); $page = $tempalte_conf[$this->page];
$json = json_decode($file, true);
$page = $json[$template]; $html = file_get_contents(TEMPLATE_PATH . $template_dir . "/" . $page["dir_name"] . "/" . $page["html_file"]);
$html = file_get_contents(TEMPLATE_PATH . $page["dir_name"] . "/" . $page["html_file"]);
if ($showAppName) {
$call = new APICall("settings/appname.php");
$response = $call->response;
if (isset($response["error"])) {
$this->showError("Failed to get Application Name: API Returned an error: " . $response["error_text"]);
}
$app_name = $response["data"]["application_name"];
$html = str_replace("%appname%", $app_name, $html);
}
// Get CSS // Get CSS
$css = ""; $css = "";
foreach ($page["css_files"] as $file) { foreach ($page["css_files"] as $file) {
// Check if File exists // Check if File exists
if (!is_file(TEMPLATE_PATH . $page["dir_name"] . "/" . $file)) { if (!is_file(TEMPLATE_PATH . $template_dir . "/" . $page["dir_name"] . "/" . $file)) {
$this->showError('CSS File <span class="code">' . $file . '</span> for Template <span class="code">' . $this->template . '</span> in <span class="code">' . __FILE__ . "</span> not found."); $this->showError('CSS File <span class="code">' . $file . '</span> for Template <span class="code">' . $this->template . '</span> in <span class="code">' . __FILE__ . "</span> not found.");
} }
// Add CSS Files to HTML // Add CSS Files to HTML
$css .= '<link rel="stylessheet" text="text/css" href="' . APP_DOMAIN . "/templates/" . $page["dir_name"] . "/" . $file . '">'; $css .= '<link rel="stylessheet" text="text/css" href="' . APP_DOMAIN . "/templates/" . $template_dir . "/" . $page["dir_name"] . "/" . $file . '">';
} }
$html = str_replace("%css%", $css, $html); $html = str_replace("%css%", $css, $html);
// Insert Args
foreach ($args as $key => $value) {
$html = str_replace($key, $value, $html);
}
die($html); die($html);
} }
} }
+33
View File
@@ -8,3 +8,36 @@ require "main.php";
// Init GB Class // Init GB Class
$gb = new gb(true); $gb = new gb(true);
// Load HTMLLoader Library
require FRONTEND_PATH . "html_loader.php";
$htmlLoader = new HTMLLoader();
// Build Entry Page
$htmlLoader->setPage("entrys_frontend");
// Get entrys
$call = new APICall("entrys/get.php");
$response = $call->response;
if ($response["data"]["empty"]) {
$html_block = "<p>There aren'n any entrys yet. Be the first one!</p>";
}else{
$entrys = json_decode($response["data"]["entrys"], true);
$html_block = "";
foreach($entrys as $entry) {
$name = $entry["name"];
$text = $entry["text"];
$date = $entry["date"];
$html_block .= '
<div class="entry">
<h3 class="md-typescale-display-small">' . $name . '</h3>
<p id="date">' . $date . '</p>
<p>' . $text . '</p>
</div>
';
}
}
$htmlLoader->show(["%entrys%" => $html_block]);
+31 -3
View File
@@ -10,8 +10,9 @@ class gb {
// Define Paths // Define Paths
define("APP_DOMAIN", $_SERVER["SERVER_NAME"]); define("APP_DOMAIN", $_SERVER["SERVER_NAME"]);
define("APP_PATH", __DIR__ . "/.."); define("APP_PATH", __DIR__ . "/../");
define("FRONTEND_PATH", __DIR__ . "/"); define("FRONTEND_PATH", __DIR__ . "/");
define("API_DIR", __DIR__ . "/../api/");
define("TEMPLATE_PATH", FRONTEND_PATH . "templates/"); define("TEMPLATE_PATH", FRONTEND_PATH . "templates/");
define("TEMPLATE_CONF", TEMPLATE_PATH . "templates.json"); define("TEMPLATE_CONF", TEMPLATE_PATH . "templates.json");
@@ -27,7 +28,34 @@ class gb {
$html = new HTMLLoader(); $html = new HTMLLoader();
// Set template to Setup an show it // Set template to Setup an show it
$html->setTemplate("setup"); $html->setTemplate("standard");
$html->show(); $html->setPage("setup");
$html->show([], false);
}
}
// Internal API Call Library
class APICall {
public $response;
private $error;
function __construct($path, $data = []) {
// Set $internal to true so the api return does not go to the browser
$internal = true;
// Check Path
if (!is_file(API_DIR . $path)) {
$this->error("API Path invalid.");
return false;
}
// Call API
require API_DIR . $path;
// Get return from the API Library
$response = $api->getAnswer();
$this->response = $response;
} }
} }
@@ -1,38 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<script type="importmap">
{
"imports": {
"@material/web/": "https://esm.run/@material/web/"
}
}
</script>
<script type="module">
import '@material/web/all.js';
import {styles as typescaleStyles} from '@material/web/typography/md-typescale-styles.js';
document.adoptedStyleSheets.push(typescaleStyles.styleSheet);
</script>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
</style>
</head>
<body>
<div class="content">
<h1 class="md-typescale-display-medium">Hello!</h1>
<p>Welcome to your Guestbook! Enter here your prefered username and password.</p>
<md-filled-text-field label="Username" type="text"></md-filled-text-field><br><br>
<md-filled-text-field label="Password" type="password"></md-filled-text-field><br><br>
<md-filled-text-field label="Repeat Password" type="password"></md-filled-text-field><br><br>
<md-filled-button disabled>Save</md-filled-button>
</div>
</body>
</html>
View File
Whitespace-only changes.
-11
View File
@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Setup</title>
</head>
<body>
<h1>La setup</h1>
</body>
</html>
@@ -0,0 +1,150 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<script type="importmap">
{
"imports": {
"@material/web/": "https://esm.run/@material/web/"
}
}
</script>
<script type="module">
import '@material/web/all.js';
import { styles as typescaleStyles } from '@material/web/typography/md-typescale-styles.js';
document.adoptedStyleSheets.push(typescaleStyles.styleSheet);
</script>
<style>
:root {
--md-sys-color-primary: rgb(58, 58, 255);
--active-menu-point-color: rgb(116, 148, 250);
--md-sys-color-surface-container: rgb(223, 228, 255);
}
body {
margin: 0;
}
.layout {
display: flex;
height: 100vh;
overflow: hidden;
}
.sidebar {
flex: 0 0 280px;
width: 280px;
overflow: hidden;
background: var(--md-sys-color-surface-container);
border-right: 1px solid var(--md-sys-color-outline-variant);
transition: flex-basis .2s ease, width .2s ease;
padding: 10px;
overflow-y: auto;
}
.sidebar-header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
white-space: nowrap;
}
.sidebar md-list {
background: transparent;
}
md-list-item.active {
background-color: var(--active-menu-point-color);
border-radius: 28px;
margin-bottom: 10px;
}
md-list-item {
border-radius: 28px;
margin-bottom: 10px;
}
.sidebar.collapsed {
flex-basis: 58px;
width: 58x;
}
.sidebar.collapsed .title,
.sidebar.collapsed .label {
display: none;
}
.content {
flex: 1;
padding: 24px;
overflow-y: auto;
}
@media (max-width: 720px) {
body {
overflow-x: hidden;
}
}
</style>
</head>
<body>
<div class="layout">
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<md-icon-button id="toggle" aria-label="Menü ein-/ausklappen">
<md-icon>menu</md-icon>
</md-icon-button>
<span class="title">Dashboard</span>
</div>
<md-list>
<md-list-item type="link" href="/admin/" class="active">
<md-icon slot="start">dashboard</md-icon>
<span class="label">Overview</span>
</md-list-item>
<md-list-item type="link" href="/admin/settings.php">
<md-icon slot="start">settings</md-icon>
<span class="label">Settings</span>
</md-list-item>
</md-list>
</aside>
<main class="content">
<h1>Dashboard</h1>
</main>
</div>
<script type="module">
const sidebar = document.getElementById('sidebar');
const items = sidebar.querySelectorAll('md-list-item');
const mq = window.matchMedia('(max-width: 720px)');
const applyBreakpoint = () => {
sidebar.classList.toggle('collapsed', mq.matches);
};
applyBreakpoint();
mq.addEventListener('change', applyBreakpoint);
document.getElementById('toggle').addEventListener('click', () => {
sidebar.classList.toggle('collapsed');
});
items.forEach(item => {
item.addEventListener('click', () => {
items.forEach(i => i.classList.remove('active'));
item.classList.add('active');
});
});
</script>
</body>
</html>
@@ -0,0 +1,115 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<script type="importmap">
{
"imports": {
"@material/web/": "https://esm.run/@material/web/"
}
}
</script>
<script type="module">
import '@material/web/all.js';
import {styles as typescaleStyles} from '@material/web/typography/md-typescale-styles.js';
document.adoptedStyleSheets.push(typescaleStyles.styleSheet);
</script>
<title>%appname% // Entrys</title>
<style>
:root {
--md-sys-color-primary: rgb(58, 58, 255);
}
p {
font-family: Verdana, Geneva, Tahoma, sans-serif;
}
hr {
border: 1px solid rgb(211, 211, 211);
}
.loader {
display: flex;
justify-content: center;
align-items: center;
}
#date {
color: gray;
}
</style>
</head>
<body>
<h1 class="md-typescale-display-large">%appname%</h1>
<md-filled-tonal-button id="entry_add">
Add Entry
</md-filled-tonal-button>
<h2 class="md-typescale-display-medium">All Entrys</h2>
%entrys%
<md-dialog id="add_entry_dialog">
<div slot="headline">
Add entry
</div>
<form slot="content" method="dialog">
<md-filled-text-field id="entry_name" label="Name"></md-filled-text-field><br><br>
<md-filled-text-field id="entry_text" label="Text" rows="5" style="resize: vertical;" type="textarea"></md-filled-text-field>
<div class="loader" id="loader" style="display: none;">
<md-circular-progress id="add_entry_load" indeterminate></md-circular-progress>
</div>
<p id="entry_return"></p>
</form>
<div slot="actions">
<md-filled-button id="entry_submit">Submit</md-filled-button>
<md-text-button id="entry_close">Close</md-text-button>
</div>
</md-dialog>
<script>
document.getElementById("entry_add").addEventListener("click", function() {
document.getElementById("add_entry_dialog").setAttribute("open", "");
});
document.getElementById("entry_close").addEventListener("click", function() {
document.getElementById("add_entry_dialog").removeAttribute("open");
});
document.getElementById("entry_submit").addEventListener("click", function() {
document.getElementById("entry_name").style.display = "none";
document.getElementById("entry_text").style.display = "none";
document.getElementById("entry_submit").style.display = "none";
document.getElementById("entry_close").style.display = "none";
document.getElementById("loader").style.display = "flex";
name = document.getElementById("entry_name").value;
text = document.getElementById("entry_text").value;
fetch("/api/entrys/add.php", {
method: "POST",
body: JSON.stringify({
name: name,
text: text
})
})
.then(response => response.json())
.then(data => {
document.getElementById("loader").style.display = "none";
if (data.status !== 200) {
document.getElementById("entry_return").innerHTML = "API returned an error: " + data.error_text;
document.getElementById("entry_close").style.display = "block";
}else{
document.getElementById("entry_return").innerHTML = "Success! Maybe your entry must be aproved by the owner.";
document.getElementById("entry_close").style.display = "block";
}
})
.catch(error => {
document.getElementById("loader").style.display = "none";
document.getElementById("entry_return").innerHTML = "There was an error: " + error;
document.getElementById("entry_close").style.display = "block";
});
});
</script>
</body>
</html>
File renamed without changes.
File renamed without changes.
+76
View File
@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>%appname% // Login</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<script type="importmap">
{
"imports": {
"@material/web/": "https://esm.run/@material/web/"
}
}
</script>
<script type="module">
import '@material/web/all.js';
import {styles as typescaleStyles} from '@material/web/typography/md-typescale-styles.js';
document.adoptedStyleSheets.push(typescaleStyles.styleSheet);
</script>
<style>
.page-content {
display: flex;
justify-content: center;
text-align: center;
align-items: center;
}
.center {
display: block;
}
</style>
</head>
<body>
<div class="page-content">
<div class="center">
<h1 class="md-typescale-display-large">%appname%</h1>
<h3 class="md-typescale-display-medium">Admin Panel Login</h3><br><br>
<p class="md-typescale-body-medium" id="error_msg" style="display: none;"></p>
<md-filled-text-field type="text" id="login_username" placeholder="Username"></md-filled-text-field><br><br>
<md-filled-text-field type="password" id="login_password" placeholder="Password"></md-filled-text-field><br><br>
<md-filled-button id="login_button">Login</md-filled-button>
</div>
</div>
<script>
document.getElementById("login_button").addEventListener("click", function() {
var username = document.getElementById("login_username").value;
var password = document.getElementById("login_password").value;
fetch("/api/auth/login.php", {
method:"POST",
body: JSON.stringify({
username: username,
password: password
}),
headers: {
"Content-type": "application/json; charset=UTF-8"
}
})
.then(response => response.json())
.then(data => {
if (data.status == 200) {
window.location.reload();
}else{
document.getElementById("error_msg").innerHTML = data.error_text;
document.getElementById("error_msg").style.display = "block";
}
})
.catch(error => {
document.getElementById("error_msg").innerHTML = error;
document.getElementById("error_msg").style.display = "block";
})
});
</script>
</body>
</html>
+145
View File
@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<script type="importmap">
{
"imports": {
"@material/web/": "https://esm.run/@material/web/"
}
}
</script>
<script type="module">
import '@material/web/all.js';
import {styles as typescaleStyles} from '@material/web/typography/md-typescale-styles.js';
document.adoptedStyleSheets.push(typescaleStyles.styleSheet);
</script>
<title>Setup</title>
<style>
:root {
--md-sys-color-primary: rgb(58, 58, 255);
}
body {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
p {
font-family: Verdana, Geneva, Tahoma, sans-serif;
}
</style>
</head>
<body>
<div class="content">
<h1 class="md-typescale-display-medium">Hello!</h1>
<p>Welcome to your Guestbook! Enter here your prefered username and password.</p>
<md-filled-text-field label="Username" type="text" id="setup-username"></md-filled-text-field><br><br>
<md-filled-text-field label="Password" type="password" id="setup-password"></md-filled-text-field><br><br>
<md-filled-text-field label="Repeat Password" type="password" id="setup-password-repeat"></md-filled-text-field><br><br>
<md-filled-button id="submit-button">Save</md-filled-button>
</div>
<md-dialog id="http-dialog">
<div slot="headline">
Warning
</div>
<form slot="content" method="dialog">
Your browser is using http instead of https. That means your connection is not encrypted and everyone in your network can read your credentials.
</form>
<div slot="actions">
<md-text-button id="http-dialog-close-button">I know what i'm doing!</md-text-button>
</div>
</md-dialog>
<md-dialog id="error-dialog">
<div slot="headline">
Error
</div>
<form slot="content" method="dialog" id="error-dialog-text">
</form>
<div slot="actions">
<md-filled-button id="close-button">Close</md-filled-button>
</div>
</md-dialog>
<md-dialog id="success-dialog">
<div slot="headline">
Success!
</div>
<form slot="content" method="dialog" id="error-dialog-text">
Your Guestbook is finaly configurated! You can find the admin Panel on <span id="success-admin-url"></span>.
</form>
<div slot="actions">
<md-filled-button id="success-dialog-home">Open Guestbook</md-filled-button>
<md-filled-button id="success-dialog-admin">Open Admin Panel</md-filled-button>
</div>
</md-dialog>
<script>
if (window.location.protocol == "http:") {
document.getElementById("http-dialog").setAttribute("open", "");
document.getElementById("http-dialog-close-button").addEventListener("click", function() {
document.getElementById("http-dialog").removeAttribute("open");
});
}
document.getElementById("submit-button").addEventListener("click", function() {
checkSetup();
});
document.getElementById("close-button").addEventListener("click", function() {
document.getElementById("error-dialog").removeAttribute("open");
});
document.getElementById("success-dialog-home").addEventListener("click", function() {
window.location.reload();
});
document.getElementById("success-dialog-admin").addEventListener("click", function() {
window.location.href = "/admin";
});
function checkSetup() {
const username = document.getElementById("setup-username").value;
const password = document.getElementById("setup-password").value;
const password_repeat = document.getElementById("setup-password-repeat").value;
// Check if Passwords match
if (password !== password_repeat) {
console.error("Passwords does not match");
document.getElementById("error-dialog-text").innerHTML = "Passwords does not match";
document.getElementById("error-dialog").setAttribute("open", "");
}
// Send data
fetch("/api/setup/save.php", {
method: "POST",
body: JSON.stringify({
username: username,
password: password,
password_repeat: password_repeat
}),
headers: {
"Content-type": "application/json; charset=UTF-8"
}
})
.then(response => response.json())
.then(data => {
if (data.status == 200) {
document.getElementById("success-admin-url").innerHTML = window.location.href + "admin";
document.getElementById("success-dialog").setAttribute("open", "");
}else{
console.error("Server returned following message: " + data.error);
document.getElementById("error-dialog-text").innerHTML = data.display_error;
document.getElementById("error-dialog").setAttribute("open", "");
}
})
}
</script>
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
{
"error":{
"name":"error",
"dir_name":"error",
"html_file":"error.html",
"css_files":[
"error.css"
]
},
"setup":{
"name":"setup",
"dir_name":"setup",
"html_file":"setup.html",
"css_files":[]
},
"entrys_frontend":{
"name":"entrys_frontend",
"dir_name":"entrys_frontend",
"html_file":"entrys_frontend.html",
"css_files":[]
},
"login":{
"name":"login",
"dir_name":"login",
"html_file":"login.html",
"css_files":[]
},
"admin_home":{
"name":"admin_home",
"dir_name":"admin_home",
"html_file":"home.html",
"css_files":[]
}
}
+3 -15
View File
@@ -1,18 +1,6 @@
{ {
"error":{ "standard":{
"name":"error", "name":"Standard Template",
"dir_name":"error", "dir_name":"standard"
"html_file":"error.html",
"css_files":[
"error.css"
]
},
"setup":{
"name":"setup",
"dir_name":"setup",
"html_file":"setup.html",
"css_files":[
"setup.css"
]
} }
} }
+29 -1
View File
@@ -1,5 +1,33 @@
<?php <?php
foreach($_SERVER as $key => $value) { /*foreach($_SERVER as $key => $value) {
echo $key . " === " . $value . "<br>"; echo $key . " === " . $value . "<br>";
}*/
//echo $_SERVER["HTTP_X_API_KEY"];
//$string = '{"user":"marc", "auth":true}';
//$secret = "281361f952279bd9530734c67ed04aed2756f0fe00cb998cd6c28324f491484c";
//echo "Hash: " . hash_hmac("sha256", $string, $secret);
//echo "Decode: " . hash_equals($string, $secret);
//print_r(PDO::getAvailableDrivers());
//print_r(json_decode('{"entrys":[{"name":"marc"}]}', true));
//print_r(explode(".", "jakob.stinkt"));
//var_dump(extension_loaded("pdo_sqlite"));
//var_dump(extension_loaded("sqlite3"));
class test {
private $test;
function __construct() {
var_dump(isset($this->test));
} }
}
new test();