diff options
| author | David T. Sadler <davidtsadler@googlemail.com> | 2021-11-13 09:22:25 +0000 |
|---|---|---|
| committer | David T. Sadler <davidtsadler@googlemail.com> | 2021-11-13 09:22:25 +0000 |
| commit | 40997195b7ee07cb1bda978186c1804371e1f16e (patch) | |
| tree | 17a6e4bd6eaca795cfa8d57b9aa6ea8ad7066593 | |
| parent | 35dc6c245d93560d7ac81df6323e8b22e571aa95 (diff) | |
Create site
30 files changed, 1013 insertions, 0 deletions
diff --git a/autoload.php b/autoload.php new file mode 100644 index 0000000..e0f60ef --- /dev/null +++ b/autoload.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +$baseDir = __DIR__ . '/src/'; + +spl_autoload_register(function (string $class) use ($baseDir) +{ + $file = $baseDir . str_replace('\\', '/', $class) . '.php'; + + if (file_exists($file)) { + require $file; + } +}); + +require "$baseDir/DTS/Functions.php"; diff --git a/config.php b/config.php new file mode 100644 index 0000000..8260093 --- /dev/null +++ b/config.php @@ -0,0 +1,8 @@ +<?php + +error_reporting(E_ALL); + +return [ + 'path_to_repository' => '/home/david/projects/temp/todo', + 'path_to_templates' => '/home/david/projects/todo.davidtsadler.com/src/templates/', +]; diff --git a/deploy.php b/deploy.php new file mode 100644 index 0000000..270e2ca --- /dev/null +++ b/deploy.php @@ -0,0 +1,39 @@ +<?php +namespace Deployer; + +require 'recipe/common.php'; + +set('application', 'bookmarks.davidtsadler.com'); + +set('repository', 'git@git.davidtsadler.com:bookmarks.davidtsadler.com.git'); + +set('shared_files', [ + 'config.php', + 'urls', +]); + +set('shared_dirs', []); + +set('writable_dirs', []); + +set('allow_anonymous_stats', false); + +host('davidtsadler.com') + ->set('deploy_path', '/var/www/{{application}}'); + +task('deploy', [ + 'deploy:info', + 'deploy:prepare', + 'deploy:lock', + 'deploy:release', + 'deploy:update_code', + 'deploy:shared', + 'deploy:writable', + 'deploy:clear_paths', + 'deploy:symlink', + 'deploy:unlock', + 'cleanup', + 'success' +]); + +after('deploy:failed', 'deploy:unlock'); diff --git a/includes/functions.php b/includes/functions.php new file mode 100644 index 0000000..7146795 --- /dev/null +++ b/includes/functions.php @@ -0,0 +1,15 @@ +<?php declare(strict_types=1); + +function respondAndExit(int $responseCode, string $header, array $headers = []): void +{ + header($header, false, $responseCode); + + foreach ($headers as $header) { + header($header); + } + + header("Access-Control-Allow-Origin: *"); + header('Content-Type: text/plain; charset=UTF-8'); + + exit(); +} diff --git a/public/create/index.php b/public/create/index.php new file mode 100644 index 0000000..8256ff5 --- /dev/null +++ b/public/create/index.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +use DTS\Todo; +use DTS\Errors; +use DTS\Old; +use DTS\Session; +use DTS\Template; + +use function DTS\Functions\respondAndExit; + +require_once(__DIR__.'/../../autoload.php'); + +$config = require_once(__DIR__.'/../../config.php'); + +$session = Session::getInstance(); + +if (filter_input(INPUT_SERVER, 'REQUEST_METHOD') !== 'GET') { + respondAndExit(405, 'Method Not Allowed'); +} + +$old = $session->get('old', new Old()); + +$errors = $session->get('errors', new Errors()); + +$template = new Template($config['path_to_templates']); + +$todo = new Todo(); + +$html = $template->render('create', compact( + 'todo', + 'errors', + 'old' +)); + +respondAndExit(200, 'OK', $html); diff --git a/public/css/site.css b/public/css/site.css new file mode 100644 index 0000000..c1010a2 --- /dev/null +++ b/public/css/site.css @@ -0,0 +1,121 @@ +/*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */ +*,::after,::before{box-sizing:border-box}html{-moz-tab-size:4;tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}body{font-family:system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji'}hr{height:0;color:inherit}abbr[title]{text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}:-moz-focusring{outline:1px dotted ButtonText}:-moz-ui-invalid{box-shadow:none}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item} +.hljs{display:block;overflow-x:auto;padding:.5em;background:#f0f0f0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#bc6060}.hljs-literal{color:#78a960}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700} + +@font-face { + font-family: 'comicsans'; + src: url('/fonts/ComicMono.ttf') format('truetype'), + url("/fonts/ComicMono.ttf") format("truetype"); +} + +body { + background-color: #1a202c; + font-family: "comicsans", monospace; + color: #ffffff; + font-size: 1.125rem; + line-height: 1.75rem; +} + +h1, +h2, +label { + font-weight: bold; + font-size: 1.125rem; +} + +h1 { + color: #BEF264; + text-align: center; + width: 100%; +} + +h2 { + color: #FCD34D; +} + +h2:before { + content: "## "; +} + +section { + padding: 1rem; +} + +a { + color: #93C5FD; + text-decoration: none; +} + +a:hover { + color: #3730A3; +} + +a:before { + content: "=> "; +} + +a.no-decoration:before { + content: ""; +} + +ul, +ol { + list-style-type: none; + padding: 0; +} + +label { + display: block; + color: #86EFAC; + margin-top: 1rem; +} + +input, +button { + border-radius: .25rem; + display: block; + width: 100%; +} + +input { + border: 2px solid #FFFFFF; + padding: .375rem .75rem; +} + +button { + background-color: #86EFAC; + border: 1px solid transparent; + cursor: pointer; + font-weight: 400; + line-height: 1.5; + margin-top: 1rem; + padding: .375rem .75rem; + text-align: center; + vertical-align: middle; +} + +input:focus-visible, +button:focus-visible { + outline: none; + box-shadow: 0 0 0 0.25rem rgb(134 239 172 / 25%); +} + +.message { + background-color: #93C5FD; + border-radius: .25rem; + border: 1px solid transparent; + color: #000000; + padding: .375rem .75rem; + text-align: center; +} + +.tag { + color: #86EFAC; +} + +@media (min-width: 1536px) { + section { + width: 80%; + margin: auto; + } +} diff --git a/public/delete/confirm/index.php b/public/delete/confirm/index.php new file mode 100644 index 0000000..9903f14 --- /dev/null +++ b/public/delete/confirm/index.php @@ -0,0 +1,32 @@ +<?php + +declare(strict_types=1); + +use DTS\TodoRepository; +use DTS\Template; + +use function DTS\Functions\respondAndExit; + +require_once(__DIR__.'/../../../autoload.php'); + +$config = require_once(__DIR__.'/../../../config.php'); + +if (filter_input(INPUT_SERVER, 'REQUEST_METHOD') !== 'GET') { + respondAndExit(405, 'Method Not Allowed'); +} + +$id = filter_input(INPUT_GET, 'id'); + +$todos = new TodoRepository($config['path_to_repository']); + +$template = new Template($config['path_to_templates']); + +$todo = $todos->find($id); + +if ($todo === null) { + respondAndExit(404, 'Not Found'); +} + +$html = $template->render('confirm_deletion', compact('todo')); + +respondAndExit(200, 'OK', $html); diff --git a/public/delete/index.php b/public/delete/index.php new file mode 100644 index 0000000..a19f568 --- /dev/null +++ b/public/delete/index.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +use DTS\TodoRepository; +use DTS\Session; + +use function DTS\Functions\respondAndExit; +use function DTS\Functions\redirectAndExit; + +require_once(__DIR__.'/../../autoload.php'); + +$config = require_once(__DIR__.'/../../config.php'); + +$session = Session::getInstance(); + +if (filter_input(INPUT_SERVER, 'REQUEST_METHOD') !== 'POST') { + respondAndExit(405, 'Method Not Allowed'); +} + +$id = filter_input(INPUT_POST, 'id'); + +$todos = new TodoRepository($config['path_to_repository']); + +$todo = $todos->find($id); + +if ($todo === null) { + respondAndExit(404, 'Not Found'); +} + +if (!$todos->delete($todo)) { + respondAndExit(500, 'Internal Server Error'); +} + +$session->set('message', 'Todo Deleted'); + +redirectAndExit('/'); diff --git a/public/edit/index.php b/public/edit/index.php new file mode 100644 index 0000000..81aa121 --- /dev/null +++ b/public/edit/index.php @@ -0,0 +1,45 @@ +<?php + +declare(strict_types=1); + +use DTS\TodoRepository; +use DTS\Errors; +use DTS\Old; +use DTS\Session; +use DTS\Template; + +use function DTS\Functions\respondAndExit; + +require_once(__DIR__.'/../../autoload.php'); + +$config = require_once(__DIR__.'/../../config.php'); + +$session = Session::getInstance(); + +if (filter_input(INPUT_SERVER, 'REQUEST_METHOD') !== 'GET') { + respondAndExit(405, 'Method Not Allowed'); +} + +$old = $session->get('old', new Old()); + +$errors = $session->get('errors', new Errors()); + +$id = filter_input(INPUT_GET, 'id'); + +$todos = new TodoRepository($config['path_to_repository']); + +$template = new Template($config['path_to_templates']); + +$todo = $todos->find($id); + +if ($todo === null) { + respondAndExit(404, 'Not Found'); +} + +$html = $template->render('edit', compact( + 'todo', + 'errors', + 'old' +)); + +respondAndExit(200, 'OK', $html); diff --git a/public/fonts/ComicMono-Bold.ttf b/public/fonts/ComicMono-Bold.ttf Binary files differnew file mode 100644 index 0000000..e03f41e --- /dev/null +++ b/public/fonts/ComicMono-Bold.ttf diff --git a/public/fonts/ComicMono.ttf b/public/fonts/ComicMono.ttf Binary files differnew file mode 100644 index 0000000..9bc7354 --- /dev/null +++ b/public/fonts/ComicMono.ttf diff --git a/public/images/favicon.png b/public/images/favicon.png Binary files differnew file mode 100644 index 0000000..4909792 --- /dev/null +++ b/public/images/favicon.png diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..4e18b9a --- /dev/null +++ b/public/index.php @@ -0,0 +1,38 @@ +<?php + +declare(strict_types=1); + +use DTS\TodoRepository; +use DTS\Session; +use DTS\Template; + +use function DTS\Functions\respondAndExit; + +require_once(__DIR__.'/../autoload.php'); + +$config = require_once(__DIR__.'/../config.php'); + +$session = Session::getInstance(); + +$todos = new TodoRepository($config['path_to_repository']); + +$template = new Template($config['path_to_templates']); + +$sort = $_GET['sort'] ?? 'asc'; + +$tag = $_GET['tag'] ?? null; + +if ($tag !== null) { + $todos->filter($tag); +} + +$todos->sort($sort === 'asc'); + +$message = $session->get('message'); + +$html = $template->render('index', compact( + 'todos', + 'message' +)); + +respondAndExit(200, 'OK', $html); diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..1f53798 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/public/store/index.php b/public/store/index.php new file mode 100644 index 0000000..c4d24fa --- /dev/null +++ b/public/store/index.php @@ -0,0 +1,52 @@ +<?php + +declare(strict_types=1); + +use DTS\Todo; +use DTS\TodoRepository; +use DTS\Old; +use DTS\Session; +use DTS\Validator; + +use function DTS\Functions\redirectAndExit; +use function DTS\Functions\respondAndExit; + +require_once(__DIR__.'/../../autoload.php'); + +$config = require_once(__DIR__.'/../../config.php'); + +$session = Session::getInstance(); + +if (filter_input(INPUT_SERVER, 'REQUEST_METHOD') !== 'POST') { + respondAndExit(405, 'Method Not Allowed'); +} + +$old = new Old($_REQUEST); + +$session->set('old', $old); + +$validator = new Validator($_REQUEST); + +if ($validator->errors->count()) { + $session->set('errors', $validator->errors); + + redirectAndExit('/create'); +} + +$validated = $validator->validated; + +$todos = new TodoRepository($config['path_to_repository']); + +$todo = new Todo(); + +$todo->task = $validated->task; +$todo->tag = $validated->tag; +$todo->addedAt = date('Y-m-d H:i:s'); + +if (!$todos->add($todo)) { + respondAndExit(500, 'Internal Server Error'); +} + +$session->set('message', 'Todo Added'); + +redirectAndExit('/'); diff --git a/public/update/index.php b/public/update/index.php new file mode 100644 index 0000000..82b3c3f --- /dev/null +++ b/public/update/index.php @@ -0,0 +1,56 @@ +<?php + +declare(strict_types=1); + +use DTS\TodoRepository; +use DTS\Old; +use DTS\Session; +use DTS\Validator; + +use function DTS\Functions\respondAndExit; +use function DTS\Functions\redirectAndExit; + +require_once(__DIR__.'/../../autoload.php'); + +$config = require_once(__DIR__.'/../../config.php'); + +$session = Session::getInstance(); + +if (filter_input(INPUT_SERVER, 'REQUEST_METHOD') !== 'POST') { + respondAndExit(405, 'Method Not Allowed'); +} + +$id = filter_input(INPUT_POST, 'id'); + +$todos = new TodoRepository($config['path_to_repository']); + +$todo = $todos->find($id); + +if ($todo === null) { + respondAndExit(404, 'Not Found'); +} + +$old = new Old($_REQUEST); + +$session->set('old', $old); + +$validator = new Validator($_REQUEST); + +if ($validator->errors->count()) { + $session->set('errors', $validator->errors); + + redirectAndExit("/edit?id=$todo->id"); +} + +$validated = $validator->validated; + +$todo->task = $validated->task; +$todo->tag = $validated->tag; + +if (!$todos->update($todo)) { + respondAndExit(500, 'Internal Server Error'); +} + +$session->set('message', 'Todo Updated'); + +redirectAndExit('/'); diff --git a/src/DTS/Errors.php b/src/DTS/Errors.php new file mode 100644 index 0000000..3c1dd5d --- /dev/null +++ b/src/DTS/Errors.php @@ -0,0 +1,30 @@ +<?php + +declare(strict_types=1); + +namespace DTS; + +class Errors +{ + private $errors = []; + + public function add(string $key, string $value): void + { + $this->errors[$key][] = $value; + } + + public function get(string $key): array + { + return $this->errors[$key]; + } + + public function has(string $key): bool + { + return array_key_exists($key, $this->errors); + } + + public function count(): int + { + return count($this->errors); + } +} diff --git a/src/DTS/Functions.php b/src/DTS/Functions.php new file mode 100644 index 0000000..2101cda --- /dev/null +++ b/src/DTS/Functions.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +namespace DTS\Functions; + +function respondAndExit(int $responseCode, string $header, string $body = '', array $headers = []): void +{ + header($header, false, $responseCode); + + foreach ($headers as $header) { + header($header); + } + + echo $body; + + exit(); +} + +function redirectAndExit(string $location): void +{ + respondAndExit(302, 'Found', '', ["Location: $location"]); +} diff --git a/src/DTS/Old.php b/src/DTS/Old.php new file mode 100644 index 0000000..0641329 --- /dev/null +++ b/src/DTS/Old.php @@ -0,0 +1,29 @@ +<?php + +declare(strict_types=1); + +namespace DTS; + +class Old +{ + const FIELDS = [ + 'task', + 'tag', + ]; + + private array $old = []; + + function __construct(array $request = []) + { + foreach(self::FIELDS as $field) { + if (array_key_exists($field, $request)) { + $this->old[$field] = $request[$field]; + } + } + } + + public function get(string $key, $default = null) + { + return $this->old[$key] ?? $default; + } +} diff --git a/src/DTS/Session.php b/src/DTS/Session.php new file mode 100644 index 0000000..35be1fe --- /dev/null +++ b/src/DTS/Session.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +namespace DTS; + +class Session +{ + private static ?self $instance = null; + + private array $session = []; + + public static function getInstance() + { + if (!self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + private function __construct() + { + session_start(); + + foreach ($_SESSION as $key => $value) { + $this->session[$key] = $value; + unset($_SESSION[$key]); + } + } + + public function set(string $key, $value): void + { + $this->session[$key] = $_SESSION[$key] = $value; + } + + public function get(string $key, $default = null) + { + return $this->session[$key] ?? $default; + } +} diff --git a/src/DTS/Template.php b/src/DTS/Template.php new file mode 100644 index 0000000..89bd7f8 --- /dev/null +++ b/src/DTS/Template.php @@ -0,0 +1,32 @@ +<?php + +declare(strict_types=1); + +namespace DTS; + +class Template +{ + private string $path; + + function __construct(string $path) + { + $this->path = $path; + } + + public function render(string $template, array $data = []): string + { + $file = "{$this->path}$template.php"; + + if (!is_file($file) || !is_readable($file)) { + throw new \Exception("Unable to locate template $file"); + } + + extract($data); + + ob_start(); + + require_once($file); + + return ob_get_clean(); + } +} diff --git a/src/DTS/Todo.php b/src/DTS/Todo.php new file mode 100644 index 0000000..9b08e56 --- /dev/null +++ b/src/DTS/Todo.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +namespace DTS; + +class Todo +{ + public int $id; + + public string $task = ''; + + public string $tag = ''; + + public string $addedAt; +} diff --git a/src/DTS/TodoRepository.php b/src/DTS/TodoRepository.php new file mode 100644 index 0000000..5ec759f --- /dev/null +++ b/src/DTS/TodoRepository.php @@ -0,0 +1,162 @@ +<?php + +declare(strict_types=1); + +namespace DTS; + +use DTS\Todo; + +class TodoRepository implements \Iterator +{ + private string $pathToRepository; + + private array $repository = []; + + private array $tags = []; + + private int $position = 0; + + function __construct(string $pathToRepository) + { + $this->pathToRepository = $pathToRepository; + + $this->load(); + } + + public function sort(bool $asc = true): TodoRepository + { + usort($this->repository, function ($a, $b) use ($asc) { + return $asc ? $a->task <=> $b->task + : $b->task <=> $a->task; + }); + + return $this; + } + + public function filter(string $tag): TodoRepository + { + $this->repository = array_filter( + $this->repository, + fn($todo) => $todo->tag === $tag + ); + + return $this; + } + + public function find(string $id): ?Todo + { + return $this->repository[$id] ?? null; + } + + public function add(Todo $todo): bool + { + $this->repository[] = $todo; + + return $this->save(); + } + + public function update(Todo $todo): bool + { + if (array_key_exists($todo->id, $this->repository)) { + $this->repository[$todo->id] = $todo; + + return $this->save(); + } + + return false; + } + + public function delete(Todo $todo): bool + { + if (array_key_exists($todo->id, $this->repository)) { + unset($this->repository[$todo->id]); + + return $this->save(); + } + + return false; + } + + public function tags(): array + { + return $this->tags; + } + + public function current() + { + return $this->repository[$this->position]; + } + + public function key() + { + return $this->position; + } + + public function next(): void + { + ++$this->position; + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return isset($this->repository[$this->position]); + } + + private function load(): void + { + if (!is_file($this->pathToRepository) || !is_readable($this->pathToRepository)) { + throw new \Exception("Unable to locate repository {$this->pathToRepository}"); + } + + if (($fp = fopen($this->pathToRepository, 'r')) === FALSE) { + throw new \Exception("Unable to read from repository {$this->pathToRepository}"); + } + + $id = 0; + + while (($data = fgetcsv($fp)) !== FALSE) { + $todo = new Todo(); + + $todo->id = $id++; + $todo->task = $data[0]; + $todo->tag = $data[1]; + $todo->addedAt = $data[2]; + + $this->repository[] = $todo; + + $this->tags[] = $todo->tag; + } + + $this->tags = array_filter(array_unique($this->tags)); + + sort($this->tags); + + fclose($fp); + } + + private function save(): bool + { + if (($fp = fopen($this->pathToRepository, 'w')) === FALSE) { + throw new \Exception("Unable to open repository {$this->pathToRepository}"); + } + + $success = true; + + foreach ($this->repository as $todo) { + $success = $success && fputcsv($fp, [ + $todo->task, + $todo->tag, + $todo->addedAt, + ]) !== false; + } + + fclose($fp); + + return $success; + } +} diff --git a/src/DTS/Validated.php b/src/DTS/Validated.php new file mode 100644 index 0000000..9610538 --- /dev/null +++ b/src/DTS/Validated.php @@ -0,0 +1,12 @@ +<?php + +declare(strict_types=1); + +namespace DTS; + +class Validated +{ + public string $task = ''; + + public string $tag = ''; +} diff --git a/src/DTS/Validator.php b/src/DTS/Validator.php new file mode 100644 index 0000000..2f9c255 --- /dev/null +++ b/src/DTS/Validator.php @@ -0,0 +1,59 @@ +<?php + +declare(strict_types=1); + +namespace DTS; + +use DTS\Errors; +use DTS\Validated; + +class Validator +{ + public Errors $errors; + + public Validated $validated; + + function __construct(array $request) + { + $this->errors = new Errors(); + + $this->validated = new Validated(); + + $this->validateTask($request['task'], 2, 256); + + $this->validateTag($request['tag'], 2, 16); + } + + private function validateTask(string $task, int $minLength, int $maxLength): void + { + $task = trim($task); + + if (strlen($task) < $minLength || strlen($task) > $maxLength) { + $this->errors->add('task', "Must be between $minLength and $maxLength in characters in length"); + } + + if (!$this->errors->has('tite')) { + $this->validated->task = $task; + } + } + + private function validateTag(string $tag, int $minLength, int $maxLength): void + { + $tag = trim($tag); + + if ($tag === '') { + return; + } + + if (strlen($tag) < $minLength || strlen($tag) > $maxLength) { + $this->errors->add('tag', "Must be between $minLength and $maxLength in characters in length"); + } + if (preg_match('/\W/', $tag) === 1) { + $this->errors->add('tag', 'May only contain word characters'); + } + + if (!$this->errors->has('tag')) { + $this->validated->tag = strtolower($tag); + } + } +} diff --git a/src/templates/confirm_deletion.php b/src/templates/confirm_deletion.php new file mode 100644 index 0000000..85cf924 --- /dev/null +++ b/src/templates/confirm_deletion.php @@ -0,0 +1,23 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Confirm - Todo</title> + <link rel="shortcut icon" href="/images/favicon.png"> + <link rel="stylesheet" href="/css/site.css"> + </head> + <body> + <section> + <h1>Todo</h1> + <h2>Confirm Deletion</h2> + <a href="/">Back</a> + <form action="/delete/" method="POST"> + <input type="hidden" name="id" value="<?= $todo->id; ?>"/> + <?= htmlentities("$todo->url | $todo->task | $todo->tag"); ?> + <button type="submit">Delete</button> + </form> + <p>Copyright © 2021 David T. Sadler.</p> + </section> + </body> +</html> diff --git a/src/templates/create.php b/src/templates/create.php new file mode 100644 index 0000000..81ec7f8 --- /dev/null +++ b/src/templates/create.php @@ -0,0 +1,22 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Add - Todo</title> + <link rel="shortcut icon" href="/images/favicon.png"> + <link rel="stylesheet" href="/css/site.css"> + </head> + <body> + <section> + <h1>Todo</h1> + <h2>Add</h2> + <a href="/">Back</a> + <form action="/store/" method="POST"> + <?php require_once('form_fields.php'); ?> + <button type="submit">Add</button> + </form> + <p>Copyright © 2021 David T. Sadler.</p> + </section> + </body> +</html> diff --git a/src/templates/edit.php b/src/templates/edit.php new file mode 100644 index 0000000..9a8a1a6 --- /dev/null +++ b/src/templates/edit.php @@ -0,0 +1,23 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Edit - Todo</title> + <link rel="shortcut icon" href="/images/favicon.png"> + <link rel="stylesheet" href="/css/site.css"> + </head> + <body> + <section> + <h1>Todo</h1> + <h2>Edit</h2> + <a href="/">Back</a> + <form action="/update/" method="POST"> + <input type="hidden" name="id" value="<?= $todo->id; ?>"/> + <?php require_once('form_fields.php'); ?> + <button type="submit">Update</button> + </form> + <p>Copyright © 2021 David T. Sadler.</p> + </section> + </body> +</html> diff --git a/src/templates/form_fields.php b/src/templates/form_fields.php new file mode 100644 index 0000000..505cf26 --- /dev/null +++ b/src/templates/form_fields.php @@ -0,0 +1,8 @@ +<label>Task<input type="text" name="task" maxlength="256" value="<?= htmlspecialchars($old->get('task', $todo->task)); ?>"></label> +<?php if ($errors->has('task')) { ?> + <p><?= htmlentities(implode(', ', $errors->get('task'))); ?></p> +<?php } ?> +<label>Tag<input type="text" name="tag" maxlength="16" value="<?= htmlspecialchars($old->get('tag', $todo->tag)); ?>"></label> +<?php if ($errors->has('tag')) { ?> + <p><?= htmlentities(implode(', ', $errors->get('tag'))); ?></p> +<?php } ?> diff --git a/src/templates/index.php b/src/templates/index.php new file mode 100644 index 0000000..d309b2c --- /dev/null +++ b/src/templates/index.php @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Todo</title> + <link rel="shortcut icon" href="/images/favicon.png"> + <link rel="stylesheet" href="/css/site.css"> + </head> + <body> + <section> + <h1>Todo</h1> + <?php if ($message) { ?> + <p class="message"><?= $message; ?></p> + <?php } ?> + <a href="/create/">Add Todo</a> + <h2>Tags</h2> + <ul> + <a href="/">All</a> + <?php foreach($todos->tags() as $tag) { ?> + <li><a href="/?tag=<?= htmlentities($tag); ?>"><?= htmlentities($tag); ?></a></li> + <?php } ?> + </ul> + <h2>Todo</h2> + <ol> + <?php foreach ($todos as $todo) { ?> + <li> + <?= htmlentities($todo->task); ?></a> | <?php if ($todo->tag) { ?><a class="no-decoration tag" href="/?tag=<?= htmlentities($todo->tag); ?>"><?= htmlentities($todo->tag); ?></a> | <?php } ?><a class="no-decoration" href="/edit/?id=<?= $todo->id; ?>">Edit</a> | <a class="no-decoration" href="/delete/confirm/?id=<?= $todo->id; ?>">Delete</a> + </li> + <?php } ?> + </ol> + <p>Copyright © 2021 David T. Sadler.</p> + </section> + </body> +</html> |
