143 lines
5.6 KiB
PHP
143 lines
5.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
const DEVELOPER_FEED_URL = 'https://git.pelpino.de/NAK-Reinickendorf/intranet.rss';
|
|
const DEVELOPER_REPO_URL = 'https://git.pelpino.de/NAK-Reinickendorf/intranet';
|
|
const DEVELOPER_FEED_LIMIT = 7;
|
|
|
|
function developer_feed_plain_text(string $value): string
|
|
{
|
|
return trim(preg_replace(
|
|
'/\s+/u',
|
|
' ',
|
|
strip_tags(preg_replace('/>\s*</', '> <', html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8')) ?? '')
|
|
) ?? '');
|
|
}
|
|
|
|
function developer_feed_items(string $url = DEVELOPER_FEED_URL): array
|
|
{
|
|
$handle = curl_init($url);
|
|
if ($handle === false) {
|
|
throw new RuntimeException('RSS-Feed konnte nicht initialisiert werden.');
|
|
}
|
|
|
|
curl_setopt_array($handle, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_HTTPHEADER => ['Accept: application/rss+xml, application/xml;q=0.9'],
|
|
CURLOPT_USERAGENT => 'NAK-Reinickendorf-Intranet/1.0',
|
|
CURLOPT_CONNECTTIMEOUT => 2,
|
|
CURLOPT_TIMEOUT => 4,
|
|
CURLOPT_FOLLOWLOCATION => true,
|
|
CURLOPT_MAXREDIRS => 3,
|
|
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
|
|
CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS,
|
|
]);
|
|
|
|
try {
|
|
$response = curl_exec($handle);
|
|
$status = (int) curl_getinfo($handle, CURLINFO_HTTP_CODE);
|
|
$error = curl_error($handle);
|
|
} finally {
|
|
curl_close($handle);
|
|
}
|
|
|
|
if ($response === false || $status < 200 || $status >= 300) {
|
|
throw new RuntimeException($error !== '' ? $error : 'HTTP ' . $status);
|
|
}
|
|
|
|
return developer_parse_feed($response);
|
|
}
|
|
|
|
function developer_parse_feed(string $xml): array
|
|
{
|
|
$previous = libxml_use_internal_errors(true);
|
|
try {
|
|
$feed = simplexml_load_string($xml, SimpleXMLElement::class, LIBXML_NONET | LIBXML_NOCDATA);
|
|
} finally {
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
}
|
|
|
|
if ($feed === false || !isset($feed->channel->item)) {
|
|
throw new RuntimeException('Ungültiger RSS-Feed.');
|
|
}
|
|
|
|
$items = [];
|
|
foreach ($feed->channel->item as $item) {
|
|
$title = developer_feed_plain_text((string) $item->title);
|
|
if ($title === '') {
|
|
continue;
|
|
}
|
|
|
|
$description = developer_feed_plain_text((string) $item->description);
|
|
$author = trim((string) $item->children('http://purl.org/dc/elements/1.1/')->creator ?: (string) $item->author);
|
|
if (preg_match('/\(([^)]+)\)\s*$/u', $author, $match) === 1) {
|
|
$author = trim($match[1]);
|
|
} else {
|
|
$author = trim(preg_replace('/<[^>]+>|\S+@\S+/u', '', $author) ?? '');
|
|
}
|
|
$link = trim((string) $item->link);
|
|
|
|
$items[] = [
|
|
'title' => $title,
|
|
'description' => $description !== $title ? $description : '',
|
|
'url' => filter_var($link, FILTER_VALIDATE_URL) && str_starts_with($link, DEVELOPER_REPO_URL) ? $link : DEVELOPER_REPO_URL,
|
|
'author' => $author !== '' ? $author : 'Git',
|
|
'timestamp' => strtotime((string) $item->pubDate) ?: 0,
|
|
];
|
|
}
|
|
|
|
usort($items, static fn(array $a, array $b): int => $b['timestamp'] <=> $a['timestamp']);
|
|
return $items;
|
|
}
|
|
|
|
function developer_feed_groups(array $items, ?DateTimeImmutable $now = null): array
|
|
{
|
|
$now ??= new DateTimeImmutable();
|
|
$today = $now->setTime(0, 0);
|
|
$week = $today->modify('monday this week');
|
|
$month = $today->modify('first day of this month');
|
|
$year = $today->setDate((int) $today->format('Y'), 1, 1);
|
|
$starts = [
|
|
'Heute' => $today,
|
|
'Gestern' => $today->modify('-1 day'),
|
|
'Diese Woche' => $week,
|
|
'Letzte Woche' => $week->modify('-1 week'),
|
|
'Diesen Monat' => $month,
|
|
'Letzten Monat' => $month->modify('-1 month'),
|
|
'Dieses Jahr' => $year,
|
|
'Letztes Jahr' => $year->modify('-1 year'),
|
|
];
|
|
|
|
$groups = [];
|
|
foreach ($items as $item) {
|
|
$date = (new DateTimeImmutable('@' . (int) $item['timestamp']))->setTimezone($now->getTimezone());
|
|
$group = 'Älter';
|
|
foreach ($starts as $label => $start) {
|
|
if ($date >= $start) {
|
|
$group = $label;
|
|
break;
|
|
}
|
|
}
|
|
$groups[$group][] = $item;
|
|
}
|
|
|
|
return $groups;
|
|
}
|
|
|
|
function developer_feed_icon(string $title): string
|
|
{
|
|
$title = strtolower($title);
|
|
|
|
return match (true) {
|
|
str_contains($title, 'merge pull'), str_starts_with($title, 'merge ') => 'M7 3a3 3 0 1 0-2 2.83v12.34a3 3 0 1 0 2 0V9h4a4 4 0 0 1 4 4v1.17a3 3 0 1 0 2 0V13a6 6 0 0 0-6-6H7V5.83A3 3 0 0 0 7 3Z',
|
|
preg_match('/\b(fix|fixed|bugfix|hotfix|repair)\b/u', $title) === 1 => 'M14.7 6.3a5 5 0 0 0-6.47 6.47l-5.94 5.94a1 1 0 0 0 0 1.42l1.58 1.58a1 1 0 0 0 1.42 0l5.94-5.94A5 5 0 0 0 17.7 9.3l-3 3-3-3 3-3Z',
|
|
preg_match('/\b(remove|removed|delete|deleted|revert)\b/u', $title) === 1 => 'M9 3h6l1 2h4v2H4V5h4l1-2Zm-3 6h12l-1 12H7L6 9Zm3 2v8h2v-8H9Zm4 0v8h2v-8h-2Z',
|
|
preg_match('/\b(refactor|cleanup|clean up|simplif|optimi[sz])\w*/u', $title) === 1 => 'M7 3a3 3 0 1 0-2 2.83v12.34a3 3 0 1 0 2 0V14h3a5 5 0 0 0 5-5V7.83a3 3 0 1 0-2 0V9a3 3 0 0 1-3 3H7V5.83A3 3 0 0 0 7 3Z',
|
|
preg_match('/\b(doc|docs|readme|wiki|tutorial)\w*/u', $title) === 1 => 'M4 3h7a2 2 0 0 1 2 2v15a3 3 0 0 0-3-3H4V3Zm16 0h-5v14h5V3Zm0 16h-5a1 1 0 0 0-1 1h6v-1Z',
|
|
preg_match('/\b(add|added|new|create|created|introduce|introduced|implement|implemented)\b/u', $title) === 1 => 'M11 5h2v6h6v2h-6v6h-2v-6H5v-2h6V5Z',
|
|
default => 'M12 3a4 4 0 0 0-3.87 3H3v2h5.13a4 4 0 0 0 7.74 0H21V6h-5.13A4 4 0 0 0 12 3Zm0 2a2 2 0 1 1 0 4 2 2 0 0 1 0-4Zm0 10a4 4 0 0 0-3.87 3H3v2h5.13a4 4 0 0 0 7.74 0H21v-2h-5.13A4 4 0 0 0 12 15Zm0 2a2 2 0 1 1 0 4 2 2 0 0 1 0-4Z',
|
|
};
|
|
}
|