Ordnername. Früh breit, später enger — dort wird das Material dichter. */ const CHRONIK_BUCKETS = [ '1908-1929' => '1908-1929', '1930-1949' => '1930-1949', '1950-1969' => '1950-1969', '1970-1989' => '1970-1989', '1990-2009' => '1990-2009', '2010-2028' => '2010-2028', ]; const CHRONIK_DOKUMENTE = 'Festschriften-und-Dokumente'; const CHRONIK_UNDATIERT = 'Undatiert'; const CHRONIK_README = '00_LIESMICH.txt'; const CHRONIK_RECHTE = '00_RECHTE-ZU-KLAEREN.txt'; /** Register liegt im REPO, nicht im Uploads-Ordner: uploads/ ist gitignored, das Register braucht Versionsgeschichte. */ function chronik_register_path(): string { return ROOT_PATH . '/docs/chronik/register.csv'; } function chronik_root(): string { return STORAGE_PATH . '/dateien/uploads/Vereinshistorie'; } /** * Wayback-Adressen auf die ursprüngliche Adresse zurückführen. * * Das Internet Archive ist nur der Aufbewahrungsort — Urheber und Rechte richten sich nach * der Seite, die dort eingefroren wurde. Ein Schnappschuss der eigenen alten Vereinsseite * gehört dem Verein, nicht dem Archiv. */ function chronik_origin_url(string $url): string { if (preg_match('#^https?://web\.archive\.org/web/[^/]+/(https?://.+)$#i', $url, $m)) { return $m[1]; } return $url; } /** * Rechtelage aus dem Host ableiten. * * Die Einschätzung ist eine ARBEITSHILFE, keine Rechtsauskunft — sie sagt nur, wo geklärt * werden muss, bevor etwas gedruckt oder auf die Website gestellt wird. Sammeln und Sichten * ist das eine, Veröffentlichen das andere; genau diese Grenze markiert das Feld. * * @return array{status:string, klaeren:bool, hinweis:string} */ function chronik_rights(string $host): array { $host = strtolower(preg_replace('/^www\./', '', $host)); $map = [ 'infranken.de' => [ 'Presse © Mediengruppe Oberfranken (inFranken/Bayerische Rundschau)', true, 'Text UND Foto sind geschützt. Für Abdruck in der Chronik bzw. Nutzung auf der Website ' . 'Nachdruckrechte beim Verlag anfragen (Mediengruppe Oberfranken, Bamberg). Für ' . 'Vereinsjubiläen wird das üblicherweise gegen Quellenangabe eingeräumt — aber schriftlich, und früh.', ], 'bayerische-rundschau.de' => [ 'Presse © Mediengruppe Oberfranken (Bayerische Rundschau)', true, 'Wie inFranken — gleicher Verlag. Nachdruckrechte schriftlich einholen.', ], 'frankenpost.de' => [ 'Presse © Mediengruppe Oberfranken (Frankenpost)', true, 'Gleiches Haus wie Bayerische Rundschau/inFranken — eine gemeinsame Anfrage an die ' . 'Mediengruppe Oberfranken deckt alle drei Titel ab.', ], 'fraenkischertag.de' => [ 'Presse © Mediengruppe Oberfranken (Fränkischer Tag)', true, 'Vierter Titel des Hauses, dieselbe Anfrage deckt ihn mit ab. Achtung beim Lokalsport ' . 'Kulmbach: dort steht oft „Foto: privat" — das Bild kommt dann aus dem Verein oder von ' . 'einer Privatperson, die Bildrechte liegen bei ihr und nicht beim Verlag. Wer das ist, ' . 'weiß die Redaktion oder die Mannschaft; danach ist es ein Anruf statt einer Anfrage.', ], 'kicker.de' => [ 'Presse © kicker / Olympia-Verlag', true, 'Reine Stammdaten (Liga, Spielort) sind Fakten und frei; Texte und Fotos nicht.', ], 'europlan-online.de' => [ '© Europlan-Online (Groundhopping-Portal)', true, 'Stadiondaten als Fakten frei verwendbar; die Stadionfotos gehören den einzelnen ' . 'Fotografen und sind einzeln anzufragen.', ], 'turneninkulmbach.info' => [ 'Turnabteilung TSV 08 Kulmbach — eigenes Material', false, 'Seite der eigenen Turnabteilung. Intern klären, wer sie betreut, dann sind die ' . 'Inhalte für die Chronik verfügbar.', ], 'anpfiff.info' => [ '© anpfiff.info', true, 'Amateurfußball-Portal. Ergebnisse frei, Berichte und Fotos nicht.', ], 'bfv.de' => [ '© Bayerischer Fußball-Verband', true, 'Reine Ergebnis- und Tabellendaten (Fakten) sind frei verwendbar; Texte, Fotos und Wappen nicht. ' . 'Wappen fremder Vereine nur mit deren Zustimmung abbilden.', ], 'fussball.de' => [ '© DFB / Bayerischer Fußball-Verband', true, 'Ergebnisse und Tabellenstände als Fakten frei; Seitenaufbau, Texte und Fotos nicht.', ], 'deutsche-turnliga.de' => [ '© Deutsche Turnliga', true, 'Ergebnisdaten frei, Texte/Fotos nicht.', ], 'tsv08kulmbach.de' => [ 'Eigenes Material (TSV 08 Kulmbach)', false, 'Aus dem Bestand des Vereins — nutzbar. Bei Personenfotos trotzdem das Recht am eigenen Bild beachten.', ], // Frühere Vereinsdomain (Jimdo, ~2016–2023). DNS läuft nicht mehr, erreichbar nur // noch über das Internet Archive — inhaltlich aber unser eigenes Material. 'tsv1908kulmbach.de' => [ 'Eigenes Material (TSV 08 Kulmbach, frühere Vereinsseite)', false, 'Alte Vereinsseite, nur noch im Internet Archive. Inhalte stammen vom Verein selbst. ' . 'Bei Personenfotos das Recht am eigenen Bild beachten.', ], 'commons.wikimedia.org' => [ 'Wikimedia Commons — freie Lizenz, Bedingung prüfen', true, 'Frei nutzbar, aber fast immer MIT Namensnennung und Lizenzangabe. Die konkrete Lizenz ' . '(CC BY-SA, CC0, PD) auf der Dateiseite ablesen und im Register eintragen.', ], 'de.wikipedia.org' => [ 'Wikipedia — CC BY-SA', true, 'Text nur mit Namensnennung und unter gleicher Lizenz weiterverwendbar. Für eine Chronik ' . 'besser als Faktenquelle nutzen und selbst formulieren.', ], 'facebook.com' => [ 'Social Media — Urheber unklar', true, 'Wer das Foto gemacht hat, steht dort meist nicht. Vor einer Veröffentlichung Urheber ' . 'ermitteln (oft der Verein selbst oder ein Mitglied) und Einwilligung abgeborgener Personen prüfen.', ], 'instagram.com' => [ 'Social Media — Urheber unklar', true, 'Wie Facebook. Eigene Vereinsposts sind unkritisch, fremde nicht.', ], 'digipress.digitale-sammlungen.de' => [ 'Bayerische Staatsbibliothek — meist gemeinfrei', false, 'Zeitungen bis ~1920er sind in der Regel gemeinfrei. Quellenangabe (Blatt, Datum, Seite, BSB) trotzdem mitführen.', ], ]; foreach ($map as $needle => [$status, $klaeren, $hinweis]) { if ($host === $needle || str_ends_with($host, '.' . $needle)) { return ['status' => $status, 'klaeren' => $klaeren, 'hinweis' => $hinweis]; } } return [ 'status' => 'Ungeklärt — Urheber unbekannt', 'klaeren' => true, 'hinweis' => 'Herkunft im Register nachtragen und vor jeder Veröffentlichung klären.', ]; } /** * Datum (YYYY, YYYY-MM oder YYYY-MM-DD) → Zielordner. * Unbekanntes oder unplausibles Datum landet in Undatiert/ statt still im falschen Jahrzehnt. */ function chronik_bucket(?string $date): string { if ($date === null || !preg_match('/^(\d{4})/', $date, $m)) { return CHRONIK_UNDATIERT; } $year = (int) $m[1]; foreach (CHRONIK_BUCKETS as $range => $dir) { [$from, $to] = array_map('intval', explode('-', $range)); if ($year >= $from && $year <= $to) { return $dir; } } return CHRONIK_UNDATIERT; } /** Datum auf YYYY-MM-DD normalisieren; unbekannte Teile werden 00 (sortiert vor dem 1. des Monats). */ function chronik_date_norm(?string $date): string { if ($date === null || !preg_match('/^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?/', $date, $m)) { return '0000-00-00'; } return sprintf('%s-%s-%s', $m[1], $m[2] ?? '00', $m[3] ?? '00'); } /** Titel → dateinamensicherer Slug. Umlaute ausgeschrieben, damit NFD/NFC nie zum Thema wird. */ function chronik_slug(string $text, int $maxLen = 60): string { $text = strtr($text, [ 'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'Ä' => 'Ae', 'Ö' => 'Oe', 'Ü' => 'Ue', 'ß' => 'ss', 'é' => 'e', 'è' => 'e', 'ê' => 'e', 'á' => 'a', 'à' => 'a', 'ç' => 'c', ]); $text = (string) preg_replace('/[^A-Za-z0-9]+/', '-', $text); $text = trim((string) preg_replace('/-+/', '-', $text), '-'); if (strlen($text) > $maxLen) { $text = substr($text, 0, $maxLen); $text = (string) preg_replace('/-[^-]*$/', '', $text) ?: $text; } return $text !== '' ? $text : 'ohne-titel'; } /** Kurzkürzel der Quelle für den Dateinamen (BR, BFV, …). */ function chronik_source_tag(string $host): string { $host = strtolower(preg_replace('/^www\./', '', $host)); $tags = [ 'infranken.de' => 'BR', 'bayerische-rundschau.de' => 'BR', 'frankenpost.de' => 'FP', 'fraenkischertag.de' => 'FT', 'kicker.de' => 'KICKER', 'europlan-online.de' => 'EUROPLAN', 'anpfiff.info' => 'ANPFIFF', 'turneninkulmbach.info' => 'TURNEN08', 'tsv1908kulmbach.de' => 'TSV08', 'bfv.de' => 'BFV', 'fussball.de' => 'FUSSBALLDE', 'deutsche-turnliga.de' => 'DTL', 'tsv08kulmbach.de' => 'TSV08', 'facebook.com' => 'FB', 'instagram.com' => 'IG', 'de.wikipedia.org' => 'WIKIPEDIA', 'commons.wikimedia.org' => 'COMMONS', 'digipress.digitale-sammlungen.de' => 'BSB', 'web.archive.org' => 'WAYBACK', ]; foreach ($tags as $needle => $tag) { if ($host === $needle || str_ends_with($host, '.' . $needle)) { return $tag; } } return strtoupper(chronik_slug(explode('.', $host)[0] ?? 'quelle', 12)); } /** Registerzeile anhängen (CSV, Semikolon — öffnet sich in Excel/LibreOffice ohne Import-Dialog). */ function chronik_register_append(array $row): void { $path = chronik_register_path(); $dir = dirname($path); if (!is_dir($dir)) { mkdir($dir, 0775, true); } $cols = ['datei', 'datum', 'titel', 'art', 'quelle', 'url', 'urheber', 'rechte', 'klaeren', 'abgerufen', 'notiz']; $new = !is_file($path); $fh = fopen($path, 'a'); if (!$fh) { return; } flock($fh, LOCK_EX); if ($new) { fputcsv($fh, $cols, ';', '"', '\\'); } fputcsv($fh, array_map(static fn(string $c): string => (string) ($row[$c] ?? ''), $cols), ';', '"', '\\'); flock($fh, LOCK_UN); fclose($fh); } /** Schon im Register? Verhindert Dubletten bei wiederholten Läufen. */ function chronik_register_has_url(string $url): bool { $path = chronik_register_path(); if (!is_file($path)) { return false; } $fh = fopen($path, 'r'); if (!$fh) { return false; } $found = false; while (($row = fgetcsv($fh, 0, ';', '"', '\\')) !== false) { if (($row[5] ?? '') === $url) { $found = true; break; } } fclose($fh); return $found; } /** Beleg-Sidecar neben dem Fundstück: alles, was der Dateiname nicht fasst. */ function chronik_sidecar_text(array $row, string $hinweis): string { $lines = [ 'FUNDSTÜCK FÜR DIE VEREINSCHRONIK — TSV 08 Kulmbach', str_repeat('=', 52), '', 'Titel: ' . ($row['titel'] ?? ''), 'Datum: ' . ($row['datum'] ?? '') . ' (00 = unbekannt)', 'Art: ' . ($row['art'] ?? ''), 'Quelle: ' . ($row['quelle'] ?? ''), 'URL: ' . ($row['url'] ?? ''), 'Urheber: ' . (($row['urheber'] ?? '') !== '' ? $row['urheber'] : 'nicht angegeben'), 'Abgerufen am: ' . ($row['abgerufen'] ?? ''), '', 'RECHTE: ' . ($row['rechte'] ?? ''), ' ' . (($row['klaeren'] ?? '') === 'ja' ? '>>> VOR VERÖFFENTLICHUNG KLÄREN <<<' : 'nutzbar'), '', wordwrap($hinweis, 88, "\n"), ]; if (($row['notiz'] ?? '') !== '') { $lines[] = ''; $lines[] = 'Notiz:'; $lines[] = wordwrap((string) $row['notiz'], 88, "\n"); } return implode("\n", $lines) . "\n"; } /** * 00_RECHTE-ZU-KLAEREN.txt aus dem Register neu schreiben. * Bewusst generiert statt fortgeschrieben: die Datei soll immer den aktuellen Stand zeigen, * auch wenn im Register etwas auf „geklärt" gesetzt wurde. */ function chronik_write_rights_todo(): int { $path = chronik_register_path(); if (!is_file($path)) { return 0; } $byRights = []; $count = 0; $fh = fopen($path, 'r'); if (!$fh) { return 0; } fgetcsv($fh, 0, ';', '"', '\\'); // Kopfzeile while (($row = fgetcsv($fh, 0, ';', '"', '\\')) !== false) { if (($row[8] ?? '') !== 'ja') { continue; } $byRights[$row[7] ?? 'Ungeklärt'][] = [ 'datei' => $row[0] ?? '', 'titel' => $row[2] ?? '', 'datum' => $row[1] ?? '', 'url' => $row[5] ?? '', // Der Verlag verlangt den Bildnachweis namentlich — wo er bekannt ist, // gehört er in die Anfrage-Anlage, nicht nur ins Register. 'urheber' => $row[6] ?? '', ]; $count++; } fclose($fh); $out = [ 'RECHTE, DIE VOR DER VERÖFFENTLICHUNG ZU KLÄREN SIND', str_repeat('=', 52), '', 'Diese Datei wird von bin/chronik-add.php erzeugt — nicht von Hand pflegen,', 'sondern die Spalte „klaeren" in docs/chronik/register.csv auf „nein" setzen,', 'sobald eine Freigabe vorliegt, und das Skript erneut laufen lassen.', '', 'Sammeln und intern sichten ist unproblematisch. Erst der Abdruck in der Chronik', 'oder die Veröffentlichung auf der Website braucht eine Erlaubnis.', '', "Offen: {$count} Fundstücke", '', ]; foreach ($byRights as $rechte => $items) { $out[] = str_repeat('-', 52); $out[] = $rechte . ' (' . count($items) . ')'; $out[] = str_repeat('-', 52); foreach ($items as $i) { $out[] = ' · ' . $i['datum'] . ' ' . $i['titel']; $out[] = ' ' . $i['datei']; if ($i['urheber'] !== '') { $out[] = ' ' . $i['urheber']; } if ($i['url'] !== '') { $out[] = ' ' . $i['url']; } } $out[] = ''; } $out[] = 'Stand: ' . date('d.m.Y H:i'); file_put_contents(chronik_root() . '/' . CHRONIK_RECHTE, implode("\n", $out) . "\n"); return $count; } /** * Entwurf der Nachdruck-Anfrage an den Verlag, als Datei im Ordner. * * Steht hier und nicht im Repo, weil der Vorstand sie braucht, nicht die Entwicklung — und * weil sie neben den Fundstücken liegen soll, auf die sie sich bezieht. Der wichtigste Satz * darin ist die Bitte um EINE Freigabe für alle drei Titel: Bayerische Rundschau, inFranken * und Frankenpost gehören zur selben Mediengruppe, drei Einzelanfragen wären unnötig. */ function chronik_verlagsanfrage_text(): string { return << Undatiert/ (wir ordnen es später zu) Festschrift, Satzung, Urkunde, Protokoll? -> Festschriften-und-Dokumente/ WIE SOLL ES HEISSEN? -------------------- Datum zuerst, dann worum es geht: 1972-07-15_Einweihung-Sportheim.jpg 1985-00-00_Mannschaftsfoto-Erste.jpg (Monat/Tag unbekannt -> 00) 1962-00-00_Aufstieg-Bericht_BR.jpg (Quelle hinten, hier: Bayerische Rundschau) Das Datum vorn sorgt dafür, dass die Liste sich von selbst richtig sortiert. Wenn du den Namen nicht hinbekommst: trotzdem hochladen. Ein falsch benanntes Foto ist unendlich viel besser als ein Foto, das im Schuhkarton bleibt. WAS UNS BEI JEDEM STÜCK HILFT ----------------------------- Wenn du es weißt, schreib es dazu (in den Dateinamen oder als kurze Textdatei daneben): · Wann ungefähr? · Wer ist zu sehen? · Wo aufgenommen? · Wer hat fotografiert / wo stand der Artikel? Der letzte Punkt ist der wichtigste und der, der am häufigsten fehlt. Ohne ihn dürfen wir das Bild später womöglich nicht drucken. RECHTE ------ Zeitungsartikel und Pressefotos gehören dem Verlag, nicht uns — auch wenn wir darin vorkommen. Was noch freigegeben werden muss, steht in 00_RECHTE-ZU-KLAEREN.txt Private Aufnahmen von Vereinsmitgliedern sind unkritisch, solange die abgebildeten Personen mit einer Veröffentlichung einverstanden sind. Fragen? Melde dich bei der Vorstandschaft. TXT; }