migrate to Twig

This commit is contained in:
Christopher Teutsch 2023-01-20 18:54:40 +01:00
parent 302d41d09d
commit 0bc2c726f5
5 changed files with 2239 additions and 225 deletions

View File

@ -2,6 +2,11 @@
"require": { "require": {
"johngrogg/ics-parser": "^3", "johngrogg/ics-parser": "^3",
"symfony/http-client": "^6.2", "symfony/http-client": "^6.2",
"symfony/http-kernel": "^6.2" "symfony/http-kernel": "^6.2",
"twig/twig": "^3.5",
"twig/intl-extra": "^3.5"
},
"require-dev": {
"vimeo/psalm": "^5.4"
} }
} }

1995
composer.lock generated

File diff suppressed because it is too large Load Diff

347
index.php
View File

@ -1,86 +1,50 @@
<?php <?php
require_once 'vendor/autoload.php'; require_once 'vendor/autoload.php';
use ICal\ICal; use ICal\ICal;
use Symfony\Component\HttpClient\CachingHttpClient; use Symfony\Component\HttpClient\CachingHttpClient;
use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpKernel\HttpCache\Store; use Symfony\Component\HttpKernel\HttpCache\Store;
use Twig\TwigFilter;
$store = new Store('/tmp/dorf.jetzt/'); const INVALID_UAS = [
$client = HttpClient::create(); "AhrefsBot",
$client = new CachingHttpClient($client, $store, array('default_ttl' => 60, 'allow_revalidate' => true)); "Googlebot",
"Yahoo",
"Go-http-client/",
"bingbot",
"CheckMarkNetwork",
"SemrushBot",
"BingPreview",
"facebookexternalhit",
"hetrix.tools",
];
static $DATE_FORMAT = 'd.m.Y H:i'; const HASH_TO_STATE = [
static $VISITORS_FILE = '/opt/dorf.jetzt_visitors';
static $DORF_IN_LOCKDOWN = false;
static $DORF_VIRTUAL_EVENTS = true;
static $ICAL_URL = 'https://chaosdorf.de/~derf/cccd_all.ics';
$state_map = array(
'closed' => (object) array(
'state_string' => 'Das Dorf ist gerade <em>geschlossen</em>.',
'svg' => 'lock',
'color' => 'red',
),
'maybe_open' => (object) array(
'state_string' => 'Das Dorf ist gerade <em>vielleicht geöffnet</em>: </p><p>Der Clubraum ist offen, aber es findet keine Veranstaltung statt.</p><p>
Der Status kann sich also kurzfristig ändern.',
'svg' => 'done',
'color' => 'brown',
),
'open' => (object) array(
'state_string' => 'Das Dorf ist gerade <em>geöffnet</em>.</p><p>
Komm gerne vorbei.',
'svg' => 'done',
'color' => 'green',
),
'private' => (object) array(
'state_string' => 'Das Dorf ist gerade <em>privat</em>: </p><p>Es sind Leute da, aber der Clubraum ist nicht geöffnet.</p><p>
Komm gerne vorbei (aber frag lieber vorher, wie lange noch Leute da sind).',
'svg' => 'lock',
'color' => 'fdd835',
),
'unknown' => (object) array(
'state_string' => 'Der Status vom Dorf ist gerade <em>unbekannt</em>',
'svg' => 'warning',
'color' => 'orange',
),
'error' => (object) array(
'state_string' => 'Der Server konnte den Status vom Dorf nicht abrufen.',
'svg' => 'error',
'color' => 'blue',
),
);
$hash_to_state = array(
'bff0167ed8aba031c49122ef4046cf1b' => 'closed', 'bff0167ed8aba031c49122ef4046cf1b' => 'closed',
'd8ec899c69283bc775952a767db9d5f5' => 'maybe_open', 'd8ec899c69283bc775952a767db9d5f5' => 'maybe_open',
'2c2672c641425e5b2acd6ee74f39ae60' => 'open', '2c2672c641425e5b2acd6ee74f39ae60' => 'open',
'66aece8ae27ffd3a656d42005fa3efbd' => 'private', '66aece8ae27ffd3a656d42005fa3efbd' => 'private',
'86c75c0ad413b06ff8291673162d0b64' => 'unknown', '86c75c0ad413b06ff8291673162d0b64' => 'unknown',
'0' => 'error', '0' => 'error',
); ];
if ($DORF_VIRTUAL_EVENTS || ! $DORF_IN_LOCKDOWN){
try { function hasValidUa(): bool
$response = $client->request('GET', 'https://chaosdorf.de/raumstatus/status.png'); {
$hash = md5($response->getContent()); if (isset($_SERVER['HTTP_USER_AGENT'])) {
} catch (\Exception $e){ if (in_array(true, array_map(fn ($ua) => str_contains($_SERVER['HTTP_USER_AGENT'], $ua), INVALID_UAS))) {
$hash = 0; return false;
} }
$state = $hash_to_state[$hash]; return true;
$ical = new ICal(false, array(
'defaultSpan' => 2,
'defaultTimeZone' => 'Europe/Berlin',
'defaultWeekStart' => 'MO',
'filterDaysBefore' => '1',
));
$ical->initUrl($ICAL_URL, $acceptLanguage = 'de');
$events = $ical->eventsFromInterval('2 week');
$first_event = $events[0];
$events = array_slice($events, 1);
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])){
$locale = locale_accept_from_http($_SERVER['HTTP_ACCEPT_LANGUAGE']);
setlocale(LC_TIME, $locale);
} }
return false;
} }
function format_event($event): string{
/**
* @param object $event
*/
function format_event($event): string
{
global $DATE_FORMAT, $ical; global $DATE_FORMAT, $ical;
$startdate_loop = $ical->iCalDateToDateTime($event->dtstart_array[3]); $startdate_loop = $ical->iCalDateToDateTime($event->dtstart_array[3]);
$startdate_str = $startdate_loop->format($DATE_FORMAT); $startdate_str = $startdate_loop->format($DATE_FORMAT);
@ -92,126 +56,137 @@ function format_event($event): string{
} }
return $startdate_str . ' &ndash; ' . $enddate_str; return $startdate_str . ' &ndash; ' . $enddate_str;
} }
$v = file_get_contents($VISITORS_FILE); /**
?> * @return array<int, array<string, DateTime|string|bool>>
<!DOCTYPE html> */
<html lang="de"> function prepare_events(array $events): array{
<head> global $ical;
<title>Was geht im Dorf.jetzt?</title> $returns = [];
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> foreach ($events as $event){
<link rel="stylesheet" href="assets/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> $start = DateTimeImmutable::createFromMutable($ical->iCalDateToDateTime($event->dtstart_array[3]));
<link href="assets/font/Material-Icons.css" rel="stylesheet"> $end = $start->add(new DateInterval($event->duration));
<meta charset="UTF-8" /> $returns[] = [
</head> 'summary' => $event->summary,
<body> 'url' => $event->url,
<div class="container"> 'start' => $start,
<nav class="navbar"> 'end' => $end,
<h1 class="text-center">Was geht im Dorf.jetzt?</h1><hr /> ];
</nav> }
<?php if($DORF_IN_LOCKDOWN): ?> return $returns;
<div class="card mb-3 text-center"> }
<div class="card-body">
<h5 class="card-title">Das Chaosdorf ist geschlossen</h5>
<img src="assets/svg/ic_lock_48px.svg" style="filter:url(assets/css/filter.svg#red);height:16rem;width:auto;" />
<p class="card-text font-weight-normal">Das Chaosdorf öffnet vorsichtig wieder.</p>
<p class="card-text font-weight-normal">Veranstaltungen finden teilweise hybrid oder online-only statt.</p>
<p class="card-text font-weight-normal">Einwählen zu den Veranstaltungen könnt ihr euch hier: <a href="https://wiki.chaosdorf.de/VirtualSpace">VirtualSpace</a></p>
</div>
</div>
<?php else: ?> $store = new Store('/tmp/dorf.jetzt/http_cache');
<div class="card mb-3 text-center"> $client = HttpClient::create();
<div class="card-body"> $client = new CachingHttpClient($client, $store, ['default_ttl' => 60, 'allow_revalidate' => true]);
<h5 class="card-title">Türstatus</h5>
<img src="assets/svg/ic_<?= $state_map[$state]->svg ?>_48px.svg" style="filter:url(assets/css/filter.svg#<?= $state_map[$state]->color ?>);height:16rem;width:auto;" /> static $DATE_FORMAT = 'd.m.Y H:i';
<p class="card-text"><?= $state_map[$state]->state_string ?></p> static $VISITORS_FILE = '/opt/dorf.jetzt_visitors';
<div class="btn-wrapper"> static $DORF_IN_LOCKDOWN = false;
<a href="https://wiki.chaosdorf.de/Raumstatus" class="btn btn-lg btn-block btn-primary">What's this?</a> static $DORF_VIRTUAL_EVENTS = true;
</div> static $ICAL_URL = 'https://chaosdorf.de/~derf/cccd_all.ics';
</div> $state_map = [
</div> 'closed' => (object) [
<?php endif ?> 'state_string' => 'Das Dorf ist gerade <em>geschlossen</em>.',
<?php if($DORF_VIRTUAL_EVENTS || !$DORF_IN_LOCKDOWN): ?> 'svg' => 'lock',
<div class="card mb-3 text-center"> 'color' => 'red',
<div class="card-body"> ],
<h5 class="card-title">Events</h5> 'maybe_open' => (object) [
<?php if (!empty($events)) : ?> 'state_string' => 'Das Dorf ist gerade <em>vielleicht geöffnet</em>: </p><p>Der Clubraum ist offen, aber es findet keine Veranstaltung statt.</p><p>
<h6 class="card-subtitle mb-2 text-muted">Nächste Veranstaltung</h6> Der Status kann sich also kurzfristig ändern.',
<p class="card-text"> 'svg' => 'done',
<h5 class="font-weight-bold"><?= htmlspecialchars($first_event->summary, ENT_QUOTES, 'UTF-8') ?></h5> 'color' => 'brown',
<span class="font-weight-normal"><?= format_event($first_event) ?></span> ],
<span class="font-weight-normal"><a href="<?= $first_event->url ?>">Details zur Veranstaltung</a></span> 'open' => (object) [
</p> 'state_string' => 'Das Dorf ist gerade <em>geöffnet</em>.</p><p>
<h5 class="card-title">Danach:</h5> Komm gerne vorbei.',
<table class="table"> 'svg' => 'done',
<tbody> 'color' => 'green',
<?php foreach ($events as $event) : ?> ],
<tr> 'private' => (object) [
<th scope="row"><?=format_event($event)?></th> 'state_string' => 'Das Dorf ist gerade <em>privat</em>: </p><p>Es sind Leute da, aber der Clubraum ist nicht geöffnet.</p><p>
<td><?= htmlspecialchars($event->summary) ?></td> Komm gerne vorbei (aber frag lieber vorher, wie lange noch Leute da sind).',
<td><a href="<?= $event->url ?>">Details</a></td> 'svg' => 'lock',
</tr> 'color' => 'fdd835',
<?php endforeach ?> ],
</tbody> 'unknown' => (object) [
</table> 'state_string' => 'Der Status vom Dorf ist gerade <em>unbekannt</em>',
<a href="https://wiki.chaosdorf.de/Chaosdorf_Wiki:Current_events" class="btn btn-lg btn-block btn-primary">Event-Kalender</a> 'svg' => 'warning',
<?php else: ?> 'color' => 'orange',
<h6 class="card-subtitle mb-2 text-muted">Aktuell keine Veranstaltungen.</h6> ],
<p class="card-text"> 'error' => (object) [
<span class="font-weight-normal"> 'state_string' => 'Der Server konnte den Status vom Dorf nicht abrufen.',
Es stehen aktuell keine Veranstaltungen an. 'svg' => 'error',
</span> 'color' => 'blue',
</p> ],
<?php endif ?> ];
</div> $hash_to_state = [
</div> 'bff0167ed8aba031c49122ef4046cf1b' => 'closed',
<?php endif ?> 'd8ec899c69283bc775952a767db9d5f5' => 'maybe_open',
</div> '2c2672c641425e5b2acd6ee74f39ae60' => 'open',
<footer class="page-footer font-small text-center"> '66aece8ae27ffd3a656d42005fa3efbd' => 'private',
<div> '86c75c0ad413b06ff8291673162d0b64' => 'unknown',
Du bist Besucher #<?= $v ?> '0' => 'error',
</div> ];
<div> $state = 'error';
<a href="https://git.iwonder.name/iwonder/dorf.jetzt">Sieh dir den Code hier an</a> if ($DORF_VIRTUAL_EVENTS || !$DORF_IN_LOCKDOWN) {
</div> try {
<div> $response = $client->request('GET', 'https://chaosdorf.de/raumstatus/status.png');
Melde Fehler bitte an <a href="mailto:help@dorf.jetzt">help@dorf.jetzt</a> $hash = md5($response->getContent());
</div> } catch (\Exception $e) {
<div> $hash = 0;
Dies ist kein offizieller Auftritt des <a href="https://chaosdorf.de">Chaos Computer Club Düsseldorf / Chaosdorf e.V.</a>. }
</div> $state = $hash_to_state[$hash];
</footer> $ical = new ICal(false, [
<?php 'defaultSpan' => 2,
// These are actually not needed, see https://getbootstrap.com/docs/4.0/getting-started/introduction/#js 'defaultTimeZone' => 'Europe/Berlin',
// <script src="assets/js/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script> 'defaultWeekStart' => 'MO',
// <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> 'filterDaysBefore' => '1',
// <script src="assets/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> ]);
?> $ical->initUrl($ICAL_URL, $acceptLanguage = 'de');
</body> $events = $ical->eventsFromInterval('2 week');
</html> // $first_event = $events[0];
<?php // $events = array_slice($events, 1);
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$locale = locale_accept_from_http($_SERVER['HTTP_ACCEPT_LANGUAGE']);
setlocale(LC_TIME, $locale);
}
}
$visitors = file_get_contents($VISITORS_FILE);
if (is_string($visitors)) {
$visitors = intval($visitors);
} else {
$visitors = 0;
}
$state_obj = $state_map[$state];
$loader = new \Twig\Loader\FilesystemLoader('templates');
$twig = new \Twig\Environment($loader, [
//'cache' => '/tmp/dorf.jetzt/twig_cache'
]);
function formatEndDt(DateTime|DateTimeImmutable $end, DateTime|DateTimeImmutable $start, string $tz = 'Europe/Berlin'): string{
$daySame = $end->setTimeZone(new DateTimeZone($tz))->format('d.m.Y') == $start->setTimeZone(new DateTimeZone($tz))->format('d.m.Y');
$endIsMidnight = $end->setTimeZone(new DateTimeZone($tz))->format('H:i') == '00:00' && $start->setTimeZone(new DateTimeZone($tz))->format('H:i') != '00:00';
if ($daySame || $endIsMidnight){
return $end->format('H:i');
} else {
return $end->format('d.m.Y H:i');
}
}
$twig->addFilter(new TwigFilter('end_datetime', 'formatEndDt'));
//$twig->addExtension(new \Twig\Extra\Intl\IntlExtension());
$twig->getExtension(\Twig\Extension\CoreExtension::class)->setTimezone('Europe/Berlin');
$render_evts = prepare_events($events);
echo($twig->render("Main.twig", [
'visitors' => $visitors,
'state_svg' => $state_obj->svg,
'state_color' => $state_obj->color,
'state_string' => $state_obj->state_string,
'events' => $render_evts,
]));
/* Initialising values */ /* Initialising values */
if(!$v) $v=0; if (hasValidUa()) {
$count_visit = true; $visitors++;
/* List of bots I found in various access logs */ file_put_contents($VISITORS_FILE, strval($visitors));
$invalid_uas = array(
"AhrefsBot",
"Googlebot",
"Yahoo",
"Go-http-client/",
"bingbot",
"CheckMarkNetwork",
"SemrushBot",
"BingPreview",
"facebookexternalhit",
"hetrix.tools",
);
foreach ($invalid_uas as $ua){
if(stripos($_SERVER['HTTP_USER_AGENT'], $ua) !== FALSE){
$count_visit = false;
}
}
if ($count_visit === TRUE){
$v++;
file_put_contents($VISITORS_FILE, $v);
} }

15
psalm.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<psalm
errorLevel="3"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<file name="index.php" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

78
templates/Main.twig Normal file
View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>Was geht im Dorf.jetzt?</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="stylesheet" href="assets/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="preload" as="style" href="assets/css/filter.svg" />
<link href="assets/font/Material-Icons.css" rel="stylesheet">
<meta charset="UTF-8" />
</head>
<body>
<div class="container">
<h1 class="text-center">Was geht im Dorf.jetzt?</h1>
<div class="card mb-3 text-center">
<div class="card-body">
<h5 class="card-title">Türstatus</h5>
<img src="assets/svg/ic_{{ state_svg }}_48px.svg" style="filter:url(assets/css/filter.svg#{{ state_color }});height:16rem;width:auto;" />
<p class="card-text">{{ state_string|raw }}</p>
<div class="btn-wrapper">
<a href="https://wiki.chaosdorf.de/Raumstatus" class="btn btn-lg btn-block btn-primary">Was bedeutet das?</a>
</div>
</div>
</div>
<div class="card mb-3 text-center">
<div class="card-body">
<h5 class="card-title">Events</h5>
{% if events|length > 0 %}
<h6 class="card-subtitle mb-2 text-muted">Nächste Veranstaltung</h6>
<p class="card-text">
<h5 class="font-weight-bold">{{ events[0].summary|e }}</h5>
<span class="font-weight-normal">{{ events[0].start|date('d.m.Y H:i', 'Europe/Berlin') }} &ndash; {{ events[0].end|end_datetime(events[0].start, 'Europe/Berlin') }}</span>
<span class="font-weight-normal"><a href="{{ events[0].url }}">Details zur Veranstaltung</a></span>
</p>
<h5 class="card-title">Danach:</h5>
<table class="table">
<tbody>
{% for event in events %}
{% if loop.index > 0 %}
<tr>
<th scope="row"> {{ event.start|date('d.m.Y H:i', 'Europe/Berlin') }} &ndash; {{ event.end|end_datetime(event.start, 'Europe/Berlin') }}</th>
<td>{{ event.summary|e }}</td>
<td><a href="{{ event.url }}">Details</a></td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
<a href="https://wiki.chaosdorf.de/Chaosdorf_Wiki:Current_events" class="btn btn-lg btn-block btn-primary">Event-Kalender</a>
{% else %}
<h6 class="card-subtitle mb-2 text-muted">Aktuell keine Veranstaltungen.</h6>
<p class="card-text">
<span class="font-weight-normal">
Es stehen aktuell keine Veranstaltungen an.
</span>
</p>
{% endif %}
</div>
</div>
</div>
<footer class="page-footer font-small text-center">
<div>
Du bist Besucher #{{ visitors }}
</div>
<div>
<a href="https://git.iwonder.name/iwonder/dorf.jetzt">Sieh dir den Code hier an</a>
</div>
<div>
Melde Fehler bitte an <a href="mailto:help@dorf.jetzt">help@dorf.jetzt</a>
</div>
<div>
Dies ist kein offizieller Auftritt des <a href="https://chaosdorf.de">Chaos Computer Club Düsseldorf / Chaosdorf e.V.</a>.
</div>
</footer>
</body>
</html>