credit-direct/app/libraries/FormValidator.php
2025-12-18 09:44:42 +01:00

1076 lines
40 KiB
PHP

<?php
namespace libraries;
/**
* Classe de validation pour les formulaires de crédit
*/
class FormValidator
{
private $errors = [];
private $data = [];
private $creditModel = null;
/**
* Constructeur
* @param array $data Les données à valider
* @param object $creditModel Instance du modèle de crédit (optionnel)
*/
public function __construct($data = [], $creditModel = null)
{
$this->data = $data;
$this->creditModel = $creditModel;
}
/**
* Valide un champ requis
* @param string $field Le nom du champ
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function required($field, $message = null)
{
if (empty($this->data[$field]) || trim($this->data[$field]) === '') {
$this->addError($field, $message ?: "Le champ {$field} est requis.");
}
return $this;
}
/**
* Valide un email
* @param string $field Le nom du champ
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function email($field, $message = null)
{
if (!empty($this->data[$field]) && !filter_var($this->data[$field], FILTER_VALIDATE_EMAIL)) {
$this->addError($field, $message ?: "Le champ {$field} doit être un email valide.");
}
return $this;
}
/**
* Valide un numéro de téléphone belge
* @param string $field Le nom du champ
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function phone($field, $message = null)
{
if (!empty($this->data[$field])) {
$phone = preg_replace('/[^0-9+]/', '', $this->data[$field]);
if (!preg_match('/^(\+32|0)[0-9]{8,9}$/', $phone)) {
$this->addError($field, $message ?: "Le champ {$field} doit être un numéro de téléphone belge valide.");
}
}
return $this;
}
/**
* Valide un code postal belge
* @param string $field Le nom du champ
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function zipCode($field, $message = null)
{
if (!empty($this->data[$field])) {
if (!preg_match('/^[0-9]{4}$/', $this->data[$field])) {
$this->addError($field, $message ?: "Le champ {$field} doit être un code postal belge valide (4 chiffres).");
}
}
return $this;
}
/**
* Valide un numéro de registre national belge
* @param string $field Le nom du champ
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function nationalNumber($field, $message = null)
{
if (!empty($this->data[$field])) {
$number = preg_replace('/[^0-9]/', '', $this->data[$field]);
if (strlen($number) !== 11) {
$this->addError($field, $message ?: "Le champ {$field} doit contenir 11 chiffres.");
return $this;
}
// Validation de la clé de contrôle du registre national
if (!$this->validateNationalNumberChecksum($number)) {
$this->addError($field, $message ?: "Le champ {$field} n'est pas un numéro de registre national valide.");
}
}
return $this;
}
/**
* Valide un numéro de compte bancaire belge (IBAN)
* @param string $field Le nom du champ
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function bankAccount($field, $message = null)
{
if (!empty($this->data[$field])) {
$account = preg_replace('/[^0-9]/', '', $this->data[$field]);
if (strlen($account) < 10 || strlen($account) > 16) {
$this->addError($field, $message ?: "Le champ {$field} doit contenir entre 10 et 16 chiffres.");
}
}
return $this;
}
/**
* Valide une date au format JJ/MM/AAAA
* @param string $field Le nom du champ
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function date($field, $message = null)
{
if (!empty($this->data[$field])) {
$date = \DateTime::createFromFormat('d/m/Y', $this->data[$field]);
if (!$date || $date->format('d/m/Y') !== $this->data[$field]) {
$this->addError($field, $message ?: "Le champ {$field} doit être une date valide au format JJ/MM/AAAA.");
}
}
return $this;
}
/**
* Valide qu'une date est dans le passé
* @param string $field Le nom du champ
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function datePast($field, $message = null)
{
if (!empty($this->data[$field])) {
$date = \DateTime::createFromFormat('d/m/Y', $this->data[$field]);
if ($date && $date > new \DateTime()) {
$this->addError($field, $message ?: "Le champ {$field} doit être une date dans le passé.");
}
}
return $this;
}
/**
* Valide qu'une date est dans le futur
* @param string $field Le nom du champ
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function dateFuture($field, $message = null)
{
if (!empty($this->data[$field])) {
$date = \DateTime::createFromFormat('d/m/Y', $this->data[$field]);
if ($date && $date < new \DateTime()) {
$this->addError($field, $message ?: "Le champ {$field} doit être une date dans le futur.");
}
}
return $this;
}
/**
* Valide un montant (nombre positif)
* @param string $field Le nom du champ
* @param float $min Montant minimum
* @param float $max Montant maximum
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function amount($field, $min = 0, $max = null, $message = null)
{
if (!empty($this->data[$field])) {
$amount = floatval($this->data[$field]);
if ($amount < $min) {
$this->addError($field, $message ?: "Le champ {$field} doit être supérieur ou égal à {$min}.");
}
if ($max !== null && $amount > $max) {
$this->addError($field, $message ?: "Le champ {$field} doit être inférieur ou égal à {$max}.");
}
}
return $this;
}
/**
* Valide un nombre d'enfants
* @param string $field Le nom du champ
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function childrenCount($field, $message = null)
{
if (!empty($this->data[$field])) {
$count = intval($this->data[$field]);
if ($count < 0 || $count > 24) {
$this->addError($field, $message ?: "Le champ {$field} doit être entre 0 et 24.");
}
}
return $this;
}
/**
* Valide qu'un champ est dans une liste de valeurs
* @param string $field Le nom du champ
* @param array $allowedValues Valeurs autorisées
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function in($field, $allowedValues, $message = null)
{
if (!empty($this->data[$field]) && !in_array($this->data[$field], $allowedValues)) {
$this->addError($field, $message ?: "Le champ {$field} doit être une des valeurs autorisées.");
}
return $this;
}
/**
* Valide la longueur d'une chaîne
* @param string $field Le nom du champ
* @param int $min Longueur minimum
* @param int $max Longueur maximum
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function length($field, $min, $max, $message = null)
{
if (!empty($this->data[$field])) {
$length = strlen($this->data[$field]);
if ($length < $min || $length > $max) {
$this->addError($field, $message ?: "Le champ {$field} doit contenir entre {$min} et {$max} caractères.");
}
}
return $this;
}
/**
* Valide un numéro de carte d'identité belge
* @param string $field Le nom du champ
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function identityCard($field, $message = null)
{
if (!empty($this->data[$field])) {
$card = preg_replace('/[^0-9]/', '', $this->data[$field]);
if (strlen($card) < 8 || strlen($card) > 12) {
$this->addError($field, $message ?: "Le champ {$field} doit contenir entre 8 et 12 chiffres.");
}
}
return $this;
}
/**
* Valide un taux (pourcentage) au format "int%" ou juste un entier positif
* @param string $field Le nom du champ
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function rate($field, $message = null)
{
if (!empty($this->data[$field])) {
$value = trim($this->data[$field]);
// Vérifier si c'est au format "X%" ou juste "X"
if (preg_match('/^(\d+)%?$/', $value, $matches)) {
$rate = intval($matches[1]);
if ($rate < 0 || $rate > 100) {
$this->addError($field, $message ?: "Le champ {$field} doit être un taux entre 0 et 100.");
}
} else {
$this->addError($field, $message ?: "Le champ {$field} doit être un taux au format 'X%' ou juste 'X'.");
}
}
return $this;
}
/**
* Valide qu'un montant est dans les limites définies pour le type de crédit
* @param string $amountField Le nom du champ montant
* @param string $creditTypeField Le nom du champ type de crédit
* @param string $message Message d'erreur personnalisé
* @return FormValidator
*/
public function creditAmountWithinLimits($amountField, $creditTypeField, $message = null)
{
if (!empty($this->data[$amountField]) && !empty($this->data[$creditTypeField])) {
$amount = floatval($this->data[$amountField]);
$creditType = $this->data[$creditTypeField];
// Récupérer les limites depuis le modèle
if (isset($this->creditModel) && method_exists($this->creditModel, 'get_credit_limits')) {
$limits = $this->creditModel->get_credit_limits($creditType);
if ($amount < $limits['limite_basse']) {
$this->addError($amountField, $message ?: "Le montant demandé ({$amount}€) est inférieur à la limite minimale de {$limits['limite_basse']}€ pour ce type de crédit.");
} elseif ($amount > $limits['limite_haute']) {
$this->addError($amountField, $message ?: "Le montant demandé ({$amount}€) dépasse la limite maximale de {$limits['limite_haute']}€ pour ce type de crédit.");
}
}
}
return $this;
}
/**
* Détermine automatiquement le type de champ et applique les bonnes validations
* @param string $field Le nom du champ
* @param mixed $value La valeur du champ
* @param bool $required Si le champ est requis
* @return FormValidator
*/
public function autoValidate($field, $value, $required = false)
{
$this->data[$field] = $value;
// Validation des champs requis
if ($required) {
$this->required($field);
}
// Détermination automatique du type de champ
$fieldType = $this->detectFieldType($field, $value);
// Application des validations selon le type détecté
switch ($fieldType) {
case 'email':
$this->email($field);
break;
case 'phone':
$this->phone($field);
break;
case 'date':
$this->date($field);
break;
case 'date_past':
$this->date($field)->datePast($field);
break;
case 'date_future':
$this->date($field)->dateFuture($field);
break;
case 'zip_code':
$this->zipCode($field);
break;
case 'national_number':
$this->nationalNumber($field);
break;
case 'bank_account':
$this->bankAccount($field);
break;
case 'identity_card':
$this->identityCard($field);
break;
case 'amount':
$this->amount($field);
break;
case 'rate':
$this->rate($field);
break;
case 'children_count':
$this->childrenCount($field);
break;
case 'text':
$this->length($field, 1, 255);
break;
}
return $this;
}
/**
* Détecte automatiquement le type de champ basé sur son nom et sa valeur
* @param string $field Le nom du champ
* @param mixed $value La valeur du champ
* @return string Le type de champ détecté
*/
private function detectFieldType($field, $value)
{
$field = strtolower($field);
// Détection par nom de champ
if (strpos($field, 'email') !== false) {
return 'email';
}
if (strpos($field, 'phone') !== false || strpos($field, 'telephone') !== false) {
return 'phone';
}
if (strpos($field, 'zip') !== false || strpos($field, 'code_postal') !== false) {
return 'zip_code';
}
if (strpos($field, 'nationalregistrationnumber') !== false || strpos($field, 'num_registre_national') !== false) {
return 'national_number';
}
if (strpos($field, 'bankaccountnumber') !== false || strpos($field, 'num_compte_bancaire') !== false) {
return 'bank_account';
}
if (strpos($field, 'cardnumber') !== false || strpos($field, 'num_carte_identite') !== false) {
return 'identity_card';
}
if (strpos($field, 'salary') !== false || strpos($field, 'salaire') !== false ||
strpos($field, 'loyer') !== false || strpos($field, 'montant') !== false ||
strpos($field, 'capital') !== false || strpos($field, 'mensualite') !== false ||
strpos($field, 'cout_total') !== false) {
return 'amount';
}
if (strpos($field, 'taux') !== false || strpos($field, 'rate') !== false) {
return 'rate';
}
if (strpos($field, 'children') !== false || strpos($field, 'enfant') !== false) {
return 'children_count';
}
// Détection par nom de champ contenant "date"
if (strpos($field, 'date') !== false) {
if (strpos($field, 'validity') !== false || strpos($field, 'validite') !== false) {
return 'date_future';
}
if (strpos($field, 'birth') !== false || strpos($field, 'naissance') !== false) {
return 'date_past';
}
return 'date';
}
// Détection par valeur si c'est une date
if (!empty($value) && $this->isDateFormat($value)) {
return 'date';
}
// Détection par valeur si c'est un email
if (!empty($value) && filter_var($value, FILTER_VALIDATE_EMAIL)) {
return 'email';
}
// Détection par valeur si c'est un montant
if (!empty($value) && is_numeric($value) && floatval($value) > 0) {
return 'amount';
}
return 'text';
}
/**
* Vérifie si une valeur est au format date JJ/MM/AAAA
* @param string $value La valeur à vérifier
* @return bool
*/
private function isDateFormat($value)
{
if (empty($value)) return false;
// Vérifier le format JJ/MM/AAAA
if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $value)) {
$date = \DateTime::createFromFormat('d/m/Y', $value);
return $date && $date->format('d/m/Y') === $value;
}
return false;
}
/**
* Valide les données de l'emprunteur principal
* @param array $data Les données du formulaire
* @return array Erreurs de validation
*/
public function validateBorrower($data)
{
$this->data = $data;
$this->errors = [];
// Informations de base
$this->required('firstname', 'Le prénom est requis.')
->required('lastname', 'Le nom est requis.')
->required('email', 'L\'email est requis.')
->email('email', 'L\'email doit être valide.')
->required('phone', 'Le téléphone est requis.')
->phone('phone', 'Le téléphone doit être un numéro belge valide.')
->required('zip', 'Le code postal est requis.')
->zipCode('zip', 'Le code postal doit être valide.')
->required('country', 'Le pays est requis.');
// Données personnelles (étape 3)
if (isset($data['birthdate'])) {
$this->required('birthdate', 'La date de naissance est requise.')
->date('birthdate', 'La date de naissance doit être au format JJ/MM/AAAA.')
->datePast('birthdate', 'La date de naissance doit être dans le passé.');
}
if (isset($data['birthplace'])) {
$this->required('birthplace', 'Le lieu de naissance est requis.');
}
if (isset($data['nationality'])) {
$this->required('nationality', 'La nationalité est requise.');
}
if (isset($data['cardnumber'])) {
$this->required('cardnumber', 'Le numéro de carte d\'identité est requis.')
->identityCard('cardnumber', 'Le numéro de carte d\'identité doit être valide.');
}
if (isset($data['cnvaliditydate'])) {
$this->required('cnvaliditydate', 'La date de validité de la carte est requise.')
->date('cnvaliditydate', 'La date de validité doit être au format JJ/MM/AAAA.')
->dateFuture('cnvaliditydate', 'La date de validité doit être dans le futur.');
}
if (isset($data['nationalregistrationnumber'])) {
$this->required('nationalregistrationnumber', 'Le numéro de registre national est requis.')
->nationalNumber('nationalregistrationnumber', 'Le numéro de registre national doit être valide.');
}
if (isset($data['bankaccountnumber'])) {
$this->required('bankaccountnumber', 'Le numéro de compte bancaire est requis.')
->bankAccount('bankaccountnumber', 'Le numéro de compte bancaire doit être valide.');
}
if (isset($data['dependentchildren'])) {
$this->childrenCount('dependentchildren', 'Le nombre d\'enfants doit être entre 0 et 24.');
}
// Informations professionnelles
if (isset($data['civilstatus'])) {
$this->required('civilstatus', 'L\'état civil est requis.');
}
if (isset($data['job'])) {
$this->required('job', 'La profession est requise.');
}
if (isset($data['salary'])) {
$this->required('salary', 'Le salaire est requis.')
->amount('salary', 0, null, 'Le salaire doit être un montant positif.');
}
if (isset($data['annual_taxable_income'])) {
$this->amount('annual_taxable_income', 0, null, 'Le revenu imposable doit être un montant positif.');
}
// Autres revenus
if (isset($data['oiamouthmealvoucher'])) {
$this->amount('oiamouthmealvoucher', 0, null, 'Le montant des chèques repas doit être positif.');
}
if (isset($data['oiamouthrentalincome'])) {
$this->amount('oiamouthrentalincome', 0, null, 'Le montant des revenus locatifs doit être positif.');
}
if (isset($data['oiamouthunemployment'])) {
$this->amount('oiamouthunemployment', 0, null, 'Le montant du chômage doit être positif.');
}
if (isset($data['oiamouthother'])) {
$this->amount('oiamouthother', 0, null, 'Le montant des autres revenus doit être positif.');
}
// Logement
if (isset($data['habitation_type'])) {
$allowedTypes = ['proprietaire', 'proprietaire_sans_pret', 'locataire', 'cohabitant'];
$this->in('habitation_type', $allowedTypes, 'Le type d\'habitation doit être valide.');
}
if (isset($data['habitation_loyer'])) {
$this->amount('habitation_loyer', 0, null, 'Le montant du loyer doit être positif.');
}
return $this->errors;
}
/**
* Valide les données du co-emprunteur
* @param array $data Les données du formulaire
* @return array Erreurs de validation
*/
public function validateCoBorrower($data)
{
$this->data = $data;
$this->errors = [];
// Préfixe 'co' pour les champs du co-emprunteur
$coFields = [
'cobirthdate' => 'birthdate',
'cobirthplace' => 'birthplace',
'conationality' => 'nationality',
'cocardnumber' => 'cardnumber',
'cocnvaliditydate' => 'cnvaliditydate',
'conationalregistrationnumber' => 'nationalregistrationnumber',
'cobankaccountnumber' => 'bankaccountnumber',
'codependentchildren' => 'dependentchildren'
];
foreach ($coFields as $coField => $baseField) {
if (isset($data[$coField]) && !empty($data[$coField])) {
$this->data[$baseField] = $data[$coField];
switch ($baseField) {
case 'birthdate':
$this->date($baseField, 'La date de naissance du co-emprunteur doit être au format JJ/MM/AAAA.')
->datePast($baseField, 'La date de naissance du co-emprunteur doit être dans le passé.');
break;
case 'birthplace':
$this->required($baseField, 'Le lieu de naissance du co-emprunteur est requis.');
break;
case 'nationality':
$this->required($baseField, 'La nationalité du co-emprunteur est requise.');
break;
case 'cardnumber':
$this->identityCard($baseField, 'Le numéro de carte d\'identité du co-emprunteur doit être valide.');
break;
case 'cnvaliditydate':
$this->date($baseField, 'La date de validité de la carte du co-emprunteur doit être au format JJ/MM/AAAA.')
->dateFuture($baseField, 'La date de validité de la carte du co-emprunteur doit être dans le futur.');
break;
case 'nationalregistrationnumber':
$this->nationalNumber($baseField, 'Le numéro de registre national du co-emprunteur doit être valide.');
break;
case 'bankaccountnumber':
$this->bankAccount($baseField, 'Le numéro de compte bancaire du co-emprunteur doit être valide.');
break;
case 'dependentchildren':
$this->childrenCount($baseField, 'Le nombre d\'enfants du co-emprunteur doit être entre 0 et 24.');
break;
}
}
}
return $this->errors;
}
/**
* Valide les crédits en cours
* @param array $data Les données du formulaire
* @return array Erreurs de validation
*/
public function validateCurrentLoans($data)
{
$this->data = $data;
$this->errors = [];
if (isset($data['currentloans']) && is_array($data['currentloans'])) {
foreach ($data['currentloans'] as $index => $loan) {
$prefix = "currentloans[{$index}]";
if (isset($loan['loantype'])) {
$this->required($prefix . '[loantype]', "Le type de créance du crédit {$index} est requis.");
}
if (isset($loan['bankname'])) {
$this->required($prefix . '[bankname]', "Le nom de la banque du crédit {$index} est requis.")
->length($prefix . '[bankname]', 2, 100, "Le nom de la banque du crédit {$index} doit contenir entre 2 et 100 caractères.");
}
if (isset($loan['borrowedcapital'])) {
$this->required($prefix . '[borrowedcapital]', "Le capital emprunté du crédit {$index} est requis.")
->amount($prefix . '[borrowedcapital]', 0, null, "Le capital emprunté du crédit {$index} doit être positif.");
}
if (isset($loan['durationmonth'])) {
$this->required($prefix . '[durationmonth]', "La durée du crédit {$index} est requise.")
->amount($prefix . '[durationmonth]', 1, 600, "La durée du crédit {$index} doit être entre 1 et 600 mois.");
}
if (isset($loan['monthlypayment'])) {
$this->required($prefix . '[monthlypayment]', "La mensualité du crédit {$index} est requise.")
->amount($prefix . '[monthlypayment]', 0, null, "La mensualité du crédit {$index} doit être positive.");
}
if (isset($loan['firstduedate'])) {
$this->required($prefix . '[firstduedate]', "La date de première échéance du crédit {$index} est requise.")
->date($prefix . '[firstduedate]', "La date de première échéance du crédit {$index} doit être au format JJ/MM/AAAA.");
}
if (isset($loan['remainingbalance'])) {
$this->required($prefix . '[remainingbalance]', "Le solde restant du crédit {$index} est requis.")
->amount($prefix . '[remainingbalance]', 0, null, "Le solde restant du crédit {$index} doit être positif.");
}
}
}
return $this->errors;
}
/**
* Valide automatiquement tous les champs d'un formulaire
* @param array $data Les données du formulaire
* @param array $requiredFields Liste des champs requis
* @return array Erreurs de validation
*/
public function autoValidateAll($data, $requiredFields = [])
{
$this->data = $data;
$this->errors = [];
foreach ($data as $field => $value) {
// Ignorer les champs spéciaux
if (in_array($field, ['credit-direct-token', 'type_credit_selected', 'sub_loan_type'])) {
continue;
}
// Déterminer si le champ est requis
$isRequired = in_array($field, $requiredFields);
// Validation automatique
$this->autoValidate($field, $value, $isRequired);
}
return $this->errors;
}
/**
* Valide les données de l'étape 2 (revenus et charges)
* @param array $data Les données du formulaire
* @return array Erreurs de validation
*/
public function validateStep2($data)
{
$this->data = $data;
$this->errors = [];
// Champs requis pour l'étape 2 UNIQUEMENT
$step2RequiredFields = [
'firstname', 'lastname', 'email', 'phone',
'civilstatus', 'job', 'salary', 'habitation_type'
];
// Validation explicite des champs requis de l'étape 2
foreach ($step2RequiredFields as $field) {
if (isset($data[$field])) {
$this->required($field);
}
}
// Validation des champs spécifiques de l'étape 2
if (isset($data['email'])) {
$this->email('email', 'L\'email doit être valide.');
}
if (isset($data['phone'])) {
$this->phone('phone', 'Le téléphone doit être un numéro belge valide.');
}
// Type de contrat et revenus
if (isset($data['contract_type']) && !empty($data['contract_type'])) {
$allowedContracts = ['cdi', 'cdd', 'fon', 'int', 'oth', 'ssc'];
$this->in('contract_type', $allowedContracts, 'Le type de contrat doit être valide.');
}
if (isset($data['independent_since'])) {
$this->date('independent_since', 'La date de début d\'activité indépendante doit être au format JJ/MM/AAAA.');
}
if (isset($data['salary'])) {
$this->amount('salary', 0, null, 'Le salaire doit être un montant positif.');
}
if (isset($data['annual_taxable_income'])) {
$this->amount('annual_taxable_income', 0, null, 'Le revenu imposable doit être un montant positif.');
}
// Autres revenus
if (isset($data['hasotherincome']) && $data['hasotherincome'] === '1') {
if (isset($data['oiamouthmealvoucher'])) {
$this->amount('oiamouthmealvoucher', 0, null, 'Le montant des chèques repas doit être positif.');
}
if (isset($data['oiamouthrentalincome'])) {
$this->amount('oiamouthrentalincome', 0, null, 'Le montant des revenus locatifs doit être positif.');
}
if (isset($data['oiamouthunemployment'])) {
$this->amount('oiamouthunemployment', 0, null, 'Le montant du chômage doit être positif.');
}
if (isset($data['oiamouthother'])) {
$this->amount('oiamouthother', 0, null, 'Le montant des autres revenus doit être positif.');
}
}
// Fichage
if (isset($data['isFiched']) && $data['isFiched'] === '1') {
$this->required('fichage_status', 'Le statut du fichage est requis.');
}
// Logement
if (isset($data['habitation_type'])) {
$allowedTypes = ['proprietaire', 'proprietaire_sans_pret', 'locataire', 'cohabitant'];
$this->in('habitation_type', $allowedTypes, 'Le type d\'habitation doit être valide.');
}
if (isset($data['habitation_loyer'])) {
$this->amount('habitation_loyer', 0, null, 'Le montant du loyer doit être positif.');
}
// Crédits en cours
if (isset($data['hascurrentloan']) && $data['hascurrentloan'] === '1') {
$this->validateCurrentLoans($data);
}
return $this->errors;
}
/**
* Valide les données de l'étape 3 (informations personnelles)
* @param array $data Les données du formulaire
* @return array Erreurs de validation
*/
public function validateStep3($data)
{
$this->data = $data;
$this->errors = [];
// Champs requis pour l'étape 3 UNIQUEMENT - Emprunteur principal
$step3RequiredFields = [
'birthdate', 'birthplace', 'nationality',
'cardnumber', 'cnvaliditydate',
'nationalregistrationnumber', 'bankaccountnumber',
'dependentchildren'
];
// Validation explicite des champs requis de l'étape 3 pour l'emprunteur principal
foreach ($step3RequiredFields as $field) {
if (isset($data[$field])) {
$this->required($field);
}
}
// Validation spécifique des champs de l'étape 3 - Emprunteur principal
if (isset($data['birthdate'])) {
$this->date('birthdate', 'La date de naissance doit être au format JJ/MM/AAAA.')
->datePast('birthdate', 'La date de naissance doit être dans le passé.');
}
if (isset($data['cnvaliditydate'])) {
$this->date('cnvaliditydate', 'La date de validité de la carte doit être au format JJ/MM/AAAA.')
->dateFuture('cnvaliditydate', 'La date de validité de la carte doit être dans le futur.');
}
if (isset($data['nationalregistrationnumber'])) {
$this->nationalNumber('nationalregistrationnumber', 'Le numéro de registre national doit être valide.');
}
if (isset($data['bankaccountnumber'])) {
$this->bankAccount('bankaccountnumber', 'Le numéro de compte bancaire doit être valide.');
}
if (isset($data['cardnumber'])) {
$this->identityCard('cardnumber', 'Le numéro de carte d\'identité doit être valide.');
}
if (isset($data['dependentchildren'])) {
$this->childrenCount('dependentchildren', 'Le nombre d\'enfants doit être entre 0 et 24.');
}
if (isset($data['montant_allocation_familiale'])) {
$this->amount('montant_allocation_familiale', 0, null, 'Le montant des allocations familiales doit être positif.');
}
// Validation du co-emprunteur UNIQUEMENT si hascoborrower est défini et égal à '1'
if (isset($data['hascoborrower']) && $data['hascoborrower'] === '1') {
// Validation des champs du co-emprunteur (tous optionnels mais si présents, doivent être valides)
if (isset($data['cobirthdate'])) {
$this->date('cobirthdate', 'La date de naissance du co-emprunteur doit être au format JJ/MM/AAAA.')
->datePast('cobirthdate', 'La date de naissance du co-emprunteur doit être dans le passé.');
}
if (isset($data['cocnvaliditydate'])) {
$this->date('cocnvaliditydate', 'La date de validité de la carte du co-emprunteur doit être au format JJ/MM/AAAA.')
->dateFuture('cocnvaliditydate', 'La date de validité de la carte du co-emprunteur doit être dans le futur.');
}
if (isset($data['conationalregistrationnumber'])) {
$this->nationalNumber('conationalregistrationnumber', 'Le numéro de registre national du co-emprunteur doit être valide.');
}
if (isset($data['cobankaccountnumber'])) {
$this->bankAccount('cobankaccountnumber', 'Le numéro de compte bancaire du co-emprunteur doit être valide.');
}
if (isset($data['cocardnumber'])) {
$this->identityCard('cocardnumber', 'Le numéro de carte d\'identité du co-emprunteur doit être valide.');
}
if (isset($data['codependentchildren'])) {
$this->childrenCount('codependentchildren', 'Le nombre d\'enfants du co-emprunteur doit être entre 0 et 24.');
}
if (isset($data['comontant_allocation_familiale'])) {
$this->amount('comontant_allocation_familiale', 0, null, 'Le montant des allocations familiales du co-emprunteur doit être positif.');
}
}
return $this->errors;
}
/**
* Valide la clé de contrôle du numéro de registre national belge
* @param string $number Le numéro à valider
* @return bool
*/
private function validateNationalNumberChecksum($number)
{
if (strlen($number) !== 11) {
return false;
}
$base = substr($number, 0, 9);
$check = substr($number, 9, 2);
$remainder = intval($base) % 97;
$expectedCheck = 97 - $remainder;
return intval($check) === $expectedCheck;
}
/**
* Ajoute une erreur
* @param string $field Le nom du champ
* @param string $message Le message d'erreur
*/
private function addError($field, $message)
{
$this->errors[$field] = $message;
}
/**
* Retourne les erreurs
* @return array
*/
public function getErrors()
{
return $this->errors;
}
/**
* Vérifie s'il y a des erreurs
* @return bool
*/
public function hasErrors()
{
return !empty($this->errors);
}
/**
* Retourne les erreurs formatées pour l'affichage
* @return string
*/
public function getFormattedErrors()
{
if (empty($this->errors)) {
return '';
}
$html = '<div class="alert alert-danger"><h4>Erreurs de validation :</h4><ul>';
foreach ($this->errors as $field => $message) {
$html .= '<li>' . htmlspecialchars($message) . '</li>';
}
$html .= '</ul></div>';
return $html;
}
/**
* Analyse les champs d'un formulaire et retourne leurs types détectés
* @param array $data Les données du formulaire
* @return array Types de champs détectés
*/
public function analyzeFields($data)
{
$fieldTypes = [];
foreach ($data as $field => $value) {
if (in_array($field, ['credit-direct-token', 'type_credit_selected', 'sub_loan_type'])) {
continue;
}
$fieldType = $this->detectFieldType($field, $value);
$fieldTypes[$field] = [
'type' => $fieldType,
'value' => $value,
'detection_method' => $this->getDetectionMethod($field, $value, $fieldType)
];
}
return $fieldTypes;
}
/**
* Retourne la méthode de détection utilisée pour un champ
* @param string $field Le nom du champ
* @param mixed $value La valeur du champ
* @param string $detectedType Le type détecté
* @return string
*/
private function getDetectionMethod($field, $value, $detectedType)
{
$field = strtolower($field);
// Vérifier si c'est par nom de champ
if (strpos($field, 'email') !== false && $detectedType === 'email') {
return 'nom_champ';
}
if (strpos($field, 'phone') !== false && $detectedType === 'phone') {
return 'nom_champ';
}
if (strpos($field, 'date') !== false && in_array($detectedType, ['date', 'date_past', 'date_future'])) {
return 'nom_champ';
}
// Vérifier si c'est par valeur
if ($detectedType === 'email' && filter_var($value, FILTER_VALIDATE_EMAIL)) {
return 'valeur';
}
if ($detectedType === 'date' && $this->isDateFormat($value)) {
return 'valeur';
}
if ($detectedType === 'amount' && is_numeric($value)) {
return 'valeur';
}
return 'nom_champ';
}
/**
* Génère un rapport d'analyse des champs
* @param array $data Les données du formulaire
* @return string Rapport HTML
*/
public function generateFieldAnalysisReport($data)
{
$fieldTypes = $this->analyzeFields($data);
$html = '<div class="field-analysis-report">';
$html .= '<h3>Analyse des types de champs</h3>';
$html .= '<table class="table table-striped">';
$html .= '<thead><tr><th>Champ</th><th>Valeur</th><th>Type détecté</th><th>Méthode de détection</th></tr></thead>';
$html .= '<tbody>';
foreach ($fieldTypes as $field => $info) {
$html .= '<tr>';
$html .= '<td><code>' . htmlspecialchars($field) . '</code></td>';
$html .= '<td>' . htmlspecialchars($info['value']) . '</td>';
$html .= '<td><span class="badge badge-info">' . htmlspecialchars($info['type']) . '</span></td>';
$html .= '<td>' . htmlspecialchars($info['detection_method']) . '</td>';
$html .= '</tr>';
}
$html .= '</tbody></table>';
$html .= '</div>';
return $html;
}
}