mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-11-09 19:20:09 +01:00
Recherche insensible aux accents sur toutes les barres de recherche (publique + backoffice).
Ajoute une fonction SQLite UDF accfold() (app/src/AccentFolding.php) qui plie les caractères accentués latin vers leur base ASCII (é→e, ç→c, …), et l'applique à chaque condition de recherche côté colonne ET côté terme (accfold(column) LIKE accfold(:term)). Comme le runtime n'a ni intl (Normalizer) ni iconv, le repli passe par une map de translitération manuelle (Western Latin-1 + Latin Extended-A, NULL-safe). Couvert : - recherche publique (searchTheses / countSearchResults) : titre, sous-titre, synopsis, auteurs, promoteurs, tags ; - popover étudiant (getThesesByAuthorName / getThesesForAuthors) : a.name ; - recherche backoffice (getThesesList / getThesesListCount) : titre, sous-titre, identifiant, auteur. L'UDF est enregistré dans Database::registerSqliteFunctions() (appelé par le constructeur et par le harnais de test TestDatabaseInstance). Évite aussi la dépréciation PHP 8.5 de PDO::sqliteCreateFunction() : la connexion est créée via \Pdo\Sqlite quand disponible (createFunction()) avec repli sur PDO (sqliteCreateFunction()) sur les anciens runtimes ; les tests passent de @dataProvider docblock à l'attribut #[DataProvider] (fin de la dépréciation PHPUnit). Détache .php-cs-fixer.cache (gitignoré) pour ne plus polluer le working copy. 294 tests PHPUnit verts sans dépréciation, phpstan niveau 5 OK.
This commit is contained in:
1 parent
0896c4b8c8
commit
a31e4ab307
7 files changed
+341
-32
No files matched your search
+91
-28
@@ -20,9 +20,10 @@ class Database
|
||||
$this->dbPath = $this->determineDatabasePath($dbPath);
|
||||
|
||||
try {
|
||||
$this->pdo = new PDO('sqlite:' . $this->dbPath);
|
||||
$this->pdo = self::newConnection($this->dbPath);
|
||||
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
||||
$this->registerSqliteFunctions($this->pdo);
|
||||
|
||||
// Enable foreign key constraints + performance pragmas
|
||||
$this->pdo->exec('PRAGMA foreign_keys = ON');
|
||||
@@ -36,6 +37,50 @@ class Database
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new SQLite PDO connection.
|
||||
*
|
||||
* Since PHP 8.5, `PDO::sqliteCreateFunction()` (used to register the
|
||||
* `accfold` UDF) is deprecated in favour of the `Pdo\Sqlite::createFunction()`
|
||||
* method. `Pdo\Sqlite` extends `PDO` and keeps the full query API, so we use
|
||||
* it directly when available and fall back to a plain `PDO` otherwise
|
||||
* (older runtimes rely on the named argument there).
|
||||
*/
|
||||
private static function newConnection(string $dbPath): PDO
|
||||
{
|
||||
// `Pdo\Sqlite` exists since PHP 8.5; on older versions class_exists() is false.
|
||||
if (class_exists('Pdo\Sqlite')) {
|
||||
return new Pdo\Sqlite('sqlite:' . $dbPath);
|
||||
}
|
||||
|
||||
return new PDO('sqlite:' . $dbPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register user-defined SQLite functions used by search queries.
|
||||
*
|
||||
* Currently exposes `accfold()` — accent folding — so SQL can match
|
||||
* diacritic-insensitively (accfold(column) LIKE accfold(term)).
|
||||
*
|
||||
* Also called for the in-memory test connection (see TestDatabase).
|
||||
*/
|
||||
public static function registerSqliteFunctions(
|
||||
PDO $pdo
|
||||
): void {
|
||||
require_once __DIR__ . '/AccentFolding.php';
|
||||
|
||||
$callback = static fn (?string $value): ?string => AccentFolding::fold($value);
|
||||
|
||||
// Since PHP 8.5, `PDO::sqliteCreateFunction()` is deprecated: use the
|
||||
// `Pdo\Sqlite::createFunction()` method instead. `PDO::sqliteCreateFunction()`
|
||||
// remains the non-deprecated API on older versions where Pdo\Sqlite doesn't exist.
|
||||
if ($pdo instanceof \Pdo\Sqlite) {
|
||||
$pdo->createFunction('accfold', $callback, 1);
|
||||
} else {
|
||||
$pdo->sqliteCreateFunction('accfold', $callback, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run one-off schema migrations.
|
||||
*/
|
||||
@@ -336,23 +381,31 @@ class Database
|
||||
*/
|
||||
private function buildSearchConditions(array $params): array
|
||||
{
|
||||
require_once __DIR__ . '/AccentFolding.php';
|
||||
|
||||
$conditions = ['vp.is_published = 1'];
|
||||
$bindings = [];
|
||||
|
||||
// Fold diacritics off a validated (already LIKE-escaped) term so both
|
||||
// sides of the comparison are accent-insensitive. Escape characters
|
||||
// (\, %, _) introduced by escapeLikeString are untouched by folding.
|
||||
$fold = static fn (string $term): string => AccentFolding::fold($term) ?? $term;
|
||||
|
||||
if (!empty($params['query'])) {
|
||||
$conditions[] = "(
|
||||
vp.title LIKE :query ESCAPE '\\' OR
|
||||
vp.subtitle LIKE :query ESCAPE '\\' OR
|
||||
vp.synopsis LIKE :query ESCAPE '\\' OR
|
||||
vp.authors LIKE :query ESCAPE '\\' OR
|
||||
vp.supervisors LIKE :query ESCAPE '\\' OR
|
||||
accfold(vp.title) LIKE :query ESCAPE '\\' OR
|
||||
accfold(vp.subtitle) LIKE :query ESCAPE '\\' OR
|
||||
accfold(vp.synopsis) LIKE :query ESCAPE '\\' OR
|
||||
accfold(vp.authors) LIKE :query ESCAPE '\\' OR
|
||||
accfold(vp.supervisors) LIKE :query ESCAPE '\\' OR
|
||||
EXISTS (
|
||||
SELECT 1 FROM thesis_tags tt2
|
||||
JOIN tags tg2 ON tg2.id = tt2.tag_id
|
||||
WHERE tt2.thesis_id = vp.id AND tg2.name LIKE :query ESCAPE '\\'
|
||||
WHERE tt2.thesis_id = vp.id
|
||||
AND accfold(tg2.name) LIKE :query ESCAPE '\\'
|
||||
)
|
||||
)";
|
||||
$bindings[':query'] = '%' . $params['query'] . '%';
|
||||
$bindings[':query'] = '%' . $fold($params['query']) . '%';
|
||||
}
|
||||
|
||||
if (!empty($params['year'])) {
|
||||
@@ -361,37 +414,38 @@ class Database
|
||||
}
|
||||
|
||||
if (!empty($params['orientation'])) {
|
||||
$conditions[] = "vp.orientation LIKE :orientation ESCAPE '\\'";
|
||||
$bindings[':orientation'] = '%' . $params['orientation'] . '%';
|
||||
$conditions[] = "accfold(vp.orientation) LIKE :orientation ESCAPE '\\'";
|
||||
$bindings[':orientation'] = '%' . $fold($params['orientation']) . '%';
|
||||
}
|
||||
|
||||
if (!empty($params['ap_program'])) {
|
||||
$conditions[] = "vp.ap_program LIKE :ap_program ESCAPE '\\'";
|
||||
$bindings[':ap_program'] = '%' . $params['ap_program'] . '%';
|
||||
$conditions[] = "accfold(vp.ap_program) LIKE :ap_program ESCAPE '\\'";
|
||||
$bindings[':ap_program'] = '%' . $fold($params['ap_program']) . '%';
|
||||
}
|
||||
|
||||
if (!empty($params['finality'])) {
|
||||
$conditions[] = "vp.finality_type LIKE :finality ESCAPE '\\'";
|
||||
$bindings[':finality'] = '%' . $params['finality'] . '%';
|
||||
$conditions[] = "accfold(vp.finality_type) LIKE :finality ESCAPE '\\'";
|
||||
$bindings[':finality'] = '%' . $fold($params['finality']) . '%';
|
||||
}
|
||||
|
||||
if (!empty($params['keyword'])) {
|
||||
$conditions[] = "EXISTS (
|
||||
SELECT 1 FROM thesis_tags tt_kw
|
||||
JOIN tags tg_kw ON tg_kw.id = tt_kw.tag_id
|
||||
WHERE tt_kw.thesis_id = vp.id AND tg_kw.name LIKE :keyword ESCAPE '\\'
|
||||
WHERE tt_kw.thesis_id = vp.id
|
||||
AND accfold(tg_kw.name) LIKE :keyword ESCAPE '\\'
|
||||
)";
|
||||
$bindings[':keyword'] = '%' . $params['keyword'] . '%';
|
||||
$bindings[':keyword'] = '%' . $fold($params['keyword']) . '%';
|
||||
}
|
||||
|
||||
if (!empty($params['format'])) {
|
||||
$conditions[] = "vp.formats LIKE :format ESCAPE '\\'";
|
||||
$bindings[':format'] = '%' . $params['format'] . '%';
|
||||
$conditions[] = "accfold(vp.formats) LIKE :format ESCAPE '\\'";
|
||||
$bindings[':format'] = '%' . $fold($params['format']) . '%';
|
||||
}
|
||||
|
||||
if (!empty($params['language'])) {
|
||||
$conditions[] = "vp.languages LIKE :language ESCAPE '\\'";
|
||||
$bindings[':language'] = '%' . $params['language'] . '%';
|
||||
$conditions[] = "accfold(vp.languages) LIKE :language ESCAPE '\\'";
|
||||
$bindings[':language'] = '%' . $fold($params['language']) . '%';
|
||||
}
|
||||
|
||||
if (isset($params['is_doctoral'])) {
|
||||
@@ -498,16 +552,18 @@ class Database
|
||||
*/
|
||||
public function getThesesByAuthorName(string $name): array
|
||||
{
|
||||
require_once __DIR__ . '/AccentFolding.php';
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT vp.id, vp.title, vp.subtitle, vp.year, vp.synopsis,
|
||||
vp.orientation, vp.finality_type, vp.authors
|
||||
FROM v_theses_public vp
|
||||
JOIN thesis_authors ta ON ta.thesis_id = vp.id
|
||||
JOIN authors a ON a.id = ta.author_id
|
||||
WHERE a.name = ?
|
||||
WHERE accfold(a.name) = accfold(?)
|
||||
ORDER BY vp.year DESC, vp.title ASC'
|
||||
);
|
||||
$stmt->execute([$name]);
|
||||
$stmt->execute([AccentFolding::fold($name)]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
@@ -524,6 +580,9 @@ class Database
|
||||
return [];
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/AccentFolding.php';
|
||||
$folded = array_map(static fn (string $n) => AccentFolding::fold($n) ?? $n, $names);
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($names), '?'));
|
||||
$stmt = $this->pdo->prepare(
|
||||
"SELECT a.name AS author_name,
|
||||
@@ -532,10 +591,10 @@ class Database
|
||||
FROM v_theses_public vp
|
||||
JOIN thesis_authors ta ON ta.thesis_id = vp.id
|
||||
JOIN authors a ON a.id = ta.author_id
|
||||
WHERE a.name IN ($placeholders)
|
||||
WHERE accfold(a.name) IN ($placeholders)
|
||||
ORDER BY a.name ASC, vp.year DESC, vp.title ASC"
|
||||
);
|
||||
$stmt->execute($names);
|
||||
$stmt->execute($folded);
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
$grouped = [];
|
||||
@@ -856,6 +915,8 @@ class Database
|
||||
*/
|
||||
public function getThesesListCount(array $filters = []): int
|
||||
{
|
||||
require_once __DIR__ . '/AccentFolding.php';
|
||||
|
||||
$sql = 'SELECT COUNT(DISTINCT t.id)
|
||||
FROM theses t
|
||||
LEFT JOIN orientations o ON t.orientation_id = o.id
|
||||
@@ -867,8 +928,8 @@ class Database
|
||||
$params = [];
|
||||
|
||||
if (!empty($filters['search'])) {
|
||||
$sql .= ' AND (t.title LIKE ? OR t.subtitle LIKE ? OR t.identifier LIKE ? OR a.name LIKE ?)';
|
||||
$searchParam = '%' . $filters['search'] . '%';
|
||||
$sql .= ' AND (accfold(t.title) LIKE ? ESCAPE \'\\\' OR accfold(t.subtitle) LIKE ? ESCAPE \'\\\' OR accfold(t.identifier) LIKE ? ESCAPE \'\\\' OR accfold(a.name) LIKE ? ESCAPE \'\\\')';
|
||||
$searchParam = '%' . AccentFolding::fold($filters['search']) . '%';
|
||||
$params[] = $searchParam;
|
||||
$params[] = $searchParam;
|
||||
$params[] = $searchParam;
|
||||
@@ -897,6 +958,8 @@ class Database
|
||||
|
||||
public function getThesesList(array $filters = [], int $limit = 0, int $offset = 0): array
|
||||
{
|
||||
require_once __DIR__ . '/AccentFolding.php';
|
||||
|
||||
$sql = 'SELECT
|
||||
t.id, t.identifier, t.title, t.subtitle, t.year,
|
||||
o.name as orientation,
|
||||
@@ -916,8 +979,8 @@ class Database
|
||||
$params = [];
|
||||
|
||||
if (!empty($filters['search'])) {
|
||||
$sql .= ' AND (t.title LIKE ? OR t.subtitle LIKE ? OR t.identifier LIKE ? OR a.name LIKE ?)';
|
||||
$searchParam = '%' . $filters['search'] . '%';
|
||||
$sql .= ' AND (accfold(t.title) LIKE ? ESCAPE \'\\\' OR accfold(t.subtitle) LIKE ? ESCAPE \'\\\' OR accfold(t.identifier) LIKE ? ESCAPE \'\\\' OR accfold(a.name) LIKE ? ESCAPE \'\\\')';
|
||||
$searchParam = '%' . AccentFolding::fold($filters['search']) . '%';
|
||||
$params[] = $searchParam;
|
||||
$params[] = $searchParam;
|
||||
$params[] = $searchParam;
|
||||
|
||||
Reference in new issue
Block a user