Beitraege optional, Unterstriche nur im Text, eigenes Symbol
Eine Website ohne Beitraege redet nirgends von Beitraegen: Archiv und Feed antworten mit 404, der Feedverweis faellt aus dem Kopf, und der Hinweiskasten der Beitragsliste bleibt im Editor statt oeffentlich zu erscheinen. Das gilt auch je Kategorie, sobald deren letzter Beitrag weg ist. Dabei gefunden: die Liste fragte je Beitrag nach Bild und Begriffen - jetzt je eine Abfrage fuer die ganze Liste (22 -> 16 Abfragen auf der Startseite des Nachbaus). Unterstrichene Links gibt es nur noch im Fliesstext, dort mit einem Schalter im Designbereich; Karten, Menue, Kontaktzeilen und der Ansehen-Streifen haben keinen mehr. Die aktuelle Seite im Menue wird ueber das Schriftgewicht kenntlich - die Akzentfarbe fiel im dunklen Fussmenue durch die Kontrastpruefung. Neu: das Symbol der Website laesst sich festlegen. Aus einem Bild der Mediathek entstehen beim Speichern quadratische PNG-Dateien in 32 und 180 px; schlaegt das fehl, wird die Einstellung nicht gespeichert. Die Browserpruefung laeuft jetzt gegen jede Installation statt nur gegen die Demo: geprueft wird, was in der Sitemap steht, und das Ladebudget trennt Bilder vom Beiwerk. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f0b634f0c1
commit
f2ef2a4901
28 files changed
+482
-64
No files matched your search
@@ -60,6 +60,33 @@ final class MediaLibrary
|
||||
return $row ? $this->present($row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mehrere Bilder auf einmal, nach Kennung sortiert.
|
||||
*
|
||||
* Eine Liste mit zwölf Beiträgen soll nicht zwölfmal nach je einem Bild fragen. Fehlende
|
||||
* Kennungen fehlen auch im Ergebnis - der Aufrufer prüft ohnehin auf null.
|
||||
*
|
||||
* @param list<string> $ids
|
||||
*
|
||||
* @return array<string, array>
|
||||
*/
|
||||
public function findMany(array $ids): array
|
||||
{
|
||||
$ids = array_values(array_unique(array_filter($ids)));
|
||||
if ([] === $ids) {
|
||||
return [];
|
||||
}
|
||||
$found = [];
|
||||
foreach ($this->db->fetchAllAssociative(
|
||||
'SELECT * FROM media WHERE id IN ('.implode(',', array_fill(0, count($ids), '?')).')',
|
||||
$ids,
|
||||
) as $row) {
|
||||
$found[$row['id']] = $this->present($row);
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
private function present(array $row): array
|
||||
{
|
||||
$row['variants'] = json_decode($row['variants'], true, 512, JSON_THROW_ON_ERROR);
|
||||
@@ -163,6 +190,73 @@ final class MediaLibrary
|
||||
return [...$data, 'id' => $id, 'version' => $data['version'] + 1];
|
||||
}
|
||||
|
||||
/** Die Größen, die ein Favicon braucht: der Reiter im Browser und das Symbol auf dem Startbildschirm. */
|
||||
public const FAVICON_SIZES = [32, 180];
|
||||
|
||||
/**
|
||||
* Erzeugt aus einem Bild der Mediathek die Symboldateien der Website.
|
||||
*
|
||||
* Ein Favicon ist quadratisch, winzig und wird von den Browsern bis heute am zuverlässigsten
|
||||
* als PNG gelesen - deshalb hier PNG statt der WebP-Varianten und ein mittiger quadratischer
|
||||
* Ausschnitt statt eines gestauchten Bildes. Erzeugt wird einmal beim Speichern der
|
||||
* Einstellung, nicht bei jedem Aufruf.
|
||||
*/
|
||||
public function renderFavicon(string $mediaId): void
|
||||
{
|
||||
$source = null;
|
||||
foreach (array_reverse(self::WIDTHS) as $width) {
|
||||
$path = $this->file($mediaId, $width);
|
||||
if (null !== $path) {
|
||||
$source = @imagecreatefromwebp($path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$source) {
|
||||
throw new \RuntimeException('Das Bild für das Symbol konnte nicht gelesen werden.');
|
||||
}
|
||||
try {
|
||||
// Mittiger quadratischer Ausschnitt: ein breites Logo würde sonst zur Briefmarke.
|
||||
$kante = min(imagesx($source), imagesy($source));
|
||||
$links = (int) ((imagesx($source) - $kante) / 2);
|
||||
$oben = (int) ((imagesy($source) - $kante) / 2);
|
||||
foreach (self::FAVICON_SIZES as $size) {
|
||||
$target = imagecreatetruecolor($size, $size);
|
||||
try {
|
||||
imagealphablending($target, false);
|
||||
imagesavealpha($target, true);
|
||||
imagecopyresampled($target, $source, 0, 0, $links, $oben, $size, $size, $kante, $kante);
|
||||
if (!imagepng($target, $this->faviconPath($size), 9)) {
|
||||
throw new \RuntimeException('Das Symbol konnte nicht gespeichert werden.');
|
||||
}
|
||||
} finally {
|
||||
imagedestroy($target);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
imagedestroy($source);
|
||||
}
|
||||
}
|
||||
|
||||
/** Der Pfad einer Symboldatei, oder null wenn sie (noch) nicht erzeugt wurde. */
|
||||
public function faviconFile(int $size): ?string
|
||||
{
|
||||
if (!in_array($size, self::FAVICON_SIZES, true)) {
|
||||
return null;
|
||||
}
|
||||
$path = $this->faviconPath($size);
|
||||
|
||||
return is_file($path) ? $path : null;
|
||||
}
|
||||
|
||||
private function faviconPath(int $size): string
|
||||
{
|
||||
if (!is_dir($this->directory) && !mkdir($this->directory, 0775, true) && !is_dir($this->directory)) {
|
||||
throw new \RuntimeException('Medienordner ist nicht beschreibbar.');
|
||||
}
|
||||
|
||||
return $this->directory.'/favicon-'.$size.'.png';
|
||||
}
|
||||
|
||||
public function file(string $id, int $width): ?string
|
||||
{
|
||||
if (!preg_match('/^[a-f0-9]{32}$/D', $id)) {
|
||||
|
||||
Reference in new issue
Block a user