News #3

Merged
Huber1 merged 3 commits from news into master 2022-11-08 15:03:43 +01:00
14 changed files with 428 additions and 8 deletions

View file

@ -0,0 +1,37 @@
<?php
namespace app\controllers;
use app\models\NewsModel;
use DateTime;
use Exception;
use framework\Controller;
use Parsedown;
class NewsController extends Controller
{
static string $tab = 'news';
private array $weekdays = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
private array $months = ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"];
public function index(): string
{
$news = NewsModel::lastFive();
$parser = new Parsedown();
$parser->setSafeMode(true);
foreach ($news as $item) {
$item->content = $parser->parse($item->content);
try {
$dt = new DateTime($item->date);
// Format Mo 7. Nov
$item->date = $this->weekdays[$dt->format("N") - 1] . " " . $dt->format("j. ") . $this->months[$dt->format("n") - 1];
} catch (Exception $e) {
}
}
return $this->view('news', ["news" => $news]);
}
}

19
app/models/NewsModel.php Normal file
View file

@ -0,0 +1,19 @@
<?php
namespace app\models;
use framework\Collection;
use framework\database\Database;
use framework\database\Model;
use framework\database\Query;
class NewsModel extends Model
{
public static string $tableName = "News";
static function lastFive(): Collection
{
$table = self::tableName();
return self::arrayToModel(Database::query(new Query("SELECT * FROM `$table` ORDER BY `date` DESC LIMIT 5")));
}
}

View file

@ -7,6 +7,8 @@
}, },
"require": { "require": {
"twig/twig": "^3.4", "twig/twig": "^3.4",
"miladrahimi/phprouter": "^5.1" "miladrahimi/phprouter": "^5.1",
"erusev/parsedown": "^1.7",
"ext-pdo": "*"
} }
} }

52
composer.lock generated
View file

@ -4,8 +4,58 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "aeb8850dadf0ed622694b70b23f24518", "content-hash": "9ce76971c8555afa913cadbcffbebd26",
"packages": [ "packages": [
{
"name": "erusev/parsedown",
"version": "1.7.4",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35"
},
"type": "library",
"autoload": {
"psr-0": {
"Parsedown": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Emanuil Rusev",
"email": "hello@erusev.com",
"homepage": "http://erusev.com"
}
],
"description": "Parser for Markdown.",
"homepage": "http://parsedown.org",
"keywords": [
"markdown",
"parser"
],
"support": {
"issues": "https://github.com/erusev/parsedown/issues",
"source": "https://github.com/erusev/parsedown/tree/1.7.x"
},
"time": "2019-12-30T22:54:17+00:00"
},
{ {
"name": "laminas/laminas-diactoros", "name": "laminas/laminas-diactoros",
"version": "2.17.0", "version": "2.17.0",

View file

@ -1,6 +1,7 @@
<?php <?php
// https://github.com/miladrahimi/phprouter // https://github.com/miladrahimi/phprouter
use app\controllers\NewsController;
use app\controllers\PGPController; use app\controllers\PGPController;
use app\controllers\APIController; use app\controllers\APIController;
use framework\Router; use framework\Router;
@ -11,6 +12,8 @@ $router = Router::create();
$router->view('/', 'index', 'home'); $router->view('/', 'index', 'home');
$router->view('/impressum', 'impressum'); $router->view('/impressum', 'impressum');
$router->get('/news', [NewsController::class, 'index']);
$router->get('/pgp/?', [PGPController::class, 'index']); $router->get('/pgp/?', [PGPController::class, 'index']);
$router->get('/pgp/{email}', [PGPController::class, 'mail']); $router->get('/pgp/{email}', [PGPController::class, 'mail']);

View file

@ -43,9 +43,12 @@
@apply font-medium text-xl mb-2 dark:text-slate-200 @apply font-medium text-xl mb-2 dark:text-slate-200
} }
h3 {
@apply font-medium text-lg mb-1 dark:text-slate-200
}
p { p {
@apply mt-2 text-lg @apply mt-2 text-lg
} }
code { code {

View file

@ -1,3 +1,4 @@
<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
@ -58,12 +59,15 @@
</svg> </svg>
<span>Startseite</span> <span>Startseite</span>
</a> </a>
<a href="https://schule.moritzhuber.de" class="menu-item noline"> <a href="/news" class="menu-item noline {% if tab == "news" %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
<path d="M0 0h24v24H0V0z" fill="none"/> <g fill="none">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 15c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1zm1-8h-2V7h2v2z"/> <path d="M0 0h24v24H0z"/>
<path d="M0 0h24v24H0z"/>
</g>
<path d="m21.15 3.85-.82.82-.95-.96c-.39-.39-1.02-.39-1.42 0l-.96.96-.96-.96c-.39-.39-1.03-.39-1.42 0l-.95.96-.96-.96a.996.996 0 0 0-1.41 0l-.96.96-.96-.96c-.39-.39-1.02-.39-1.42 0L7 4.67l-.96-.96c-.39-.39-1.03-.39-1.42 0l-.95.96-.82-.82a.5.5 0 0 0-.85.36V19c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4.21a.5.5 0 0 0-.85-.36zM11 19H4v-6h7v6zm9 0h-7v-2h7v2zm0-4h-7v-2h7v2zm0-4H4V8h16v3z"/>
</svg> </svg>
<span>Infotafel</span> <span>Aktuelles</span>
</a> </a>
<a href="/pgp" class="menu-item noline {% if tab == "pgp" %}active{% endif %}"> <a href="/pgp" class="menu-item noline {% if tab == "pgp" %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24"> <svg xmlns="http://www.w3.org/2000/svg" height="24" width="24">
@ -72,6 +76,13 @@
</svg> </svg>
<span>PGP-Schlüssel</span> <span>PGP-Schlüssel</span>
</a> </a>
<a href="https://schule.moritzhuber.de" class="menu-item noline">
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24">
<path d="M0 0h24v24H0V0z" fill="none"/>
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 15c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1zm1-8h-2V7h2v2z"/>
</svg>
<span>Infotafel</span>
</a>
<a href="https://cloud.moritzhuber.de/s/upload-moritz" class="menu-item noline"> <a href="https://cloud.moritzhuber.de/s/upload-moritz" class="menu-item noline">
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24"> <svg xmlns="http://www.w3.org/2000/svg" height="24" width="24">
<path d="M0 0h24v24H0V0z" fill="none"/> <path d="M0 0h24v24H0V0z" fill="none"/>

18
src/views/news.twig Normal file
View file

@ -0,0 +1,18 @@
{% extends "frame.twig" %}
{% block title %}Aktuelles{% endblock %}
{% block content %}
<h2>Aktuelles</h2>
<div class="mt-4">
{% for item in news %}
<article
class="p-2 text-lg mt-2 rounded-xl bg-emerald-50 dark:bg-slate-800 border-2 border-emerald-100 dark:border dark:border-slate-700">
<div class="text-base text-emerald-600">{{ item.date }}</div>
{{ item.content|raw }}
</article>
{% else %}
Keine Einträge, schau wann anders nochmal vorbei
{% endfor %}
</div>
{% endblock %}

View file

@ -0,0 +1,95 @@
<?php
namespace framework;
use ArrayIterator;
use IteratorAggregate;
use Traversable;
/**
* Implements better arrays
**/
class Collection implements IteratorAggregate
{
protected array $items = [];
/**
* Create a new Collection
* @param array $items
*/
public function __construct(array $items = [])
{
$this->items = $items;
}
/**
* Create a collection with the given range.
*
* @param int $from
* @param int $to
* @return static<int, int>
*/
public static function range(int $from, int $to): static
{
return new static(range($from, $to));
}
/**
* Return all items of the collection
* @return array
*/
public function all(): array
{
return $this->items;
}
/**
* Flip items in the collection
* @return $this
*/
public function flip(): static
{
return new static(array_flip($this->items));
}
/**
* returns first element of collection
* @return mixed|null
*/
public function first(): mixed
{
return $this->items[0] ?? null;
}
/**
* returns last element of collection
* @return false|mixed|null
*/
public function last(): mixed
{
return end($this->items) ?? null;
}
public function add($item): self
{
$this->items[] = $item;
return $this;
}
/**
* return all items as json
* @return bool|string
*/
public function json(): bool|string
{
return json_encode($this->items);
}
/**
* @inheritDoc
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->items);
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace framework;
class Controller
{
public static function tab(): ?string
{
return get_called_class()::$tab ?? null;
}
function view(string $view, array $data = []): string
{
return view($view, array_merge($data, ['tab' => self::tab()]));
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace framework\database;
use PDO;
use PDOException;
class Database
{
static PDO $pdo;
static bool $init = false;
static function query(Query $query): array
{
self::init();
try {
if (!empty($query->parameters)) {
$stmt = self::$pdo->prepare($query->query);
$values = array_values($query->parameters);
$stmt->execute([...$values]);
} else {
$stmt = self::$pdo->query($query->query, PDO::FETCH_ASSOC);
}
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
}
die();
}
private static function init(): void
{
if (self::$init)
return;
$host = env("DB_HOST");
$dbname = env("DB_NAME");
try {
static::$pdo = new PDO(
"mysql:host=$host;dbname=$dbname;charset=utf8",
env("DB_USER"),
env("DB_PASS"),
);
} catch (PDOException $e) {
}
self::$init = true;
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace framework\database;
use framework\Collection;
/**
* @method static tableName()
* @property $id
*/
abstract class Model
{
static string $tableName;
public static function __callStatic(string $name, array $arguments)
{
return match ($name) {
"tableName" => get_called_class()::$tableName ??
str_replace("Model", "s", getClassName(get_called_class())),
default => null,
};
}
protected static function arrayToModel(array|Collection|null $data): Collection|self|null
{
if ($data === null) {
return null;
} elseif (empty($data)) {
return new Collection();
}
if (isAssociativeArray($data)) {
$className = get_called_class();
$model = new $className();
foreach ($data as $key => $value) {
$model->$key = $value;
}
return $model;
} else {
$models = new Collection();
foreach ($data as $item) {
$models->add(self::arrayToModel($item));
}
return $models;
}
}
static function all(): Collection
{
$table = self::tableName();
return self::arrayToModel(Database::query(new Query("SELECT * FROM `$table`")));
}
static function findById($id): ?self
{
$table = self::tableName();
$model = self::arrayToModel(
Database::query(
new Query("SELECT * FROM `$table` WHERE `id` = (?) LIMIT 1", ["id" => $id])
)
);
return $model->first();
}
static function where($condition, $value): Collection
{
$table = self::tableName();
return self::arrayToModel(
Database::query(
new Query("SELECT * FROM `$table` WHERE `$condition` = (?)", [$condition => $value])
)
);
}
function delete(): void
{
$table = self::tableName();
Database::query(
new Query("DELETE FROM `$table` WHERE `id` = (?)", [
"id" => $this->id,
])
);
}
function json(): string
{
return json_encode($this);
}
}

View file

@ -0,0 +1,15 @@
<?php
namespace framework\database;
class Query
{
public string $query;
public array $parameters;
public function __construct(string $query, array $parameters = [])
{
$this->query = $query;
$this->parameters = $parameters;
}
}

View file

@ -13,6 +13,20 @@ function run_script(string $name, ...$params): bool|string|null
return shell_exec("sh $script_location $params"); return shell_exec("sh $script_location $params");
} }
function getClassName(string $class): string
{
$exp = explode("\\", $class);
return end($exp);
}
function isAssociativeArray(array $array): bool
{
if ([] == $array) {
return false;
}
return array_keys($array) !== range(0, count($array) - 1);
}
function view(string $view, array $data = [], int $status = 200): string function view(string $view, array $data = [], int $status = 200): string
{ {
$loader = new \Twig\Loader\FilesystemLoader(ROOT . '/src/views'); $loader = new \Twig\Loader\FilesystemLoader(ROOT . '/src/views');