Serve images without PHP, add the 720 and 1280 steps, trim two hot paths

- Image widths are 480, 720, 960, 1280, 1600 and 2400. A phone needed
  721 pixels and got 960. Existing images receive the new steps from
  a task; stored pages pick them up at render time, so no document is
  rewritten. Mobile backgrounds use 720.
- A variant served once is mirrored to public/media, and a rewrite rule
  lets the web server deliver it from there. var/media stays the source.
- The hero preload follows the image each device really shows.
- The post navigation reads the order and then two posts instead of
  up to 500 documents.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Fable 5.2 committed 2026-11-10 12:24:30 +01:00
1 parent dca2665138
commit 7d0369bada
32 files changed
+550 -57

No files matched your search

+27 -16
View File
@@ -239,26 +239,37 @@ final class PostRepository
[$pageId, 'category'],
);
// Dieselbe Sortierung wie im Archiv, damit „vorheriges" dort dasselbe heißt wie hier.
$all = $this->published(1, '' === $termId ? null : $termId, 500, $locale, $language)['posts'];
// present() gibt bewusst keine Kennung heraus - in eine Liste gehören Titel und Adresse.
// Verglichen wird deshalb über die Adresse, die innerhalb einer Website eindeutig ist.
$own = $this->settings->publicPath((string) $this->db->fetchOne('SELECT path FROM page WHERE id = ?', [$pageId]));
$index = null;
foreach ($all as $position => $post) {
if ($post['href'] === $own) {
$index = $position;
break;
}
// Geholt wird erst nur die Reihenfolge - Kennungen, keine Dokumente - und danach genau
// das, was gezeigt wird: die zwei Nachbarn. Vorher lud jeder Aufruf eines Beitrags bis zu
// 500 veröffentlichte Fassungen mit vollem Dokument, um zwei Titel zu lesen, und ab dem
// 501. Beitrag einer Kategorie gab es gar keine Nachbarn mehr.
$where = "p.website_id = ? AND p.locale = ? AND p.type = 'post' AND p.published_revision_id IS NOT NULL AND p.deleted_at IS NULL";
$params = ['default', $locale];
$join = '';
if ('' !== $termId) {
$join = ' JOIN page_term pt ON pt.page_id = p.id AND pt.term_id = ?';
array_unshift($params, $termId);
}
if (null === $index) {
$order = $this->db->fetchFirstColumn('SELECT p.id FROM page p'.$join.' WHERE '.$where.' ORDER BY p.published_at IS NULL, p.published_at DESC, p.path ASC', $params);
$index = array_search($pageId, $order, true);
if (false === $index) {
return ['previous' => null, 'next' => null];
}
// „Vorheriges" ist im Archiv der neuere Beitrag, weil dort das Neueste oben steht.
$wanted = array_filter(['previous' => $order[$index - 1] ?? null, 'next' => $order[$index + 1] ?? null]);
if ([] === $wanted) {
return ['previous' => null, 'next' => null];
}
$rows = $this->db->fetchAllAssociativeIndexed(
'SELECT p.id AS kennung, p.id, p.path, p.excerpt, p.author, p.featured_media_id, p.published_at, r.document FROM page p'
.' JOIN revision r ON r.id = p.published_revision_id AND r.page_id = p.id WHERE p.id IN ('.implode(',', array_fill(0, count($wanted), '?')).')',
array_values($wanted),
);
$images = $this->media->findMany(array_map(static fn (array $row) => (string) $row['featured_media_id'], $rows));
$terms = $this->termsForMany(array_keys($rows), $language);
$post = fn (?string $id): ?array => null !== $id && isset($rows[$id]) ? $this->present($rows[$id], $images, $terms[$id] ?? ['category' => [], 'tag' => []], $language) : null;
return [
// „Vorheriges" ist im Archiv der neuere Beitrag, weil dort das Neueste oben steht.
'previous' => $all[$index - 1] ?? null,
'next' => $all[$index + 1] ?? null,
];
return ['previous' => $post($wanted['previous'] ?? null), 'next' => $post($wanted['next'] ?? null)];
}
/**