First commit

This commit is contained in:
tom 2021-10-14 10:41:30 +02:00
parent 815239516d
commit 61c79d48a6
16 changed files with 622 additions and 1 deletions

7
.gitignore vendored
View File

@ -1 +1,6 @@
Parsedown.php
credentials.csv
pages/*
public/font/*
public/img/*
!.gitkeep

0
pages/.gitkeep Normal file
View File

View File

@ -0,0 +1,11 @@
<div id="admin-bar">
<h1>Accord's CMS</h1>
<?php
if (session_status() == PHP_SESSION_NONE) session_start();
if (isset($_SESSION['loginUsername'])) {
echo '<div id="logout">Welcome ' . $_SESSION['loginUsername'] . '<a class="button" href="/admin/logout.php">Logout</a></div>';
} else {
header('Location: /');
}
?>
</div>

37
public/admin/delete.php Normal file
View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="/css/master.css">
<link rel="stylesheet" href="/css/admin.css">
</head>
<body>
<div class="container">
<?php require_once($_SERVER["DOCUMENT_ROOT"] . "/admin/admin-bar.php") ?>
<?php require_once($_SERVER["DOCUMENT_ROOT"] . "/admin/tools.php") ?>
<div class="content">
<?php
if (isset($_GET['page'])) {
$page = new Page($_GET['page']);
if (isset($_GET['confirm'])) {
$page->delete();
header('Location: /admin');
exit();
}
echo "<h2>Deletion of $page->title</h2>";
echo "<p>Are you sure you want to delete this page?</p>";
echo "<a class='button' href='/admin'>Cancel<a>";
echo "<a class='button' href='/admin/delete.php?page=$page->slug&confirm=true'>Confirm<a>";
}
?>
</body>
</html>

80
public/admin/edit.php Normal file
View File

@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="/css/master.css">
<link rel="stylesheet" href="/css/admin.css">
<link rel="stylesheet" href="/css/edit.css">
</head>
<body>
<div class="container">
<?php require_once($_SERVER["DOCUMENT_ROOT"] . "/admin/admin-bar.php") ?>
<?php require_once($_SERVER["DOCUMENT_ROOT"] . "/admin/tools.php") ?>
<div class="content">
<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$originalSlug = $_POST['originalSlug'];
$newSlug = sluggify($_POST['slug']);
if (!$originalSlug) {
// This is the creation of a new page
$page = new Page();
$page->title = $_POST['title'];
$page->slug = $newSlug;
$page->author = $_SESSION['loginUsername'];
$page->cDate = time();
$page->mDate = time();
$page->content = $_POST['content'];
} else {
// This is modification of an existing page
$page = new Page($originalSlug);
$page->title = $_POST['title'];
$page->mDate = time();
$page->content = $_POST['content'];
if ($originalSlug !== $newSlug) {
// The page needs to be moved
$page->slug = $originalSlug;
$page->rename($newSlug);
}
}
$page->write();
header('Location: /admin');
exit();
} else if (isset($_GET['page'])) {
$page = new Page($_GET['page']);
if ($page->slug) {
echo "<h2>Editing $page->title</h2>";
} else {
echo "<h2>Editing a new page</h2>";
}
echo "
<form action='edit.php' method='post'>
https://new.accords-library.com/<input type='text' name='slug' placeholder='' value='$page->slug' required><br>
Title: <input type='text' name='title' placeholder='A great title...' value='$page->title' required><br>
<textarea name='content' placeholder='Some awesome content...'>$page->content</textarea><br>
<input type='hidden' name='originalSlug' value='" . $_GET['page'] . "'>
<input class='button' type='submit'>
</form>
";
}
?>
</body>
</html>

55
public/admin/index.php Normal file
View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Accord's CMS</title>
<link rel="stylesheet" href="/css/master.css">
<link rel="stylesheet" href="/css/admin.css">
</head>
<body>
<div class="container">
<?php require_once($_SERVER["DOCUMENT_ROOT"] . "/admin/admin-bar.php") ?>
<?php require_once($_SERVER["DOCUMENT_ROOT"] . "/admin/tools.php") ?>
<div class="content">
<div class="title">
<h2>Pages</h2>
<a class='button' href='/admin/edit.php?page='>Create a new page</a>
</div>
<div class="page-list">
<p>Title</p>
<p>Author</p>
<p>Last edit</p>
<p></p>
<p></p>
<p></p>
<?php
// Get all MD files
foreach (getListSlugPages() as $pageSlug) {
$page = new Page($pageSlug);
echo "<p> - " . $page->title . "</p>";
echo "<p>" . $page->author . "</p>";
echo "<p>" . unixToDate($page->mDate) . "</p>";
echo "<a class='button' href='/$page->slug'>View</a>";
echo "<a class='button' href='/admin/edit.php?page=$page->slug'>Edit</a>";
echo "<a class='button' href='/admin/delete.php?page=$page->slug'>Delete</a>";
}
echo '</div>';
?>
</div>
</div>
</body>
</html>

5
public/admin/logout.php Normal file
View File

@ -0,0 +1,5 @@
<?php
session_start();
session_destroy();
header('Location: /login');
?>

112
public/admin/tools.php Normal file
View File

@ -0,0 +1,112 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
{
$pagesFolder = $_SERVER["DOCUMENT_ROOT"] . '/../pages/';
class Page {
public $title;
public $slug;
public $author;
public $cDate;
public $mDate;
public $content;
function __construct($pageSlug = '') {
if (existPage($pageSlug)) {
$pageJSON = json_decode(file_get_contents(getPageJSONPath($pageSlug)));
foreach ($pageJSON as $key => $value) {
$this->$key = $value;
}
$this->slug = $pageSlug;
}
}
function write() {
$filePath = getPageJSONPath($this->slug);
// Remove attributes that should be serialized
$slug = $this->slug;
unset($this->slug);
$file = fopen($filePath, 'w');
fwrite($file, json_encode($this));
fclose($file);
// Add them back
$this->slug = $slug;
}
function delete() {
$filePath = getPageJSONPath($this->slug);
if (file_exists($filePath)) {
unlink($filePath);
}
}
function rename($newSlug) {
rename(getPageJSONPath($this->slug), getPageJSONPath($newSlug));
$this->slug = $newSlug;
}
function parse() {
require_once($_SERVER["DOCUMENT_ROOT"] . "/../Parsedown.php");
$parsedown = new Parsedown();
$parsedown->setSafeMode(true);
return $parsedown->text($this->content);
}
}
function getListSlugPages() {
global $pagesFolder;
$pages = scandir($pagesFolder);
$pages = array_slice($pages, 2);
$result = array();
foreach ($pages as $page) {
if (substr($page, -5, 5) == '.json') {
array_push($result, substr($page, 0, -5));
}
}
return $result;
}
function existPage($pageSlug) {
return file_exists(getPageJSONPath($pageSlug));
}
function getPageJSONPath($pageSlug) {
global $pagesFolder;
return $pagesFolder . $pageSlug . '.json';
}
function unixToDate($unixTime) {
return date('Y-m-d', $unixTime);
}
function sluggify($string) {
$string = strtolower($string);
$string = str_replace(' ', '-', $string);
$string = str_split($string);
$result = "";
$slugAcceptable = "abcdefghijklmnopqrstuvwxyz0123456789-";
foreach ($string as $c) {
if (stripos($slugAcceptable, $c) !== false) $result .= $c;
}
$result = trim($result, "-");
return $result;
}
}
?>

88
public/css/admin.css Normal file
View File

@ -0,0 +1,88 @@
@font-face {
font-family: customFont;
src: url("/font/Quicksand-VariableFont_wght.ttf");
}
:root {
--break-point: 60em;
}
body {
width: 100%;
display: grid;
grid-template-columns: 1fr var(--break-point) 1fr;
place-content: center;
overflow-x: hidden;
}
body > .container {
margin-top: 2rem;
margin-bottom: 2rem;
border: var(--default-border);
box-shadow: var(--default-box-shadow);
grid-column: 2;
transition: 1s margin-top;
}
body > .container > .content {
padding: 2rem;
}
#admin-bar {
max-width: var(--break-point);
display: grid;
grid-template-columns: 1fr;
grid-auto-flow: column;
align-items: center;
padding-left: 2rem;
padding-right: 2rem;
box-sizing: border-box;
background-color: var(--color-main-dark);
color: var(--color-main-light);
}
#admin-bar > #logout > .button {
margin-left: 1rem;
}
.content > .title {
display: grid;
grid-gap: 1rem;
grid-auto-flow: column;
place-content: start;
place-items: center start;
}
.page-list {
display: grid;
grid-template-columns: 2fr 1fr 1fr auto auto auto;
grid-row-gap: 0.5rem;
}
.page-list > * {
width: auto;
}
.page-list > .button {
place-self: center;
}
.page-list p {
margin-top: .5em;
margin-bottom: .5em;
font-weight: bold;
}
@media only screen and (max-width: 60em) {
body {
grid-template-columns: 1fr;
}
body > .container {
grid-column: 1;
margin-top: 0;
border: unset;
box-shadow: unset;
}
}

31
public/css/edit.css Normal file
View File

@ -0,0 +1,31 @@
body {
display: grid;
place-content: center;
}
textarea {
padding: 1em;
margin-top: 1em;
min-width: 100%;
max-width: 100%;
height: 30em;
box-sizing: border-box;
background-color: inherit;
border: var(--default-border);
border-radius: 1em;
}
input[type = text] {
background-color: inherit;
border: var(--default-border);
padding: 0.3em 1em;
border-radius: 9999px;
}
input[type = submit] {
margin-top: 1em;
}
textarea:focus-visible, input:focus-visible {
outline: unset;
}

76
public/css/login.css Normal file
View File

@ -0,0 +1,76 @@
body {
display: grid;
place-content: center;
height: 100vh;
overflow: hidden;
font-size: 120%;
text-align: center;
}
#container {
background: var(--color-main-light);
border: var(--default-border);
margin-bottom: 15vh;
width: 20em;
box-shadow: var(--default-box-shadow);
padding: 2em;
}
#container img {
width: 50%;
justify-self: center;
}
#myForm {
display: inline-grid;
grid-auto-flow: row;
padding: 1em;
width: 100%;
box-sizing: border-box;
}
#myForm > input, #myForm > button {
text-align: center;
padding: 0.5em;
border: solid 1px var(--color-main-dark);
border-radius: 9999px;
margin-top: 0.75em;
font-size: 100%;
font-family: customFont;
color: var(--color_background);
cursor: pointer;
}
#myForm > input:first-of-type {
margin-top: 0em;
}
#myForm > button {
color: white;
background: var(--color-main-dark);
font-weight: 600;
font-size: 125%;
}
#answer {
margin: 0;
padding: 1em;
border-radius: 0 0 var(--default_border_radius) var(--default_border_radius);
background: var(--color_accent);
font-size: 120%;
font-weight: 900;
}
@keyframes shake {
0% { transform: translate(0px, 0);}
10% { transform: translate(1px, 0);}
20% { transform: translate(3px, 0);}
30% { transform: translate(5px, 0);}
40% { transform: translate(3px, 0);}
50% { transform: translate(1px, 0);}
60% { transform: translate(-1px, 0);}
70% { transform: translate(-3px, 0));}
80% { transform: translate(-5px, 0);}
90% { transform: translate(-3px, 0);}
100% { transform: translate(-1px, 0));}
}

39
public/css/master.css Normal file
View File

@ -0,0 +1,39 @@
@font-face {
font-family: customFont;
src: url("/font/Quicksand-VariableFont_wght.ttf");
}
:root {
--color-main-light: #FFF8E7;
--color-main-base: #ffEBCD;
--color-main-dark: #954535;
--color-main-black: #1B1811;
--default-border: solid 1px var(--color-main-dark);
--default-box-shadow: #95453529 0 0 2em;
}
body {
background: var(--color-main-light);
color: var(--color-main-black);
font-family: customFont;
margin: 0;
padding: 0;
}
.button {
background: var(--color-main-light);
padding: 0.3em 1.1em;
color: var(--color-main-dark);
transition: .1s background-color, .1s color, .1s border;
border: solid 2px var(--color-main-dark);
border-radius: 9999px;
text-decoration-line: none;
margin-left: 0.3em;
margin-right: 0.3em;
}
.button:hover {
background: var(--color-main-dark);
color: var(--color-main-light);
border: solid 2px var(--color-main-dark);
}

0
public/font/.gitkeep Normal file
View File

0
public/img/.gitkeep Normal file
View File

21
public/index.php Normal file
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<?php require_once($_SERVER["DOCUMENT_ROOT"] . "/admin/tools.php") ?>
<?php
if (isset($_GET['p'])) {
$page = new Page($_GET['p']);
echo $page->parse();
}
?>
</body>
</html>

61
public/login/index.php Normal file
View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Accord's Library - Login</title>
<link rel="stylesheet" href="/css/master.css">
<link rel="stylesheet" href="/css/login.css">
</head>
<div id="container">
<form method="POST" action="/login" id="myForm">
<img src="/img/favicon.png" alt="">
<h1>Accord's Library</h1>
<input type="text" name="username" placeholder="Username" value="<?php if (isset($_POST['username'])) echo $_POST['username'] ?>" autocomplete="username" required>
<input type="password" name="password" placeholder="Password" autocomplete="current-password" required>
<button type="submit" name="submitButton" value="Submit">Sign In</button>
</form>
<?php
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
function verifyKey($username, $password) {
$csv = file_get_contents($_SERVER["DOCUMENT_ROOT"] . '/../credentials.csv');
$hashes = explode(PHP_EOL, $csv);
foreach ($hashes as $hash) {
$hash = explode(';', $hash);
if ($hash[0] == $username) {
$hash = substr($hash[2], 0, -1);
return password_verify($password, $hash);
}
}
return false;
}
if ($_POST['submitButton'] == "Submit") {
$username = filter_var($_POST["username"], FILTER_SANITIZE_STRING);
$password = filter_var($_POST["password"], FILTER_SANITIZE_STRING);
if (verifyKey($username, $password)) {
$_SESSION['loginUsername'] = $username;
header('Location: /admin');
} else {
unset($_SESSION['loginUsername']);
echo '<p id="answer">The account name or password that you have entered is incorrect.</p>';
echo '<style>body{animation: bw 1s;animation-fill-mode: forwards;}#container{animation: shake 0.2s;animation-iteration-count: 2;}</style>';
}
//echo '<p>' . $username . ';' . password_hash($password, PASSWORD_DEFAULT) . '</p>';
}
?>
</div>
</html>