fix: unify CSV import/export columns, fix import JS error, fix createThesis VALUES

- Fix createThesis SQL: extra ? placeholder in VALUES (27 values for 26 columns)
- Add createThesis integration tests to catch column/value mismatches
- Add CSV_COLUMNS as single source of truth in ExportController, deriving
  csvHeaders() and positional fallback from it
- Add missing columns to export query + CSV: duration_pages, duration_minutes,
  has_annexes, license_custom, contact_visible, objet
- Add missing columns to import INSERT: license_id, license_custom, cc2r,
  exemplaire_baiu, exemplaire_erg, objet, contact_visible, duration_pages,
  duration_minutes, has_annexes
- Resolve license name → license_id during import
- Fix XamxamInitFilePonds: add file-upload-filepond.js to admin-entry.js
  so FilePond initialization code is available on the admin list page
- Add FilePond vendor CSS to admin.min.css bundle (import dialog styling)
- Generate .admin-file-hint from csvHeaders() instead of hardcoded stale list
- Use positional fallback from CSV_COLUMNS for import cell parsing
This commit is contained in:
Pontoporeia
2026-07-10 11:56:27 +02:00
parent ce936e3b71
commit 22a49f7cb6
11 changed files with 355 additions and 42 deletions
+65
View File
@@ -250,6 +250,59 @@ class ExportController
/**
* Column headers matching the import format.
*/
/**
* Single source of truth for CSV column definitions.
* Each entry: [header_label, field_key, import_search_key]
*
* field_key = key in the SELECT row and export data array
* import_key = lowercase token used to match header rows during import
*/
public const CSV_COLUMNS = [
['Identifiant', 'identifier', 'identifiant'],
['Titre', 'title', 'titre'],
['Sous-titre', 'subtitle', 'sous-titre'],
['Auteur·ice(s)', 'authors', 'auteur'],
['Contact', 'contact', 'contact'],
['Promoteur·ice(s) interne', 'promoteurs_internes', 'promoteur'],
['Lecteur·ice(s) interne', 'lecteurs_internes', 'lecteur'],
['Lecteur·ice(s) externe', 'lecteurs_externes', 'externe'],
['Promoteur·ice(s) université', 'promoteurs_ulb', 'ulb'],
['Format(s)', 'formats', 'format'],
['Année', 'year', 'année'],
['AP', 'ap_program', 'ap'],
['Orientation', 'orientation', 'orientation'],
['Finalité', 'finality', 'finalité'],
['Mots-clés', 'keywords', 'mots-clés'],
['Synopsis', 'synopsis', 'synopsis'],
['Contexte', 'context', 'contexte'],
['Remarques', 'remarks', 'remarques'],
['Langue', 'languages', 'langue'],
['Autorisation', 'access_type', 'autorisation'],
['Licence', 'license', 'licence'],
['Points sur 20', 'jury_points', 'points'],
['Lien BAIU', 'baiu_link', 'lien baiu'],
['CC2r', 'cc2r', 'cc2r'],
['Exemplaire BAIU', 'exemplaire_baiu', 'exemplaire'],
['Exemplaire ERG', 'exemplaire_erg', 'exemplaire-erg'],
['Pages', 'duration_pages', 'pages'],
['Durée (minutes)', 'duration_minutes', 'durée'],
['Annexes', 'has_annexes', 'annexes'],
['Licence personnalisée', 'license_custom', 'licence-perso'],
['Contact visible', 'contact_visible', 'contact-visible'],
['Objet', 'objet', 'objet'],
];
/**
* Derived from CSV_COLUMNS for backward compatibility.
*/
public static function csvHeaders(): array
{
return array_column(self::CSV_COLUMNS, 0);
}
/**
* @deprecated Use csvHeaders() instead.
*/
public const CSV_HEADERS = [
'Identifiant',
'Titre',
@@ -277,6 +330,12 @@ class ExportController
'CC2r',
'Exemplaire BAIU',
'Exemplaire ERG',
'Pages',
'Durée (minutes)',
'Annexes',
'Licence personnalisée',
'Contact visible',
'Objet',
];
/**
@@ -396,6 +455,12 @@ class ExportController
!empty($t['cc2r']) ? 'Oui' : 'Non',
!empty($t['exemplaire_baiu']) ? 'Oui' : 'Non',
!empty($t['exemplaire_erg']) ? 'Oui' : 'Non',
isset($t['duration_pages']) && $t['duration_pages'] !== null ? (string) $t['duration_pages'] : '',
isset($t['duration_minutes']) && $t['duration_minutes'] !== null ? (string) $t['duration_minutes'] : '',
!empty($t['has_annexes']) ? 'Oui' : 'Non',
$t['license_custom'] ?? '',
$t['contact_visible'] ?? '',
$t['objet'] ?? '',
];
}