Files
intranet/web/intranet_repo/projects/developerpanel/util/developer_helpers.php
T

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',
};
}