Compare commits

...

11 Commits

Author SHA1 Message Date
tracer 2a8fa3f397 dir commit 2022-10-21 20:26:20 +02:00
tracer f6a5e96576 added year and contact info 2022-10-21 20:09:30 +02:00
tracer b731e2c0e1 rewrite everything to index.php 2022-10-21 20:07:54 +02:00
tracer f15c2f9ff2 just names changed 2022-10-21 19:55:06 +02:00
tracer 7d15313503 updated comment, cleaner names 2022-10-21 19:54:23 +02:00
tracer 98e6c7fc64 some cleanup of variable names 2022-10-21 19:53:44 +02:00
tracer 0a334498df adapted the tables 2022-10-21 19:51:09 +02:00
tracer 02c6154629 initial commit 2022-10-21 14:56:14 +02:00
tracer 65e2836f23 initial commit 2022-10-21 14:54:42 +02:00
tracer 983da7fe88 initial commit 2022-10-21 14:49:12 +02:00
tracer 9b3a6b1f3a initial commit 2022-10-21 14:48:56 +02:00
12 changed files with 461 additions and 28 deletions

2
.gitinore Normal file
View File

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

View File

@ -1,6 +1,6 @@
MIT License 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: 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:

7
config.json.sample Normal file
View File

@ -0,0 +1,7 @@
{
"dbHost": "localhost",
"dbPort": 3306,
"dbDatabase": "tracer_addressbook",
"dbUser": "tracer_addressbook",
"dbPassword": "secret",
}

13
public/.htaccess Normal file
View 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>

View File

@ -3,12 +3,38 @@
namespace App\Controller; namespace App\Controller;
use App\Service\Template; use App\Service\Template;
use stdClass;
class AddressBook extends stdClass class AddressBook
{ {
public function __construct(Template $template) public function __construct(private readonly Template $template)
{ {
$template->render(templateName: 'index.tpl'); }
public function main(): void
{
try {
$this->template->render(templateName: 'index.tpl');
} catch (\Exception $e) {
die($e->getMessage());
}
}
public function admin(string $command = '')
{
try {
$this->template->render(templateName: 'admin/index.tpl');
} catch (\Exception $e) {
die($e->getMessage());
} }
} }
public function login()
{
try {
$this->template->render(templateName: 'admin/index.tpl');
} catch (\Exception $e) {
die($e->getMessage());
}
}
}

View File

@ -9,7 +9,8 @@ class User
private string $password, private string $password,
private string $first = '', private string $first = '',
private string $last = '', private string $last = '',
private int $id = 0 private int $id = 0,
private bool $isAdmin = false
) )
{ {
// empty body // empty body

View File

@ -0,0 +1,255 @@
<?php
namespace App\Repository;
use App\Service\Config;
use App\Service\DatabaseConnection;
use App\Entity\User;
use PDO;
use PDOException;
/**
*
*/
class DomainRepository
{
public function __construct(
private readonly DatabaseConnection $databaseConnection,
private readonly Config $configController)
{
// empty body
}
/**
* @return array
*/
public function findAll(): array
{
$users = [];
$sql = "
SELECT id, nick, first, last, is_admin
FROM " . DatabaseConnection::TABLE_USERS . "
ORDER BY name";
try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->execute();
while ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
$domain = new Domain(name: $result['name'], panel: $result['panel'], id: $result['id']);
$domains[] = $domain;
}
return $domains;
} catch (PDOException $e) {
exit($e->getMessage());
}
}
/**
* @param int $id
*
* @return bool|\App\Entity\Domain
*/
public function findByID(int $id): bool|Domain
{
$this->logger->debug(message: "findById($id)");
$sql = "
SELECT id, name, panel
FROM . " . DatabaseConnection::TABLE_DOMAINS . "
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 Domain(name: $result['name'], panel: $result['panel'], id: $result['id']);
} else {
return false;
}
} catch (PDOException $e) {
exit($e->getMessage());
}
}
/**
* @param String $name
*
* @return \App\Entity\Domain|bool
*/
public function findByName(string $name): Domain|bool
{
$this->logger->debug(message: "findByName($name)");
$sql = "
SELECT id, name, panel
FROM " . DatabaseConnection::TABLE_DOMAINS . "
WHERE name = :name";
try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->bindParam(param: ':name', var: $name);
$statement->execute();
if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
return new Domain(name: $result['name'], panel: $result['panel'], id: $result['id']);
} else {
return false;
}
} catch (PDOException $e) {
exit($e->getMessage());
}
}
/**
* @param string $host
*
* @return \App\Entity\Domain|bool
*/
public function findByHost(string $host): Domain|bool
{
$this->logger->debug(message: "findByHost($host)");
$host = strtolower(string: trim(string: $host));
$count = substr_count(haystack: $host, needle: '.');
if ($count == 2) {
if (strlen(string: explode(separator: '.', string: $host)[1]) > 3) {
$host = explode(separator: '.', string: $host, limit: 2)[1];
}
} elseif ($count > 2) {
$host = $this->findByHost(host: explode(separator: '.', string: $host, limit: 2)[1]);
}
if ($domain = $this->findByName(name: $host)) {
return $domain;
} else {
return false;
}
}
/**
* @param \App\Entity\Domain $domain
*
* @return string|false
*/
public function insert(Domain $domain): bool|string
{
$domainName = $domain->getName();
$this->logger->info(message: "insert($domainName)");
$sql = "
INSERT INTO " . DatabaseConnection::TABLE_DOMAINS . " (name, panel)
VALUES (:name, :panel)";
try {
$name = $domain->getName();
$panel = $domain->getPanel();
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->bindParam(param: ':name', var: $name);
$statement->bindParam(param: ':panel', var: $panel);
$statement->execute();
return $this->databaseConnection->getConnection()->lastInsertId();
} catch (PDOException $e) {
exit($e->getMessage());
}
}
/**
* @param \App\Entity\Domain $domain
*
* @return false|int
*/
public function update(Domain $domain): bool|int
{
$domainName = $domain->getName();
$this->logger->debug(message: "update($domainName)");
$id = $domain->getId();
$current = $this->findByID(id: $id);
if (empty($domain->getName())) {
$name = $current->getName();
} else {
$name = $domain->getName();
}
if (empty($domain->getPanel())) {
$panel = $current->getPanel();
} else {
$panel = $domain->getPanel();
}
$sql = "
UPDATE " . DatabaseConnection::TABLE_DOMAINS . " SET
name = :name,
panel = :panel
WHERE id = :id";
try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->bindParam(param: 'id', var: $id);
$statement->bindParam(param: 'name', var: $name);
$statement->bindParam(param: 'panel', var: $panel);
$statement->execute();
return $statement->rowCount();
} catch (PDOException $e) {
echo $e->getMessage();
return false;
}
}
/**
* @param \App\Entity\Domain $domain
*
* @return int
*/
public function delete(Domain $domain): int
{
$domainName = $domain->getName();
$this->logger->debug(message: "delete($domainName)");
$sql = "
DELETE FROM " . DatabaseConnection::TABLE_DOMAINS . "
WHERE id = :id";
try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$id = $domain->getId();
$statement->bindParam(param: 'id', var: $id);
$statement->execute();
return $statement->rowCount();
} catch (PDOException $e) {
exit($e->getMessage());
}
}
/**
* @param String $field
*
* @return int
*/
public function getLongestEntry(string $field): int
{
$sql = "
SELECT MAX(LENGTH(" . $field . ")) as length FROM " . DatabaseConnection::TABLE_DOMAINS;
try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->execute();
$result = $statement->fetch();
return $result['length'];
} catch (PDOException $e) {
exit($e->getMessage());
}
}
}

40
src/Service/Config.php Normal file
View File

@ -0,0 +1,40 @@
<?php
namespace App\Service;
use Exception;
/**
*
*/
class Config
{
private array $config;
/**
* @throws Exception
*/
public function __construct()
{
$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)) {
throw new Exception(message: 'Missing config file');
}
$configJSON = file_get_contents(filename: $configFile);
if (json_decode(json: $configJSON) === null) {
throw new Exception(message: 'Config file is not valid JSON.');
}
$this->config = json_decode(json: $configJSON, associative: true);
}
public function getConfig(string $configKey): string
{
return $this->config[$configKey];
}
}

View File

@ -3,31 +3,30 @@
namespace App\Service; namespace App\Service;
use App\Controller\AddressBook; use App\Controller\AddressBook;
use Exception;
use stdClass;
class Container class Container
{ {
// no autowiring yet, maybe later, but it might fit for a demo // caveat: Classes are always instantiated
// No autowiring (yet, maybe later, but it might fit for a demo)
private Template $template; private Template $template;
private AddressBook $addressBook; private AddressBook $addressBook;
private Router $router;
public function __construct() public function __construct()
{ {
$this->template = new Template(templateDir: dirname(path: __DIR__, levels: 2) . '/templates/'); $this->template = new Template(templateDir: dirname(path: __DIR__, levels: 2) . '/templates/');
$this->addressBook = new AddressBook(template: $this->template); $this->addressBook = new AddressBook(template: $this->template);
$this->router = new Router();
} }
public function get(string $className): object
/**
* @throws Exception
*/
public function get(string $class): stdClass
{ {
return match($class) { return match ($className) {
'App\Controller\AddressBook' => $this->addressBook, 'App\Controller\AddressBook' => $this->addressBook,
default => throw new Exception(message: "Missing class definition: $class") 'App\Service\Router' => $this->router,
//default => throw new Exception(message: "Missing class definition: $class")
default => die("Missing class definition: $className")
}; };
} }
} }

View File

@ -0,0 +1,37 @@
<?php
namespace App\Service;
use PDO;
/**
*
*/
class DatabaseConnection
{
private PDO $dbConnection;
const TABLE_PREFIX = '';
const TABLE_USERS = self::TABLE_PREFIX . "users";
const TABLE_ADDRESSES = self::TABLE_PREFIX . "addresses";
public function __construct(private readonly Config $configController)
{
$dbHost = $this->configController->getConfig(configKey: 'dbHost');
$dbPort = $this->configController->getConfig(configKey: 'dbPort');
$dbDatabase = $this->configController->getConfig(configKey: 'dbDatabase');
$dbUser = $this->configController->getConfig(configKey: 'dbUser');
$dbPassword = $this->configController->getConfig(configKey: 'dbPassword');
$this->dbConnection = new PDO(
dsn: "mysql:host=$dbHost;port=$dbPort;charset=utf8mb4;dbname=$dbDatabase",
username: $dbUser,
password: $dbPassword
);
}
public function getConnection(): PDO
{
return $this->dbConnection;
}
}

53
src/Service/Router.php Normal file
View File

@ -0,0 +1,53 @@
<?php
namespace App\Service;
class Router
{
private array $uri;
private array $routes;
public function __construct()
{
$uri = parse_url(url: $_SERVER['REQUEST_URI'], component: PHP_URL_PATH);
$this->uri = explode(separator: '/', string: $uri);
}
public function registerRoute(string $route, \Closure $callback): void
{
$this->routes[$route] = $callback;
}
private function matchRoute($url = '/users/tracer/posts/tracer', $method = 'GET')
{
$reqUrl = $url;
$reqUrl = rtrim(string: $reqUrl, characters: "/");
foreach ($this->routes as $route => $closure) {
// convert urls like '/users/:uid/posts/:pid' to regular expression
// $pattern = "@^" . preg_replace('/\\\:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', preg_quote($route['url'])) . "$@D";
$pattern = "@^" . preg_replace('\\/users/:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', $route) . "$@D";
// echo $pattern."\n";
$params = [];
// check if the current request params the expression
$match = preg_match($pattern, $reqUrl, $params);
if ($match) {
// remove the first match
array_shift($params);
// call the callback with the matched positions as params
// return call_user_func_array($route['callback'], $params);
return [$route, $params];
}
}
return [];
}
public function handleRouting(): void
{
$foo = $this->matchRoute();
var_dump($foo);
}
}

View File

@ -4,12 +4,12 @@ spl_autoload_register(callback: function($className) {
$prefix = 'App'; $prefix = 'App';
$baseDir = __DIR__; $baseDir = __DIR__;
$len = strlen(string: $prefix); $prefixLen = strlen(string: $prefix);
if (strncmp(string1: $prefix, string2: $className, length: $len) !== 0) { if (strncmp(string1: $prefix, string2: $className, length: $prefixLen) !== 0) {
return; die("Invalid class: $className");
} }
$realClassName = substr(string: $className, offset: $len); $realClassNamePSRpath = substr(string: $className, offset: $prefixLen);
$classLocation = $baseDir . str_replace(search: '\\', replace: '/', subject: $realClassName) . '.php'; $classLocation = $baseDir . str_replace(search: '\\', replace: '/', subject: $realClassNamePSRpath) . '.php';
if (file_exists(filename: $classLocation)) { if (file_exists(filename: $classLocation)) {
require $classLocation; require $classLocation;
} else { } else {