Fix language-search fragment

- mots-clé and language where sharing the same q variable for the input value; they now have unique variables.

The admin language-search-fragment was missing App::boot() which the tag-search
fragment had. This caused the language suggestion dropdown to not return results
in Firefox. Both fragments now follow the same bootstrap pattern.

Rewrote language-search-fragment.php to use the same clean pattern as
tag-search-fragment.php: ->searchLanguages(), simple exact match check,
no predefined exclusion list. Both fragments now share identical structure.

fix: exclude main languages (français, anglais, néerlandais) from language-search suggestions
This commit is contained in:
Pontoporeia
2026-05-10 13:20:40 +02:00
parent a3ded16915
commit 6224e3ede0
6 changed files with 53 additions and 65 deletions

View File

@@ -4,58 +4,23 @@
*
* Shared HTMX fragment: returns matching language suggestions for the
* "Autre(s) langue(s)" interactive search input.
*
* Included by:
* - /admin/language-search-fragment.php (AdminAuth gated)
* - partage/index.php special route (public, session already booted)
*
* Expected POST:
* q — search query string (partial language name)
* language_autre[] — already selected language names (for exclusion)
*/
require_once __DIR__ . '/../../src/Database.php';
$query = trim(preg_replace('/\s+/', ' ', strtolower($_POST['q'] ?? '')));
$query = trim(preg_replace('/\s+/', ' ', strtolower($_POST['language_search_q'] ?? '')));
$currentLanguages = isset($_POST['language_autre']) && is_array($_POST['language_autre'])
? array_map(function($l) { return trim(preg_replace('/\s+/', ' ', strtolower($l))); }, $_POST['language_autre'])
: [];
error_log("[lang-search] q=" . var_export($query, true) . " cur=" . json_encode($currentLanguages));
$db = Database::getInstance();
$results = $db->searchLanguages($query);
// Search existing languages by name, excluding predefined ones (already shown as checkboxes)
$predefined = ["français", "anglais", "néerlandais", "francais", "neerlandais"];
if ($query !== '') {
$placeholders = implode(',', array_fill(0, count($predefined), '?'));
$stmt = $db->getConnection()->prepare(
"SELECT l.id, UPPER(SUBSTR(l.name,1,1)) || SUBSTR(l.name,2) as name,
COUNT(DISTINCT tl.thesis_id) as thesis_count
FROM languages l
LEFT JOIN thesis_languages tl ON l.id = tl.language_id
WHERE LOWER(l.name) LIKE ?
AND LOWER(l.name) NOT IN ($placeholders)
GROUP BY l.id
ORDER BY LOWER(l.name) = ? DESC, thesis_count DESC, LOWER(l.name)
LIMIT 10"
);
$stmt->execute(array_merge([$query . '%'], $predefined, [$query]));
} else {
$placeholders = implode(',', array_fill(0, count($predefined), '?'));
$stmt = $db->getConnection()->prepare(
"SELECT l.id, UPPER(SUBSTR(l.name,1,1)) || SUBSTR(l.name,2) as name,
COUNT(DISTINCT tl.thesis_id) as thesis_count
FROM languages l
LEFT JOIN thesis_languages tl ON l.id = tl.language_id
WHERE LOWER(l.name) NOT IN ($placeholders)
GROUP BY l.id
ORDER BY thesis_count DESC, LOWER(l.name)
LIMIT 10"
);
$stmt->execute($predefined);
}
error_log("[lang-search] raw results count=" . count($results) . " rows=" . json_encode(array_slice($results, 0, 5)));
$results = $stmt->fetchAll();
// Deduplicate results by lowercase name
// Deduplicate
$seen = [];
$results = array_values(array_filter($results, function($lang) use (&$seen) {
$key = strtolower($lang['name']);
@@ -64,13 +29,17 @@ $results = array_values(array_filter($results, function($lang) use (&$seen) {
return true;
}));
// Filter out already-selected languages (case-insensitive)
$results = array_values(array_filter($results, function($lang) use ($currentLanguages) {
return !in_array(strtolower($lang['name']), $currentLanguages, true);
// Exclude the main languages (handled by the checkbox list above)
$excludedLanguages = ['français', 'anglais', 'néerlandais'];
// Filter out already-selected and excluded main languages
$results = array_values(array_filter($results, function($lang) use ($currentLanguages, $excludedLanguages) {
$lower = strtolower($lang['name']);
return !in_array($lower, $currentLanguages, true)
&& !in_array($lower, $excludedLanguages, true);
}));
// Check if query exactly matches an existing language (case-insensitive)
// Also check against predefined languages to avoid suggesting creation of a checkbox language
// Exact match check
$exactExists = false;
foreach ($results as $lang) {
if (strcasecmp($lang['name'], $query) === 0) {
@@ -78,16 +47,12 @@ foreach ($results as $lang) {
break;
}
}
if (!$exactExists && $query !== '') {
$normalisedQuery = strtolower($query);
$normalisedPredefined = array_map('strtolower', $predefined);
if (in_array($normalisedQuery, $normalisedPredefined, true)) {
$exactExists = true;
}
}
// If no exact match and query non-empty, suggest creation
$canCreate = ($query !== '' && !$exactExists && !in_array($query, $currentLanguages, true));
$inCurrent = in_array($query, $currentLanguages, true);
$isExcluded = in_array($query, $excludedLanguages, true);
$canCreate = ($query !== '' && !$exactExists && !$inCurrent && !$isExcluded);
error_log("[lang-search] exactExists=" . var_export($exactExists, true) . " inCurrent=" . var_export($inCurrent, true) . " canCreate=" . var_export($canCreate, true));
?>
<?php if (empty($results) && !$canCreate): ?>
<div class="tag-search-empty">Aucune langue trouvée.</div>

View File

@@ -15,7 +15,7 @@
*/
require_once __DIR__ . '/../../src/Database.php';
$query = trim(preg_replace('/\s+/', ' ', strtolower($_POST['q'] ?? '')));
$query = trim(preg_replace('/\s+/', ' ', strtolower($_POST['tag_search_q'] ?? '')));
$currentTags = isset($_POST['tag']) && is_array($_POST['tag'])
? array_map(function($t) { return trim(preg_replace('/\s+/', ' ', strtolower($t))); }, $_POST['tag'])
: [];