| Current Path : /home/happyrenas/find.myreco.online/ajax/ |
Linux webd005.cluster105.gra.hosting.ovh.net 5.15.206-ovh-vps-grsec-zfs-classid #1 SMP Fri May 15 02:41:25 UTC 2026 x86_64 |
| Current File : /home/happyrenas/find.myreco.online/ajax/contact_host.php |
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
session_start();
require_once __DIR__ . '/../configuration.php';
require_once __DIR__ . '/../includes/fonctions.php';
/* =========================================================
Helpers
========================================================= */
function json_response(array $payload, int $httpCode = 200): void
{
http_response_code($httpCode);
echo json_encode($payload, JSON_UNESCAPED_UNICODE);
exit;
}
/**
* Échappement SQL (fallback)
*/
if (!isset($esc) || !is_callable($esc)) {
$esc = static function ($v) use ($db): string {
if ($v === null) {
return 'NULL';
}
return "'" . $db->escape((string)$v) . "'";
};
}
/* =========================================================
Method check
========================================================= */
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
json_response(['ok' => false, 'error' => 'Méthode invalide'], 405);
}
/* =========================================================
Inputs (tokens + selection_json)
========================================================= */
$tokens = $_POST['tokens'] ?? [];
if (!is_array($tokens)) {
$tokens = [$tokens];
}
$tokens = array_values(array_unique(array_filter(array_map(
static fn($t) => trim((string)$t),
$tokens
))));
$selection_json = (string)($_POST['selection_json'] ?? '[]');
$items = json_decode($selection_json, true);
if (!is_array($items)) {
$items = [];
}
if (!$tokens) {
json_response(['ok' => false, 'error' => 'Aucun hébergement sélectionné.']);
}
/* Index des items par token */
$itemsByToken = [];
foreach ($items as $it) {
$t = trim((string)($it['token'] ?? ''));
if ($t !== '') {
$itemsByToken[$t] = $it;
}
}
/* =========================================================
Inputs (form)
========================================================= */
$nom = trim((string)($_POST['nom'] ?? ''));
$email = trim((string)($_POST['email'] ?? ''));
$date_debut = trim((string)($_POST['date_debut'] ?? ''));
$date_fin = trim((string)($_POST['date_fin'] ?? ''));
$voyageur_nombre = trim((string)($_POST['voyageur_nombre'] ?? ''));
$user_message = trim((string)($_POST['message'] ?? ''));
/* UID commun à toute la demande */
try {
$request_uid = bin2hex(random_bytes(16));
} catch (Throwable $e) {
json_response(['ok' => false, 'error' => 'Erreur technique (UID).'], 500);
}
/* =========================================================
Validation
========================================================= */
if ($nom === '' || $email === '' || $user_message === '') {
json_response(['ok' => false, 'error' => 'Champs manquants']);
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
json_response(['ok' => false, 'error' => 'Email invalide']);
}
$len = mb_strlen($user_message);
if ($len < 5) {
json_response(['ok' => false, 'error' => 'Message trop court']);
}
if ($len > 5000) {
json_response(['ok' => false, 'error' => 'Message trop long']);
}
if ($date_debut === '' || $date_fin === '' || $voyageur_nombre === '') {
json_response(['ok' => false, 'error' => 'Dates / voyageurs manquants']);
}
if (
!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_debut) ||
!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_fin)
) {
json_response(['ok' => false, 'error' => 'Format de date invalide']);
}
$tz = new DateTimeZone('Europe/Paris');
$dtDebut = DateTimeImmutable::createFromFormat('!Y-m-d', $date_debut, $tz);
$errDebut = DateTimeImmutable::getLastErrors();
if (!$dtDebut || ($errDebut['warning_count'] ?? 0) || ($errDebut['error_count'] ?? 0)) {
json_response(['ok' => false, 'error' => 'Date de début invalide']);
}
$dtFin = DateTimeImmutable::createFromFormat('!Y-m-d', $date_fin, $tz);
$errFin = DateTimeImmutable::getLastErrors();
if (!$dtFin || ($errFin['warning_count'] ?? 0) || ($errFin['error_count'] ?? 0)) {
json_response(['ok' => false, 'error' => 'Date de fin invalide']);
}
$today = new DateTimeImmutable('today', $tz);
if ($dtDebut < $today || $dtFin < $today) {
json_response(['ok' => false, 'error' => 'Merci de choisir des dates à partir d’aujourd’hui.']);
}
if ($dtDebut > $dtFin) {
json_response(['ok' => false, 'error' => 'La date de début doit être antérieure ou égale à la date de fin']);
}
if (!ctype_digit($voyageur_nombre) || (int)$voyageur_nombre < 1 || (int)$voyageur_nombre > 99) {
json_response(['ok' => false, 'error' => 'Nombre de voyageurs invalide']);
}
$nuit_nombre = (int)$dtDebut->diff($dtFin)->days;
if ($dtFin < $dtDebut) {
$nuit_nombre = 0;
}
/* =========================================================
Config queue
========================================================= */
$expediteur = 'contact@myreco.online';
$dest_cc = '';
$status = 'queued';
$next_attempt = date('Y-m-d H:i:s');
/* =========================================================
Template modèle 1 (mail vers hébergeur)
========================================================= */
$modele_id_host = 1;
$templatePath = __DIR__ . '/../matrice/modele' . (int)$modele_id_host . '.html';
if (!is_file($templatePath)) {
json_response(['ok' => false, 'error' => "Template introuvable: modele_id={$modele_id_host}"]);
}
$templateHtml = file_get_contents($templatePath);
if ($templateHtml === false || trim($templateHtml) === '') {
json_response(['ok' => false, 'error' => "Template vide ou illisible: modele_id={$modele_id_host}"]);
}
/* =========================================================
Templates synthèse client
========================================================= */
$recapMainPath = __DIR__ . '/../matrice/modele_recap_client.html';
$recapItemPath = __DIR__ . '/../matrice/partials/recap_item.html';
if (!is_file($recapMainPath) || !is_file($recapItemPath)) {
json_response([
'ok' => false,
'error' => 'Templates synthèse manquants: modele_recap_client.html et/ou partials/recap_item.html'
]);
}
$recapMainTpl = file_get_contents($recapMainPath);
$recapItemTpl = file_get_contents($recapItemPath);
if (!$recapMainTpl || !$recapItemTpl) {
json_response(['ok' => false, 'error' => 'Templates synthèse vides/illisibles']);
}
/* =========================================================
Traitement
========================================================= */
$insertedIds = [];
$skipped = [];
$recapItems = [];
$sentItems = [];
$manualItems = [];
$user_message_html = nl2br(h($user_message));
foreach ($tokens as $t) {
$t = trim((string)$t);
if ($t === '' || mb_strlen($t) > 80) {
$skipped[] = $t;
continue;
}
/* Métadonnées venant du front */
$meta = $itemsByToken[$t] ?? [];
$name = (string)($meta['name'] ?? 'Hébergement');
$ville = (string)($meta['ville'] ?? '');
$country = (string)($meta['country'] ?? '');
/* Données DB */
$site = '';
$site_reservation = '';
$email_reservation = '';
$nom_commercial = '';
$type_hebergement = '';
$donnees_specifiques = [];
$equipements_json = [];
$activites_json = [];
$tarif_nuit = null;
$parking = '';
$photo_locale = '';
$full_address = '';
$equipements_txt = '';
$activites_txt = '';
$rowHeb = $db->get_row("
SELECT
site,
site_reservation,
email_reservation,
full_address,
country,
nom_commercial,
type_hebergement,
donnees_specifiques,
equipements_json,
activites_json,
tarif_nuit,
parking,
photo_locale
FROM heb
WHERE token = '" . $db->escape($t) . "'
LIMIT 1
");
if ($rowHeb) {
$country = $rowHeb->country ?: $country;
$site = (string)$rowHeb->site;
$site_reservation = (string)$rowHeb->site_reservation;
$email_reservation = (string)$rowHeb->email_reservation;
$full_address = (string)$rowHeb->full_address;
$nom_commercial = (string)$rowHeb->nom_commercial;
$type_hebergement = (string)$rowHeb->type_hebergement;
$parking = (string)$rowHeb->parking;
$photo_locale = trim((string)$rowHeb->photo_locale);
$donnees_specifiques = json_decode((string)$rowHeb->donnees_specifiques, true);
if (!is_array($donnees_specifiques)) {
$donnees_specifiques = [];
}
$equipements_json = json_decode((string)$rowHeb->equipements_json, true);
if (!is_array($equipements_json)) {
$equipements_json = [];
}
$activites_json = json_decode((string)$rowHeb->activites_json, true);
if (!is_array($activites_json)) {
$activites_json = [];
}
$tarif_nuit = ($rowHeb->tarif_nuit !== null && $rowHeb->tarif_nuit !== '')
? (string)$rowHeb->tarif_nuit
: null;
$equipements_txt = json_list_to_string($rowHeb->equipements_json ?? '[]');
$activites_txt = json_list_to_string($rowHeb->activites_json ?? '[]');
}
/* =========================================================
Photo URL
========================================================= */
$photo_url = APP_BASE_URL . '/img/placeholder.svg';
if ($photo_locale !== '') {
$absBase = rtrim('/home/happyrenas/myreco.online', '/');
$absPath = $absBase . $photo_locale;
if (is_file($absPath)) {
$photo_url = rtrim(UPLOADS_WWW_BASE_URL, '/') . $photo_locale;
}
}
$hebergement_label = $nom_commercial ?: $name;
/* =========================================================
Collecte pour synthèse client
========================================================= */
$type = (string)($meta['type'] ?? 'Hébergement');
$prix = (string)($meta['prix'] ?? '');
$resa_fallback = '';
if ($site_reservation === '' && $email_reservation === '') {
$resa_fallback = 'Site non renseigné';
}
$recapItems[] = [
'token' => h($t),
'photo_url' => h($photo_url),
'hebergement_nom' => h($hebergement_label),
'hebergement_type' => h($type),
'prix' => h($prix),
'capacite' => h((string)($meta['capacite'] ?? '')),
'chambres' => h((string)($meta['chambres'] ?? '')),
'sdb' => h((string)($meta['sdb'] ?? '')),
'parking' => h((string)($meta['parking'] ?? $parking)),
'site_reservation' => h($site_reservation),
'email_reservation' => h($email_reservation),
'resa_fallback' => h($resa_fallback),
'equipements' => htmlspecialchars($equipements_txt, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
'activites' => htmlspecialchars($activites_txt, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
];
$itemLine = [
'hebergement_nom' => h($hebergement_label),
'capacite' => h((string)($meta['capacite'] ?? '')),
'chambres' => h((string)($meta['chambres'] ?? '')),
'sdb' => h((string)($meta['sdb'] ?? '')),
'site_reservation' => h($site_reservation),
];
/* =========================================================
Cas 1 : email hébergeur absent => contact manuel
========================================================= */
if ($email_reservation === '') {
$manualItems[] = $itemLine;
continue;
}
/* =========================================================
Cas 2 : email hébergeur présent => création queue
========================================================= */
try {
$action_token = bin2hex(random_bytes(16));
} catch (Throwable $e) {
$skipped[] = $t;
continue;
}
$acceptUrl = APP_BASE_URL . '/public/dispo.php?a=accept&t=' . $action_token . '&sig=' . make_sig('accept', $action_token);
$refuseUrl = APP_BASE_URL . '/public/dispo.php?a=refuse&t=' . $action_token . '&sig=' . make_sig('refuse', $action_token);
$objet = '[MyReco] ' . $name . ' - ' . $nom . ' - ' . $hebergement_label . ' du ' . date_fr_auto($date_debut) . ' au ' . date_fr_auto($date_fin) . ' - ' . $voyageur_nombre . ' pers.';
$vars = [
'client_nom' => h($nom),
'client_email' => h($email),
'hebergement_nom' => h($name),
'site' => h($site),
'site_reservation' => h($site_reservation),
'email_reservation' => h($email_reservation),
'message' => $user_message_html,
'date_debut' => h(date_fr_auto($date_debut)),
'date_fin' => h(date_fr_auto($date_fin)),
'voyageur_nombre' => h($voyageur_nombre),
'lien_acceptation' => h($acceptUrl),
'lien_refus' => h($refuseUrl),
];
$msg_db = renderTemplate($templateHtml, $vars);
$context = [
'token' => $t,
'hebergement' => [
'name' => $name,
'photo_locale' => $photo_locale,
'photo_url' => $photo_url,
'nom_commercial' => $hebergement_label,
'type_hebergement' => $type_hebergement,
'site' => $site,
'site_reservation' => $site_reservation,
'email_reservation' => $email_reservation,
'ville' => $ville,
'full_address' => $full_address,
'country' => $country,
'tarif_nuit' => $tarif_nuit,
'parking' => $parking,
'donnees_specifiques' => $donnees_specifiques,
'equipements_json' => $equipements_json,
'activites_json' => $activites_json,
],
'form' => [
'nom' => $nom,
'email' => $email,
'date_debut' => $date_debut,
'date_fin' => $date_fin,
'voyageur_nombre' => (int)$voyageur_nombre,
'nuit_nombre' => $nuit_nombre,
'message' => $user_message,
],
'request_uid' => $request_uid,
];
$context_json = json_encode($context, JSON_UNESCAPED_UNICODE);
$sql = "
INSERT INTO email_queue
(action_token, request_uid, token, modele_id, expediteur, destinataire_principal, destinataire_copie,
objet, message, context_json,
status, error_message, retries, last_attempt, next_attempt, created_at)
VALUES
(
" . $esc($action_token) . ",
" . $esc($request_uid) . ",
" . $esc($t) . ",
" . (int)$modele_id_host . ",
" . $esc($expediteur) . ",
" . $esc($email_reservation) . ",
" . $esc($dest_cc) . ",
" . $esc($objet) . ",
" . $esc($msg_db) . ",
" . $esc($context_json) . ",
" . $esc($status) . ",
NULL,
0,
NULL,
" . $esc($next_attempt) . ",
NOW()
)
";
$ok = $db->query($sql);
if (!$ok) {
$skipped[] = $t;
continue;
}
$insertedIds[] = (int)$db->insert_id;
$sentItems[] = $itemLine;
}
/* =========================================================
Si rien du tout n'est exploitable
========================================================= */
if (!$recapItems) {
json_response([
'ok' => false,
'error' => "Aucune demande n'a pu être enregistrée.",
'skipped' => $skipped
]);
}
/* =========================================================
Génération HTML des blocs résumé
========================================================= */
$sent_items_html = '';
foreach ($sentItems as $it) {
$sent_items_html .= '<div style="margin:0 0 8px 0;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;color:#111827;">'
. '<strong>' . $it['hebergement_nom'] . '</strong> : '
. 'Capacité : ' . ($it['capacite'] !== '' ? $it['capacite'] : '—')
. ' Chambres : ' . ($it['chambres'] !== '' ? $it['chambres'] : '—')
. ' SDB : ' . ($it['sdb'] !== '' ? $it['sdb'] : '—')
. '</div>';
}
$manual_items_html = '';
foreach ($manualItems as $it) {
$siteManual = $it['site_reservation'] !== '' ? $it['site_reservation'] : 'Site non renseigné';
$manual_items_html .= '<div style="margin:0 0 8px 0;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;color:#111827;">'
. '<strong>' . $it['hebergement_nom'] . '</strong> : '
. '🌐 ' . $siteManual
. '</div>';
}
$manual_message_copy = nl2br(h(
"Bonjour,\n"
. "Je vous contacte suite à ma recherche sur le site de réservation \"Find\" (https://find.myreco.online).\n"
. "Je recherche un hébergement pour {$voyageur_nombre} voyageur(s) • " . date_fr_auto($date_debut) . " → " . date_fr_auto($date_fin) . ".\n\n"
. "{$nom}\n"
. "{$email}\n\n"
. "{$user_message}\n\n"
. "Cordialement,\n"
. "{$nom}"
));
/* =========================================================
Génération des cards détail déjà existantes
========================================================= */
$items_html = '';
foreach ($recapItems as $varsItem) {
$chunk = tpl_if_blocks($recapItemTpl, $varsItem);
$chunk = tpl_vars($chunk, $varsItem);
$items_html .= $chunk;
}
/* =========================================================
Insertion synthèse client (1 seul email)
========================================================= */
$modele_id_recap = 4;
$varsRecap = [
'request_uid' => h($request_uid),
'lien_demande' => h(APP_BASE_URL . '/demande/' . $request_uid),
'voyageur_nombre' => h($voyageur_nombre),
'date_debut' => h(date_fr_auto($date_debut)),
'date_fin' => h(date_fr_auto($date_fin)),
'voyageur_nom' => h($nom),
'voyageur_email' => h($email),
'message' => nl2br(h($user_message)),
'hebergements_count' => h((string)count($recapItems)),
'items_html' => $items_html,
'has_sent_items' => count($sentItems) > 0 ? '1' : '',
'has_manual_items' => count($manualItems) > 0 ? '1' : '',
'sent_count' => h((string)count($sentItems)),
'manual_count' => h((string)count($manualItems)),
'sent_items_html' => $sent_items_html,
'manual_items_html' => $manual_items_html,
'manual_message_copy' => $manual_message_copy,
];
$recapHtml = tpl_if_blocks($recapMainTpl, $varsRecap);
$recapHtml = tpl_vars($recapHtml, $varsRecap);
try {
$recap_action_token = bin2hex(random_bytes(16));
} catch (Throwable $e) {
$recap_action_token = '';
}
$ville_objet = '';
foreach ($itemsByToken as $itMeta) {
$tmpVille = trim((string)($itMeta['ville'] ?? ''));
if ($tmpVille !== '') {
$ville_objet = $tmpVille;
break;
}
}
$recap_objet = '[MyReco] Synthèse demande de disponibilité'
. ($ville_objet !== '' ? ' ' . $ville_objet : '')
. ' du ' . date_fr_auto($date_debut)
. ' au ' . date_fr_auto($date_fin)
. ' - ' . $voyageur_nombre . ' pers.';
$recap_context = [
'request_uid' => $request_uid,
'tokens' => $tokens,
'form' => [
'nom' => $nom,
'email' => $email,
'date_debut' => $date_debut,
'date_fin' => $date_fin,
'voyageur_nombre' => (int)$voyageur_nombre,
'nuit_nombre' => $nuit_nombre,
'message' => $user_message,
],
'sent_items' => $sentItems,
'manual_items' => $manualItems,
'items' => $recapItems,
];
$recap_context_json = json_encode($recap_context, JSON_UNESCAPED_UNICODE);
$okRecap = $db->query("
INSERT INTO email_queue
(action_token, request_uid, token, modele_id, expediteur, destinataire_principal, destinataire_copie,
objet, message, context_json,
status, error_message, retries, last_attempt, next_attempt, created_at)
VALUES
(
" . $esc($recap_action_token) . ",
" . $esc($request_uid) . ",
" . $esc('') . ",
" . (int)$modele_id_recap . ",
" . $esc($expediteur) . ",
" . $esc($email) . ",
" . $esc($dest_cc) . ",
" . $esc($recap_objet) . ",
" . $esc($recapHtml) . ",
" . $esc($recap_context_json) . ",
" . $esc($status) . ",
NULL,
0,
NULL,
" . $esc($next_attempt) . ",
NOW()
)
");
$recap_id = $okRecap ? (int)$db->insert_id : 0;
json_response([
'ok' => true,
'request_uid' => $request_uid,
'queued' => count($insertedIds),
'manual_count' => count($manualItems),
'ids' => $insertedIds,
'skipped' => $skipped,
'recap_id' => $recap_id
]);