Ergänzt AJAX-Livesuche für Seiten, Beiträge und Mediathek
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s

This commit is contained in:
kantarci committed 2026-09-14 11:09:24 +02:00
1 parent 11b60fc5d8
commit 41d87f62a4
20 files changed
+703 -311

No files matched your search

+12 -7
View File
@@ -39,17 +39,22 @@ final class MediaLibrary
*
* @return array{items: list<array<string, mixed>>, total: int, page: int, pages: int, perPage: int}
*/
public function page(int $page, bool $withArchived = false, int $perPage = self::PER_PAGE): array
public function page(int $page, bool $withArchived = false, int $perPage = self::PER_PAGE, string $query = ''): array
{
$where = $withArchived ? '' : ' WHERE archived = 0';
$total = (int) $this->db->fetchOne('SELECT COUNT(*) FROM media'.$where);
$qb = $this->db->createQueryBuilder()->from('media');
if (!$withArchived) {
$qb->andWhere('archived = 0');
}
if ('' !== trim($query)) {
$qb->andWhere('(LOWER(title) LIKE :query ESCAPE \'!\' OR LOWER(alt) LIKE :query ESCAPE \'!\')')
->setParameter('query', '%'.str_replace(['!', '%', '_'], ['!!', '!%', '!_'], mb_strtolower(trim($query))).'%');
}
$total = (int) (clone $qb)->select('COUNT(*)')->executeQuery()->fetchOne();
$perPage = max(1, min(96, $perPage));
$pages = max(1, (int) ceil($total / $perPage));
$page = max(1, min($pages, $page));
$items = $this->db->fetchAllAssociative(
'SELECT * FROM media'.$where.' ORDER BY created_at DESC, id DESC LIMIT ? OFFSET ?',
[$perPage, ($page - 1) * $perPage],
);
$items = $qb->select('*')->orderBy('created_at', 'DESC')->addOrderBy('id', 'DESC')
->setMaxResults($perPage)->setFirstResult(($page - 1) * $perPage)->executeQuery()->fetchAllAssociative();
return [
'items' => array_map($this->present(...), $items),
+11 -2
View File
@@ -54,14 +54,23 @@ final class EditorController extends AbstractController
}
unset($page);
return $this->render('admin/pages.html.twig', [
$view = [
'pages' => 'trash' === $status ? [] : $pages,
'trash' => 'trash' === $status ? array_map(static fn (array $p): array => [...$p, 'deletedAt' => $p['deleted_at']], $pages) : [],
'parentChoices' => $parentChoices, 'postLayouts' => PostLayouts::all(),
'contentType' => $type, 'result' => $result, 'query' => $query, 'status' => $status,
'listRoute' => 'post' === $type ? 'admin_posts' : 'admin_pages',
'areaChoices' => ['header' => $this->partials->choices('header'), 'footer' => $this->partials->choices('footer')],
]);
];
if ($request->isXmlHttpRequest()) {
return $this->json([
'html' => $this->renderView('admin/content-results.html.twig', $view),
'total' => $result['total'],
'parameters' => ['q' => $query, 'status' => $status, 'limit' => $result['limit'], 'seite' => $result['page']],
]);
}
return $this->render('admin/pages.html.twig', $view);
}
#[Route('/api/content-choices', name: 'admin_content_choices', methods: ['GET'])]
+13 -5
View File
@@ -24,11 +24,18 @@ final class MediaController extends AbstractController
// The screen renders the images itself, page by page. The dialog stays what it is: the way
// to pick an image while editing, not the way to see what the library holds.
$archived = $request->query->getBoolean('archiv');
$query = mb_substr(trim($request->query->getString('q')), 0, 160);
$library = $this->media->page($request->query->getInt('seite', 1), $archived, query: $query);
$view = ['library' => $library, 'archived' => $archived, 'query' => $query];
if ($request->isXmlHttpRequest()) {
return $this->json([
'html' => $this->renderView('admin/media-results.html.twig', $view),
'total' => $library['total'],
'parameters' => ['q' => $query, 'archiv' => $archived ? '1' : '0', 'seite' => $library['page']],
]);
}
return $this->render('admin/media.html.twig', [
'library' => $this->media->page($request->query->getInt('seite', 1), $archived),
'archived' => $archived,
]);
return $this->render('admin/media.html.twig', $view);
}
#[Route('/%kant.admin_path%/media/upload', name: 'admin_media_upload', methods: ['POST'])]
@@ -90,7 +97,8 @@ final class MediaController extends AbstractController
$query = array_filter([
'seite' => $request->request->getInt('seite', 1) > 1 ? $request->request->getInt('seite') : null,
'archiv' => $request->request->getBoolean('archiv') ? '1' : null,
]);
'q' => mb_substr(trim($request->request->getString('q')), 0, 160),
], static fn ($value): bool => null !== $value && '' !== $value);
return $this->redirectToRoute('admin_media', $query);
}
+3
View File
@@ -21,6 +21,9 @@ final class ConnectionFactory
}
$connection = DriverManager::getConnection($params, $configuration);
if ('pdo_sqlite' === $params['driver']) {
// SQLite's built-in LOWER only handles ASCII: searching for "über" must also find
// "Über uns", just as on MySQL. No database rewrite or search-specific schema needed.
$connection->getNativeConnection()->sqliteCreateFunction('lower', static fn (?string $value): ?string => null === $value ? null : mb_strtolower($value, 'UTF-8'), 1, \PDO::SQLITE_DETERMINISTIC);
$connection->executeStatement('PRAGMA foreign_keys = ON');
$connection->executeStatement('PRAGMA busy_timeout = 5000');
}