monatsplan - ckalender read fixed, developerpanel - rss feed implemented
This commit is contained in:
@@ -4,16 +4,28 @@
|
||||
// Initialisierung
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
|
||||
|
||||
require_once __DIR__.'/util/developer_helpers.php';
|
||||
|
||||
// Zugriff nur für Entwickler
|
||||
if (!isset($groups["Entwickler"]) || $groups["Entwickler"] != 1) {
|
||||
header("Location: {$g_httpHomePath}");
|
||||
exit();
|
||||
}
|
||||
|
||||
$feedError = '';
|
||||
try {
|
||||
$feedItems = developer_feed_items();
|
||||
} catch (RuntimeException) {
|
||||
$feedItems = [];
|
||||
$feedError = 'Der Git-RSS-Feed ist momentan nicht erreichbar.';
|
||||
}
|
||||
$feedGroups = developer_feed_groups($feedItems);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
<link rel="stylesheet" href="/intranet_repo/projects/_central/layouts/developerpanel.css">
|
||||
</head>
|
||||
<body class="theme-green">
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
|
||||
@@ -26,26 +38,81 @@
|
||||
<div class="mainpage_container">
|
||||
<button type="button" class="button_mainpage unselectable bg_green" onclick="window.open('https://git.pelpino.de/NAK-Reinickendorf/intranet', '_blank', 'noopener,noreferrer');">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="32" height="32" aria-hidden="true" focusable="false"><path d="M23.546 10.93 13.067.452a1.55 1.55 0 0 0-2.188 0L8.708 2.627l2.76 2.76a1.838 1.838 0 0 1 2.327 2.341l2.658 2.66a1.837 1.837 0 1 1-1.102 1.033L12.87 8.94v6.526a1.837 1.837 0 1 1-1.512-.053V8.828a1.838 1.838 0 0 1-.999-2.409L7.636 3.697.45 10.881a1.55 1.55 0 0 0 0 2.188l10.48 10.48a1.55 1.55 0 0 0 2.186 0l10.43-10.43a1.55 1.55 0 0 0 0-2.188Z"/></svg>
|
||||
<div class="text">Git Repository</div>
|
||||
<div class="text">Quellcode (Git)</div>
|
||||
</button>
|
||||
|
||||
<button type="button" class="button_mainpage unselectable bg_green" onclick="window.open('https://pgadmin.pelpino.de/', '_blank', 'noopener,noreferrer');">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="32" height="32" aria-hidden="true" focusable="false"><path d="M12 2C7.03 2 3 3.79 3 6v12c0 2.21 4.03 4 9 4s9-1.79 9-4V6c0-2.21-4.03-4-9-4Zm0 2c4.42 0 7 .99 7 2s-2.58 2-7 2-7-.99-7-2 2.58-2 7-2Zm0 16c-4.42 0-7-.99-7-2v-2.13C6.63 16.57 9.14 17 12 17s5.37-.43 7-1.13V18c0 1.01-2.58 2-7 2Zm0-5c-4.42 0-7-.99-7-2v-2.13C6.63 11.57 9.14 12 12 12s5.37-.43 7-1.13V13c0 1.01-2.58 2-7 2Z"/></svg>
|
||||
<div class="text">Postgres-Datenbank</div>
|
||||
<div class="text">Datenbank (Postgres)</div>
|
||||
</button>
|
||||
|
||||
<button type="button" class="button_mainpage unselectable bg_green" onclick="window.open('https://git.pelpino.de/NAK-Reinickendorf/intranet/projects/2', '_blank', 'noopener,noreferrer');">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="32" height="32" aria-hidden="true" focusable="false"><path d="M3 3h5v18H3V3Zm6.5 0h5v11h-5V3ZM16 3h5v7h-5V3Zm0 8.5h5V21h-5v-9.5ZM9.5 15.5h5V21h-5v-5.5Z"/></svg>
|
||||
<div class="text">Kanban-Board</div>
|
||||
<div class="text">Projekt-Board</div>
|
||||
</button>
|
||||
|
||||
<button type="button" class="button_mainpage unselectable bg_green" onclick="window.open('https://git.pelpino.de/NAK-Reinickendorf/intranet/wiki', '_blank', 'noopener,noreferrer');">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="32" height="32" aria-hidden="true" focusable="false"><path d="M4 3a3 3 0 0 0-3 3v13.5A1.5 1.5 0 0 0 2.5 21H11V5.5A2.5 2.5 0 0 0 8.5 3H4Zm16 0h-4.5A2.5 2.5 0 0 0 13 5.5V21h8.5a1.5 1.5 0 0 0 1.5-1.5V6a3 3 0 0 0-3-3Z"/></svg>
|
||||
<div class="text">Wiki</div>
|
||||
</button>
|
||||
|
||||
<section class="content_panel developer-feed" aria-labelledby="developer-feed-title">
|
||||
<div class="developer-feed_header">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.002 2.725a.75.75 0 0 1 .797-.699C8.79 2.42 13.58 7.21 13.974 13.201a.75.75 0 0 1-1.497.098 10.5 10.5 0 0 0-9.776-9.776.747.747 0 0 1-.7-.798ZM2.84 7.05h-.002a7 7 0 0 1 6.113 6.111.75.75 0 0 1-1.49.178 5.5 5.5 0 0 0-4.8-4.8.75.75 0 0 1 .179-1.489M2 13a1 1 0 1 1 2 0 1 1 0 0 1-2 0"/></svg>
|
||||
<h2 id="developer-feed-title">Git-Aktivität</h2>
|
||||
</div>
|
||||
|
||||
<?php if ($feedItems === []): ?>
|
||||
<p class="developer-feed_empty"><?= $feedError ?: 'Noch keine Git-Aktivität vorhanden.' ?></p>
|
||||
<?php else: ?>
|
||||
<div id="developer-feed-list">
|
||||
<?php $itemIndex = 0; ?>
|
||||
<?php foreach ($feedGroups as $group => $items): ?>
|
||||
<section class="developer-feed_group"<?= $itemIndex >= DEVELOPER_FEED_LIMIT ? ' hidden' : '' ?>>
|
||||
<h3><?= htmlspecialchars($group, ENT_QUOTES, 'UTF-8') ?></h3>
|
||||
<ul>
|
||||
<?php foreach ($items as $item): ?>
|
||||
<li<?= $itemIndex++ >= DEVELOPER_FEED_LIMIT ? ' hidden' : '' ?>>
|
||||
<a href="<?= htmlspecialchars($item['url'], ENT_QUOTES, 'UTF-8') ?>" target="_blank" rel="noopener noreferrer">
|
||||
<span class="developer-feed_icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="<?= developer_feed_icon($item['title']) ?>"/></svg></span>
|
||||
<span class="developer-feed_message">
|
||||
<strong><?= htmlspecialchars($item['title'], ENT_QUOTES, 'UTF-8') ?></strong>
|
||||
<?php if ($item['description'] !== ''): ?>
|
||||
<span class="developer-feed_description"><?= htmlspecialchars($item['description'], ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<?php endif; ?>
|
||||
<span class="developer-feed_meta">
|
||||
<span><?= htmlspecialchars($item['author'], ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<?php if ($item['timestamp'] > 0): ?><span>·</span><time datetime="<?= date('c', $item['timestamp']) ?>"><?= date('d.m.Y · H:i', $item['timestamp']) ?></time><?php endif; ?>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</section>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php if (count($feedItems) > DEVELOPER_FEED_LIMIT): ?>
|
||||
<button class="developer-feed_more" type="button" id="developer-feed-more" aria-expanded="false" aria-controls="developer-feed-list">Mehr anzeigen</button>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
|
||||
<?php if (count($feedItems) > DEVELOPER_FEED_LIMIT): ?>
|
||||
<script>
|
||||
document.getElementById('developer-feed-more').addEventListener('click', function () {
|
||||
const expanded = this.getAttribute('aria-expanded') === 'true';
|
||||
const list = document.getElementById('developer-feed-list');
|
||||
list.querySelectorAll('li').forEach((item, index) => item.hidden = expanded && index >= <?= DEVELOPER_FEED_LIMIT ?>);
|
||||
list.querySelectorAll('.developer-feed_group').forEach(group => group.hidden = !group.querySelector('li:not([hidden])'));
|
||||
this.setAttribute('aria-expanded', String(!expanded));
|
||||
this.textContent = expanded ? 'Mehr anzeigen' : 'Weniger anzeigen';
|
||||
});
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
<?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',
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user