Compare commits

...

50 Commits

Author SHA1 Message Date
e262987298 split up into Vanilla and CakePHP 2022-11-25 12:01:28 +01:00
5f1f5e847d changed dir structure to handle second version of the book 2022-11-25 11:46:00 +01:00
b6fd7876d3 added addressbook dump 2022-11-01 18:37:46 +01:00
1dc31bcb1b check if message exists 2022-10-30 16:03:44 +01:00
3ff406d053 removed static message div 2022-10-30 16:02:03 +01:00
6abe519a44 added error message in text box 2022-10-30 16:01:29 +01:00
c6c88456cf keep sort order when changing the column, only revert in same colum 2022-10-29 14:26:42 +02:00
5a805aba07 removed sort indicator from html table 2022-10-29 14:07:12 +02:00
1541d05715 removed sort indicator from html table 2022-10-29 14:06:56 +02:00
dc78e203ea added ajax error handling 2022-10-28 17:02:16 +02:00
17a90358a7 added styling for info & error boxes and test buttons 2022-10-28 17:01:55 +02:00
0266a89ae3 added error handling to ajax calls 2022-10-28 17:01:29 +02:00
56a3e584e7 changed return to bool on update and delete 2022-10-28 17:01:08 +02:00
e2bf38299b changed return to bool on update and delete 2022-10-28 17:00:53 +02:00
24c8a3d9d7 added renderJson 2022-10-28 17:00:16 +02:00
dba37e57f9 added test buttons for info & error 2022-10-28 16:59:36 +02:00
c68cf1643e added info & error boxes 2022-10-28 16:58:58 +02:00
affe02ec04 added htmlspecialchars to visible fields 2022-10-27 15:42:43 +02:00
b678720ebd added maxlength 2022-10-27 15:38:33 +02:00
14f9f247bc changed some wording in comments 2022-10-27 12:13:37 +02:00
65a87acc48 fixed a stupid bug breaking the sort of row numbers larger than 9 2022-10-27 12:13:06 +02:00
e04cf94edd added htmlspecialchars for output 2022-10-27 10:53:32 +02:00
9ee8ae39df added opcache_ireset() for debugging 2022-10-27 10:37:05 +02:00
70aa4d1f06 changed some wording in comments 2022-10-26 13:16:04 +02:00
2f99531780 changed render from void to never 2022-10-26 13:15:41 +02:00
60860a0bce removed unused exception 2022-10-26 13:15:08 +02:00
bb3d0f6e1b lowercase login user 2022-10-26 13:14:39 +02:00
7ee8e8860e lowercase login user 2022-10-26 13:14:29 +02:00
32dcab7592 removed owner in update, we don't use it 2022-10-26 12:47:42 +02:00
88cf11d45d removed init from sortOrder 2022-10-26 12:46:03 +02:00
6c203a0213 added DOCTYPE 2022-10-26 12:45:36 +02:00
9867df0a04 sort only if table is actually displayed 2022-10-26 12:33:38 +02:00
0fba7bc0a0 break up if user already exists 2022-10-26 12:32:37 +02:00
f46e73c647 changed icon to html entity 2022-10-26 12:23:52 +02:00
1a8b31b3a2 changed icon to html entity 2022-10-26 12:21:50 +02:00
a832a3c60b changed default order from asc to desc as it will be reversed on first call 2022-10-26 12:13:42 +02:00
904fd798f6 removed redundant initializer from newTitle 2022-10-26 12:12:34 +02:00
4ed3dcd603 Added indicator to show if sorting is ascending or descending 2022-10-26 11:58:27 +02:00
8bc252ba4d finished things up 2022-10-26 11:16:20 +02:00
78f25fdfd3 removed some PPHPStorm warnings. 2022-10-26 11:13:04 +02:00
e2da6000b3 fixed a bug while generating the addAddress route. 2022-10-26 11:11:58 +02:00
32133a0d05 some format changes 2022-10-25 19:40:33 +02:00
572cc1eb89 reverted last name change, not needed 2022-10-25 19:40:33 +02:00
48585f14ab removed debug statements 2022-10-25 19:40:33 +02:00
47439fe358 removed debug statements 2022-10-25 19:40:33 +02:00
8e0a2ff6e8 every click on a column title changes the sort order 2022-10-25 19:40:33 +02:00
ce798e3b65 Delete '.gitinore' 2022-10-25 17:57:01 +02:00
21fd674dd0 initial commit 2022-10-25 17:56:29 +02:00
8864875699 initial commit 2022-10-25 16:09:17 +02:00
fe0a3c212d removed welcome until fixed 2022-10-25 16:09:17 +02:00
42 changed files with 566 additions and 178 deletions

1
.gitignore vendored Normal file

@ -0,0 +1 @@
/config.json

@ -1,2 +0,0 @@
config.json

@ -1,3 +1,8 @@
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. :-)
This repo hold a basic programming task I had to do for my new job.
The address book itself was then done in a few hours.
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.

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

@ -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

@ -0,0 +1,7 @@
{
"dbHost": "localhost",
"dbPort": 3306,
"dbDatabase": "tracer_addressbook",
"dbUser": "tracer_addressbook",
"dbPassword": "SKTh_6#YM?%q"
}

@ -11,10 +11,8 @@ function addAddress(url) {
}
function editAddress(id) {
console.log("editButon")
if (document.getElementById('edit_button_' + id).value === 'Save') {
// save
console.log("save")
const url = "/address/update";
fetch(url, {
method: "POST",
@ -32,7 +30,14 @@ function editAddress(id) {
.then(
response => response.text()
).then(
html => console.log(html)
json => {
let jsonObject = JSON.parse(json)
if (jsonObject.status === 200) {
setInfo('Data successfully saved.')
} else {
setError(jsonObject.message);
}
}
);
document.getElementById('first_' + id).disabled = true
@ -45,7 +50,6 @@ function editAddress(id) {
document.getElementById('edit_button_' + id).value = 'Edit'
} else {
//switch to edit
console.log("switch to edit")
document.getElementById('first_' + id).disabled = false
document.getElementById('last_' + id).disabled = false
document.getElementById('street_' + id).disabled = false
@ -58,7 +62,6 @@ function editAddress(id) {
}
function deleteAddress(id) {
console.log("del")
if (confirm('Are you sure?')) {
const url = "/address/delete";
fetch(url, {
@ -70,47 +73,134 @@ function deleteAddress(id) {
.then(
response => response.text()
).then(
html => console.log(html)
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) {
console.log("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) + '&nbsp;&#11015;'
} else {
newTitle = currentTitle[0] + currentTitle.substring(1) + '&nbsp;&#11014;'
}
currentTitleElement.innerHTML = newTitle
const table = document.getElementById('address_table');
let dirty = true;
// loop until clean
while (dirty) {
console.log('dirty', dirty)
// assume we are finished
dirty = false
const rows = table.rows;
console.log(rows)
for (let i = 1; i < (rows.length - 2); i++) {
let x = rows[i]
let rowXId = x.id
let rowXNumber = rowXId.charAt(rowXId.length -1)
let rowXNumber = rowXId.match(/\d+/)
let valueX = document.getElementById(column + '_' + rowXNumber).value
let y = rows[i + 1]
let rowYId = y.id
let rowYNumber = rowYId.charAt(rowYId.length -1)
let rowYNumber = rowYId.match(/\d+/)
let valueY = document.getElementById(column + '_' + rowYNumber).value
console.log(valueX, valueY)
// mind asc & desc
let sortOrder = 1
console.log(valueX.localeCompare(valueY))
let sortOrder
if (currentSortOrder === 'asc') {
sortOrder = 1
} else {
sortOrder = -1
}
if (valueX.localeCompare(valueY) === sortOrder) {
console.log('switch A')
// switch rows
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')
}
})

@ -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;
}

@ -12,6 +12,11 @@ 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';
@ -28,10 +33,6 @@ $security = $container->get(className: SecurityController::class);
$addressBook = $container->get(className: AddressBookController::class);
$addressBookAdmin = $container->get(className: AddressBookAdminController::class);
// TODO maybe refactor route adding to the controllers?
// I currently think that makes sense.
$router->addRoute(name: 'app_login', route: '/login', callback: function () use ($security) {
$security->login();
});

@ -104,7 +104,11 @@ class AddressBookAdminController
{
$this->adminCheck();
// TODO currently breaks on inserting a duplicate nick
$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);
@ -147,8 +151,10 @@ class AddressBookAdminController
die("Error deleting user");
}
} else {
// TODO use 404
die("Nick: $nick not found");
$this->template->render(templateName: 'status/404.html.php', vars: [
'user' => $this->user,
'router' => $this->router
]);
}
}

@ -11,6 +11,7 @@ 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;
@ -71,16 +72,61 @@ class AddressBookController
{
$_POST = json_decode(json: file_get_contents(filename: "php://input"), associative: true);
$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']);
$this->addressRepository->update(address: $address);
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
{
echo "in del";
$_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'])) {
$this->addressRepository->delete(addressBookEntry: $address);
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'
]);
}
}

@ -32,6 +32,7 @@ class SecurityController
$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();

@ -38,7 +38,15 @@ class AddressRepository
$statement->execute();
$addresses = [];
while ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
$address = new AddressBookEntry(owner: $result['owner'], first: $result['first'], last: $result['last'], street: $result['street'], zip: $result['zip'], city: $result['city'], phone: $result['phone'], id: $result['id']);
$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;
@ -60,7 +68,15 @@ class AddressRepository
$statement->bindParam(param: ':id', var: $id);
$statement->execute();
if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
return new AddressBookEntry(owner: $result['owner'], first: $result['first'], last: $result['last'], street: $result['street'], zip: $result['zip'], city: $result['city'], phone: $result['phone'], id: $result['id']);
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;
}
@ -105,7 +121,6 @@ class AddressRepository
public function update(AddressBookEntry $address): bool|int
{
$id = $address->getId();
$owner = $address->getOwner();
$first = $address->getFirst();
$last = $address->getLast();
$street = $address->getStreet();
@ -132,9 +147,7 @@ class AddressRepository
$statement->bindParam(param: 'zip', var: $zip);
$statement->bindParam(param: 'city', var: $city);
$statement->bindParam(param: 'phone', var: $phone);
$statement->execute();
return $statement->rowCount();
return $statement->execute();
} catch (PDOException $e) {
echo $e->getMessage();
return false;
@ -152,9 +165,7 @@ class AddressRepository
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$id = $addressBookEntry->getId();
$statement->bindParam(param: 'id', var: $id);
$statement->execute();
return $statement->rowCount();
return $statement->execute();
} catch (PDOException $e) {
exit($e->getMessage());
}

@ -38,7 +38,13 @@ class UserRepository
$statement->execute();
while ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
$user = new User(nick: $result['nick'], password: $result['password'], first: $result['first'], last: $result['last'], id: $result['id'], isAdmin: $result['is_admin']);
$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;
@ -60,7 +66,13 @@ class UserRepository
$statement->bindParam(param: ':id', var: $id);
$statement->execute();
if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
return new User(nick: $result['nick'], password: $result['password'], first: $result['first'], last: $result['last'], id: $result['id'], isAdmin: $result['is_admin']);
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;
}
@ -71,6 +83,8 @@ class UserRepository
public function findByNick(string $nick): ?User
{
$nick = strtolower(string: $nick);
$sql = "
SELECT id, nick, password, first, last, is_admin
FROM " . DatabaseConnection::TABLE_USERS . "
@ -81,7 +95,13 @@ class UserRepository
$statement->bindParam(param: ':nick', var: $nick);
$statement->execute();
if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
return new User(nick: $result['nick'], password: $result['password'], first: $result['first'], last: $result['last'], id: $result['id'], isAdmin: $result['is_admin']);
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;
}
@ -117,7 +137,7 @@ class UserRepository
}
public function update(User $user): bool|int
public function update(User $user): bool
{
$id = $user->getId();
$nick = $user->getNick();
@ -149,9 +169,7 @@ class UserRepository
$statement->bindParam(param: 'first', var: $first);
$statement->bindParam(param: 'last', var: $last);
$statement->bindParam(param: 'is_admin', var: $isAdmin);
$statement->execute();
return $statement->rowCount();
return $statement->execute();
} catch (PDOException $e) {
echo $e->getMessage();
return false;
@ -159,7 +177,7 @@ class UserRepository
}
public function delete(User $user): int
public function delete(User $user): bool
{
$sql = "
DELETE FROM " . DatabaseConnection::TABLE_USERS . "
@ -169,9 +187,7 @@ class UserRepository
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$id = $user->getId();
$statement->bindParam(param: 'id', var: $id);
$statement->execute();
return $statement->rowCount();
return $statement->execute();
} catch (PDOException $e) {
exit($e->getMessage());
}

@ -9,8 +9,6 @@
namespace App\Service;
use Exception;
/**
*
*/

@ -35,7 +35,7 @@ class Router
/*
* This method takes a route like /admin/users/{user} and creates a regex to match on call
* More complex routes as /posts/{thread}/show/{page} are supported as well.
* More complex routes like /posts/{thread}/show/{page} are supported as well.
*/
function addRoute(string $name, string $route, Closure $callback): void
{
@ -84,7 +84,7 @@ class Router
foreach ($this->dynamicRoutes as $route) {
// PHPStorm doesn't know that $parameters are always available,
// (as it is a dynamic route) so the init the array just to mke PHPstorm happy.
// (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) {
@ -104,7 +104,6 @@ class Router
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
@ -119,16 +118,7 @@ class Router
}
}
}
// no 404, this is reached only if the code is wrong
// TODO doesn't find the last route
/*
foreach (array_merge($this->dynamicRoutes, $this->staticRoutes) as $route) {
echo $route->getRoute() . '<br>';
if ($routeName == $route->getRoute()) {
echo "equal";
}
}
die("Missing Route: $routeName");
*/
// no 404, this is reached only if the code is buggy
die("Missing Route: $routeName");
}
}

@ -13,8 +13,6 @@ namespace App\Service;
* using PHP as a templating engine.
*/
use JetBrains\PhpStorm\NoReturn;
class Template
{
/*
@ -28,8 +26,7 @@ class Template
/*
* Add variables to template and throw it out
*/
#[NoReturn]
public function render(string $templateName, array $vars = []): void
public function render(string $templateName, array $vars = []): never
{
// assign template vars
foreach ($vars as $name => $value) {
@ -44,4 +41,14 @@ class Template
}
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);
}
}

@ -0,0 +1,7 @@
<script src="/assets/js/functions.js"></script>
<?php if (!empty($message)): ?>
<script>setError('<?= $message ?>')</script>
<?php endif; ?>
</body>
</html>

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Address Book
<?php if (!empty($user->getNick())): ?>
&nbsp;-&nbsp;<?= $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'); ?>">&#8962;&nbsp;Home</a>
<a href="<?= $router->path('app_admin'); ?>">&#9881;&nbsp;Admin</a>
<?php if (empty($user) || $user->getAuth() == \App\Enums\UserAuth::AUTH_ANONYMOUS): ?>
<a href="<?= $router->path('app_login'); ?>">&#9094;&nbsp;Login</a>
<?php else: ?>
<a href="<?= $router->path('app_logout'); ?>">&#9099;&nbsp;Logout</a>
<?php endif; ?>
<br>
<div id="info_box">Info</div>
<div id="error_box">
<span class="close_button" onclick="closeError()">&times;</span>
<div id="error_text"></div>
</div>
<br>

@ -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' ?>

@ -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'); ?>">&#128113;&nbsp;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>

@ -2,19 +2,19 @@
<form method="POST">
<label for="nick">Username</label>
<input type="text" name="nick" id="nick" required>
<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" required>
<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" required>
<input type="text" name="first" id="first" maxlength="40" required>
<br>
<label for="last">Last</label>
<input type="text" name="last" id="last" required>
<input type="text" name="last" id="last" maxlength="40" required>
<br>
<label for="is_admin">Is Admin</label>

@ -4,19 +4,19 @@
<input type="hidden" name="id" value="<?= $editUser->getId() ?>">
<label for="nick">Username</label>
<input type="text" name="nick" id="nick" value="<?= $editUser->getNick() ?>" required>
<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">
<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() ?>" required>
<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() ?>" required>
<input type="text" name="last" id="last" value="<?= $editUser->getLast() ?>" maxlength="40" required>
<br>
<label for="is_admin">Is Admin</label>

@ -18,37 +18,28 @@
<?php foreach ($addresses as $address): ?>
<?php $id = $address->getId(); ?>
<tr id="row_<?= $id ?>">
<td><input type="text" id="first_<?= $id ?>" value="<?= $address->getFirst(); ?>" disabled></td>
<td><input type="text" id="last_<?= $id ?>" value="<?= $address->getLast(); ?>" disabled></td>
<td><input type="text" id="street_<?= $id ?>" value="<?= $address->getStreet(); ?>" disabled></td>
<td><input type="text" id="zip_<?= $id ?>" value="<?= $address->getZip(); ?>" disabled></td>
<td><input type="text" id="city_<?= $id ?>" value="<?= $address->getCity(); ?>" disabled></td>
<td><input type="text" id="phone_<?= $id ?>" value="<?= $address->getPhone(); ?>" disabled></td>
<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 ?>)">
<input type="button" value="Edit" id="edit_button_<?= $id ?>" onclick="editAddress('<?= $id ?>')">
</td>
<td>
<input type="button" value="Delete" onclick="deleteAddress(<?= $id ?>)">
<input type="button" value="Delete" onclick="deleteAddress('<?= $id ?>')">
<input type="hidden" id="owner_<?= $id ?>" value="<?= $id ?>">
</td>
</tr>
<?php endforeach; ?>
<tr style="display:none;">
<td id="first_sort">asc</td>
<td id="last_sort">asc</td>
<td id="street_sort">asc</td>
<td id="zip_sort">asc</td>
<td id="city_sort">asc</td>
<td id="phone_sort">asc</td>
</tr>
</table>
</form>
<!-- TODO why is the route not found? $addPath = $router->path('/address/add'); -->
<input type="button" value="Add Address" onclick="addAddress('address/add')">
<?php $addAddress = $router->path('address_add'); ?>
<input type="button" value="Add Address" onclick="addAddress('<?= $addAddress ?>')">
<?php else: ?>
Your addresses wil be listed soon
Your addresses will be listed soon
<?php endif; ?>
<?php include '_footer.html.php' ?>

@ -5,12 +5,6 @@
<?php else: ?>
<br>
<?php if ($message): ?>
<div class="info">
<?= $message ?>
</div>
<?php endif; ?>
<form method="POST">
<label for="nick">Username</label>
<input type="text" name="nick" id="nick">

@ -1,38 +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;
}
table, th, td {
border: 1px solid;
}
label {
display: block;
padding: 1ex;
}

@ -1,3 +0,0 @@
<script src="/assets/js/functions.js"></script>
</body>
</html>

@ -1,24 +0,0 @@
<html lang="en">
<head>
<title>Address Book
<?php if (!empty($user->getNick())): ?>
&nbsp;-&nbsp;<?= $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; ?>
<?php if (!empty($user->getNick())): ?>
<br>
<-- TODO fix for anonymous Welcome back, <?= $user->getNick(); ?> -->
<?php endif; ?>
<br>

@ -1,6 +0,0 @@
<?php include dirname(path: __DIR__) . '/_header.html.php'; ?>
<h1>Address Book - Admin</h1>
<a href="<?= $router->path('app_admin_users') ?>">👱Users</a>
<?php include dirname(path: __DIR__) . '/_footer.html.php' ?>