prepare("SELECT email, first_name, last_name FROM auth_users WHERE id = ? LIMIT 1"); $stmt->bind_param("i", $userId); $stmt->execute(); $res = $stmt->get_result(); $u = $res->fetch_assoc(); $stmt->close(); $userEmail = $u['email'] ?? ''; $userName = trim(($u['first_name'] ?? '') . ' ' . ($u['last_name'] ?? '')); /*********************** * 2) BOOKING INFO (nome classe + data) + ownership check ***********************/ $stmt = $conn->prepare(" SELECT bc.idbookingclass, bc.idorder, bc.status, bc.bookingstart, s.servicename, ss.dateschedule FROM bookingclass bc LEFT JOIN service s ON bc.idservice = s.idservice LEFT JOIN serviceschedule ss ON bc.idserviceschedule = ss.idserviceschedule WHERE bc.idbookingclass = ? AND bc.iduser = ? LIMIT 1 "); $stmt->bind_param("ii", $id, $userId); $stmt->execute(); $res = $stmt->get_result(); $bk = $res->fetch_assoc(); $stmt->close(); if (!$bk) { header("Location: userpanel.php?deleted=0&err=notfound"); exit(); } $className = $bk['servicename'] ?? 'Lezione'; $classDateRaw = $bk['dateschedule'] ?? null; $classDateTxt = $classDateRaw ? date("d-m-Y H:i", strtotime($classDateRaw)) : 'N/D'; $cancelDateTxt = date("d-m-Y H:i"); /*********************** * 3) CONTROLLI (stesse regole di userpanel.php) ***********************/ // Solo prenotazioni confermate: pending e lista d'attesa hanno i loro percorsi if (($bk['status'] ?? '') !== 'booked') { header("Location: userpanel.php?deleted=0&err=status"); exit(); } // Finestra oraria $classTime = new DateTime($classDateRaw ?: $bk['bookingstart']); $currentTime = new DateTime(); // Lezione già iniziata o passata: non si cancella if ($classTime <= $currentTime) { header("Location: userpanel.php?deleted=0&err=window"); exit(); } // Lezione di oggi: fino alle 17:00 entro le 00:01, dopo le 17:00 entro le 12:00 if ($classTime->format('Y-m-d') === $currentTime->format('Y-m-d')) { $classHour = (int) $classTime->format('H'); $classMinute = (int) $classTime->format('i'); $isBefore1700 = ($classHour < 17) || ($classHour === 17 && $classMinute === 0); $deadline = new DateTime($classTime->format($isBefore1700 ? 'Y-m-d 00:01:00' : 'Y-m-d 12:00:00')); if ($currentTime > $deadline) { header("Location: userpanel.php?deleted=0&err=window"); exit(); } } /*********************** * 3b) STORICO + RIPROGRAMMAZIONE + CANCELLAZIONE * Tutto in una transazione: o riesce tutto o niente. ***********************/ $idOrder = $bk['idorder'] !== null ? (int) $bk['idorder'] : null; $ord = null; $incremented = 'N'; $ticketLost = false; try { $conn->begin_transaction(); // Limite riprogrammazioni (stessa regola del pulsante Riprogramma). // FOR UPDATE: blocca la riga dell'ordine contro doppi click. if ($idOrder !== null) { $stmt = $conn->prepare("SELECT reprogrammed, maxreschedule FROM orderbook WHERE idorderbook = ? FOR UPDATE"); $stmt->bind_param("i", $idOrder); $stmt->execute(); $ord = $stmt->get_result()->fetch_assoc(); $stmt->close(); if ($ord) { if ((int) $ord['reprogrammed'] >= (int) $ord['maxreschedule']) { // Limite raggiunto: può cancellare (libera il posto) ma il ticket va perso $ticketLost = true; } else { $incremented = 'Y'; } } } // Copia nello storico (prima di eliminare) $stmt = $conn->prepare(" INSERT INTO bookingclass_cancellations (idbookingclass, idserviceschedule, bookingstart, status, idservice, iduser, idorder, prevbookingstart, is_reprogrammed, is_gift, booking_created_at, cancelled_at, cancelled_by, reprogrammed_incremented) SELECT idbookingclass, idserviceschedule, bookingstart, status, idservice, iduser, idorder, prevbookingstart, is_reprogrammed, is_gift, created_at, NOW(), ?, ? FROM bookingclass WHERE idbookingclass = ? AND iduser = ? "); $storicoFlag = $ticketLost ? 'L' : $incremented; $stmt->bind_param("isii", $userId, $storicoFlag, $id, $userId); $stmt->execute(); $copied = $stmt->affected_rows; $stmt->close(); if ($copied !== 1) { throw new RuntimeException('notfound'); } // La cancellazione conta come una riprogrammazione if ($incremented === 'Y') { $stmt = $conn->prepare("UPDATE orderbook SET reprogrammed = reprogrammed + 1 WHERE idorderbook = ?"); $stmt->bind_param("i", $idOrder); $stmt->execute(); $stmt->close(); } // Limite raggiunto: la prenotazione resta come persa (status 'lost'), // il posto in sala si libera ma il ticket non torna da programmare. // Altrimenti eliminazione normale: il ticket torna disponibile. if ($ticketLost) { $stmt = $conn->prepare("UPDATE bookingclass SET status = 'lost', lostlesson = 'Y' WHERE idbookingclass = ? AND iduser = ?"); } else { $stmt = $conn->prepare("DELETE FROM bookingclass WHERE idbookingclass = ? AND iduser = ?"); } $stmt->bind_param("ii", $id, $userId); $stmt->execute(); $deleted = $stmt->affected_rows; $stmt->close(); if ($deleted !== 1) { throw new RuntimeException('notfound'); } $conn->commit(); } catch (Throwable $e) { try { $conn->rollback(); } catch (Throwable $ignored) { } $code = in_array($e->getMessage(), ['limit', 'notfound'], true) ? $e->getMessage() : 'db'; if ($code === 'db') { error_log("delete_lesson #{$id} utente #{$userId}: " . $e->getMessage()); } header("Location: userpanel.php?deleted=0&err=" . $code); exit(); } // Connessione $bkngstm gestita globalmente: non la chiudiamo qui /*********************** * 4) EMAIL (TO utente + CC info@yogasoul.it) * Usa le chiavi del tuo .env: MAIL_HOST, MAIL_PORT, MAIL_USERNAME, ... ***********************/ try { // Carica vendor/autoload + dotenv (il tuo file esistente) require_once(__DIR__ . '/class/mailer.php'); $mail = new \PHPMailer\PHPMailer\PHPMailer(true); // Legge le variabili in stile Laravel (con trim per eventuali virgolette) $host = trim($_ENV['MAIL_HOST'] ?? 'mail.yogasoul.it', "\" \t\n\r\0\x0B"); $port = (int)($_ENV['MAIL_PORT'] ?? 465); $user = $_ENV['MAIL_USERNAME'] ?? ''; $pass = $_ENV['MAIL_PASSWORD'] ?? ''; $enc = strtolower(trim($_ENV['MAIL_ENCRYPTION'] ?? 'ssl', "\" \t\n\r\0\x0B")); $fromEmail = trim($_ENV['MAIL_FROM_ADDRESS'] ?? 'info@yogasoul.it', "\" \t\n\r\0\x0B"); $fromName = trim($_ENV['MAIL_FROM_NAME'] ?? 'YogiBook', "\" \t\n\r\0\x0B"); // Config SMTP $mail->isSMTP(); $mail->Host = $host; $mail->SMTPAuth = true; $mail->Username = $user; $mail->Password = $pass; $mail->Port = $port; if (in_array($enc, ['tls', 'ssl'], true)) { $mail->SMTPSecure = $enc; } // Timeout (utile su SMTP hosting) $mail->Timeout = 20; $mail->setFrom($fromEmail, $fromName); $mail->CharSet = 'UTF-8'; $mail->isHTML(true); // Destinatari if (!empty($userEmail)) { $mail->addAddress($userEmail, $userName ?: ''); } $mail->addCC('info@yogasoul.it'); // Log impostazioni usate (senza password) mail_log($mailLogFile, "SMTP in uso host={$host} porta={$port} enc={$enc} user={$user}"); // Contenuto (ITALIANO) $mail->Subject = "Lezione cancellata - {$className}"; $mail->Body = "
Ciao " . htmlspecialchars($userName ?: 'Yogi') . ",
La tua lezione è stata cancellata.
" . ($ticketLost ? "Avendo già usato tutte le riprogrammazioni del pacchetto, questa lezione non potrà essere recuperata.
" : "") . "
Lezione: " . htmlspecialchars($className) . "
Data/ora prevista: " . htmlspecialchars($classDateTxt) . "
Data/ora cancellazione: " . htmlspecialchars($cancelDateTxt) . "
ID prenotazione: #{$id}
Grazie,
— " . htmlspecialchars($fromName) . "