diff --git a/resources/lang/en.php b/resources/lang/en.php
index 52f2f6f..cd8aaf2 100644
--- a/resources/lang/en.php
+++ b/resources/lang/en.php
@@ -122,4 +122,5 @@ return [
'public_site.settings_heading'=>'Public website','public_site.theme_custom'=>'Custom','public_site.site_name'=>'Website name','public_site.custom_css'=>'Custom CSS','public_site.custom_css_help'=>'Used by the custom theme. Imports and external resources are blocked.','public_site.invalid_css'=>'The stylesheet contains a forbidden instruction.','public_site.routing_management_help'=>'Management stays at / and the public website at /site.','public_site.publish_animal'=>'Publish this animal on the public website','public_site.publish_animal_help'=>'The profile will only be visible when the animal is also available for adoption. Only public photos and the text below are used.','lab_reference.add_species'=>'Add a species to lab reports','lab_reference.copy_from'=>'Copy reference ranges from','lab_reference.target_species'=>'New species','lab_reference.copy_help'=>'Ranges are copied as a starting point and remain editable.','lab_reference.invalid_species'=>'Choose two different valid species.','lab_reference.nothing_copied'=>'This species already has these reference ranges.','statistics.hair_types'=>'Hair types','animals.filtered_by'=>'Filter: :value','service.icad.home_unavailable'=>'The I-CAD website is unavailable','service.icad.search_unavailable'=>'I-CAD search is unavailable','service.icad.record_unavailable'=>'The I-CAD record is unavailable',
'public_site.image_library'=>'Image library','public_site.image_file'=>'Image to add','public_site.image_upload'=>'Upload image','public_site.image_help'=>'Upload an image, place the cursor in a text field, then choose “Insert”. Accepted formats: JPEG, PNG and WebP.','public_site.image_none'=>'No image uploaded.','public_site.image_insert'=>'Insert','public_site.image_invalid'=>'Choose a valid image.',
'rooms.duplicate'=>'A room with this name or code already exists.','settings.species_duplicate'=>'A species with this name or code already exists.',
+ 'settings.invalid_rna'=>'The RNA number must start with W followed by 9 digits.','settings.invalid_siret'=>'The SIRET number must contain 14 digits and be valid.','rooms.name_too_long'=>'A room name cannot exceed 100 characters.','rooms.box_name_too_long'=>'A box name cannot exceed 100 characters.','animal.name_too_long'=>'An animal name cannot exceed 150 characters.',
];
diff --git a/resources/lang/fr.php b/resources/lang/fr.php
index 3c8d71f..a898da4 100644
--- a/resources/lang/fr.php
+++ b/resources/lang/fr.php
@@ -5,7 +5,7 @@ return [
'error.unexpected_reference'=>'Une erreur interne est survenue. Référence : :reference.',
'auth.remember_device'=>'Rester connecté sur cet appareil','auth.remember_device_help'=>'Une session révocable sera conservée pendant 90 jours. Le mot de passe n’est jamais enregistré.','devices.title'=>'Appareils connectés','devices.intro'=>'Sessions persistantes autorisées pour votre compte. Révoquez immédiatement un appareil perdu ou partagé.','devices.revoke_all'=>'Tout déconnecter','devices.revoke_all_confirm'=>'Déconnecter tous les appareils, y compris celui-ci ?','devices.revoked'=>'Appareil déconnecté.','devices.this_device'=>'Cet appareil','devices.active'=>'Actif','devices.inactive'=>'Révoqué ou expiré','devices.created'=>'Ajouté le :date','devices.last_used'=>'Dernière utilisation : :date','devices.expires'=>'Expiration : :date','devices.revoke'=>'Révoquer','devices.none'=>'Aucun appareil de confiance enregistré.',
'public_site.to_complete'=>'À compléter','public_site.default_legal'=>"Éditeur du site\n\nLe présent site est édité par :association, association déclarée sous le numéro RNA :rna et identifiée sous le numéro SIRET :siret.\nSiège social : :address\nContact : :email\n\nDirection de la publication\n\nLa direction de la publication est assurée par la représentante ou le représentant légal de l’association. Nom : [À compléter].\n\nHébergement\n\nHébergeur : [raison sociale, adresse et coordonnées à compléter].\n\nPropriété intellectuelle\n\nSauf mention contraire, les textes, photographies et éléments graphiques de ce site appartiennent à l’association ou sont utilisés avec l’autorisation de leurs titulaires. Toute réutilisation doit respecter les droits applicables et l’image des animaux et des personnes présentées.\n\nResponsabilité\n\nL’association s’efforce de fournir des informations exactes et à jour. Les disponibilités et caractéristiques des animaux peuvent évoluer ; leur publication ne vaut pas acceptation automatique d’une adoption.",'public_site.default_privacy'=>"Responsable du traitement\n\nLe responsable du traitement est :association.\nAdresse : :address\nContact pour l’exercice de vos droits : :email\n\nDonnées concernées et finalités\n\nLe site public présente les animaux proposés à l’adoption et les moyens de contacter l’association. Lorsque vous contactez l’association, les informations que vous transmettez peuvent être utilisées pour répondre à votre demande, étudier une adoption, gérer un don ou assurer le suivi administratif correspondant.\n\nDestinataires et conservation\n\nLes données sont accessibles aux membres autorisés de l’association et, lorsque cela est nécessaire, à ses partenaires habilités. Elles ne sont pas vendues. Elles sont conservées pendant la durée nécessaire à la finalité poursuivie et aux obligations légales ou comptables applicables, puis supprimées ou anonymisées selon la politique de l’association.\n\nCookies et services externes\n\nLe site public n’utilise pas de dispositif publicitaire ou de mesure d’audience intégré par Globinours. La carte de contact utilise les tuiles OpenStreetMap ; son affichage entraîne une connexion aux serveurs cartographiques concernés et la transmission de données techniques telles que l’adresse IP. La partie de gestion utilise un cookie de session indispensable à l’authentification.\n\nVos droits\n\nVous pouvez demander l’accès, la rectification, l’effacement ou la limitation de vos données et, lorsque les conditions sont réunies, vous opposer au traitement ou demander leur portabilité. Adressez votre demande à :email. Vous pouvez également saisir l’autorité de contrôle compétente, notamment la CNIL en France.\n\nMise à jour\n\nCette politique doit être adaptée aux traitements réellement effectués par l’association et mise à jour lorsqu’ils évoluent.",
- 'privacy.heading'=>'Confidentialité et conservation','privacy.no_auto_delete'=>'Aucune suppression automatique : la liste sert à une revue humaine.','privacy.reason'=>'Motif','privacy.none_review'=>'Aucun contact à réexaminer.','privacy.access_register'=>'Registre des accès privés','privacy.contact_not_found'=>'Contact introuvable.','privacy.export_failed'=>'Impossible de produire l’export.','privacy.reason_required'=>'Le motif est obligatoire.','privacy.anonymous_contact'=>'Contact anonymisé',
+ 'privacy.heading'=>'Confidentialité et conservation','privacy.no_auto_delete'=>'Aucune suppression n’est effectuée automatiquement. Cette liste vous aide à identifier les dossiers à examiner.','privacy.reason'=>'Motif','privacy.none_review'=>'Aucun contact à réexaminer.','privacy.access_register'=>'Registre des accès privés','privacy.contact_not_found'=>'Contact introuvable.','privacy.export_failed'=>'Impossible de produire l’export.','privacy.reason_required'=>'Le motif est obligatoire.','privacy.anonymous_contact'=>'Contact anonymisé',
'settings.privacy'=>'Confidentialité / RGPD','privacy.title'=>'Confidentialité et conservation','privacy.intro'=>'Pilotez la conservation des coordonnées, les exports et les accès aux fichiers privés.','privacy.saved'=>'Politique de conservation enregistrée.','privacy.retention'=>'Durée de conservation','privacy.retention_years'=>'Durée après la dernière activité (années)','privacy.retention_help'=>'Cette règle produit une liste de contrôle : aucune donnée n’est supprimée automatiquement.','privacy.review'=>'Contacts à réexaminer','privacy.no_review'=>'Aucun contact ne dépasse actuellement cette durée.','privacy.last_activity'=>'Dernière activité','privacy.export'=>'Exporter','privacy.anonymize'=>'Anonymiser','privacy.anonymize_reason'=>'Motif obligatoire','privacy.anonymize_confirm'=>'Anonymiser ce contact ? Une sauvegarde sera créée avant l’opération.','privacy.access_log'=>'Accès aux fichiers privés','privacy.no_access'=>'Aucun accès enregistré.','privacy.resource'=>'Ressource','privacy.actor'=>'Utilisateur','privacy.action'=>'Action','privacy.date'=>'Date','privacy.anonymized'=>'Contact anonymisé. Sauvegarde : :backup','public_site.legal'=>'Mentions légales','public_site.privacy'=>'Confidentialité','public_site.legal_privacy'=>'Mentions légales et confidentialité','public_site.legal_hint'=>'Ces textes sont publiés sur deux pages dédiées. Faites-les valider selon la situation de votre association.',
'settings.group_general'=>'Configuration','settings.group_business'=>'Référentiels métier','settings.group_access'=>'Accès','settings.group_maintenance'=>'Maintenance','public_site.main_address'=>'Page affichée à l’adresse principale','public_site.routing_management'=>'Globinours à la racine','public_site.routing_management_help'=>'La gestion reste sur / et le mini-site sur /site.','public_site.routing_public'=>'Site public à la racine','public_site.routing_public_help'=>'Le site utilise / et l’accès à la gestion se fait par /gestion.','public_site.routing_no_server'=>'Ce choix est géré par Globinours : aucune modification Apache ou Nginx n’est nécessaire.',
'public_site.published'=>'Publié sur le site','public_site.waiting_publication'=>'Publication en attente de disponibilité',
@@ -122,4 +122,5 @@ return [
'public_site.settings_heading'=>'Site public','public_site.theme_custom'=>'Personnalisé','public_site.site_name'=>'Nom du site','public_site.custom_css'=>'CSS personnalisé','public_site.custom_css_help'=>'Actif avec le thème personnalisé. Les imports et les ressources externes sont bloqués.','public_site.invalid_css'=>'La feuille de style contient une instruction interdite.','public_site.routing_management_help'=>'La gestion reste sur / et le site public sur /site.','public_site.publish_animal'=>'Publier cet animal sur le site public','public_site.publish_animal_help'=>'La fiche ne sera visible que si l’animal est également disponible à l’adoption. Seules les photos publiques et le texte ci-dessous seront utilisés.','lab_reference.add_species'=>'Ajouter une espèce aux bilans','lab_reference.copy_from'=>'Copier les références de','lab_reference.target_species'=>'Nouvelle espèce','lab_reference.copy_help'=>'Les intervalles sont copiés comme point de départ, puis restent modifiables.','lab_reference.invalid_species'=>'Choisissez deux espèces différentes et valides.','lab_reference.nothing_copied'=>'Cette espèce possède déjà ces références.','statistics.hair_types'=>'Types de poil','animals.filtered_by'=>'Filtre : :value','service.icad.home_unavailable'=>'Le site I-CAD est indisponible','service.icad.search_unavailable'=>'La recherche I-CAD est indisponible','service.icad.record_unavailable'=>'La fiche I-CAD est indisponible',
'public_site.image_library'=>'Bibliothèque d’images','public_site.image_file'=>'Image à ajouter','public_site.image_upload'=>'Ajouter l’image','public_site.image_help'=>'Ajoutez une image, placez le curseur dans un texte puis choisissez « Insérer ». Formats acceptés : JPEG, PNG et WebP.','public_site.image_none'=>'Aucune image ajoutée.','public_site.image_insert'=>'Insérer','public_site.image_invalid'=>'Choisissez une image valide.',
'rooms.duplicate'=>'Une salle portant ce nom ou ce code existe déjà.','settings.species_duplicate'=>'Une espèce portant ce nom ou ce code existe déjà.',
+ 'settings.invalid_rna'=>'Le numéro RNA doit commencer par W et être suivi de 9 chiffres.','settings.invalid_siret'=>'Le numéro SIRET doit contenir 14 chiffres et être valide.','rooms.name_too_long'=>'Le nom d’une salle ne peut pas dépasser 100 caractères.','rooms.box_name_too_long'=>'Le nom d’un box ne peut pas dépasser 100 caractères.','animal.name_too_long'=>'Le nom d’un animal ne peut pas dépasser 150 caractères.',
];
diff --git a/tests/run.php b/tests/run.php
index 1dacc97..060fa18 100644
--- a/tests/run.php
+++ b/tests/run.php
@@ -11,6 +11,7 @@ foreach (
[
'DB',
'Migrations',
+ 'Ids',
'I18n',
'AppSettings',
'SecurityService',
@@ -116,6 +117,12 @@ try {
$assert(AppSettings::get('app_language') === 'en');
I18n::setLocale('fr');
});
+ $test('Les identifiants RNA et SIRET sont contrôlés', function () use ($assert): void {
+ $assert(Ids::validRna('W123456789'));
+ $assert(!Ids::validRna('W1234'));
+ $assert(Ids::validSiret('73282932000074'));
+ $assert(!Ids::validSiret('73282932000075'));
+ });
$test('Les quatre bilans biologiques existent pour chats et chiens', function () use ($db, $assert): void {
$rows = $db
->query(