Unser PHP Script CoreNews ist ein minimalistischer, Dashboard RSS Reader mit Fokus auf Design und einfachster Handhabung. Quellen können direkt im Dashboard hinzugefügt, kategorisiert (Tech, Politik, Sport etc.) oder gelöscht werden. Via Cronjob wäre das Script via zB. index.php?sync=1 aufrufbar.
<?php
$cssPath = 'css/style.css';
$jsPath = 'js/script.js';
$dbPath = 'data/news_ultra.sqlite';
$error = null;
try {
if (!is_dir('css')) mkdir('css', 0755, true);
if (!is_dir('js')) mkdir('js', 0755, true);
if (!is_dir('data')) mkdir('data', 0755, true);
if (!file_exists($cssPath) || filemtime(__FILE__) > filemtime($cssPath)) {
$css = "
:root {
--bg: #030712; --sidebar-bg: rgba(0, 0, 0, 0.4); --card-bg: rgba(17, 24, 39, 0.7);
--accent: #4f46e5; --accent-glow: rgba(79, 70, 229, 0.4); --text-main: #cbd5e1;
--text-bright: #ffffff; --text-dim: #475569; --border: rgba(255, 255, 255, 0.05);
--error: #ef4444;
}
* { box-sizing: border-box; }
body { font-family: 'Plus Jakarta Sans', sans-serif; background: var(--bg); margin: 0; color: var(--text-main); display: flex; height: 100vh; overflow: hidden; }
.sidebar { width: 320px; background: var(--sidebar-bg); backdrop-filter: blur(20px); border-right: 1px solid var(--border); padding: 40px; display: flex; flex-direction: column; height: 100vh; flex-shrink: 0; }
.content-area { flex: 1; overflow-y: auto; padding: 60px 80px; }
.alert { background: rgba(239, 68, 68, 0.1); border: 1px solid var(--error); color: var(--error); padding: 15px; border-radius: 12px; margin-bottom: 30px; font-size: 13px; font-weight: 600; }
.logo-container { display: flex; align-items: center; gap: 15px; margin-bottom: 50px; }
.logo-icon { width: 45px; height: 45px; background: var(--accent); border-radius: 15px; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 25px var(--accent-glow); transform: rotate(12deg); }
.logo-icon svg { transform: rotate(-12deg); }
.logo-text h1 { margin: 0; font-size: 20px; color: var(--text-bright); letter-spacing: -1px; }
.logo-text span { font-size: 8px; font-weight: 800; color: var(--text-dim); letter-spacing: 2px; text-transform: uppercase; display: block; }
.content-header { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 60px; }
.headline-group p { color: var(--text-dim); font-weight: 600; margin: 10px 0 0 0; }
.headline { font-size: 42px; font-weight: 800; margin: 0; letter-spacing: -2px; }
.gradient-text { background: linear-gradient(to right, #818cf8, #c084fc); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.section-title { font-size: 10px; font-weight: 900; color: #64748b; text-transform: uppercase; margin-bottom: 15px; letter-spacing: 1px; }
.input-field { width: 100%; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 12px; padding: 12px; color: white; margin-bottom: 12px; outline: none; transition: 0.3s; }
.btn-primary { background: var(--accent); color: white; border: none; padding: 14px; border-radius: 12px; font-weight: 800; cursor: pointer; text-transform: uppercase; font-size: 11px; letter-spacing: 1px; transition: 0.3s; display: inline-flex; align-items: center; justify-content: center; text-decoration: none; }
.btn-primary:hover { background: #4338ca; transform: translateY(-2px); box-shadow: 0 10px 20px rgba(79, 70, 229, 0.3); }
.btn-sidebar { width: 100%; }
.feed-list { margin-top: 30px; overflow-y: auto; flex-grow: 1; }
.feed-item { display: flex; justify-content: space-between; align-items: center; padding: 12px; background: rgba(255, 255, 255, 0.03); border-radius: 12px; margin-bottom: 8px; font-size: 12px; }
.news-grid { column-count: 3; column-gap: 24px; }
@media (max-width: 1400px) { .news-grid { column-count: 2; } }
@media (max-width: 900px) { .news-grid { column-count: 1; } }
.news-card { break-inside: avoid; background: var(--card-bg); backdrop-filter: blur(12px); border: 1px solid var(--border); border-radius: 2.5rem; padding: 30px; margin-bottom: 24px; transition: 0.5s ease; }
.news-card:hover { border-color: var(--accent); box-shadow: 0 0 25px rgba(99, 102, 241, 0.2); }
.card-meta { display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px; }
.category-tag { font-size: 9px; font-weight: 900; padding: 4px 12px; background: rgba(99, 102, 241, 0.1); color: #818cf8; border: 1px solid rgba(99, 102, 241, 0.2); border-radius: 10px; text-transform: uppercase; font-style: italic; }
.news-title { font-size: 19px; font-weight: 700; line-height: 1.35; margin-bottom: 15px; }
.news-title a { color: var(--text-bright); text-decoration: none; }
.news-excerpt { font-size: 13px; color: #94a3b8; line-height: 1.6; margin-bottom: 30px; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.card-footer { display: flex; justify-content: space-between; align-items: center; padding-top: 20px; border-top: 1px solid var(--border); }
.btn-delete { color: #ef4444; text-decoration: none; font-size: 10px; font-weight: 900; opacity: 0.4; transition: 0.3s; }
.btn-delete:hover { opacity: 1; }
::-webkit-scrollbar { width: 5px; }
::-webkit-scrollbar-thumb { background: #1f2937; border-radius: 10px; }
";
file_put_contents($cssPath, $css);
}
} catch (Exception $e) { $error = "Dateisystem-Fehler: " . $e->getMessage(); }
try {
$db = new PDO("sqlite:$dbPath");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec("CREATE TABLE IF NOT EXISTS feeds (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, url TEXT UNIQUE, cat TEXT)");
$db->exec("CREATE TABLE IF NOT EXISTS news (id INTEGER PRIMARY KEY AUTOINCREMENT, fid INTEGER, title TEXT, link TEXT UNIQUE, txt TEXT, date DATETIME)");
if (isset($_POST['add'])) {
$url = filter_var($_POST['u'], FILTER_VALIDATE_URL);
if ($url) {
$s = $db->prepare("INSERT OR IGNORE INTO feeds (name, url, cat) VALUES (?, ?, ?)");
$s->execute([htmlspecialchars($_POST['n']), $url, htmlspecialchars($_POST['c'])]);
header("Location: ?"); exit;
} else { $error = "Ungültige URL angegeben."; }
}
if (isset($_GET['del'])) {
$db->prepare("DELETE FROM feeds WHERE id = ?")->execute([$_GET['del']]);
header("Location: ?"); exit;
}
if (isset($_GET['sync'])) {
$res = $db->query("SELECT * FROM feeds");
while ($f = $res->fetch(PDO::FETCH_ASSOC)) {
libxml_use_internal_errors(true);
$xml = @simplexml_load_file($f['url']);
if ($xml === false) { continue; /* Stillschweigend überspringen */ }
foreach ($xml->channel->item as $i) {
$s = $db->prepare("INSERT OR IGNORE INTO news (fid, title, link, txt, date) VALUES (?, ?, ?, ?, ?)");
$s->execute([$f['id'], (string)$i->title, (string)$i->link, strip_tags((string)$i->description), date('Y-m-d H:i:s', strtotime((string)$i->pubDate))]);
}
}
header("Location: ?"); exit;
}
} catch (PDOException $e) { $error = "Datenbank-Fehler: " . $e->getMessage(); }
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>CoreNews RSS</title>
<link rel="stylesheet" href="<?= $cssPath ?>">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;800&display=swap" rel="stylesheet">
</head>
<body>
<aside class="sidebar">
<div class="logo-container">
<div class="logo-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3"><path d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
</div>
<div class="logo-text">
<h1>CORE<span class="gradient-text">NEWS</span></h1>
<span>RSS</span>
</div>
</div>
<form method="POST">
<p class="section-title">Quelle hinzufügen</p>
<input type="text" name="n" placeholder="Name" required class="input-field">
<input type="url" name="u" placeholder="RSS Link" required class="input-field">
<select name="c" class="input-field">
<option>Technologie</option><option>Wirtschaft</option><option>Politik</option><option>Wissenschaft</option>
</select>
<button name="add" class="btn-primary btn-sidebar">Add Channel</button>
</form>
<div class="feed-list">
<p class="section-title">Aktive Kanäle</p>
<?php
try {
$fl = $db->query("SELECT * FROM feeds ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC);
foreach($fl as $f): ?>
<div class="feed-item">
<span><?= htmlspecialchars($f['name']) ?></span>
<a href="?del=<?= $f['id'] ?>" class="btn-delete">LÖSCHEN</a>
</div>
<?php endforeach; } catch(Exception $e) {} ?>
</div>
</aside>
<main class="content-area">
<?php if ($error): ?>
<div class="alert">⚠️ <?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<div class="content-header">
<div class="headline-group">
<h2 class="headline">Latest <span class="gradient-text">Intelligence</span></h2>
<p>Status: System online & gesichert.</p>
</div>
<a href="?sync=1" class="btn-primary">Sync Starten</a>
</div>
<div class="news-grid">
<?php
try {
$news = $db->query("SELECT n.*, f.name as fn, f.cat as fc FROM news n JOIN feeds f ON n.fid = f.id ORDER BY n.date DESC LIMIT 45");
while($n = $news->fetch(PDO::FETCH_ASSOC)): ?>
<article class="news-card">
<div class="card-meta">
<span class="category-tag"><?= htmlspecialchars($n['fc']) ?></span>
<span class="card-time"><?= date('H:i', strtotime($n['date'])) ?> Uhr</span>
</div>
<h3 class="news-title"><a href="<?= htmlspecialchars($n['link']) ?>" target="_blank"><?= htmlspecialchars($n['title']) ?></a></h3>
<p class="news-excerpt"><?= htmlspecialchars($n['txt']) ?></p>
<div class="card-footer">
<span class="source-name"><?= htmlspecialchars($n['fn']) ?></span>
<a href="<?= htmlspecialchars($n['link']) ?>" target="_blank" class="read-more">QUELLE →</a>
</div>
</article>
<?php endwhile; } catch(Exception $e) { echo "<p>Noch keine Nachrichten vorhanden. Bitte synchronisieren.</p>"; } ?>
</div>
</main>
</body>
</html>
