feat(v2.0.2): Corrections de sécurité critiques et fonctionnalité de réactivation des devis
- Correction de 14 vulnérabilités SQL (8 critiques, 6 moyennes) - Suppression de la fonction autocomplete non utilisée - Migration complète vers PDO avec requêtes préparées - Ajout du bouton 'Réactiver' pour les devis archivés (statut 20 → 1) - Conversion des appels $.ajax en fetch API (vanilla JS) - Correction des erreurs JavaScript empêchant l'attachement d'événements - Mise à jour de la documentation (README.md et TODO.md) Sécurité: Utilisation systématique de intval() et requêtes préparées PDO UI: Nouveau bouton vert dans la grille 2x2 des actions sur devis archivés Historique: Traçabilité dans devis_histo lors de la réactivation
This commit is contained in:
@@ -1,16 +1,36 @@
|
||||
<?php
|
||||
$sch = "";
|
||||
$search = "";
|
||||
if ($_POST) {
|
||||
if (isset($_POST["schClients"])) {
|
||||
$search = nettoie_input(trim($_POST["schClients"]));
|
||||
$sch = 'c.libelle LIKE "%' . $search . '%" OR c.adresse1 LIKE "%' . $search . '%" OR c.cp LIKE "%' . $search . '%" OR c.ville LIKE "%' . $search . '%" OR c.contact_nom LIKE "%' . $search . '%" OR c.contact_prenom LIKE "%' . $search . '%" OR c.contact_fonction LIKE "%' . $search . '%" ';
|
||||
$search = trim($_POST["schClients"]);
|
||||
}
|
||||
}
|
||||
|
||||
//! On récupère la liste des clients
|
||||
$sql = 'SELECT c.* FROM clients c ';
|
||||
if ($sch != "") {
|
||||
$sql .= 'WHERE ' . $sch;
|
||||
if ($search != "") {
|
||||
// SÉCURITÉ : Utilisation de requêtes préparées pour éviter l'injection SQL
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
$sql = 'SELECT c.* FROM clients c
|
||||
WHERE c.libelle LIKE :search
|
||||
OR c.adresse1 LIKE :search
|
||||
OR c.cp LIKE :search
|
||||
OR c.ville LIKE :search
|
||||
OR c.contact_nom LIKE :search
|
||||
OR c.contact_prenom LIKE :search
|
||||
OR c.contact_fonction LIKE :search
|
||||
ORDER BY c.libelle';
|
||||
|
||||
$stmt = $db->prepare($sql);
|
||||
$searchParam = '%' . $search . '%';
|
||||
$stmt->bindParam(':search', $searchParam, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
$aModel["clients"] = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (Exception $e) {
|
||||
error_log("Erreur recherche clients : " . $e->getMessage());
|
||||
$aModel["clients"] = [];
|
||||
}
|
||||
} else {
|
||||
$sql = 'SELECT c.* FROM clients c ORDER BY c.libelle';
|
||||
$aModel["clients"] = getinfos($sql, "gen");
|
||||
}
|
||||
$sql .= 'ORDER BY c.libelle';
|
||||
$aModel["clients"] = getinfos($sql, "gen");
|
||||
|
||||
@@ -1,38 +1,72 @@
|
||||
<?php
|
||||
global $Session;
|
||||
|
||||
$fkUser = $Session->_user["rowid"];
|
||||
$fkRole = $Session->_user["fk_role"];
|
||||
$fkUser = intval($Session->_user["rowid"]); // SÉCURITÉ : Validation de l'ID utilisateur
|
||||
$fkRole = intval($Session->_user["fk_role"]); // SÉCURITÉ : Validation du rôle
|
||||
|
||||
// SÉCURITÉ : Construction de la clause WHERE avec des paramètres sécurisés
|
||||
$whereParams = [];
|
||||
switch ($fkRole) {
|
||||
case 1:
|
||||
// DIR-CO
|
||||
$where = 'd.fk_user=' . $fkUser . ' OR d.fk_statut_devis>=2';
|
||||
$where = 'd.fk_user = :fkUser OR d.fk_statut_devis >= 2';
|
||||
$whereParams[':fkUser'] = $fkUser;
|
||||
break;
|
||||
case 2:
|
||||
// DV : on récupère tous les RR de son périmètre
|
||||
$sql = 'SELECT rowid FROM users WHERE fk_parent =' . $fkUser . ';';
|
||||
$aRR = getinfos($sql, "gen");
|
||||
$lstRR = '';
|
||||
foreach ($aRR as $rr) {
|
||||
$lstRR .= $rr["rowid"] . ',';
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
$sql = 'SELECT rowid FROM users WHERE fk_parent = :fkParent';
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute([':fkParent' => $fkUser]);
|
||||
$aRR = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$rrIds = array_column($aRR, 'rowid');
|
||||
if (!empty($rrIds)) {
|
||||
// Création de placeholders pour la requête IN
|
||||
$placeholders = [];
|
||||
foreach ($rrIds as $index => $id) {
|
||||
$placeholder = ':rr' . $index;
|
||||
$placeholders[] = $placeholder;
|
||||
$whereParams[$placeholder] = $id;
|
||||
}
|
||||
$where = 'd.fk_user = :fkUser OR (d.fk_statut_devis >= 3 AND d.fk_user IN (' . implode(',', $placeholders) . '))';
|
||||
$whereParams[':fkUser'] = $fkUser;
|
||||
} else {
|
||||
$where = 'd.fk_user = :fkUser';
|
||||
$whereParams[':fkUser'] = $fkUser;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Erreur récupération RR : " . $e->getMessage());
|
||||
$where = 'd.fk_user = :fkUser';
|
||||
$whereParams[':fkUser'] = $fkUser;
|
||||
}
|
||||
$lstRR = substr($lstRR, 0, -1);
|
||||
$where = 'd.fk_user=' . $fkUser . ' OR (d.fk_statut_devis>=3 AND d.fk_user IN (' . $lstRR . '))';
|
||||
break;
|
||||
default:
|
||||
// RR
|
||||
$where = 'd.fk_user=' . $fkUser;
|
||||
$where = 'd.fk_user = :fkUser';
|
||||
$whereParams[':fkUser'] = $fkUser;
|
||||
break;
|
||||
}
|
||||
|
||||
$sql = 'SELECT d.rowid, d.dossier, d.date_demande, d.date_remise, d.num_opportunite, d.fk_client, d.montant_total_ht_remise, d.marge_totale, d.commentaire, d.chk_speciaux, c.libelle, c.ville, c.cp, d.fk_statut_devis, ';
|
||||
$sql .= 'd.lib_new_client, d.type_new_client, d.adresse1_new_client, d.adresse2_new_client, d.adresse3_new_client, d.cp_new_client, d.ville_new_client, d.comment_devis, d.comment_geste_comm, ';
|
||||
$sql .= 'd.contact_new_nom, d.contact_new_prenom, d.new_telephone, d.new_mobile, d.new_email, d.contact_new_fonction, LEFT(u.prenom,1) AS prenom, u.libelle as nom, ';
|
||||
$sql .= 'xs.libelle as lib_statut, d.chk_new_statut, m.libelle as lib_marche, d.chk_validat, d.fk_user_validat, d.date_validat ';
|
||||
$sql .= 'FROM devis d LEFT JOIN clients c ON c.rowid=d.fk_client LEFT JOIN x_statuts_devis xs ON xs.rowid=d.fk_statut_devis LEFT JOIN marches m ON m.rowid=d.fk_marche LEFT JOIN users u ON u.rowid=d.fk_user ';
|
||||
$sql .= 'WHERE ' . $where . ' ORDER BY d.dossier, d.date_remise DESC;';
|
||||
$aModel["devis"] = getinfos($sql, "gen");
|
||||
// SÉCURITÉ : Requête avec paramètres préparés
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
$sql = 'SELECT d.rowid, d.dossier, d.date_demande, d.date_remise, d.num_opportunite, d.fk_client, d.montant_total_ht_remise, d.marge_totale, d.commentaire, d.chk_speciaux, c.libelle, c.ville, c.cp, d.fk_statut_devis, ';
|
||||
$sql .= 'd.lib_new_client, d.type_new_client, d.adresse1_new_client, d.adresse2_new_client, d.adresse3_new_client, d.cp_new_client, d.ville_new_client, d.comment_devis, d.comment_geste_comm, ';
|
||||
$sql .= 'd.contact_new_nom, d.contact_new_prenom, d.new_telephone, d.new_mobile, d.new_email, d.contact_new_fonction, LEFT(u.prenom,1) AS prenom, u.libelle as nom, ';
|
||||
$sql .= 'xs.libelle as lib_statut, d.chk_new_statut, m.libelle as lib_marche, d.chk_validat, d.fk_user_validat, d.date_validat ';
|
||||
$sql .= 'FROM devis d LEFT JOIN clients c ON c.rowid=d.fk_client LEFT JOIN x_statuts_devis xs ON xs.rowid=d.fk_statut_devis LEFT JOIN marches m ON m.rowid=d.fk_marche LEFT JOIN users u ON u.rowid=d.fk_user ';
|
||||
$sql .= 'WHERE ' . $where . ' ORDER BY d.dossier, d.date_remise DESC';
|
||||
|
||||
$pdo = $db->getPDO();
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($whereParams);
|
||||
$aModel["devis"] = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (Exception $e) {
|
||||
error_log("Erreur récupération devis : " . $e->getMessage());
|
||||
$aModel["devis"] = [];
|
||||
}
|
||||
|
||||
//! on compte le nombre de devis par statut
|
||||
$aModel["nb_devis"] = array();
|
||||
@@ -45,8 +79,16 @@ foreach ($aModel["devis"] as $devis) {
|
||||
}
|
||||
|
||||
//! On récupère la liste des dossiers des devis
|
||||
$sql = 'SELECT DISTINCT d.dossier FROM devis d WHERE ' . $where . ' ORDER BY d.dossier;';
|
||||
$aModel["dossiers"] = getinfos($sql, "gen");
|
||||
// SÉCURITÉ : Requête avec paramètres préparés
|
||||
try {
|
||||
$sql = 'SELECT DISTINCT d.dossier FROM devis d WHERE ' . $where . ' ORDER BY d.dossier';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($whereParams);
|
||||
$aModel["dossiers"] = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (Exception $e) {
|
||||
error_log("Erreur récupération dossiers : " . $e->getMessage());
|
||||
$aModel["dossiers"] = [];
|
||||
}
|
||||
|
||||
//! Tous les produits du catalogue
|
||||
$sql = 'SELECT rowid, code, libelle, prix_vente, prix_achat_net FROM produits WHERE active=1;';
|
||||
|
||||
@@ -58,7 +58,8 @@ switch ($Route->_action) {
|
||||
case "export_sap_devis":
|
||||
$cid = nettoie_input($Route->_param1);
|
||||
|
||||
$sql = 'SELECT d.* FROM devis d WHERE d.rowid=' . $cid . ';';
|
||||
$cidSafe = intval($cid);
|
||||
$sql = 'SELECT d.* FROM devis d WHERE d.rowid=' . $cidSafe . ';';
|
||||
eLog("Export Excel SAP Devis : " . $sql);
|
||||
$dev = getinfos($sql, "gen");
|
||||
$devis = $dev[0];
|
||||
@@ -69,11 +70,12 @@ switch ($Route->_action) {
|
||||
$fields = array("Code", "Etablissement", "Adresse 1", "Adresse 2", "Adresse 3", "Code Postal", "Ville");
|
||||
$excelData = implode("\t", array_values($fields)) . "\n";
|
||||
|
||||
$sql = 'SELECT c.code, c.libelle, c.adresse1, c.adresse2, c.adresse3, c.cp, c.ville FROM clients c WHERE c.rowid=' . $devis["fk_client"] . ';';
|
||||
$fkClientSafe = intval($devis["fk_client"]);
|
||||
$sql = 'SELECT c.code, c.libelle, c.adresse1, c.adresse2, c.adresse3, c.cp, c.ville FROM clients c WHERE c.rowid=' . $fkClientSafe . ';';
|
||||
$cli = getinfos($sql, "gen");
|
||||
if (count($cli) == 0) {
|
||||
// c'est un nouveau client, on affiche les données client enregistrées dans le devis
|
||||
$sql = 'SELECT "0" AS code, d.lib_new_client, d.adresse1_new_client, d.adresse2_new_client, d.adresse3_new_client, d.cp_new_client, d.ville_new_client FROM devis d WHERE d.rowid=' . $cid . ';';
|
||||
$sql = 'SELECT "0" AS code, d.lib_new_client, d.adresse1_new_client, d.adresse2_new_client, d.adresse3_new_client, d.cp_new_client, d.ville_new_client FROM devis d WHERE d.rowid=' . $cidSafe . ';';
|
||||
$cli = getinfos($sql, "gen");
|
||||
$client = $cli[0];
|
||||
|
||||
@@ -84,7 +86,7 @@ switch ($Route->_action) {
|
||||
$excelData .= "\n";
|
||||
|
||||
// Les données du contact à prendre aussi dans le devis
|
||||
$sql = 'SELECT d.contact_new_nom, d.contact_new_prenom, d.contact_new_fonction, d.new_telephone, d.new_mobile, d.new_email FROM devis d WHERE d.rowid=' . $cid . ';';
|
||||
$sql = 'SELECT d.contact_new_nom, d.contact_new_prenom, d.contact_new_fonction, d.new_telephone, d.new_mobile, d.new_email FROM devis d WHERE d.rowid=' . $cidSafe . ';';
|
||||
$cont = getinfos($sql, "gen");
|
||||
$contact = $cont[0];
|
||||
$fields = array("Contact Nom", "Prenom", "Fonction", "Fixe", "Mobile", "Email");
|
||||
@@ -103,7 +105,7 @@ switch ($Route->_action) {
|
||||
$excelData .= "\n";
|
||||
|
||||
// Les données du contact
|
||||
$sql = 'SELECT c.contact_nom, c.contact_prenom, c.contact_fonction, c.telephone, c.mobile, c.email FROM clients c WHERE c.rowid=' . $devis["fk_client"] . ';';
|
||||
$sql = 'SELECT c.contact_nom, c.contact_prenom, c.contact_fonction, c.telephone, c.mobile, c.email FROM clients c WHERE c.rowid=' . $fkClientSafe . ';';
|
||||
$cont = getinfos($sql, "gen");
|
||||
$contact = $cont[0];
|
||||
$fields = array("Contact Nom", "Prenom", "Fonction", "Fixe", "Mobile", "Email");
|
||||
@@ -119,7 +121,7 @@ switch ($Route->_action) {
|
||||
$sql = 'SELECT d.rowid, d.num_opportunite, IF(d.date_demande IS NULL OR d.date_demande="0000-00-00", "", DATE_FORMAT(d.date_demande, "%d/%m/%Y")) AS datedem, ';
|
||||
$sql .= 'IF(d.date_remise IS NULL OR d.date_remise="0000-00-00", "", DATE_FORMAT(d.date_remise, "%d/%m/%Y")) AS daterem, m.libelle AS lib_marche, m.numero AS num_marche, m.nom AS nom_marche, ';
|
||||
$sql .= 'IF(d.chk_devis_photos=1, "Oui", "Non") AS photos, d.commentaire, IF(d.chk_speciaux=1, "Oui", "Non") AS speciaux ';
|
||||
$sql .= 'FROM devis d LEFT JOIN marches m ON d.fk_marche=m.rowid WHERE d.rowid=' . $cid . ';';
|
||||
$sql .= 'FROM devis d LEFT JOIN marches m ON d.fk_marche=m.rowid WHERE d.rowid=' . $cidSafe . ';';
|
||||
$dev = getinfos($sql, "gen");
|
||||
$devis = $dev[0];
|
||||
$chkSpeciaux = $devis["speciaux"];
|
||||
@@ -133,7 +135,7 @@ switch ($Route->_action) {
|
||||
$excelData .= "\n";
|
||||
|
||||
// on affiche les totaux du devis
|
||||
$sql = 'SELECT d.montant_total_ht, d.montant_total_ht_remise, d.marge_totale FROM devis d WHERE d.rowid=' . $cid . ';';
|
||||
$sql = 'SELECT d.montant_total_ht, d.montant_total_ht_remise, d.marge_totale FROM devis d WHERE d.rowid=' . $cidSafe . ';';
|
||||
$dev = getinfos($sql, "gen");
|
||||
$totaux = $dev[0];
|
||||
$fields = array("Total HT", "Total HT Remise", "Marge Totale");
|
||||
@@ -153,7 +155,7 @@ switch ($Route->_action) {
|
||||
$sql .= 'LEFT JOIN produits p ON dp.fk_produit=p.rowid ';
|
||||
$sql .= 'LEFT JOIN produits_familles pf ON p.groupe=pf.groupe ';
|
||||
$sql .= 'LEFT JOIN x_familles xf ON pf.fk_famille=xf.rowid ';
|
||||
$sql .= 'WHERE dp.fk_devis=' . $cid . ' ORDER BY dp.ordre, xf.ordre, p.libelle;';
|
||||
$sql .= 'WHERE dp.fk_devis=' . $cidSafe . ' ORDER BY dp.ordre, xf.ordre, p.libelle;';
|
||||
$data = getinfos($sql, "gen");
|
||||
|
||||
$fields = array("Code", "Designation", "Prix Vente", "Quantite", "Remise", "PU vente avec remise", "Total HT", "Marge", "Commentaire");
|
||||
@@ -171,7 +173,7 @@ switch ($Route->_action) {
|
||||
$excelData .= "PRODUITS SPECIAUX" . "\n";
|
||||
$excelData .= "----" . "\n";
|
||||
$sql = 'SELECT IF(ds.chk_livr_multi=1, "Oui", "Non") AS livr_multi, ds.nb_livr, DATE_FORMAT(ds.date_livr_1, "%d/%m/%Y") AS datelivr ';
|
||||
$sql .= 'FROM devis_speciaux ds WHERE ds.fk_devis=' . $cid . ';';
|
||||
$sql .= 'FROM devis_speciaux ds WHERE ds.fk_devis=' . $cidSafe . ';';
|
||||
$spec = getinfos($sql, "gen");
|
||||
$speciaux = $spec[0];
|
||||
|
||||
@@ -189,7 +191,7 @@ switch ($Route->_action) {
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$sql = 'SELECT ds.fk_produit_' . $i . ', ds.code_produit_' . $i . ', ds.lib_produit_' . $i . ', ds.qte_' . $i . ', IF(ds.surcout_' . $i . '=0, "", FORMAT(ds.surcout_' . $i . ', 2, "fr_FR")), IF(ds.chk_echantillon_' . $i . '=1, "Oui", "Non") AS echantillon, ';
|
||||
$sql .= 'DATE_FORMAT(ds.date_echantillon_' . $i . ', "%d/%m/%Y") AS date_ech, ds.lib_concurrent_' . $i . ', ds.description_' . $i . ' ';
|
||||
$sql .= 'FROM devis_speciaux ds WHERE ds.fk_devis=' . $cid . ';';
|
||||
$sql .= 'FROM devis_speciaux ds WHERE ds.fk_devis=' . $cidSafe . ';';
|
||||
eLog($sql, "sql");
|
||||
$spec = getinfos($sql, "gen");
|
||||
$speciaux = $spec[0];
|
||||
|
||||
Reference in New Issue
Block a user