Compare commits
135 Commits
fdd24d90da
...
switchToCa
Author | SHA1 | Date | |
---|---|---|---|
e262987298 | |||
5f1f5e847d | |||
b6fd7876d3 | |||
1dc31bcb1b | |||
3ff406d053 | |||
6abe519a44 | |||
c6c88456cf | |||
5a805aba07 | |||
1541d05715 | |||
dc78e203ea | |||
17a90358a7 | |||
0266a89ae3 | |||
56a3e584e7 | |||
e2bf38299b | |||
24c8a3d9d7 | |||
dba37e57f9 | |||
c68cf1643e | |||
affe02ec04 | |||
b678720ebd | |||
14f9f247bc | |||
65a87acc48 | |||
e04cf94edd | |||
9ee8ae39df | |||
70aa4d1f06 | |||
2f99531780 | |||
60860a0bce | |||
bb3d0f6e1b | |||
7ee8e8860e | |||
32dcab7592 | |||
88cf11d45d | |||
6c203a0213 | |||
9867df0a04 | |||
0fba7bc0a0 | |||
f46e73c647 | |||
1a8b31b3a2 | |||
a832a3c60b | |||
904fd798f6 | |||
4ed3dcd603 | |||
8bc252ba4d | |||
78f25fdfd3 | |||
e2da6000b3 | |||
32133a0d05 | |||
572cc1eb89 | |||
48585f14ab | |||
47439fe358 | |||
8e0a2ff6e8 | |||
ce798e3b65 | |||
21fd674dd0 | |||
8864875699 | |||
fe0a3c212d | |||
d2f5427df1 | |||
5ed539a471 | |||
5bc0b7966b | |||
d0e1d2e87d | |||
1b794f775d | |||
744117d958 | |||
cce18f6516 | |||
a973a4362f | |||
3bf0c2b44f | |||
ec022d09e4 | |||
148eff1557 | |||
ab4a00d25c | |||
753b832cbc | |||
e5f4656d71 | |||
117903ac7c | |||
4665e1706f | |||
8189abfd29 | |||
72503d7fe2 | |||
3f4653b81f | |||
0ed667d594 | |||
fddc58fb14 | |||
ad63a40a0d | |||
e13bbee1e6 | |||
c6af0d1b1c | |||
fd368d895d | |||
e1d1ef5eeb | |||
cfc4e22497 | |||
66f02c8c6e | |||
10efd6d1a6 | |||
5e8c945170 | |||
54ecb6b15e | |||
7160fd0804 | |||
2b34a89068 | |||
ab7ac074ba | |||
6461046c31 | |||
d2cdcdded4 | |||
7e19efd420 | |||
ffe4d43600 | |||
c05aee49d5 | |||
10d56751e4 | |||
71f8e1623e | |||
2836e0d878 | |||
61aa012dd6 | |||
ed1e795941 | |||
7559651447 | |||
bf5425737b | |||
0a9a253b84 | |||
442cd25aa6 | |||
63d4f339e3 | |||
2cd75e4694 | |||
18a754add7 | |||
9b5f3a2847 | |||
a88e861792 | |||
6284918056 | |||
33618b3be0 | |||
b60d9481ab | |||
582698a8d0 | |||
06c9b9443d | |||
74381857f2 | |||
f652132911 | |||
b4ab876463 | |||
5447a7dbad | |||
a3b2bf27f1 | |||
4a85f45e3a | |||
9a485b5424 | |||
d19e7d5b25 | |||
cbbfe45c1b | |||
9cacf9ced9 | |||
8a7a4a2253 | |||
e9fd8e153d | |||
f571d7548a | |||
a55b14c71c | |||
87959d34b7 | |||
1ad567f337 | |||
2a8fa3f397 | |||
f6a5e96576 | |||
b731e2c0e1 | |||
f15c2f9ff2 | |||
7d15313503 | |||
98e6c7fc64 | |||
0a334498df | |||
02c6154629 | |||
65e2836f23 | |||
983da7fe88 | |||
9b3a6b1f3a |
.gitignoreREADME.md
Vanilla
LICENSEREADME.mdaddressbook.sqlconfig.jsonconfig.json.sample
public
src
templates
public
src
templates
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/config.json
|
@ -1,2 +1,8 @@
|
||||
# addressbook
|
||||
This repo hold a basic programming task I had to do for my new job.
|
||||
|
||||
The task was to write a simple address book with plain PHP and Javascript.
|
||||
No external resources were allowed, so I had to write my own router DBAL and so on.
|
||||
The result can be found in the Vanilla-Folder.
|
||||
|
||||
Now I got the job and will start working on a CakePHP project, so I decided to rewrite the address book in CakePHP.
|
||||
Work will happen in the CakePHP-Folder.
|
||||
|
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
Copyright (c) 2022 Micha Espey <tracer@24unix.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
3
Vanilla/README.md
Normal file
3
Vanilla/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
As I was not allowed to use any framework, respectively no foreign code, most of the time was spent, well creating some kind of framework myself. :-)
|
||||
|
||||
The address book itself was then done in a few hours.
|
88
Vanilla/addressbook.sql
Normal file
88
Vanilla/addressbook.sql
Normal file
@ -0,0 +1,88 @@
|
||||
-- MariaDB dump 10.19 Distrib 10.5.15-MariaDB, for debian-linux-gnu (x86_64)
|
||||
--
|
||||
-- Host: localhost Database: tracer_addressbook
|
||||
-- ------------------------------------------------------
|
||||
-- Server version 10.5.15-MariaDB-0+deb11u1-log
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8 */;
|
||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
|
||||
--
|
||||
-- Table structure for table `addresses`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `addresses`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `addresses` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`owner` int(11) NOT NULL,
|
||||
`first` varchar(80) NOT NULL,
|
||||
`last` varchar(80) NOT NULL,
|
||||
`street` varchar(80) NOT NULL,
|
||||
`zip` varchar(10) NOT NULL,
|
||||
`city` varchar(80) NOT NULL,
|
||||
`phone` varchar(30) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `fk_user` (`owner`),
|
||||
CONSTRAINT `fk_user` FOREIGN KEY (`owner`) REFERENCES `users` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `addresses`
|
||||
--
|
||||
|
||||
LOCK TABLES `addresses` WRITE;
|
||||
/*!40000 ALTER TABLE `addresses` DISABLE KEYS */;
|
||||
INSERT INTO `addresses` VALUES (1,2,'a \"test\"','a','Webfoot Street','1313','Duckburg2','555-12345'),(4,1,'c','b','street4','zip4','city4','phone4'),(6,1,'Huey','Duck','Webfoot Street','1010','Duckburg','555.3456'),(7,1,'Dewey','Duck','Webfoot Street','2020','Duckburg','555-9876'),(8,1,'Louie','Duck','Webfoot Street','3030','Duckburg 3','555-8765'),(11,1,'b','Clapton','sdfg','12456^^^>','https://xd.adobe.com/','23343'),(14,1,'d','aa','','','<script>alert(\'test\')</script>','x'),(16,1,'Adam \"The Badass\"','Black','piouhpouhpouh','132213','piugpiugh','9760978');
|
||||
/*!40000 ALTER TABLE `addresses` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `users`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `users` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`password` varchar(256) NOT NULL,
|
||||
`nick` varchar(20) NOT NULL,
|
||||
`first` varchar(40) NOT NULL,
|
||||
`last` varchar(40) NOT NULL,
|
||||
`is_admin` tinyint(1) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `nick` (`nick`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `users`
|
||||
--
|
||||
|
||||
LOCK TABLES `users` WRITE;
|
||||
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
|
||||
INSERT INTO `users` VALUES (1,'$argon2i$v=19$m=65536,t=4,p=1$OFV0Rnl6OXNWZXZJNTFjTw$fC9K1ykszZ/UaEm21XR9M3+XxnBc+dZ7PRIn5aaGw8I','donald','Donald','Duck',1),(2,'$argon2i$v=19$m=65536,t=4,p=1$NVRKNm0xUmplYkcwTFZXdw$GLp1jjLDBRjKSw6nH8SqqSls6fQPi4Hb7ot0k3naf5s','Daisy','Daisy','Duck',0);
|
||||
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
-- Dump completed on 2022-11-01 18:37:04
|
7
Vanilla/config.json
Normal file
7
Vanilla/config.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"dbHost": "localhost",
|
||||
"dbPort": 3306,
|
||||
"dbDatabase": "tracer_addressbook",
|
||||
"dbUser": "tracer_addressbook",
|
||||
"dbPassword": "SKTh_6#YM?%q"
|
||||
}
|
7
Vanilla/config.json.sample
Normal file
7
Vanilla/config.json.sample
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"dbHost": "localhost",
|
||||
"dbPort": 3306,
|
||||
"dbDatabase": "tracer_addressbook",
|
||||
"dbUser": "tracer_addressbook",
|
||||
"dbPassword": "secret",
|
||||
}
|
13
Vanilla/public/.htaccess
Normal file
13
Vanilla/public/.htaccess
Normal file
@ -0,0 +1,13 @@
|
||||
DirectoryIndex index.php
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_URI}::$0 ^(/.+)/(.*)::\2$
|
||||
RewriteRule .* - [E=BASE:%1]
|
||||
RewriteCond %{HTTP:Authorization} .+
|
||||
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
|
||||
RewriteCond %{ENV:REDIRECT_STATUS} =""
|
||||
RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ %{ENV:BASE}/index.php [L]
|
||||
</IfModule>
|
206
Vanilla/public/assets/js/functions.js
Normal file
206
Vanilla/public/assets/js/functions.js
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
function addAddress(url) {
|
||||
location.href = url
|
||||
}
|
||||
|
||||
function editAddress(id) {
|
||||
if (document.getElementById('edit_button_' + id).value === 'Save') {
|
||||
// save
|
||||
const url = "/address/update";
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
id: id,
|
||||
owner: document.getElementById('owner_' + id).value,
|
||||
first: document.getElementById('first_' + id).value,
|
||||
last: document.getElementById('last_' + id).value,
|
||||
street: document.getElementById('street_' + id).value,
|
||||
zip: document.getElementById('zip_' + id).value,
|
||||
city: document.getElementById('city_' + id).value,
|
||||
phone: document.getElementById('phone_' + id).value,
|
||||
})
|
||||
})
|
||||
.then(
|
||||
response => response.text()
|
||||
).then(
|
||||
json => {
|
||||
let jsonObject = JSON.parse(json)
|
||||
if (jsonObject.status === 200) {
|
||||
setInfo('Data successfully saved.')
|
||||
} else {
|
||||
setError(jsonObject.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
document.getElementById('first_' + id).disabled = true
|
||||
document.getElementById('last_' + id).disabled = true
|
||||
document.getElementById('street_' + id).disabled = true
|
||||
document.getElementById('zip_' + id).disabled = true
|
||||
document.getElementById('city_' + id).disabled = true
|
||||
document.getElementById('phone_' + id).disabled = true
|
||||
|
||||
document.getElementById('edit_button_' + id).value = 'Edit'
|
||||
} else {
|
||||
//switch to edit
|
||||
document.getElementById('first_' + id).disabled = false
|
||||
document.getElementById('last_' + id).disabled = false
|
||||
document.getElementById('street_' + id).disabled = false
|
||||
document.getElementById('zip_' + id).disabled = false
|
||||
document.getElementById('city_' + id).disabled = false
|
||||
document.getElementById('phone_' + id).disabled = false
|
||||
|
||||
document.getElementById('edit_button_' + id).value = 'Save'
|
||||
}
|
||||
}
|
||||
|
||||
function deleteAddress(id) {
|
||||
if (confirm('Are you sure?')) {
|
||||
const url = "/address/delete";
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
id: id
|
||||
})
|
||||
})
|
||||
.then(
|
||||
response => response.text()
|
||||
).then(
|
||||
json => {
|
||||
let jsonObject = JSON.parse(json)
|
||||
if (jsonObject.status === 200) {
|
||||
setInfo('Data successfully saved.')
|
||||
} else {
|
||||
setError(jsonObject.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
let row = document.getElementById('row_' + id)
|
||||
row.parentNode.removeChild(row)
|
||||
}
|
||||
}
|
||||
|
||||
function upCase(text) {
|
||||
return text[0].toUpperCase() + text.substring(1);
|
||||
}
|
||||
|
||||
function sortBy(column) {
|
||||
// clear titles
|
||||
const titles = ['first', 'last', 'street', 'zip', 'city', 'phone']
|
||||
titles.forEach((title) =>
|
||||
document.getElementById(title).innerHTML = upCase(title)
|
||||
)
|
||||
|
||||
console.log("col", column)
|
||||
console.log("curcol", currentColumn)
|
||||
|
||||
if (currentColumn === column) {
|
||||
console.log("in switch")
|
||||
// switch direction on every call on same column
|
||||
if (currentSortOrder === 'asc') {
|
||||
currentSortOrder = 'desc'
|
||||
} else {
|
||||
currentSortOrder = 'asc'
|
||||
}
|
||||
console.log("col", column)
|
||||
} else {
|
||||
currentColumn = column
|
||||
}
|
||||
|
||||
let currentTitleElement = document.getElementById(column)
|
||||
let currentTitle = currentTitleElement.innerHTML
|
||||
let newTitle
|
||||
|
||||
if (currentSortOrder === 'asc') {
|
||||
newTitle = currentTitle[0] + currentTitle.substring(1) + ' ⬇'
|
||||
} else {
|
||||
newTitle = currentTitle[0] + currentTitle.substring(1) + ' ⬆'
|
||||
}
|
||||
currentTitleElement.innerHTML = newTitle
|
||||
|
||||
const table = document.getElementById('address_table');
|
||||
let dirty = true;
|
||||
// loop until clean
|
||||
while (dirty) {
|
||||
// assume we are finished
|
||||
dirty = false
|
||||
const rows = table.rows;
|
||||
for (let i = 1; i < (rows.length - 2); i++) {
|
||||
let x = rows[i]
|
||||
let rowXId = x.id
|
||||
let rowXNumber = rowXId.match(/\d+/)
|
||||
let valueX = document.getElementById(column + '_' + rowXNumber).value
|
||||
|
||||
let y = rows[i + 1]
|
||||
let rowYId = y.id
|
||||
let rowYNumber = rowYId.match(/\d+/)
|
||||
let valueY = document.getElementById(column + '_' + rowYNumber).value
|
||||
|
||||
let sortOrder
|
||||
if (currentSortOrder === 'asc') {
|
||||
sortOrder = 1
|
||||
} else {
|
||||
sortOrder = -1
|
||||
}
|
||||
if (valueX.localeCompare(valueY) === sortOrder) {
|
||||
x.parentNode.insertBefore(y, x);
|
||||
dirty = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function setInfo(info) {
|
||||
const infoBox = document.getElementById('info_box')
|
||||
infoBox.innerHTML = info
|
||||
infoBox.style.display = 'block'
|
||||
infoBox.classList.add('panel_float')
|
||||
setTimeout(() => {
|
||||
infoBox.style.display = 'none'
|
||||
}, 2500)
|
||||
}
|
||||
|
||||
function setError(error) {
|
||||
const errorBox = document.getElementById('error_box')
|
||||
const errorText = document.getElementById('error_text')
|
||||
const infoButton = document.getElementById('info_button')
|
||||
if (errorBox.style.display === 'block') {
|
||||
errorBox.style.display = 'none'
|
||||
return
|
||||
}
|
||||
if (infoButton != null) {
|
||||
infoButton.disabled = true
|
||||
}
|
||||
errorText.innerHTML = error
|
||||
errorBox.style.display = 'block'
|
||||
errorBox.classList.add('panel_float')
|
||||
}
|
||||
|
||||
function closeError() {
|
||||
const errorBox = document.getElementById('error_box')
|
||||
const infoButton = document.getElementById('info_button')
|
||||
if (infoButton) {
|
||||
infoButton.disabled = false
|
||||
}
|
||||
errorBox.style.display = 'none'
|
||||
|
||||
}
|
||||
|
||||
// global scope
|
||||
let currentSortOrder = 'desc'
|
||||
let currentColumn = 'last'
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const table = document.getElementById('address_table') || false
|
||||
if (table) {
|
||||
sortBy('last')
|
||||
}
|
||||
})
|
127
Vanilla/public/assets/styles/main.css
Normal file
127
Vanilla/public/assets/styles/main.css
Normal file
@ -0,0 +1,127 @@
|
||||
body {
|
||||
background: #1f1f1f;
|
||||
color: #cdcdcd;
|
||||
}
|
||||
|
||||
/* unvisited link */
|
||||
a:link {
|
||||
color: #ff8844;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* visited link */
|
||||
a:visited {
|
||||
color: #ff8844;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* mouse over link */
|
||||
a:hover {
|
||||
color: #ff8844;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* selected link */
|
||||
a:active {
|
||||
color: #ff8844;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
padding: 1ex;
|
||||
}
|
||||
|
||||
.panel_float {
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
z-index: 2400;
|
||||
opacity: 0.70;
|
||||
margin: auto;
|
||||
top: 110px !important;
|
||||
-webkit-transition: all 0.5s ease-in-out;
|
||||
-moz-transition: all 0.5s ease-in-out;
|
||||
-ms-transition: all 0.5s ease-in-out;
|
||||
-o-transition: all 0.5s ease-in-out;
|
||||
transition: all 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
#info_box {
|
||||
background-color: #3fc52a;
|
||||
border: solid #cdcdcd;
|
||||
border-radius: 5px;
|
||||
color: #1f1f1f;
|
||||
display: none;
|
||||
padding: 10px;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
width: 50%;
|
||||
margin-left: 200px;
|
||||
margin-right: 200px;
|
||||
}
|
||||
|
||||
.info_button {
|
||||
border-radius: 5px;
|
||||
border: solid #cdcdcd;
|
||||
color: #1f1f1f;
|
||||
padding: 8px 32px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
margin: 4px 2px;
|
||||
background-color: #3fc52a;
|
||||
transition-duration: 0.4s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#error_box {
|
||||
background-color: #e06844;
|
||||
border: solid #cdcdcd;
|
||||
border-radius: 5px;
|
||||
color: #1f1f1f;
|
||||
display: none;
|
||||
padding: 10px;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
width: 50%;
|
||||
margin-left: 200px;
|
||||
margin-right: 200px;
|
||||
}
|
||||
|
||||
.error_button {
|
||||
border-radius: 5px;
|
||||
border: solid #cdcdcd;
|
||||
color: #1f1f1f;
|
||||
padding: 8px 32px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
margin: 4px 2px;
|
||||
background-color: #e06844;
|
||||
transition-duration: 0.4s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close_button {
|
||||
margin-left: 15px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
float: right;
|
||||
font-size: 22px;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.close_button:hover {
|
||||
color: black;
|
||||
}
|
80
Vanilla/public/index.php
Normal file
80
Vanilla/public/index.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
ini_set(option: 'display_errors', value: 1);
|
||||
ini_set(option: 'display_startup_errors', value: 1);
|
||||
// no one sane should ignore deprecations
|
||||
error_reporting(error_level: E_ALL);
|
||||
|
||||
// just during dev
|
||||
opcache_reset();
|
||||
|
||||
|
||||
|
||||
session_start();
|
||||
|
||||
require dirname(path: __DIR__) . '/src/bootstrap.php';
|
||||
|
||||
use App\Controller\AddressBookAdminController;
|
||||
use App\Controller\AddressBookController;
|
||||
use App\Controller\SecurityController;
|
||||
use App\Service\Container;
|
||||
use App\Service\Router;
|
||||
|
||||
$container = new Container();
|
||||
$router = $container->get(className: Router::class);
|
||||
$security = $container->get(className: SecurityController::class);
|
||||
$addressBook = $container->get(className: AddressBookController::class);
|
||||
$addressBookAdmin = $container->get(className: AddressBookAdminController::class);
|
||||
|
||||
$router->addRoute(name: 'app_login', route: '/login', callback: function () use ($security) {
|
||||
$security->login();
|
||||
});
|
||||
|
||||
$router->addRoute(name: 'app_logout', route: '/logout', callback: function () use ($security) {
|
||||
$security->logout();
|
||||
});
|
||||
|
||||
$router->addRoute(name: 'app_main', route: '/', callback: function () use ($addressBook) {
|
||||
$addressBook->main();
|
||||
});
|
||||
|
||||
$router->addRoute(name: 'app_admin', route: '/admin', callback: function () use ($addressBookAdmin) {
|
||||
$addressBookAdmin->admin();
|
||||
});
|
||||
|
||||
$router->addRoute(name: 'app_admin_users', route: '/admin/users', callback: function () use ($addressBookAdmin) {
|
||||
$addressBookAdmin->adminUser();
|
||||
});
|
||||
|
||||
$router->addRoute(name: 'app_admin_users_edit', route: '/admin/users/{nick}', callback: function (array $parameters) use ($addressBookAdmin) {
|
||||
$addressBookAdmin->adminUserEdit(parameters: $parameters);
|
||||
});
|
||||
|
||||
$router->addRoute(name: 'app_admin_users_add', route: '/admin/users/add', callback: function () use ($addressBookAdmin) {
|
||||
$addressBookAdmin->adminUserAdd();
|
||||
});
|
||||
|
||||
$router->addRoute(name: 'app_admin_users_delete', route: '/admin/users/delete/{nick}', callback: function (array $parameters) use ($addressBookAdmin) {
|
||||
$addressBookAdmin->adminUserDelete(parameters: $parameters);
|
||||
});
|
||||
|
||||
$router->addRoute(name: 'address_add', route: '/address/add', callback: function () use ($addressBook) {
|
||||
$addressBook->addAddress();
|
||||
});
|
||||
|
||||
$router->addRoute(name: 'address_add', route: '/address/update', callback: function () use ($addressBook) {
|
||||
$addressBook->updateAddress();
|
||||
});
|
||||
|
||||
$router->addRoute(name: 'address_add', route: '/address/delete', callback: function () use ($addressBook) {
|
||||
$addressBook->deleteAddress();
|
||||
});
|
||||
|
||||
$router->handleRouting();
|
161
Vanilla/src/Controller/AddressBookAdminController.php
Normal file
161
Vanilla/src/Controller/AddressBookAdminController.php
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Service\Router;
|
||||
use App\Service\Template;
|
||||
use App\Repository\UserRepository;
|
||||
|
||||
class AddressBookAdminController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Template $template,
|
||||
private readonly User $user,
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly Router $router
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
private function adminCheck(): void
|
||||
{
|
||||
if (!$this->user->isAdmin()) {
|
||||
$this->template->render(templateName: 'status/403.html.php', vars: [
|
||||
'user' => $this->user,
|
||||
'router' => $this->router
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function admin(): never
|
||||
{
|
||||
$this->adminCheck();
|
||||
$this->template->render(templateName: 'admin/index.html.php', vars: [
|
||||
'user' => $this->user,
|
||||
'router' => $this->router
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function adminUser(): never
|
||||
{
|
||||
$this->adminCheck();
|
||||
|
||||
$users = $this->userRepository->findAll();
|
||||
|
||||
$this->template->render(templateName: 'admin/users.html.php', vars: [
|
||||
'user' => $this->user,
|
||||
'users' => $users,
|
||||
'router' => $this->router
|
||||
]);
|
||||
}
|
||||
|
||||
public function adminUserEdit(array $parameters): never
|
||||
{
|
||||
$this->adminCheck();
|
||||
|
||||
if (!empty($_POST)) {
|
||||
if (!empty($_POST['is_admin'])) {
|
||||
$isAdmin = 1;
|
||||
} else {
|
||||
$isAdmin = 0;
|
||||
}
|
||||
|
||||
if (empty($_POST['new_password'])) {
|
||||
$current = $this->userRepository->findByID(id: $_POST['id']);
|
||||
$password = $current->getPassword();
|
||||
$updateUser = new User(nick: $_POST['nick'], password: $password, first: $_POST['first'], last: $_POST['last'], id: $_POST['id'], isAdmin: $isAdmin);
|
||||
} else {
|
||||
$password = $_POST['new_password'];
|
||||
$updateUser = new User(nick: $_POST['nick'], newPassword: $password, first: $_POST['first'], last: $_POST['last'], id: $_POST['id'], isAdmin: $isAdmin);
|
||||
}
|
||||
|
||||
$this->userRepository->update(user: $updateUser);
|
||||
|
||||
$users = $this->userRepository->findAll();
|
||||
|
||||
$this->template->render(templateName: 'admin/users.html.php', vars: [
|
||||
'user' => $this->user,
|
||||
'users' => $users,
|
||||
'router' => $this->router
|
||||
]);
|
||||
}
|
||||
|
||||
$editUser = $this->userRepository->findByNick(nick: $parameters['nick']);
|
||||
|
||||
|
||||
$this->template->render(templateName: 'admin/users_edit.html.php', vars: [
|
||||
'user' => $this->user,
|
||||
'editUser' => $editUser,
|
||||
'router' => $this->router
|
||||
]);
|
||||
}
|
||||
|
||||
public function adminUserAdd(): never
|
||||
{
|
||||
$this->adminCheck();
|
||||
|
||||
$nick = $_POST['nick'];
|
||||
|
||||
if ($this->userRepository->findByNick(nick: $nick)) {
|
||||
die("User: $nick already exists");
|
||||
}
|
||||
if (!empty($_POST)) {
|
||||
$isAdmin = empty($_POST['is_admin']) ? 0 : 1;
|
||||
$user = new User(nick: $_POST['nick'], newPassword: $_POST['new_password'], first: $_POST['first'], last: $_POST['last'], isAdmin: $isAdmin);
|
||||
|
||||
if ($this->userRepository->insert(user: $user)) {
|
||||
$users = $this->userRepository->findAll();
|
||||
|
||||
$this->template->render(templateName: 'admin/users.html.php', vars: [
|
||||
'user' => $this->user,
|
||||
'users' => $users,
|
||||
'router' => $this->router
|
||||
]);
|
||||
} else {
|
||||
die("Error inserting user");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->template->render(templateName: 'admin/users_add.html.php', vars: [
|
||||
'user' => $this->user,
|
||||
'router' => $this->router
|
||||
]);
|
||||
}
|
||||
|
||||
public function adminUserDelete(array $parameters): never
|
||||
{
|
||||
$this->adminCheck();
|
||||
|
||||
$nick = $parameters['nick'];
|
||||
if ($user = $this->userRepository->findByNick(nick: $nick)) {
|
||||
if ($this->userRepository->delete(user: $user)) {
|
||||
$users = $this->userRepository->findAll();
|
||||
|
||||
$this->template->render(templateName: 'admin/users.html.php', vars: [
|
||||
'user' => $this->user,
|
||||
'users' => $users,
|
||||
'router' => $this->router
|
||||
]);
|
||||
} else {
|
||||
die("Error deleting user");
|
||||
}
|
||||
} else {
|
||||
$this->template->render(templateName: 'status/404.html.php', vars: [
|
||||
'user' => $this->user,
|
||||
'router' => $this->router
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
133
Vanilla/src/Controller/AddressBookController.php
Normal file
133
Vanilla/src/Controller/AddressBookController.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Entity\AddressBookEntry;
|
||||
use App\Enums\StatusCode;
|
||||
use App\Enums\UserAuth;
|
||||
use App\Service\Router;
|
||||
use App\Service\Template;
|
||||
use App\Repository\AddressRepository;
|
||||
|
||||
class AddressBookController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Template $template,
|
||||
private readonly User $user,
|
||||
private readonly AddressRepository $addressRepository,
|
||||
private readonly Router $router
|
||||
)
|
||||
{
|
||||
// empty body
|
||||
}
|
||||
|
||||
public function main(): never
|
||||
{
|
||||
if ($this->user->getAuth() != UserAuth::AUTH_ANONYMOUS) {
|
||||
$addresses = $this->addressRepository->findAll();
|
||||
}
|
||||
|
||||
$this->template->render(templateName: 'index.html.php', vars: [
|
||||
'user' => $this->user,
|
||||
'router' => $this->router,
|
||||
'addresses' => $addresses ?? []
|
||||
]);
|
||||
}
|
||||
|
||||
public function addAddress(): never
|
||||
{
|
||||
if (!empty($_POST)) {
|
||||
$address = new AddressBookEntry(owner: $_POST['owner'], first: $_POST['first'], last: $_POST['last'], street: $_POST['street'], zip: $_POST['zip'], city: $_POST['city'], phone: $_POST['phone']);
|
||||
|
||||
if ($this->addressRepository->insert(address: $address)) {
|
||||
$addresses = $this->addressRepository->findAll();
|
||||
|
||||
$this->template->render(templateName: 'index.html.php', vars: [
|
||||
'user' => $this->user,
|
||||
'addresses' => $addresses,
|
||||
'router' => $this->router
|
||||
]);
|
||||
} else {
|
||||
die("Error inserting user");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->template->render(templateName: 'addressbook/add_address.html.php', vars: [
|
||||
'user' => $this->user,
|
||||
'router' => $this->router
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function updateAddress(): void
|
||||
{
|
||||
$_POST = json_decode(json: file_get_contents(filename: "php://input"), associative: true);
|
||||
|
||||
if (empty($_POST)) {
|
||||
$this->template->renderJson(results: [
|
||||
'status' => 400,
|
||||
'message' => 'BAD REQUEST'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($address = new AddressBookEntry(owner: $_POST['owner'], first: $_POST['first'], last: $_POST['last'], street: $_POST['street'], zip: $_POST['zip'], city: $_POST['city'], phone: $_POST['phone'], id: $_POST['id'])) {
|
||||
if ($this->addressRepository->update(address: $address)) {
|
||||
$status = 200;
|
||||
$message = 'OK';
|
||||
} else {
|
||||
$status = 400;
|
||||
$message = 'BAD_REQUEST';
|
||||
}
|
||||
} else {
|
||||
$status = 400;
|
||||
$message = "BAD REQUEST";
|
||||
}
|
||||
|
||||
$this->template->renderJson(results: [
|
||||
'status' => $status,
|
||||
'message' => $message
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteAddress(): void
|
||||
{
|
||||
$_POST = json_decode(json: file_get_contents(filename: "php://input"), associative: true);
|
||||
|
||||
if (empty($_POST)) {
|
||||
$this->template->renderJson(results: [
|
||||
'status' => 400,
|
||||
'message' => 'BAD REQUEST'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($address = $this->addressRepository->findByID(id: $_POST['id'])) {
|
||||
if ($this->addressRepository->delete(addressBookEntry: $address)) {
|
||||
$this->template->renderJson(results: [
|
||||
'status' => 200,
|
||||
'message' => 'OK'
|
||||
]);
|
||||
} else {
|
||||
$this->template->renderJson(results: [
|
||||
'status' => 400,
|
||||
'message' => 'BAD REQUEST'
|
||||
]);
|
||||
|
||||
}
|
||||
} else {
|
||||
$this->template->renderJson(results: [
|
||||
'status' => 400,
|
||||
'message' => 'BAD REQUEST'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
65
Vanilla/src/Controller/SecurityController.php
Normal file
65
Vanilla/src/Controller/SecurityController.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Service\Router;
|
||||
use App\Service\Template;
|
||||
|
||||
class SecurityController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Template $template,
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly Router $router
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function login(): never
|
||||
{
|
||||
if (!empty($_POST)) {
|
||||
$nick = $_POST['nick'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if ($nick && $password) {
|
||||
$nick = strtolower(string: $nick);
|
||||
if ($user = $this->userRepository->findbyNick(nick: $nick)) {
|
||||
if (password_verify(password: $password, hash: $user->getPassword())) {
|
||||
$_SESSION['user_id'] = $user->getId();
|
||||
header(header: 'Location: /');
|
||||
exit(0);
|
||||
} else {
|
||||
$message = "Wrong credentials.";
|
||||
}
|
||||
} else {
|
||||
$message = "User not found.";
|
||||
}
|
||||
} else {
|
||||
$message = 'You need to enter your credentials.';
|
||||
}
|
||||
}
|
||||
|
||||
$this->template->render(templateName: 'security/login.html.php', vars: [
|
||||
'user' => $user ?? new User(),
|
||||
'message' => $message ?? '',
|
||||
'router' => $this->router
|
||||
]);
|
||||
}
|
||||
|
||||
function logout(): void
|
||||
{
|
||||
session_unset();
|
||||
header(header: 'Location: /');
|
||||
}
|
||||
|
||||
}
|
107
Vanilla/src/Entity/AddressBookEntry.php
Normal file
107
Vanilla/src/Entity/AddressBookEntry.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
class AddressBookEntry
|
||||
{
|
||||
public function __construct(
|
||||
private int $owner,
|
||||
private string $first,
|
||||
private string $last,
|
||||
private string $street,
|
||||
private string $zip,
|
||||
private string $city,
|
||||
private string $phone,
|
||||
private int $id = 0,
|
||||
)
|
||||
{
|
||||
// empty body
|
||||
}
|
||||
|
||||
public function getOwner(): int
|
||||
{
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
public function setOwner(int $owner): void
|
||||
{
|
||||
$this->owner = $owner;
|
||||
}
|
||||
|
||||
public function getStreet(): string
|
||||
{
|
||||
return $this->street;
|
||||
}
|
||||
|
||||
public function setStreet(string $street): void
|
||||
{
|
||||
$this->street = $street;
|
||||
}
|
||||
|
||||
public function getZip(): string
|
||||
{
|
||||
return $this->zip;
|
||||
}
|
||||
|
||||
public function setZip(string $zip): void
|
||||
{
|
||||
$this->zip = $zip;
|
||||
}
|
||||
|
||||
public function getCity(): string
|
||||
{
|
||||
return $this->city;
|
||||
}
|
||||
|
||||
public function setCity(string $city): void
|
||||
{
|
||||
$this->city = $city;
|
||||
}
|
||||
|
||||
public function getPhone(): string
|
||||
{
|
||||
return $this->phone;
|
||||
}
|
||||
|
||||
public function setPhone(string $phone): void
|
||||
{
|
||||
$this->phone = $phone;
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId(int $id): void
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getFirst(): string
|
||||
{
|
||||
return $this->first;
|
||||
}
|
||||
|
||||
public function setFirst(string $first): void
|
||||
{
|
||||
$this->first = $first;
|
||||
}
|
||||
|
||||
public function getLast(): string
|
||||
{
|
||||
return $this->last;
|
||||
}
|
||||
|
||||
public function setLast(string $last): void
|
||||
{
|
||||
$this->last = $last;
|
||||
}
|
||||
}
|
77
Vanilla/src/Entity/Route.php
Normal file
77
Vanilla/src/Entity/Route.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Closure;
|
||||
|
||||
class Route
|
||||
{
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private string $route,
|
||||
private string $regEx,
|
||||
private array $parameters,
|
||||
private Closure $callback
|
||||
)
|
||||
{
|
||||
// empty body
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): void
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getRoute(): string
|
||||
{
|
||||
return $this->route;
|
||||
}
|
||||
|
||||
public function setRoute(string $route): void
|
||||
{
|
||||
$this->route = $route;
|
||||
}
|
||||
|
||||
public function getRegEx(): string
|
||||
{
|
||||
return $this->regEx;
|
||||
}
|
||||
|
||||
public function setRegEx(string $regEx): void
|
||||
{
|
||||
$this->regEx = $regEx;
|
||||
}
|
||||
|
||||
public function getParameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
public function setParameters(array $parameters): void
|
||||
{
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
public function getCallback(): Closure
|
||||
{
|
||||
return $this->callback;
|
||||
}
|
||||
|
||||
public function setCallback(Closure $callback): void
|
||||
{
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
}
|
114
Vanilla/src/Entity/User.php
Normal file
114
Vanilla/src/Entity/User.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Enums\UserAuth;
|
||||
|
||||
class User
|
||||
{
|
||||
public function __construct(
|
||||
private string $nick = '',
|
||||
private string $password = '',
|
||||
private readonly string $newPassword = '',
|
||||
private string $first = '',
|
||||
private string $last = '',
|
||||
private int $id = 0,
|
||||
private bool $isAdmin = false,
|
||||
private UserAuth $userAuth = UserAuth::AUTH_ANONYMOUS
|
||||
)
|
||||
{
|
||||
if (!empty($this->newPassword)) {
|
||||
echo "password";
|
||||
$this->password = password_hash(password: $this->newPassword, algo: PASSWORD_ARGON2I);
|
||||
}
|
||||
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
// ANONYMOUS has id 0
|
||||
if ($this->id != 0) {
|
||||
if ($this->isAdmin) {
|
||||
$this->userAuth = UserAuth::AUTH_ADMIN;
|
||||
} else {
|
||||
$this->userAuth = UserAuth::AUTH_USER;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getNick(): string
|
||||
{
|
||||
return $this->nick;
|
||||
}
|
||||
|
||||
public function setNick(string $nick): void
|
||||
{
|
||||
$this->nick = $nick;
|
||||
}
|
||||
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function setPassword(string $password): void
|
||||
{
|
||||
$this->password = $password;
|
||||
}
|
||||
|
||||
public function getFirst(): string
|
||||
{
|
||||
return $this->first;
|
||||
}
|
||||
|
||||
public function setFirst(string $first): void
|
||||
{
|
||||
$this->first = $first;
|
||||
}
|
||||
|
||||
public function getLast(): string
|
||||
{
|
||||
return $this->last;
|
||||
}
|
||||
|
||||
public function setLast(string $last): void
|
||||
{
|
||||
$this->last = $last;
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId(int $id): void
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function isAdmin(): bool
|
||||
{
|
||||
return $this->isAdmin;
|
||||
}
|
||||
|
||||
public function setIsAdmin(bool $isAdmin): void
|
||||
{
|
||||
$this->isAdmin = $isAdmin;
|
||||
}
|
||||
|
||||
public function getAuth(): UserAuth
|
||||
{
|
||||
return $this->userAuth;
|
||||
}
|
||||
|
||||
public function setAuth(UserAuth $userAuth): void
|
||||
{
|
||||
$this->userAuth = $userAuth;
|
||||
}
|
||||
|
||||
}
|
17
Vanilla/src/Enums/UserAuth.php
Normal file
17
Vanilla/src/Enums/UserAuth.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum UserAuth
|
||||
{
|
||||
case AUTH_ANONYMOUS;
|
||||
case AUTH_USER;
|
||||
case AUTH_ADMIN;
|
||||
}
|
174
Vanilla/src/Repository/AddressRepository.php
Normal file
174
Vanilla/src/Repository/AddressRepository.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\AddressBookEntry;
|
||||
use App\Service\DatabaseConnection;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* Handles CRUD of Addresses class.
|
||||
*/
|
||||
class AddressRepository
|
||||
{
|
||||
public function __construct(private readonly DatabaseConnection $databaseConnection)
|
||||
{
|
||||
// empty body
|
||||
}
|
||||
|
||||
public function findAll(string $orderBy = 'last'): array
|
||||
{
|
||||
$sql = "
|
||||
SELECT id, owner, first, last, street, zip, city, phone
|
||||
FROM " . DatabaseConnection::TABLE_ADDRESSES . "
|
||||
ORDER BY :order";
|
||||
|
||||
try {
|
||||
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||
$statement->bindParam(param: ':order', var: $orderBy);
|
||||
|
||||
$statement->execute();
|
||||
$addresses = [];
|
||||
while ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
|
||||
$address = new AddressBookEntry(
|
||||
owner: htmlspecialchars(string: $result['owner']),
|
||||
first: htmlspecialchars(string: $result['first']),
|
||||
last: htmlspecialchars(string: $result['last']),
|
||||
street: htmlspecialchars(string: $result['street']),
|
||||
zip: htmlspecialchars(string: $result['zip']),
|
||||
city: htmlspecialchars(string: $result['city']),
|
||||
phone: htmlspecialchars(string: $result['phone']),
|
||||
id: htmlspecialchars(string: $result['id']));
|
||||
$addresses[] = $address;
|
||||
}
|
||||
return $addresses;
|
||||
} catch (PDOException $e) {
|
||||
exit($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function findByID(int $id): ?AddressBookEntry
|
||||
{
|
||||
$sql = "
|
||||
SELECT id, owner, first, last, street, zip, city, phone
|
||||
FROM " . DatabaseConnection::TABLE_ADDRESSES . "
|
||||
WHERE id = :id";
|
||||
|
||||
try {
|
||||
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||
$statement->bindParam(param: ':id', var: $id);
|
||||
$statement->execute();
|
||||
if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
|
||||
return new AddressBookEntry(
|
||||
owner: htmlspecialchars(string: $result['owner']),
|
||||
first: htmlspecialchars(string: $result['first']),
|
||||
last: htmlspecialchars(string: $result['last']),
|
||||
street: htmlspecialchars(string: $result['street']),
|
||||
zip: htmlspecialchars(string: $result['zip']),
|
||||
city: htmlspecialchars(string: $result['city']),
|
||||
phone: htmlspecialchars(string: $result['phone']),
|
||||
id: htmlspecialchars(string: $result['id']));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
exit($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function insert(AddressBookEntry $address): bool|string
|
||||
{
|
||||
$sql = "
|
||||
INSERT INTO " . DatabaseConnection::TABLE_ADDRESSES . " (owner, first, last, city, zip, street, phone)
|
||||
VALUES (:owner, :first, :last, :city, :zip, :street, :phone)";
|
||||
|
||||
try {
|
||||
$owner = $address->getOwner();
|
||||
$first = $address->getFirst();
|
||||
$last = $address->getLast();
|
||||
$city = $address->getCity();
|
||||
$zip = $address->getZip();
|
||||
$street = $address->getStreet();
|
||||
$phone = $address->getPhone();
|
||||
|
||||
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||
$statement->bindParam(param: ':owner', var: $owner);
|
||||
$statement->bindParam(param: ':first', var: $first);
|
||||
$statement->bindParam(param: ':last', var: $last);
|
||||
$statement->bindParam(param: ':city', var: $city);
|
||||
$statement->bindParam(param: ':zip', var: $zip);
|
||||
$statement->bindParam(param: ':street', var: $street);
|
||||
$statement->bindParam(param: ':phone', var: $phone);
|
||||
$statement->execute();
|
||||
|
||||
return $this->databaseConnection->getConnection()->lastInsertId();
|
||||
} catch (PDOException $e) {
|
||||
exit($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function update(AddressBookEntry $address): bool|int
|
||||
{
|
||||
$id = $address->getId();
|
||||
$first = $address->getFirst();
|
||||
$last = $address->getLast();
|
||||
$street = $address->getStreet();
|
||||
$zip = $address->getZip();
|
||||
$city = $address->getCity();
|
||||
$phone = $address->getPhone();
|
||||
|
||||
$sql = "
|
||||
UPDATE " . DatabaseConnection::TABLE_ADDRESSES . " SET
|
||||
first = :first,
|
||||
last = :last,
|
||||
street = :street,
|
||||
zip = :zip,
|
||||
city = :city,
|
||||
phone = :phone
|
||||
WHERE id = :id";
|
||||
|
||||
try {
|
||||
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||
$statement->bindParam(param: 'id', var: $id);
|
||||
$statement->bindParam(param: 'first', var: $first);
|
||||
$statement->bindParam(param: 'last', var: $last);
|
||||
$statement->bindParam(param: 'street', var: $street);
|
||||
$statement->bindParam(param: 'zip', var: $zip);
|
||||
$statement->bindParam(param: 'city', var: $city);
|
||||
$statement->bindParam(param: 'phone', var: $phone);
|
||||
return $statement->execute();
|
||||
} catch (PDOException $e) {
|
||||
echo $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function delete(AddressBookEntry $addressBookEntry): int
|
||||
{
|
||||
$sql = "
|
||||
DELETE FROM " . DatabaseConnection::TABLE_ADDRESSES . "
|
||||
WHERE id = :id";
|
||||
|
||||
try {
|
||||
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||
$id = $addressBookEntry->getId();
|
||||
$statement->bindParam(param: 'id', var: $id);
|
||||
return $statement->execute();
|
||||
} catch (PDOException $e) {
|
||||
exit($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
196
Vanilla/src/Repository/UserRepository.php
Normal file
196
Vanilla/src/Repository/UserRepository.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Service\DatabaseConnection;
|
||||
use App\Entity\User;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* Handles CRUD of User class.
|
||||
*/
|
||||
class UserRepository
|
||||
{
|
||||
public function __construct(private readonly DatabaseConnection $databaseConnection)
|
||||
{
|
||||
// empty body
|
||||
}
|
||||
|
||||
public function findAll(string $orderBy = 'nick'): array
|
||||
{
|
||||
$users = [];
|
||||
$sql = "
|
||||
SELECT id, nick, password, first, last, is_admin
|
||||
FROM " . DatabaseConnection::TABLE_USERS . "
|
||||
ORDER BY :order";
|
||||
|
||||
try {
|
||||
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||
$statement->bindParam(param: ':order', var: $orderBy);
|
||||
|
||||
$statement->execute();
|
||||
while ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
|
||||
$user = new User(
|
||||
nick: htmlspecialchars(string: $result['nick']),
|
||||
password: $result['password'],
|
||||
first: htmlspecialchars(string: $result['first']),
|
||||
last: htmlspecialchars(string: $result['last']),
|
||||
id: $result['id'],
|
||||
isAdmin: $result['is_admin']);
|
||||
$users[] = $user;
|
||||
}
|
||||
return $users;
|
||||
} catch (PDOException $e) {
|
||||
exit($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function findByID(int $id): ?User
|
||||
{
|
||||
$sql = "
|
||||
SELECT id, nick, password, first, last, is_admin
|
||||
FROM " . DatabaseConnection::TABLE_USERS . "
|
||||
WHERE id = :id";
|
||||
|
||||
try {
|
||||
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||
$statement->bindParam(param: ':id', var: $id);
|
||||
$statement->execute();
|
||||
if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
|
||||
return new User(
|
||||
nick: htmlspecialchars(string: $result['nick']),
|
||||
password: $result['password'],
|
||||
first: htmlspecialchars(string: $result['first']),
|
||||
last: htmlspecialchars(string: $result['last']),
|
||||
id: $result['id'],
|
||||
isAdmin: $result['is_admin']);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
exit($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function findByNick(string $nick): ?User
|
||||
{
|
||||
$nick = strtolower(string: $nick);
|
||||
|
||||
$sql = "
|
||||
SELECT id, nick, password, first, last, is_admin
|
||||
FROM " . DatabaseConnection::TABLE_USERS . "
|
||||
WHERE nick = :nick";
|
||||
|
||||
try {
|
||||
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||
$statement->bindParam(param: ':nick', var: $nick);
|
||||
$statement->execute();
|
||||
if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
|
||||
return new User(
|
||||
nick: htmlspecialchars(string: $result['nick']),
|
||||
password: $result['password'],
|
||||
first: htmlspecialchars(string: $result['first']),
|
||||
last: htmlspecialchars(string: $result['last']),
|
||||
id: $result['id'],
|
||||
isAdmin: $result['is_admin']);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
exit($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function insert(User $user): bool|string
|
||||
{
|
||||
$sql = "
|
||||
INSERT INTO " . DatabaseConnection::TABLE_USERS . " (nick, password, first, last, is_admin)
|
||||
VALUES (:nick, :password, :first, :last, :is_admin)";
|
||||
|
||||
try {
|
||||
$nick = $user->getNick();
|
||||
$password = $user->getPassword();
|
||||
$first = $user->getFirst();
|
||||
$last = $user->getLast();
|
||||
$isAdmin = $user->isAdmin() ? 1 : 0;
|
||||
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||
$statement->bindParam(param: ':nick', var: $nick);
|
||||
$statement->bindParam(param: ':password', var: $password);
|
||||
$statement->bindParam(param: ':first', var: $first);
|
||||
$statement->bindParam(param: ':last', var: $last);
|
||||
$statement->bindParam(param: ':is_admin', var: $isAdmin);
|
||||
$statement->execute();
|
||||
|
||||
return $this->databaseConnection->getConnection()->lastInsertId();
|
||||
} catch (PDOException $e) {
|
||||
exit($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function update(User $user): bool
|
||||
{
|
||||
$id = $user->getId();
|
||||
$nick = $user->getNick();
|
||||
$first = $user->getFirst();
|
||||
$last = $user->getLast();
|
||||
$isAdmin = $user->isAdmin() ? 1 : 0;
|
||||
|
||||
if ($user->getPassword()) {
|
||||
$password = $user->getPassword();
|
||||
} else {
|
||||
$current = $this->findByID(id: $id);
|
||||
$password = $current->getPassword();
|
||||
}
|
||||
|
||||
$sql = "
|
||||
UPDATE " . DatabaseConnection::TABLE_USERS . " SET
|
||||
nick = :nick,
|
||||
password = :password,
|
||||
first = :first,
|
||||
last = :last,
|
||||
is_admin = :is_admin
|
||||
WHERE id = :id";
|
||||
|
||||
try {
|
||||
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||
$statement->bindParam(param: 'id', var: $id);
|
||||
$statement->bindParam(param: 'nick', var: $nick);
|
||||
$statement->bindParam(param: 'password', var: $password);
|
||||
$statement->bindParam(param: 'first', var: $first);
|
||||
$statement->bindParam(param: 'last', var: $last);
|
||||
$statement->bindParam(param: 'is_admin', var: $isAdmin);
|
||||
return $statement->execute();
|
||||
} catch (PDOException $e) {
|
||||
echo $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function delete(User $user): bool
|
||||
{
|
||||
$sql = "
|
||||
DELETE FROM " . DatabaseConnection::TABLE_USERS . "
|
||||
WHERE id = :id";
|
||||
|
||||
try {
|
||||
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||
$id = $user->getId();
|
||||
$statement->bindParam(param: 'id', var: $id);
|
||||
return $statement->execute();
|
||||
} catch (PDOException $e) {
|
||||
exit($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
43
Vanilla/src/Service/Config.php
Normal file
43
Vanilla/src/Service/Config.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Config
|
||||
{
|
||||
private array $config;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Check for either config.json.local or config.json.
|
||||
$configFile = dirname(path: __DIR__, levels: 2) . "/config.json.local";
|
||||
if (!file_exists(filename: $configFile)) {
|
||||
$configFile = dirname(path: __DIR__, levels: 2) . "/config.json";
|
||||
}
|
||||
|
||||
if (!file_exists(filename: $configFile)) {
|
||||
die('Missing config file');
|
||||
}
|
||||
$configJSON = file_get_contents(filename: $configFile);
|
||||
|
||||
if (json_decode(json: $configJSON) === null) {
|
||||
die('Config file is not valid JSON.');
|
||||
}
|
||||
|
||||
$this->config = json_decode(json: $configJSON, associative: true);
|
||||
}
|
||||
|
||||
public function getConfig(string $configKey): string
|
||||
{
|
||||
return $this->config[$configKey];
|
||||
}
|
||||
}
|
68
Vanilla/src/Service/Container.php
Normal file
68
Vanilla/src/Service/Container.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Controller\AddressBookAdminController;
|
||||
use App\Controller\AddressBookController;
|
||||
use App\Controller\SecurityController;
|
||||
use App\Entity\User;
|
||||
use App\Repository\AddressRepository;
|
||||
use App\Repository\UserRepository;
|
||||
|
||||
/*
|
||||
* A quick and dirty class container for DI.
|
||||
* Caveat: Classes are always instantiated
|
||||
* No autowiring (yet, maybe later, but it might fit for a demo)
|
||||
*/
|
||||
|
||||
class Container
|
||||
{
|
||||
|
||||
private AddressBookController $addressBook;
|
||||
private AddressBookAdminController $addressBookAdmin;
|
||||
private AddressRepository $addressRepository;
|
||||
private Config $config;
|
||||
private DatabaseConnection $databaseConnection;
|
||||
private Router $router;
|
||||
private SecurityController $securityController;
|
||||
private Template $template;
|
||||
private User $user;
|
||||
private UserRepository $userRepository;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = new Config();
|
||||
$this->databaseConnection = new DatabaseConnection(config: $this->config);
|
||||
$this->template = new Template(templateDir: dirname(path: __DIR__, levels: 2) . '/templates/');
|
||||
$this->router = new Router(template: $this->template);
|
||||
$this->userRepository = new UserRepository(databaseConnection: $this->databaseConnection);
|
||||
$this->addressRepository = new AddressRepository(databaseConnection: $this->databaseConnection);
|
||||
$this->securityController = new SecurityController(template: $this->template, userRepository: $this->userRepository, router: $this->router);
|
||||
if (empty($_SESSION['user_id'])) {
|
||||
$this->user = new User(); // ANONYMOUS
|
||||
} else {
|
||||
$this->user = $this->userRepository->findByID(id: $_SESSION['user_id']);
|
||||
}
|
||||
$this->addressBook = new AddressBookController(template: $this->template, user: $this->user, addressRepository: $this->addressRepository, router: $this->router);
|
||||
$this->addressBookAdmin = new AddressBookAdminController(template: $this->template, user: $this->user, userRepository: $this->userRepository, router: $this->router);
|
||||
}
|
||||
|
||||
public function get(string $className): object
|
||||
{
|
||||
return match ($className) {
|
||||
'App\Controller\AddressBookController' => $this->addressBook,
|
||||
'App\Controller\AddressBookAdminController' => $this->addressBookAdmin,
|
||||
'App\Controller\SecurityController' => $this->securityController,
|
||||
'App\Service\Router' => $this->router,
|
||||
//default => throw new Exception(message: "Missing class definition: $class")
|
||||
default => die("Missing class definition: $className")
|
||||
};
|
||||
}
|
||||
}
|
46
Vanilla/src/Service/DatabaseConnection.php
Normal file
46
Vanilla/src/Service/DatabaseConnection.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* Take care of the PDO object.
|
||||
*/
|
||||
class DatabaseConnection
|
||||
{
|
||||
private PDO $dbConnection;
|
||||
|
||||
// Currently no prefixes are used, but could be easily added to config.json.
|
||||
const TABLE_PREFIX = '';
|
||||
const TABLE_USERS = self::TABLE_PREFIX . "users";
|
||||
const TABLE_ADDRESSES = self::TABLE_PREFIX . "addresses";
|
||||
|
||||
public function __construct(private readonly Config $config)
|
||||
{
|
||||
$dbHost = $this->config->getConfig(configKey: 'dbHost');
|
||||
$dbPort = $this->config->getConfig(configKey: 'dbPort');
|
||||
$dbDatabase = $this->config->getConfig(configKey: 'dbDatabase');
|
||||
$dbUser = $this->config->getConfig(configKey: 'dbUser');
|
||||
$dbPassword = $this->config->getConfig(configKey: 'dbPassword');
|
||||
|
||||
$this->dbConnection = new PDO(
|
||||
dsn: "mysql:host=$dbHost;port=$dbPort;charset=utf8mb4;dbname=$dbDatabase",
|
||||
username: $dbUser,
|
||||
password: $dbPassword
|
||||
);
|
||||
$this->dbConnection->setAttribute(attribute: PDO::ATTR_ERRMODE, value: PDO::ERRMODE_EXCEPTION);
|
||||
}
|
||||
|
||||
public function getConnection(): PDO
|
||||
{
|
||||
return $this->dbConnection;
|
||||
}
|
||||
}
|
124
Vanilla/src/Service/Router.php
Normal file
124
Vanilla/src/Service/Router.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022. Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
|
||||
use App\Entity\Route;
|
||||
use Closure;
|
||||
|
||||
/*
|
||||
* A small router implementation for the address book demo.
|
||||
* Currently it doesn't handle GET requests, as not needed.
|
||||
* But if I reuse the code in my bindApi I'll maybe support GET as well.
|
||||
*/
|
||||
|
||||
class Router
|
||||
{
|
||||
/*
|
||||
* The easiest way to differentiate between static and dynamic routes is using
|
||||
* two arrays, no need to pollute the class Route with that information
|
||||
*/
|
||||
private array $staticRoutes = [];
|
||||
private array $dynamicRoutes = [];
|
||||
|
||||
public function __construct(private readonly Template $template)
|
||||
{
|
||||
// empty body
|
||||
}
|
||||
|
||||
/*
|
||||
* This method takes a route like /admin/users/{user} and creates a regex to match on call
|
||||
* More complex routes like /posts/{thread}/show/{page} are supported as well.
|
||||
*/
|
||||
function addRoute(string $name, string $route, Closure $callback): void
|
||||
{
|
||||
// check for parameters
|
||||
preg_match_all(pattern: "/(?<={).+?(?=})/", subject: $route, matches: $matches);
|
||||
$parameters = $matches[0];
|
||||
|
||||
// create regex for route:
|
||||
$regex = preg_replace(pattern: '/{.+?}/', replacement: '([a-zA-Z0-9]*)', subject: $route);
|
||||
// escape \ in regex
|
||||
$regex = '/^' . str_replace(search: "/", replace: '\\/', subject: $regex) . '$/i';
|
||||
|
||||
$route = new Route(name: $name, route: $route, regEx: $regex, parameters: $parameters, callback: $callback);
|
||||
|
||||
if ($parameters) {
|
||||
$this->dynamicRoutes[] = $route;
|
||||
} else {
|
||||
$this->staticRoutes[] = $route;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if there is a known route and executes the callback.
|
||||
*/
|
||||
public function handleRouting(): void
|
||||
{
|
||||
$requestUri = $_SERVER['REQUEST_URI'];
|
||||
|
||||
/*
|
||||
* Static routes have precedence over dynamic ones, so
|
||||
* /admin/user/add to add and
|
||||
* /admin/user/{name} to edit is possible.
|
||||
* A user named "add" of course not :)
|
||||
*
|
||||
* But who wants to call their users "add" or "delete"?
|
||||
* That's as weird as Little Bobby Tables … (https://xkcd.com/327/)
|
||||
*/
|
||||
foreach ($this->staticRoutes as $route) {
|
||||
if (preg_match(pattern: $route->getRegex(), subject: $requestUri, matches: $matches)) {
|
||||
call_user_func(callback: $route->getCallback());
|
||||
|
||||
// We've found our route, bail out.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->dynamicRoutes as $route) {
|
||||
// PHPStorm doesn't know that $parameters are always available,
|
||||
// (as these are dynamic routes) so I init the array just to make PHPStorm happy.
|
||||
$parameters = [];
|
||||
if (preg_match(pattern: $route->getRegex(), subject: $requestUri, matches: $matches)) {
|
||||
foreach ($route->getParameters() as $id => $parameter) {
|
||||
$parameters[$parameter] = $matches[$id + 1];
|
||||
}
|
||||
// PHP is mad about named parameters in call_user_func when adding parameters.
|
||||
// Uncaught Error: Unknown named parameter $args in <sourceFile>
|
||||
// But PHPStorm seems happy without them. So what?
|
||||
call_user_func($route->getCallback(), $parameters);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// if no route is matched, throw a 404
|
||||
$this->template->render(templateName: 'status/404.html.php');
|
||||
}
|
||||
|
||||
public function path(string $routeName, array $vars = [])
|
||||
{
|
||||
foreach (array_merge($this->dynamicRoutes, $this->staticRoutes) as $route) {
|
||||
if ($route->getName() == $routeName) {
|
||||
if ($vars) {
|
||||
// build route for dynamic routes
|
||||
$route = $route->getRoute();
|
||||
// replace placeholder with current values
|
||||
foreach ($vars as $key => $value) {
|
||||
$route = str_replace(search: '{' . $key . '}', replace: $value, subject: $route);
|
||||
}
|
||||
return $route;
|
||||
} else {
|
||||
return $route->getRoute();
|
||||
}
|
||||
}
|
||||
}
|
||||
// no 404, this is reached only if the code is buggy
|
||||
die("Missing Route: $routeName");
|
||||
}
|
||||
}
|
54
Vanilla/src/Service/Template.php
Normal file
54
Vanilla/src/Service/Template.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022 Micha Espey <tracer@24unix.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
/*
|
||||
* As I'm not allowed to use 3rd party code like Twig or Smarty I ended up
|
||||
* using PHP as a templating engine.
|
||||
*/
|
||||
|
||||
class Template
|
||||
{
|
||||
/*
|
||||
* Just store the information about the template base dir.
|
||||
*/
|
||||
public function __construct(private readonly string $templateDir)
|
||||
{
|
||||
// empty body
|
||||
}
|
||||
|
||||
/*
|
||||
* Add variables to template and throw it out
|
||||
*/
|
||||
public function render(string $templateName, array $vars = []): never
|
||||
{
|
||||
// assign template vars
|
||||
foreach ($vars as $name => $value) {
|
||||
$$name = $value;
|
||||
}
|
||||
|
||||
$templateFile = $this->templateDir . $templateName;
|
||||
if (file_exists(filename: $templateFile)) {
|
||||
include $this->templateDir . $templateName;
|
||||
} else {
|
||||
die("Missing template: $templateFile");
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* For AJAX calls, return json
|
||||
*/
|
||||
public function renderJson(array $results): never
|
||||
{
|
||||
http_response_code(response_code: $results['status']);
|
||||
echo json_encode(value: $results);
|
||||
exit(0);
|
||||
}
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
<?php
|
||||
|
||||
spl_autoload_register(callback: function($className) {
|
||||
$prefix = 'App';
|
||||
spl_autoload_register(callback: function ($className) {
|
||||
$prefix = 'App';
|
||||
$baseDir = __DIR__;
|
||||
|
||||
$len = strlen(string: $prefix);
|
||||
if (strncmp(string1: $prefix, string2: $className, length: $len) !== 0) {
|
||||
return;
|
||||
$prefixLen = strlen(string: $prefix);
|
||||
if (strncmp(string1: $prefix, string2: $className, length: $prefixLen) !== 0) {
|
||||
die("Invalid class: $className");
|
||||
}
|
||||
$realClassName = substr(string: $className, offset: $len);
|
||||
$classLocation = $baseDir . str_replace(search: '\\', replace: '/', subject: $realClassName) . '.php';
|
||||
$realClassNamePSRpath = substr(string: $className, offset: $prefixLen);
|
||||
$classLocation = $baseDir . str_replace(search: '\\', replace: '/', subject: $realClassNamePSRpath) . '.php';
|
||||
if (file_exists(filename: $classLocation)) {
|
||||
require $classLocation;
|
||||
} else {
|
7
Vanilla/templates/_footer.html.php
Normal file
7
Vanilla/templates/_footer.html.php
Normal file
@ -0,0 +1,7 @@
|
||||
<script src="/assets/js/functions.js"></script>
|
||||
<?php if (!empty($message)): ?>
|
||||
<script>setError('<?= $message ?>')</script>
|
||||
<?php endif; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
27
Vanilla/templates/_header.html.php
Normal file
27
Vanilla/templates/_header.html.php
Normal file
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Address Book
|
||||
<?php if (!empty($user->getNick())): ?>
|
||||
- <?= $user->getNick() ?>
|
||||
<?php endif; ?>
|
||||
</title>
|
||||
<link rel="stylesheet" href="/assets/styles/main.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Address Book</h1>
|
||||
<a href="<?= $router->path('app_main'); ?>">⌂ Home</a>
|
||||
<a href="<?= $router->path('app_admin'); ?>">⚙ Admin</a>
|
||||
<?php if (empty($user) || $user->getAuth() == \App\Enums\UserAuth::AUTH_ANONYMOUS): ?>
|
||||
<a href="<?= $router->path('app_login'); ?>">⎆ Login</a>
|
||||
<?php else: ?>
|
||||
<a href="<?= $router->path('app_logout'); ?>">⎋ Logout</a>
|
||||
<?php endif; ?>
|
||||
<br>
|
||||
<div id="info_box">Info</div>
|
||||
<div id="error_box">
|
||||
<span class="close_button" onclick="closeError()">×</span>
|
||||
<div id="error_text"></div>
|
||||
</div>
|
||||
<br>
|
35
Vanilla/templates/addressbook/add_address.html.php
Normal file
35
Vanilla/templates/addressbook/add_address.html.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php include dirname(path: __DIR__) . '/_header.html.php'; ?>
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="owner" id="owner" value="<?= $user->getId() ?>">
|
||||
|
||||
<label for="first">First</label>
|
||||
<input type="text" name="first" id="first" required>
|
||||
<br>
|
||||
|
||||
<label for="last">Last</label>
|
||||
<input type="text" name="last" id="last" required>
|
||||
<br>
|
||||
|
||||
<label for="city">City</label>
|
||||
<input type="text" name="city" id="city">
|
||||
<br>
|
||||
|
||||
<label for="zip">Zip</label>
|
||||
<input type="text" name="zip" id="zip">
|
||||
<br>
|
||||
|
||||
<label for="street">Street</label>
|
||||
<input type="text" name="street" id="street">
|
||||
<br>
|
||||
|
||||
<label for="phone">Phone</label>
|
||||
<input type="text" name="phone" id="phone">
|
||||
<br>
|
||||
|
||||
<!-- maybe later -->
|
||||
<!-- <input type="hidden" name="_csrf" value="csrf_token" -->
|
||||
<input type="submit" value="Save">
|
||||
</form>
|
||||
|
||||
<?php include dirname(path: __DIR__) . '/_footer.html.php' ?>
|
10
Vanilla/templates/admin/index.html.php
Normal file
10
Vanilla/templates/admin/index.html.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php include dirname(path: __DIR__) . '/_header.html.php'; ?>
|
||||
|
||||
<br>
|
||||
<h1>Address Book - Admin</h1>
|
||||
<a href="<?= $router->path('app_admin_users'); ?>">👱 Users</a>
|
||||
|
||||
<?php include dirname(path: __DIR__) . '/_footer.html.php' ?>
|
||||
|
||||
<button type="button" class="info_button" id="info_button" onclick="setInfo('Test Info - auto hide')">Info</button>
|
||||
<button type="button" class="error_button" onclick="setError('Test Error - must be closed manually')">Error</button>
|
35
Vanilla/templates/admin/users.html.php
Normal file
35
Vanilla/templates/admin/users.html.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php include dirname(path: __DIR__) . '/_header.html.php'; ?>
|
||||
|
||||
<h2>User list</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Nick</th>
|
||||
<th>First</th>
|
||||
<th>Last</th>
|
||||
<th>Is Admin</th>
|
||||
<th colspan="2"> </th>
|
||||
</tr>
|
||||
<?php foreach ($users as $userLine): ?>
|
||||
<tr>
|
||||
<td><?= $userLine->getNick() ?></td>
|
||||
<td><?= $userLine->getFirst() ?></td>
|
||||
<td><?= $userLine->getLast() ?></td>
|
||||
<td style="text-align: center;">
|
||||
<?php if ($userLine->isAdmin()): ?>
|
||||
☑️
|
||||
<?php else: ?>
|
||||
👎
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<a href="<?= $router->path('app_admin_users_edit', vars: ['nick' => $userLine->getNick()]); ?>">edit</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="<?= $router->path('app_admin_users_delete', vars: ['nick' => $userLine->getNick()]); ?>">delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<a href="<?= $router->path('app_admin_users_add'); ?>">Add User</a>
|
||||
|
||||
<?php include dirname(path: __DIR__) . '/_footer.html.php' ?>
|
29
Vanilla/templates/admin/users_add.html.php
Normal file
29
Vanilla/templates/admin/users_add.html.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php include dirname(path: __DIR__) . '/_header.html.php'; ?>
|
||||
|
||||
<form method="POST">
|
||||
<label for="nick">Username</label>
|
||||
<input type="text" name="nick" id="nick" maxlength="20" required>
|
||||
<br>
|
||||
|
||||
<label for="new_password">Password</label>
|
||||
<input type="password" name="new_password" id="new_password" maxlength="40" required>
|
||||
<br>
|
||||
|
||||
<label for="first">First</label>
|
||||
<input type="text" name="first" id="first" maxlength="40" required>
|
||||
<br>
|
||||
|
||||
<label for="last">Last</label>
|
||||
<input type="text" name="last" id="last" maxlength="40" required>
|
||||
<br>
|
||||
|
||||
<label for="is_admin">Is Admin</label>
|
||||
<input type="checkbox" name="is_admin" id="is_admin">
|
||||
<br>
|
||||
|
||||
<!-- maybe later -->
|
||||
<!-- <input type="hidden" name="_csrf" value="csrf_token" -->
|
||||
<input type="submit" value="Save">
|
||||
</form>
|
||||
|
||||
<?php include dirname(path: __DIR__) . '/_footer.html.php' ?>
|
31
Vanilla/templates/admin/users_edit.html.php
Normal file
31
Vanilla/templates/admin/users_edit.html.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php include dirname(path: __DIR__) . '/_header.html.php'; ?>
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="id" value="<?= $editUser->getId() ?>">
|
||||
|
||||
<label for="nick">Username</label>
|
||||
<input type="text" name="nick" id="nick" value="<?= $editUser->getNick() ?>" maxlength="20" required>
|
||||
<br>
|
||||
|
||||
<label for="new_password">Password (leave empty to keep the current one)</label>
|
||||
<input type="password" name="new_password" id="new_password" maxlength="40">
|
||||
<br>
|
||||
|
||||
<label for="first">First</label>
|
||||
<input type="text" name="first" id="first" value="<?= $editUser->getFirst() ?>" maxlength="40" required>
|
||||
<br>
|
||||
|
||||
<label for="last">Last</label>
|
||||
<input type="text" name="last" id="last" value="<?= $editUser->getLast() ?>" maxlength="40" required>
|
||||
<br>
|
||||
|
||||
<label for="is_admin">Is Admin</label>
|
||||
<input type="checkbox" name="is_admin" id="is_admin" <?= $editUser->isAdmin()?'checked':'' ?>>
|
||||
<br>
|
||||
|
||||
<!-- maybe later -->
|
||||
<!-- <input type="hidden" name="_csrf" value="csrf_token" -->
|
||||
<input type="submit" value="Save">
|
||||
</form>
|
||||
|
||||
<?php include dirname(path: __DIR__) . '/_footer.html.php' ?>
|
45
Vanilla/templates/index.html.php
Normal file
45
Vanilla/templates/index.html.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php include '_header.html.php' ?>
|
||||
|
||||
<br>
|
||||
<h2>Welcome to Address Book</h2>
|
||||
|
||||
<?php if(!empty($addresses)): ?>
|
||||
<form method="POST">
|
||||
<table id="address_table">
|
||||
<tr>
|
||||
<th id="first" onclick="sortBy('first')">First</th>
|
||||
<th id="last" onclick="sortBy('last')">Last</th>
|
||||
<th id="street" onclick="sortBy('street')">Street</th>
|
||||
<th id="zip" onclick="sortBy('zip')">Zip</th>
|
||||
<th id="city" onclick="sortBy('city')">City</th>
|
||||
<th id="phone" onclick="sortBy('phone')">Phone</th>
|
||||
<th colspan="2"> </th>
|
||||
</tr>
|
||||
<?php foreach ($addresses as $address): ?>
|
||||
<?php $id = $address->getId(); ?>
|
||||
<tr id="row_<?= $id ?>">
|
||||
<td><input type="text" id="first_<?= $id ?>" value="<?= $address->getFirst(); ?>" maxlength="80" disabled></td>
|
||||
<td><input type="text" id="last_<?= $id ?>" value="<?= $address->getLast(); ?>" maxlength="80" disabled></td>
|
||||
<td><input type="text" id="street_<?= $id ?>" value="<?= $address->getStreet(); ?>" maxlength="80" disabled></td>
|
||||
<td><input type="text" id="zip_<?= $id ?>" value="<?= $address->getZip(); ?>" maxlength="10" disabled></td>
|
||||
<td><input type="text" id="city_<?= $id ?>" value="<?= $address->getCity(); ?>" maxlength="80" disabled></td>
|
||||
<td><input type="text" id="phone_<?= $id ?>" value="<?= $address->getPhone(); ?>" maxlength="30" disabled></td>
|
||||
<td>
|
||||
<input type="button" value="Edit" id="edit_button_<?= $id ?>" onclick="editAddress('<?= $id ?>')">
|
||||
</td>
|
||||
<td>
|
||||
<input type="button" value="Delete" onclick="deleteAddress('<?= $id ?>')">
|
||||
<input type="hidden" id="owner_<?= $id ?>" value="<?= $id ?>">
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<?php $addAddress = $router->path('address_add'); ?>
|
||||
<input type="button" value="Add Address" onclick="addAddress('<?= $addAddress ?>')">
|
||||
<?php else: ?>
|
||||
Your addresses will be listed soon …
|
||||
<?php endif; ?>
|
||||
|
||||
<?php include '_footer.html.php' ?>
|
21
Vanilla/templates/security/login.html.php
Normal file
21
Vanilla/templates/security/login.html.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php include dirname(path: __DIR__) . '/_header.html.php'; ?>
|
||||
|
||||
<?php if (false): ?>
|
||||
You need Admin rights to access this area.
|
||||
<?php else: ?>
|
||||
<br>
|
||||
|
||||
<form method="POST">
|
||||
<label for="nick">Username</label>
|
||||
<input type="text" name="nick" id="nick">
|
||||
|
||||
<label for="password">Password</label>
|
||||
<input type="password" name="password" id="password">
|
||||
|
||||
<!-- maybe later -->
|
||||
<!-- <input type="hidden" name="_csrf" value="csrf_token" -->
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php include dirname(path: __DIR__) . '/_footer.html.php' ?>
|
5
Vanilla/templates/status/403.html.php
Normal file
5
Vanilla/templates/status/403.html.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php include dirname(path: __DIR__) . '/_header.html.php'; ?>
|
||||
|
||||
<h2>403 Permission denied</h2>
|
||||
|
||||
<?php include dirname(path: __DIR__) . '/_footer.html.php' ?>
|
3
Vanilla/templates/status/404.html.php
Normal file
3
Vanilla/templates/status/404.html.php
Normal file
@ -0,0 +1,3 @@
|
||||
<h2>404 Page not found</h2>
|
||||
|
||||
The requested URL cannot be found on this server.
|
@ -1,29 +0,0 @@
|
||||
body {
|
||||
background: #1f1f1f;
|
||||
color: #cdcdcd;
|
||||
}
|
||||
|
||||
/* unvisited link */
|
||||
a:link {
|
||||
color: #ff8844;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* visited link */
|
||||
a:visited {
|
||||
color: #ff8844;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* mouse over link */
|
||||
a:hover {
|
||||
color: #ff8844;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* selected link */
|
||||
a:active {
|
||||
color: #ff8844;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<?php
|
||||
ini_set(option: 'display_errors', value: 1);
|
||||
ini_set(option: 'display_startup_errors', value: 1);
|
||||
error_reporting(error_level: E_ALL);
|
||||
|
||||
require dirname(path: __DIR__) . '/src/bootstrap.php';
|
||||
|
||||
use App\Controller\AddressBook;
|
||||
|
||||
$container = new \App\Service\Container();
|
||||
|
||||
$addressBook = $container->get(AddressBook::class);
|
||||
//$addressBook = new AddressBook();
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Service\Template;
|
||||
use stdClass;
|
||||
|
||||
class AddressBook extends stdClass
|
||||
{
|
||||
public function __construct(Template $template)
|
||||
{
|
||||
$template->render(templateName: 'index.tpl');
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
class AddressBookEntry
|
||||
{
|
||||
public function __construct(
|
||||
private int $userid,
|
||||
private string $first,
|
||||
private string $last,
|
||||
private string $nick,
|
||||
)
|
||||
{
|
||||
// empty body
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
class User
|
||||
{
|
||||
public function __construct(
|
||||
private string $nick,
|
||||
private string $password,
|
||||
private string $first = '',
|
||||
private string $last = '',
|
||||
private int $id = 0
|
||||
)
|
||||
{
|
||||
// empty body
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Controller\AddressBook;
|
||||
use Exception;
|
||||
use stdClass;
|
||||
|
||||
class Container
|
||||
{
|
||||
// no autowiring yet, maybe later, but it might fit for a demo
|
||||
|
||||
private Template $template;
|
||||
private AddressBook $addressBook;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->template = new Template(templateDir: dirname(path: __DIR__, levels: 2) . '/templates/');
|
||||
$this->addressBook = new AddressBook(template: $this->template);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get(string $class): stdClass
|
||||
{
|
||||
return match($class) {
|
||||
'App\Controller\AddressBook' => $this->addressBook,
|
||||
default => throw new Exception(message: "Missing class definition: $class")
|
||||
};
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use Exception;
|
||||
|
||||
class Template
|
||||
{
|
||||
public function __construct(private readonly string $templateDir)
|
||||
{
|
||||
// empty body
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function render(string $templateName): void
|
||||
{
|
||||
$template = file_get_contents(filename: $this->templateDir . $templateName);
|
||||
|
||||
// search for includes
|
||||
preg_match_all(pattern: '/{% include ?\'?(.*?)\'? ?%}/i', subject: $template, matches: $matches, flags: PREG_SET_ORDER);
|
||||
|
||||
foreach ($matches as $value) {
|
||||
$token = $value[0];
|
||||
$include = $this->templateDir . $value[1];
|
||||
if (file_exists(filename: $include)) {
|
||||
$replacement = file_get_contents(filename: $include);
|
||||
} else {
|
||||
throw new Exception(message: "Missing included file: $include");
|
||||
}
|
||||
$template = str_replace(search: $token, replace: $replacement, subject: $template);
|
||||
}
|
||||
|
||||
// remove the original template code
|
||||
$template = preg_replace(pattern: '/{% include ?\'?(.*?)\'? ?%}/i', replacement: '', subject: $template);
|
||||
|
||||
echo $template;
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
<!-- mind the javascript -->
|
||||
</body>
|
||||
</html>
|
@ -1,5 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Address Book</title>
|
||||
<link rel="stylesheet" href="/assets/styles/main.css">
|
||||
</head>
|
@ -1,8 +0,0 @@
|
||||
{% include '_header.tpl' %}
|
||||
|
||||
<h1>Address Book</h1>
|
||||
<a href="/">🏠 Home</a>
|
||||
<a href="/">⚙ Admin</a>
|
||||
<a href="/">🚪 Login</a>
|
||||
|
||||
{% include '_footer.tpl' %}
|
Reference in New Issue
Block a user