[-v] * * Beispiele: * php bin/anzeige-add.php ~/Desktop/anzeigen/ alle Bilder im Ordner * php bin/anzeige-add.php ~/Desktop/eldorado.jpg nur diese eine * * DER DATEINAME IST DER SLUG: `eldorado.jpg` wird zu `img/anzeigen/eldorado-*.jpg` * und findet damit den bestehenden Eintrag wieder. Ein neues Motiv für einen * bekannten Sponsor heißt also einfach wieder `eldorado.jpg`. * * Anzeigen sind ein DAUERHAFTER Bestand (data/anzeigen.json), kein Zubehör einer * einzelnen Ausgabe: Sponsorenmotive ändern sich selten, die Stadionzeitung * erscheint oft. bin/stadionzeitung-add.php holt sie sich von hier, pro Heft ist * nichts abzuliefern. Vorher lag jede Anzeige als Kopie unter jeder Ausgabe und * ihr Alt-Text musste jedes Mal neu geschrieben werden. * * Das Skript schreibt ausschließlich `base` und `widths`. `sponsor`, `alt`, * `aktiv` und die REIHENFOLGE der Liste (= Reihenfolge im Heft) sind * chat-gepflegt und überleben jeden Lauf unangetastet — dasselbe Prinzip wie bei * bin/veranstaltung-bilder.php. Bei einem neuen Sponsor wird `sponsor` aus dem * Dateinamen vorbelegt und muss im Chat richtiggestellt werden; das Skript sagt, * bei welchen. */ if (PHP_SAPI !== 'cli') { exit(1); } require dirname(__DIR__) . '/app/bootstrap.php'; $args = array_values(array_filter(array_slice($argv, 1), static fn (string $a): bool => $a !== '-v')); $verbose = in_array('-v', $argv, true); $log = static function (string $msg, string $level = 'INFO') use ($verbose): void { log_write('stadionzeitung', $level, $msg); if ($verbose || $level === 'ERROR' || $level === 'WARN') { echo '[' . date('c') . "] {$level} {$msg}\n"; } }; $fail = static function (string $msg) use ($log): never { $log("{$msg} — bestehende Daten bleiben unangetastet.", 'ERROR'); exit(1); }; if ($args === []) { fwrite(STDERR, "Nutzung: php bin/anzeige-add.php [-v]\n"); exit(1); } // --- Quelldateien einsammeln --- $exts = 'jpg,jpeg,png,webp,JPG,JPEG,PNG,WEBP'; $quelle = $args[0]; if (is_dir($quelle)) { $sources = glob(rtrim($quelle, '/') . "/*.{{$exts}}", GLOB_BRACE) ?: []; natsort($sources); $sources = array_values($sources); } elseif (is_file($quelle)) { $sources = [$quelle]; } else { $fail("Nicht gefunden: {$quelle}"); } if ($sources === []) { $fail("Keine Bilder in {$quelle} gefunden."); } // Selbst-Import verhindern: zeigt jemand auf den Zielordner, wären die Quellen // die erzeugten Varianten (`eldorado-1440.jpg`), und aus jeder Breite entstünde // ein eigener Sponsor („eldorado 1440"). Einmal reingelaufen, deshalb der Riegel. $zielReal = realpath(PUBLIC_PATH . '/assets/img/anzeigen'); foreach ($sources as $src) { if ($zielReal !== false && realpath(dirname($src)) === $zielReal) { $fail('Quelle liegt im Zielordner public/assets/img/anzeigen/ — das sind die erzeugten Varianten, nicht die Originale.'); } } // --- Lock gegen parallele Läufe --- if (!is_dir(STORAGE_PATH . '/cache')) { mkdir(STORAGE_PATH . '/cache', 0755, true); } $lock = fopen(STORAGE_PATH . '/cache/anzeigen.lock', 'c'); if (!$lock || !flock($lock, LOCK_EX | LOCK_NB)) { $log('Läuft bereits (Lock belegt) — Lauf übersprungen.', 'WARN'); exit(1); } $dataFile = DATA_PATH . '/anzeigen.json'; $data = is_file($dataFile) ? (json_decode((string) file_get_contents($dataFile), true) ?: ['anzeigen' => []]) : ['anzeigen' => []]; $destDir = PUBLIC_PATH . '/assets/img/anzeigen'; if (!is_dir($destDir)) { mkdir($destDir, 0755, true); } $widths = [480, 960, 1440]; /** Dateiname zu Slug: Kleinschreibung, Umlaute weg, alles andere zu Bindestrichen. */ $slugify = static function (string $text): string { $text = strtr($text, ['ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'Ä' => 'Ae', 'Ö' => 'Oe', 'Ü' => 'Ue', 'ß' => 'ss']); $text = strtolower($text); return trim(preg_replace('/[^a-z0-9]+/', '-', $text) ?? '', '-'); }; $neu = []; $erneuert = []; foreach ($sources as $src) { $slug = $slugify(pathinfo($src, PATHINFO_FILENAME)); if ($slug === '') { $log('Dateiname ergibt keinen Slug, übersprungen: ' . basename($src), 'WARN'); continue; } // Alte Varianten dieses Slugs weg, sonst bleiben bei einem kleineren Motiv // Breiten von vorher liegen und die JSON verspricht Dateien, die zum Bild // nicht mehr passen. foreach (glob("{$destDir}/{$slug}-*.jpg") ?: [] as $alt) { unlink($alt); } try { // Qualität 82 wie bei den Plakaten: Anzeigen tragen Text, und Ringing um // Buchstaben sieht man. $variants = resize_image_variants($src, "{$destDir}/{$slug}", $widths, 82); } catch (RuntimeException $e) { $fail('Anzeige nicht verarbeitbar (' . basename($src) . '): ' . $e->getMessage()); } $generated = array_values(array_unique(array_column($variants, 'width'))); sort($generated); // Bestehenden Eintrag suchen und NUR base/widths ersetzen. $index = null; foreach ($data['anzeigen'] as $i => $eintrag) { if (($eintrag['slug'] ?? '') === $slug) { $index = $i; break; } } if ($index === null) { // Neuer Sponsor: Platzhalter-Name aus dem Slug, im Chat zu ersetzen. $data['anzeigen'][] = [ 'slug' => $slug, 'sponsor' => str_replace('-', ' ', $slug), 'aktiv' => true, 'base' => "img/anzeigen/{$slug}", 'widths' => $generated, ]; $neu[] = $slug; } else { $data['anzeigen'][$index]['base'] = "img/anzeigen/{$slug}"; $data['anzeigen'][$index]['widths'] = $generated; $erneuert[] = $slug; } $log("Anzeige {$slug}: " . implode('/', $generated) . 'px'); } $payload = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); $tmp = $dataFile . '.tmp'; file_put_contents($tmp, $payload . "\n"); if (!rename($tmp, $dataFile)) { $fail('Konnte data/anzeigen.json nicht schreiben.'); } $log(sprintf('Anzeigen-Bestand aktualisiert: %d neu, %d erneuert.', count($neu), count($erneuert))); echo 'Anzeigen-Bestand: ' . count($data['anzeigen']) . ' Motive, ' . count($neu) . ' neu, ' . count($erneuert) . " erneuert.\n"; if ($neu !== []) { echo "Sponsorenname noch Platzhalter bei: " . implode(', ', $neu) . "\n"; echo "In data/anzeigen.json richtigstellen (daraus entsteht der Alt-Text „Anzeige: …\").\n"; }