Compare commits

...

2 Commits

Author SHA1 Message Date
solocla 754c5f93f0 fixed promemoria and timing cancellation 2025-10-09 21:46:29 +02:00
solocla 26fb165c98 added notification and cancellation mail 2025-10-08 17:33:26 +02:00
20 changed files with 2108 additions and 747 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
APP_ENV=production
APP_DEBUG=false
APP_DEBUG=true
APP_KEY=base64:aj3bR0zA9I8nZ1Rm5alncE4QFTPNoHVkd8YSRJEImwY=
APP_URL=https://yogibook.yogasoul.it
+318
View File
@@ -0,0 +1,318 @@
<?php
// Abilita visualizzazione errori PHP (solo per debug)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
// Connessione al database
include('include/headscript.php');
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("Connessione al database fallita: " . $conn->connect_error);
}
// Inizializza log
$logFile = 'cancella_prenotazione_log.txt';
$logMessage = "Esecuzione cancellazione: " . date('Y-m-d H:i:s') . "\n";
// Recupera parametri GET: idbookingclass e token
if (!isset($_GET['idbookingclass']) || !isset($_GET['token'])) {
$logMessage .= "Parametri mancanti: idbookingclass o token\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
echo "<h1>Cancellazione non possibile</h1>";
echo "<p>Parametri mancanti.</p>";
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
$conn->close();
exit;
}
// Sanitizza idbookingclass
$idbookingclass = filter_var($_GET['idbookingclass'], FILTER_VALIDATE_INT);
$token = $_GET['token'];
if (!$idbookingclass) {
$logMessage .= "Errore: idbookingclass non valido: " . $_GET['idbookingclass'] . "\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
echo "<h1>Cancellazione non possibile</h1>";
echo "<p>Parametro idbookingclass non valido.</p>";
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
$conn->close();
exit;
}
// Verifica validità: token corrisponde, lezione futura
$query = "SELECT bc.*, ob.expireon, au.email, au.first_name, s.servicename
FROM bookingclass bc
LEFT JOIN orderbook ob ON bc.idorder = ob.order_id
LEFT JOIN auth_users au ON bc.iduser = au.id
LEFT JOIN service s ON bc.idservice = s.idservice
WHERE bc.idbookingclass = ?
AND bc.cancellation_token = ?
AND bc.status = 'booked'
AND bc.bookingstart > NOW()";
$stmt = $conn->prepare($query);
if (!$stmt) {
$logMessage .= "Errore preparazione query per ID $idbookingclass: " . $conn->error . "\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
echo "<h1>Cancellazione non possibile</h1>";
echo "<p>Errore nella preparazione della query.</p>";
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
$conn->close();
exit;
}
$stmt->bind_param("is", $idbookingclass, $token);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
$checkQuery = "SELECT * FROM bookingclass
WHERE idbookingclass = ?
AND cancellation_token = ?
AND status = 'booked'
AND bookingstart > NOW()";
$checkStmt = $conn->prepare($checkQuery);
$checkStmt->bind_param("is", $idbookingclass, $token);
$checkStmt->execute();
$checkResult = $checkStmt->get_result();
if ($checkResult->num_rows > 0) {
$row = $checkResult->fetch_assoc();
$bookingstart = $row['bookingstart'];
$lessonTime = new DateTime($bookingstart);
$hour = (int)$lessonTime->format('H');
$minute = (int)$lessonTime->format('i');
$isBefore1700 = ($hour < 17) || ($hour === 17 && $minute === 0);
$logMessage .= "Tentativo di cancellazione fallito per ID $idbookingclass: orario oltre il limite (" . ($isBefore1700 ? "00:01" : "12:00") . ")\n";
echo "<h1>Cancellazione non possibile</h1>";
echo "<p>Non è possibile cancellare la lezione dopo le " . ($isBefore1700 ? "00:01" : "12:00") . " del giorno della lezione.</p>";
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
} else {
$logMessage .= "Tentativo di cancellazione fallito per ID $idbookingclass: link non valido o lezione non prenotata\n";
echo "<h1>Cancellazione non possibile</h1>";
echo "<p>Il link non è valido o la lezione non è prenotata.</p>";
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
}
file_put_contents($logFile, $logMessage, FILE_APPEND);
$checkStmt->close();
$stmt->close();
$conn->close();
exit;
}
$row = $result->fetch_assoc();
$bookingstart = $row['bookingstart'];
$newtimeformat = date("d-m-Y H:i", strtotime($bookingstart));
$expireon = $row['expireon'] ? date("d-m-Y", strtotime($row['expireon'])) : "sconosciuta";
$emailuser = $row['email'];
$firstname = $row['first_name'] ?? 'Utente';
$servicename = $row['servicename'] ?? 'Sconosciuta';
$iduser = $row['iduser'];
$idservice = $row['idservice'];
$idorderbook = $row['idorder'];
// Verifica il limite di cancellazione
$lessonTime = new DateTime($bookingstart);
$hour = (int)$lessonTime->format('H');
$minute = (int)$lessonTime->format('i');
$isBefore1700 = ($hour < 17) || ($hour === 17 && $minute === 0);
$currentTime = new DateTime();
$lessonDate = $lessonTime->format('Y-m-d');
if ($isBefore1700) {
$deadline = new DateTime("$lessonDate 00:01:00");
} else {
$deadline = new DateTime("$lessonDate 12:00:00");
}
if ($currentTime > $deadline) {
$logMessage .= "Tentativo di cancellazione fallito per ID $idbookingclass: orario oltre il limite (" . $deadline->format('Y-m-d H:i:s') . ")\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
echo "<h1>Cancellazione non possibile</h1>";
echo "<p>Non è possibile cancellare la lezione dopo le " . ($isBefore1700 ? "00:01" : "12:00") . " del giorno della lezione.</p>";
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
$stmt->close();
$conn->close();
exit;
}
// Gestisci azione di cancellazione
if (isset($_GET['action']) && $_GET['action'] === 'cancel') {
$updateQuery = "UPDATE bookingclass
SET status = 'cancelled'
WHERE idbookingclass = ? AND cancellation_token = ?";
$updateStmt = $conn->prepare($updateQuery);
$updateStmt->bind_param("is", $idbookingclass, $token);
$updateStmt->execute();
if (!filter_var($emailuser, FILTER_VALIDATE_EMAIL)) {
$logMessage .= "Email non valida per ID $idbookingclass: $emailuser\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
echo "<h1>Cancellazione confermata</h1>";
echo "<p>La lezione del $newtimeformat è stata cancellata con successo, ma non è stato possibile inviare l'email di conferma.</p>";
echo "<p>Ricordati di riprenotare entro la scadenza del tuo abbonamento ($expireon).</p>";
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
$stmt->close();
$conn->close();
exit;
}
$cancellationDeadline = $isBefore1700 ? "00:01" : "12:00";
$messagecancel = "<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 18px; line-height: 34.2px;'><strong>Ciao $firstname,</strong></span></p>
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>La tua lezione ($servicename) del $newtimeformat è stata cancellata con successo!</span></p>
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Hai cancellato la lezione entro il limite delle $cancellationDeadline del giorno della lezione.</span></p>
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Ricordati di riprenotare entro la scadenza del tuo abbonamento ($expireon).</span></p>
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Per vedere e gestire le tue lezioni clicca qui: <a href='https://yogibook.yogasoul.it'>YogiBook</a></span></p>
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Ci vediamo sul tappetino!</span></p>
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Il Team Yogasoul</span></p>";
$messageedit = $messagecancel;
$buttonedit = "<a href='https://yogibook.yogasoul.it/' target='_blank' class='v-button v-font-size' style='box-sizing: border-box;display: inline-block;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #FFFFFF; background-color: #3AAEE0; border-radius: 4px;-webkit-border-radius: 4px; -moz-border-radius: 4px; width:auto; max-width:100%; overflow-wrap: break-word; word-break: break-word; word-wrap:break-word; mso-border-alt: none;font-size: 14px;'>
<span style='display:block;padding:10px 20px;line-height:120%;'><span style='line-height: 16.8px;'>YogiBook - YogaSoul</span></span>
</a>";
require_once 'phpmailer/src/Exception.php';
require_once 'phpmailer/src/PHPMailer.php';
require_once 'phpmailer/src/SMTP.php';
$mail = new PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = 'mail.yogasoul.it';
$mail->SMTPAuth = true;
$mail->Username = 'info@yogasoul.it';
$mail->Password = '!Testolina88';
$mail->SMTPSecure = 'tls';
$mail->Port = 587;
if (!file_exists('mail/emailtemplate2.php')) {
throw new Exception("File emailtemplate2.php non trovato.");
}
include('mail/emailtemplate2.php');
if (!isset($mailmessage1)) {
throw new Exception("Variabile \$mailmessage1 non definita in emailtemplate2.php.");
}
$htmlContent = str_replace('{message}', $messagecancel, $mailmessage1);
$mail->From = 'info@yogasoul.it';
$mail->FromName = 'YogiBook [YogaSoul]';
$mail->addAddress($emailuser);
$mail->Subject = "YogiBook - Lezione cancellata con successo!";
$mail->Body = $htmlContent;
$mail->AltBody = 'This is the body in plain text for non-HTML mail clients';
$mail->send();
$logMessage .= "Cancellazione confermata per ID $idbookingclass ($newtimeformat), email inviata a $emailuser\n";
echo "<h1>Cancellazione confermata</h1>";
echo "<p>La lezione del $newtimeformat è stata cancellata con successo.</p>";
echo "<p>Ricordati di riprenotare entro la scadenza del tuo abbonamento ($expireon).</p>";
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
} catch (Exception $e) {
$logMessage .= "Cancellazione confermata per ID $idbookingclass ($newtimeformat), ma errore invio email a $emailuser: " . $mail->ErrorInfo . "\n";
echo "<h1>Cancellazione confermata</h1>";
echo "<p>La lezione del $newtimeformat è stata cancellata con successo, ma non è stato possibile inviare l'email di conferma.</p>";
echo "<p>Ricordati di riprenotare entro la scadenza del tuo abbonamento ($expireon).</p>";
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
}
file_put_contents($logFile, $logMessage, FILE_APPEND);
$updateStmt->close();
$stmt->close();
$conn->close();
exit;
}
$logMessage .= "Accesso a modale per ID $idbookingclass, token: $token, opzione riprogrammazione: rebook-from-cancel.php?idbookingclass=$idbookingclass&token=" . urlencode($token) . "\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="utf-8" />
<title>YogiBook - Cancellazione Lezione</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@10"></script>
<link href="assets/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
}
</style>
</head>
<body>
<div class="container">
<h1>Gestione Lezione</h1>
<p>Lezione: <?php echo htmlspecialchars($servicename); ?> del <?php echo htmlspecialchars($newtimeformat); ?></p>
<script>
document.addEventListener('DOMContentLoaded', function() {
console.log("Modale SweetAlert2 avviato per ID <?php echo $idbookingclass; ?>, token: <?php echo urlencode($token); ?>");
Swal.fire({
title: "Cosa vuoi fare?",
html: "Puoi cancellare la lezione del <?php echo htmlspecialchars($newtimeformat); ?> o riprogrammarla.<br>Scadenza abbonamento: <?php echo htmlspecialchars($expireon); ?><br><br><strong>Debug:</strong> Reindirizzamento previsto: rebook-from-cancel.php?idbookingclass=<?php echo $idbookingclass; ?>&token=<?php echo urlencode($token); ?>",
icon: "question",
showCancelButton: true,
confirmButtonColor: "#d33",
cancelButtonColor: "#3085d6",
confirmButtonText: "Cancella Lezione",
cancelButtonText: "Riprogramma Lezione",
showDenyButton: true,
denyButtonText: "Torna al Portale",
denyButtonColor: "#6c757d"
}).then((result) => {
if (result.isConfirmed) {
console.log("Cliccato 'Cancella Lezione' per ID <?php echo $idbookingclass; ?>");
Swal.fire({
title: "Attenzione!",
text: "La lezione sarà cancellata. Ricordati di riprenotare entro la scadenza del tuo abbonamento (<?php echo htmlspecialchars($expireon); ?>)!",
icon: "warning",
confirmButtonText: "Conferma Cancellazione",
showCancelButton: true,
cancelButtonText: "Annulla"
}).then((confirmResult) => {
if (confirmResult.isConfirmed) {
console.log("Conferma cancellazione, reindirizzamento a: ?idbookingclass=<?php echo $idbookingclass; ?>&token=<?php echo urlencode($token); ?>&action=cancel");
window.location.replace("?idbookingclass=<?php echo $idbookingclass; ?>&token=<?php echo urlencode($token); ?>&action=cancel");
} else {
console.log("Cancellazione annullata");
}
});
} else if (result.isCancel) {
console.log("Cliccato 'Riprogramma Lezione' per ID <?php echo $idbookingclass; ?>, reindirizzamento a: rebook-from-cancel.php?idbookingclass=<?php echo $idbookingclass; ?>&token=<?php echo urlencode($token); ?>");
try {
window.location.replace("rebook-from-cancel.php?idbookingclass=<?php echo $idbookingclass; ?>&token=<?php echo urlencode($token); ?>");
} catch (e) {
console.error("Errore durante il reindirizzamento: ", e);
alert("Errore: impossibile reindirizzare a rebook-from-cancel.php. Controlla la console del browser.");
}
} else if (result.isDenied) {
console.log("Cliccato 'Torna al Portale', reindirizzamento a: https://yogibook.yogasoul.it");
window.location.replace("rebook-from-cancel.php?idbookingclass=<?php echo $idbookingclass; ?>&token=<?php echo urlencode($token); ?>");
}
});
});
</script>
</div>
</body>
</html>
<?php
$stmt->close();
$conn->close();
?>
+26
View File
@@ -0,0 +1,26 @@
<?php
require_once('include/headscript.php');
$conn = mysqli_connect($servername, $username, $password, $dbname);
if (!$conn) {
die(json_encode(['error' => 'Connessione al database fallita']));
}
// Query per ottenere tutte le classi disponibili
$sql = "SELECT id, servicename, day, time FROM classes ORDER BY servicename, day, time";
$result = mysqli_query($conn, $sql);
$classes = [];
while ($row = mysqli_fetch_assoc($result)) {
$classes[] = [
'id' => $row['id'],
'servicename' => $row['servicename'],
'day' => $row['day'],
'time' => $row['time']
];
}
mysqli_close($conn);
header('Content-Type: application/json');
echo json_encode($classes);
+35
View File
@@ -0,0 +1,35 @@
<?php
require_once('include/headscript.php');
$conn = mysqli_connect($servername, $username, $password, $dbname);
if (!$conn) {
die(json_encode(['error' => 'Connessione al database fallita']));
}
$class_id = isset($_POST['class_id']) ? intval($_POST['class_id']) : 0;
if ($class_id <= 0) {
die(json_encode(['error' => 'ID classe non valido']));
}
// Query per ottenere le date disponibili per la classe specificata
// Supponiamo che ci sia una tabella 'class_schedule' con le date disponibili
$sql = "SELECT DISTINCT DATE(bookingstart) as available_date
FROM class_schedule
WHERE class_id = ?
ORDER BY available_date";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, 'i', $class_id);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
$availableDates = [];
while ($row = mysqli_fetch_assoc($result)) {
$availableDates[] = $row['available_date'];
}
mysqli_stmt_close($stmt);
mysqli_close($conn);
header('Content-Type: application/json');
echo json_encode(['availableDates' => $availableDates]);
+181
View File
@@ -0,0 +1,181 @@
<?php
// Abilita visualizzazione errori PHP (solo per debug)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
include('include/headscript.php');
// Includi PHPMailer una sola volta, all'inizio dello script
require 'phpmailer/src/Exception.php';
require 'phpmailer/src/PHPMailer.php';
require 'phpmailer/src/SMTP.php';
// Verifica connessione al database
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("Connessione al database fallita: " . $conn->connect_error);
}
// Inizializza contatore e log
$emailCount = 0;
$errors = [];
$logFile = 'promemoria_cron_log.txt';
$logMessage = "Esecuzione cron: " . date('Y-m-d H:i:s') . "\n";
// Genera UUID per tutti i record senza token
$updateQuery = "UPDATE bookingclass
SET cancellation_token = UUID()
WHERE status = 'booked'
AND cancellation_token IS NULL";
$updateStmt = $conn->prepare($updateQuery);
if ($updateStmt) {
$updateStmt->execute();
$affectedRows = $updateStmt->affected_rows;
$logMessage .= "Generati $affectedRows token UUID per prenotazioni senza token.\n";
} else {
$errors[] = "Errore preparazione query per generazione token: " . $conn->error;
$logMessage .= "Errore generazione token: " . $conn->error . "\n";
}
// Seleziona prenotazioni per domani (dalle 17:00 in poi) e dopodomani (fino alle 16:59)
$tomorrow = date('Y-m-d', strtotime('+1 day'));
$dayAfterTomorrow = date('Y-m-d', strtotime('+2 days'));
$query = "SELECT bc.*, au.email, au.first_name, s.servicename
FROM bookingclass bc
LEFT JOIN auth_users au ON bc.iduser = au.id
LEFT JOIN service s ON bc.idservice = s.idservice
WHERE bc.status = 'booked' AND (
(DATE(bc.bookingstart) = ? AND TIME(bc.bookingstart) >= '17:00:00') OR
(DATE(bc.bookingstart) = ? AND TIME(bc.bookingstart) <= '16:59:59')
)";
$stmt = $conn->prepare($query);
if (!$stmt) {
$errors[] = "Errore preparazione query: " . $conn->error;
file_put_contents($logFile, $logMessage . "Errore query: " . $conn->error . "\n", FILE_APPEND);
echo "Errore preparazione query: " . $conn->error;
exit;
}
$stmt->bind_param("ss", $tomorrow, $dayAfterTomorrow);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
$logMessage .= "Nessuna prenotazione trovata per domani (dalle 17:00) o dopodomani (fino alle 16:59).\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
echo "Nessuna prenotazione trovata.\n";
exit;
}
while ($row = $result->fetch_assoc()) {
$idbookingclass = $row['idbookingclass'];
$token = $row['cancellation_token'];
// Verifica che il token esista
if (empty($token)) {
$errors[] = "Token mancante per ID $idbookingclass dopo aggiornamento UUID.";
$logMessage .= "Token mancante per ID $idbookingclass dopo aggiornamento UUID.\n";
continue;
}
$firstname = $row['first_name'] ?? 'Utente';
$emailuser = $row['email'];
$servicename = $row['servicename'] ?? 'Sconosciuta';
$bookingstart = $row['bookingstart'];
$dataformat = date("d-m-Y H:i", strtotime($bookingstart));
// Determina il limite di cancellazione in base all'orario della lezione
$lessonTime = new DateTime($bookingstart);
$isTomorrow = $lessonTime->format('Y-m-d') === $tomorrow;
$hour = (int)$lessonTime->format('H');
$minute = (int)$lessonTime->format('i');
$isBefore1700 = ($hour < 17) || ($hour === 17 && $minute === 0);
$cancellationDeadline = $isBefore1700 ? "00:01" : "12:00";
// Verifica email valida
if (!filter_var($emailuser, FILTER_VALIDATE_EMAIL)) {
$errors[] = "Email non valida per ID $idbookingclass: $emailuser";
$logMessage .= "Email non valida per ID $idbookingclass: $emailuser\n";
continue;
}
// Link cancellazione
$link = "https://yogibook.yogasoul.it/cancella-prenotazione.php?idbookingclass=$idbookingclass&token=$token";
// Messaggio email
$message = "<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 18px; line-height: 34.2px;'><strong>Ciao $firstname,</strong></span></p>
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Promemoria: hai la lezione $servicename del $dataformat.</span></p>
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Puoi cancellarla fino alle $cancellationDeadline del giorno della lezione cliccando qui:</span></p>
<a href='$link' target='_blank'>Cancella prenotazione</a>
<br>
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Ci vediamo sul tappetino!</span></p>
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Il Team Yogasoul</span></p>";
// Definisci $messageedit per il template
$messageedit = $message;
// Definisci $buttonedit
$buttonedit = "<a href='https://yogibook.yogasoul.it/' target='_blank' class='v-button v-font-size' style='box-sizing: border-box;display: inline-block;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #FFFFFF; background-color: #3AAEE0; border-radius: 4px;-webkit-border-radius: 4px; -moz-border-radius: 4px; width:auto; max-width:100%; overflow-wrap: break-word; word-break: break-word; word-wrap:break-word; mso-border-alt: none;font-size: 14px;'>
<span style='display:block;padding:10px 20px;line-height:120%;'><span style='line-height: 16.8px;'>YogiBook - YogaSoul</span></span>
</a>";
$mail = new PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = 'mail.yogasoul.it';
$mail->SMTPAuth = true;
$mail->Username = 'info@yogasoul.it';
$mail->Password = '!Testolina88';
$mail->SMTPSecure = 'tls';
$mail->Port = 587;
if (!file_exists('mail/emailtemplate2.php')) {
throw new Exception("File emailtemplate2.php non trovato.");
}
include('mail/emailtemplate2.php');
if (!isset($mailmessage1)) {
throw new Exception("Variabile \$mailmessage1 non definita in emailtemplate2.php.");
}
$htmlContent = str_replace('{message}', $message, $mailmessage1);
$mail->From = 'info@yogasoul.it';
$mail->FromName = 'YogiBook [YogaSoul]';
$mail->addAddress($emailuser);
$mail->Subject = "YogiBook - Promemoria lezione!";
$mail->Body = $htmlContent;
$mail->AltBody = 'Promemoria lezione.';
$mail->send();
$emailCount++;
$logMessage .= "Email inviata a $emailuser per lezione ID $idbookingclass ($dataformat)\n";
} catch (Exception $e) {
$errors[] = "Errore invio email a $emailuser (ID $idbookingclass): " . $mail->ErrorInfo;
$logMessage .= "Errore invio a $emailuser (ID $idbookingclass): " . $mail->ErrorInfo . "\n";
}
sleep(2);
}
// Scrivi log
file_put_contents($logFile, $logMessage, FILE_APPEND);
// Output debug
echo "Esecuzione completata: $emailCount email inviate.\n";
if (!empty($errors)) {
echo "Errori rilevati:\n";
foreach ($errors as $error) {
echo "- $error\n";
}
} else {
echo "Nessun errore.\n";
}
echo "Dettagli nel file di log: $logFile\n";
$conn->close();
+4
View File
@@ -0,0 +1,4 @@
Esecuzione cron: 2025-10-08 13:39:42
Esecuzione cron: 2025-10-08 13:40:48
Esecuzione cron: 2025-10-08 13:54:46
Email inviata a info@claudiosironi.com per lezione ID 8 (09-10-2025 18:15)
+484
View File
@@ -0,0 +1,484 @@
<?php
// Abilita visualizzazione errori PHP (solo per debug)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// Includi headscript.php
require_once('include/headscript.php');
// Connessione al database
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("Connessione al database fallita: " . $conn->connect_error);
}
// Inizializza log
$logFile = 'rebook_from_cancel_log.txt';
$logMessage = "Esecuzione riprogrammazione: " . date('Y-m-d H:i:s') . "\n";
// Recupera parametri GET
if (!isset($_GET['idbookingclass']) || !isset($_GET['token'])) {
$logMessage .= "Parametri mancanti: idbookingclass o token\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
echo "<h1>Riprogrammazione non possibile</h1>";
echo "<p>Parametri mancanti.</p>";
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
$conn->close();
exit;
}
$idbookingclass = filter_var($_GET['idbookingclass'], FILTER_VALIDATE_INT);
$token = $_GET['token'];
if (!$idbookingclass) {
$logMessage .= "Errore: idbookingclass non valido: " . $_GET['idbookingclass'] . "\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
echo "<h1>Riprogrammazione non possibile</h1>";
echo "<p>Parametro idbookingclass non valido.</p>";
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
$conn->close();
exit;
}
// Verifica validità della prenotazione
$query = "SELECT bc.*, ob.expireon, au.email, au.first_name, s.servicename
FROM bookingclass bc
LEFT JOIN orderbook ob ON bc.idorder = ob.order_id
LEFT JOIN auth_users au ON bc.iduser = au.id
LEFT JOIN service s ON bc.idservice = s.idservice
WHERE bc.idbookingclass = ?
AND bc.cancellation_token = ?
AND bc.status = 'booked'
AND bc.bookingstart > NOW()";
$stmt = $conn->prepare($query);
if (!$stmt) {
$logMessage .= "Errore preparazione query per ID $idbookingclass: " . $conn->error . "\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
echo "<h1>Riprogrammazione non possibile</h1>";
echo "<p>Errore nella preparazione della query.</p>";
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
$conn->close();
exit;
}
$stmt->bind_param("is", $idbookingclass, $token);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
$logMessage .= "Tentativo di riprogrammazione fallito per ID $idbookingclass: link non valido o lezione non prenotata\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
echo "<h1>Riprogrammazione non possibile</h1>";
echo "<p>Il link non è valido o la lezione non è prenotata.</p>";
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
$stmt->close();
$conn->close();
exit;
}
$row = $result->fetch_assoc();
$bookingstart = $row['bookingstart'];
$newtimeformat = date("d-m-Y H:i", strtotime($bookingstart));
$expireon = $row['expireon'] ? date("d-m-Y", strtotime($row['expireon'])) : "sconosciuta";
$emailuser = $row['email'];
$firstname = $row['first_name'] ?? 'Utente';
$servicename = $row['servicename'] ?? 'Sconosciuta';
$iduser = $row['iduser'];
$idserviceordered = $row['idservice'];
$idorder = $row['idorder'];
// Verifica il limite di cancellazione/riprogrammazione
$lessonTime = new DateTime($bookingstart);
$hour = (int)$lessonTime->format('H');
$minute = (int)$lessonTime->format('i');
$isBefore1700 = ($hour < 17) || ($hour === 17 && $minute === 0);
$currentTime = new DateTime();
$lessonDate = $lessonTime->format('Y-m-d');
if ($isBefore1700) {
$deadline = new DateTime("$lessonDate 00:01:00");
} else {
$deadline = new DateTime("$lessonDate 12:00:00");
}
if ($currentTime > $deadline) {
$logMessage .= "Tentativo di riprogrammazione fallito per ID $idbookingclass: orario oltre il limite (" . $deadline->format('Y-m-d H:i:s') . ")\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
echo "<h1>Riprogrammazione non possibile</h1>";
echo "<p>Non è possibile riprogrammare la lezione dopo le " . ($isBefore1700 ? "00:01" : "12:00") . " del giorno della lezione.</p>";
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
$stmt->close();
$conn->close();
exit;
}
// Recupera la data di scadenza dell'ordine
$expiryDate = new DateTime($row['expireon']);
// Query sulla tabella associateclass
$sql = "SELECT idassociateservice FROM associateclass WHERE idmainservice = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $idserviceordered);
$stmt->execute();
$result = $stmt->get_result();
$idassociateservices = array();
array_push($idassociateservices, $idserviceordered);
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$idassociateservices[] = $row['idassociateservice'];
}
}
$stmt->close();
// Verifica se è stata specificata una richiesta per cambiare il mese
if (isset($_GET['prev_month'])) {
$currentMonthStart = $_GET['prev_month'] . '-01';
} elseif (isset($_GET['next_month'])) {
$currentMonthStart = $_GET['next_month'] . '-01';
} else {
$currentMonthStart = date("Y-m-01");
}
$currentMonthEnd = date("Y-m-t", strtotime($currentMonthStart));
// Aggiungi filtro per la data di scadenza
$expiryCondition = '';
if ($expiryDate) {
$expiryCondition = "AND serviceschedule.dateschedule <= '{$expiryDate->format('Y-m-d 23:59:59')}'";
}
// Query per le lezioni disponibili
$placeholders = implode(',', array_fill(0, count($idassociateservices), '?'));
$query = "SELECT ss.*, s.servicename, s.colorclass, s.maxcapacity
FROM serviceschedule ss
LEFT JOIN service s ON ss.idservice = s.idservice
WHERE ss.dateschedule BETWEEN ? AND DATE_ADD(?, INTERVAL 1 DAY)
$expiryCondition
AND ss.idservice IN ($placeholders)
ORDER BY ss.dateschedule";
$stmt = $conn->prepare($query);
$types = 'ss' . str_repeat('i', count($idassociateservices));
$params = array_merge([$currentMonthStart, $currentMonthEnd], $idassociateservices);
$stmt->bind_param($types, ...$params);
$stmt->execute();
$bookedclass = $stmt->get_result();
// Mappa dei mesi in italiano
$italianMonths = [
"January" => "Gennaio",
"February" => "Febbraio",
"March" => "Marzo",
"April" => "Aprile",
"May" => "Maggio",
"June" => "Giugno",
"July" => "Luglio",
"August" => "Agosto",
"September" => "Settembre",
"October" => "Ottobre",
"November" => "Novembre",
"December" => "Dicembre"
];
$logMessage .= "Caricata pagina di riprogrammazione per ID $idbookingclass, mese: $currentMonthStart\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="utf-8" />
<title>YogiBook - Riprogramma Lezione</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta content="YogiBook - Prenotazione facile YogaSOul" name="description" />
<meta content="Advanced Creative Solutions" author />
<link rel="shortcut icon" href="assets/images/favicon.ico">
<link href="assets/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<link href="assets/css/icons.min.css" rel="stylesheet" type="text/css" />
<link href="assets/css/app.min.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@10"></script>
<style>
.custom-card {
margin: 10px auto;
display: flex;
width: 90%;
max-width: 700px;
background-color: white;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: transform 0.2s;
}
.custom-card:hover {
transform: translateY(-5px);
}
.custom-date-box {
flex: 1;
background-color: red;
color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0;
font-size: 60px;
font-weight: bold;
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
.custom-day {
line-height: 1;
}
.custom-month {
font-size: 28px;
}
.custom-event-details {
flex: 2;
display: flex;
flex-direction: column;
padding: 10px 20px;
background-color: lightblue;
}
.custom-heading {
margin-top: 0;
font-size: 24px;
}
.custom-paragraph {
margin-bottom: 5px;
}
.custom-actions {
display: none;
flex-direction: row;
justify-content: space-between;
margin-top: 10px;
}
.custom-card.expanded .custom-actions {
display: flex;
}
.custom-action-button {
background-color: #f0f0f0;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.custom-action-button:hover {
background-color: #e0e0e0;
}
@media (max-width: 768px) {
.custom-card {
flex-direction: column;
}
.custom-date-box,
.custom-event-details {
width: 100%;
border-radius: 0;
}
.custom-event-time {
font-size: 24px;
}
}
.month-navigation {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
}
.month-nav-button {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
}
.current-month {
font-size: 24px;
margin: 0 20px;
}
.booking-details {
margin-top: 10px;
border-top: 1px solid #ccc;
padding-top: 10px;
}
.booking-button {
background-color: #1ebf73;
color: #fff;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
}
.booking-button:hover {
background-color: #17a663;
}
</style>
</head>
<body>
<div id="layout-wrapper">
<header id="page-topbar" class="isvertical-topbar">
<div class="navbar-header">
<div class="d-flex">
<?php include('include/logoarea.php'); ?>
<button type="button" class="btn btn-sm px-3 font-size-24 header-item waves-effect vertical-menu-btn">
<i class="bx bx-menu align-middle"></i>
</button>
<div class="page-title-box align-self-center d-none d-md-block">
<h4 class="page-title mb-0">Riprogramma la tua lezione</h4>
</div>
</div>
<div class="d-flex">
<?php include('include/languageselection.php'); ?>
<?php include('include/profiletopbar.php'); ?>
</div>
</div>
</header>
<?php include('include/sidebar.php'); ?>
<div class="main-content">
<div class="page-content">
<div class="container-fluid">
<div class="row">
<div class="col-xl-12">
<div class="card">
<div class="card-body">
<h3>Riprogrammazione Lezione</h3>
<p>Lezione attuale: <?php echo htmlspecialchars($servicename); ?> del <?php echo htmlspecialchars($newtimeformat); ?></p>
<p>Scadenza abbonamento: <?php echo htmlspecialchars($expireon); ?></p>
</div>
</div>
</div>
</div>
<div class="month-navigation">
<a href="?idbookingclass=<?php echo $idbookingclass; ?>&token=<?php echo urlencode($token); ?>&prev_month=<?php echo date('Y-m', strtotime('-1 month', strtotime($currentMonthStart))); ?>" class="arrow-link">
<i class="fas fa-chevron-left fa-2x"></i>
</a>
<h2><?php echo $italianMonths[date("F", strtotime($currentMonthStart))] . ' ' . date("Y", strtotime($currentMonthStart)); ?></h2>
<a href="?idbookingclass=<?php echo $idbookingclass; ?>&token=<?php echo urlencode($token); ?>&next_month=<?php echo date('Y-m', strtotime('+1 month', strtotime($currentMonthStart))); ?>" class="arrow-link">
<i class="fas fa-chevron-right fa-2x"></i>
</a>
</div>
<?php if ($bookedclass->num_rows == 0): ?>
<p>Classi non presenti per questo mese o oltre la data di scadenza.</p>
<a href="https://yogibook.yogasoul.it" class="btn btn-primary">Torna al portale</a>
<?php else: ?>
<?php while ($row = $bookedclass->fetch_assoc()): ?>
<?php
$dateschedule = $row['dateschedule'];
$dateObj = new DateTime($dateschedule);
$dayInItalian = $dateObj->format("d");
$monthInItalian = $italianMonths[$dateObj->format("F")];
$newDateFormat = $dateObj->format("d-m-Y H:i");
$eventId = $row['idserviceschedule'];
$bookingQuery = "SELECT iduser FROM bookingclass WHERE idserviceschedule = ? AND status='booked'";
$stmtBooking = $conn->prepare($bookingQuery);
$stmtBooking->bind_param("i", $eventId);
$stmtBooking->execute();
$bookingResult = $stmtBooking->get_result();
$countPersons = $bookingResult->num_rows;
$stmtBooking->close();
$maxcapacity = $row['maxcapacity'];
$freeplace = $maxcapacity - $countPersons;
$idcheckservice = $row['idserviceschedule'];
$query = "SELECT * FROM bookingclass WHERE idserviceschedule = ? AND iduser = ?";
$stmtCheck = $conn->prepare($query);
$stmtCheck->bind_param("ii", $idcheckservice, $iduser);
$stmtCheck->execute();
$resultcheck = $stmtCheck->get_result();
$alreadybooked = $resultcheck->num_rows > 0 ? 'Y' : 'N';
$stmtCheck->close();
?>
<div class="custom-card" onclick="toggleCard(this)">
<?php if ($alreadybooked == 'Y'): ?>
<div class="custom-date-box" style="background-color:#FF6609">
<?php elseif ($freeplace > 0): ?>
<div class="custom-date-box" style="background-color:#1ebf73">
<?php else: ?>
<div class="custom-date-box" style="background-color:#9C9C9C">
<?php endif; ?>
<div class="custom-day"><?php echo $dayInItalian; ?></div>
<div class="custom-month"><?php echo $monthInItalian; ?></div>
</div>
<?php if ($alreadybooked == 'Y'): ?>
<div class="custom-event-details" style="background-color:#FFAC7A">
<?php elseif ($freeplace > 0): ?>
<div class="custom-event-details" style="background-color:<?php echo htmlspecialchars($row['colorclass']); ?>">
<?php else: ?>
<div class="custom-event-details" style="background-color:#CDCDCD">
<?php endif; ?>
<h2 class="custom-heading"><?php echo htmlspecialchars($row['servicename']); ?> - <?php echo $countPersons; ?>/<?php echo $maxcapacity; ?></h2>
<p class="custom-paragraph">Quando: <?php echo $newDateFormat; ?></p>
<p class="custom-paragraph">Luogo: via Valassina 62/B Seregno - Sala Contesto Yoga</p>
<div class="booking-details">
<?php if ($alreadybooked == 'Y'): ?>
<button class="booking-button"><i class="fa fa-check"></i> Sei già prenotata/o</button>
<?php elseif ($freeplace > 0): ?>
<a href="rebookandgo.php?idpreviousbooking=<?php echo $idbookingclass; ?>&idservicenew=<?php echo $row['idservice']; ?>&idnewbooking=<?php echo $row['idserviceschedule']; ?>&iduser=<?php echo $iduser; ?>">
<button class="booking-button"><i class="fas fa-arrow-circle-right"></i> Riprogramma Classe</button>
</a>
<?php else: ?>
<button class="booking-button"><i class="fa fa-stop"></i> Classe Piena</button>
<?php endif; ?>
</div>
</div>
</div>
<?php endwhile; ?>
<?php endif; ?>
<div class="month-navigation">
<a href="?idbookingclass=<?php echo $idbookingclass; ?>&token=<?php echo urlencode($token); ?>&prev_month=<?php echo date('Y-m', strtotime('-1 month', strtotime($currentMonthStart))); ?>" class="arrow-link">
<i class="fas fa-chevron-left fa-2x"></i>
</a>
<h2><?php echo $italianMonths[date("F", strtotime($currentMonthStart))] . ' ' . date("Y", strtotime($currentMonthStart)); ?></h2>
<a href="?idbookingclass=<?php echo $idbookingclass; ?>&token=<?php echo urlencode($token); ?>&next_month=<?php echo date('Y-m', strtotime('+1 month', strtotime($currentMonthStart))); ?>" class="arrow-link">
<i class="fas fa-chevron-right fa-2x"></i>
</a>
</div>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
</div>
<script>
function toggleCard(card) {
card.classList.toggle("expanded");
}
</script>
<script src="assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="assets/libs/metismenujs/metismenujs.min.js"></script>
<script src="assets/libs/simplebar/simplebar.min.js"></script>
<script src="assets/libs/eva-icons/eva.min.js"></script>
<script src="assets/js/app.js"></script>
</body>
</html>
<?php
$stmt->close();
$conn->close();
?>
+57
View File
@@ -0,0 +1,57 @@
<?php
require_once('include/headscript.php');
$conn = mysqli_connect($servername, $username, $password, $dbname);
if (!$conn) {
die(json_encode(['success' => false, 'message' => 'Connessione al database fallita']));
}
$id_booking_class = isset($_POST['id']) ? intval($_POST['id']) : 0;
$class_id = isset($_POST['class_id']) ? intval($_POST['class_id']) : 0;
$new_date = isset($_POST['new_date']) ? $_POST['new_date'] : '';
if ($id_booking_class <= 0 || $class_id <= 0 || empty($new_date)) {
die(json_encode(['success' => false, 'message' => 'Dati non validi']));
}
// Ottieni i dettagli della classe attuale
$sql = "SELECT bookingstart, servicename, day, time FROM bookingclass WHERE idbookingclass = ?";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, 'i', $id_booking_class);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
$current_class = mysqli_fetch_assoc($result);
if (!$current_class) {
die(json_encode(['success' => false, 'message' => 'Lezione non trovata']));
}
// Ottieni i dettagli della nuova classe
$sql = "SELECT servicename, day, time FROM classes WHERE id = ?";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, 'i', $class_id);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
$new_class = mysqli_fetch_assoc($result);
if (!$new_class) {
die(json_encode(['success' => false, 'message' => 'Classe non trovata']));
}
// Aggiorna la lezione
$sql = "UPDATE bookingclass
SET bookingstart = ?, prevbookingstart = ?, servicename = ?, day = ?, time = ?
WHERE idbookingclass = ?";
$stmt = mysqli_prepare($conn, $sql);
$new_bookingstart = $new_date . ' ' . $new_class['time'] . ':00';
mysqli_stmt_bind_param($stmt, 'sssssi', $new_bookingstart, $current_class['bookingstart'], $new_class['servicename'], $new_class['day'], $new_class['time'], $id_booking_class);
if (mysqli_stmt_execute($stmt)) {
echo json_encode(['success' => true]);
} else {
echo json_encode(['success' => false, 'message' => 'Errore durante l\'aggiornamento della lezione']);
}
mysqli_stmt_close($stmt);
mysqli_close($conn);
+619 -322
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 569 KiB

+205 -382
View File
@@ -11,7 +11,6 @@ $optionquery->setQuery("SELECT * FROM option");
$optionquery->execute();
?>
<?php
$bookedclass = new WA_MySQLi_RS("bookedclass", $bkngstm, 0);
// Verifica se è stata specificata una richiesta per cambiare il mese
@@ -38,11 +37,9 @@ $bookedclass->setQuery("SELECT * FROM bookingclass
WHERE bookingclass.iduser = '$iduserlogin' AND bookingclass.status = 'booked'
AND serviceschedule.dateschedule BETWEEN '$currentMonthStart' AND DATE_ADD('$currentMonthEnd', INTERVAL 1 DAY) ORDER BY serviceschedule.dateschedule");
$bookedclass->execute();
?>
<?php
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
@@ -98,10 +95,6 @@ $query = "SELECT COUNT(*) AS total,
LEFT JOIN serviceschedule ON bookingclass.idserviceschedule = serviceschedule.idserviceschedule
WHERE bookingclass.iduser = $iduser";
$result = $conn->query($query);
if ($result) {
$row = $result->fetch_assoc();
@@ -119,7 +112,6 @@ $conn->close();
<html lang="en">
<head>
<meta charset="utf-8" />
<title>YogiBook - Prenotazioni YogaSoul</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -214,7 +206,6 @@ $conn->close();
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.custom-action-button:hover {
@@ -261,6 +252,48 @@ $conn->close();
width: 100%;
}
</style>
<style>
.pastel-color {
border: 1px solid #D1C4CC;
padding: 0px;
text-align: center;
border-radius: 10px;
margin-right: 20px;
font-size: 18px;
}
.pastel-color.acquistate {
background-color: #E2C4FB;
}
.pastel-color.praticate {
background-color: #C4E1FB;
}
.pastel-color.prenotate {
background-color: #CDFBC4;
}
.pastel-color.conferma {
background-color: #FBFAC4;
}
.pastel-color.programmare {
background-color: #FBE4C4;
}
.pastel-color.perse {
background-color: #FBC7C4;
}
@media (max-width: 768px) {
.pastel-color {
margin-right: 0;
margin-bottom: 20px;
}
}
</style>
<script>
function confirmDelete(id, idservice, deletePageUrl) {
Swal.fire({
@@ -274,163 +307,52 @@ $conn->close();
cancelButtonText: "Annulla"
}).then((result) => {
if (result.isConfirmed) {
// Reindirizza direttamente alla pagina di cancellazione con l'ID e l'ID del servizio come parametri.
window.location.href = `${deletePageUrl}?id=${id}&idserviceordered=${idservice}`;
}
});
}
</script>
<style>
.pastel-color {
border: 1px solid #D1C4CC;
padding: 0px;
text-align: center;
border-radius: 10px;
margin-right: 20px;
/* Spazio tra i box */
font-size: 18px;
}
.pastel-color.acquistate {
background-color: #E2C4FB;
/* Azzurro pastello */
}
.pastel-color.praticate {
background-color: #C4E1FB;
/* Verde pastello */
}
.pastel-color.prenotate {
background-color: #CDFBC4;
/* Rosa pastello */
}
.pastel-color.conferma {
background-color: #FBFAC4;
/* Arancio pastello */
}
.pastel-color.programmare {
background-color: #FBE4C4;
/* Arancio pastello */
}
.pastel-color.perse {
background-color: #FBC7C4;
/* Arancio pastello */
}
}
@media (max-width: 768px) {
.pastel-color {
margin-right: 0;
/* Rimuovi lo spazio tra i box */
margin-bottom: 20px;
/* Spazio tra i box */
}
}
</style>
<body>
<!-- <body data-layout="horizontal"> -->
<!-- Begin page -->
<div id="layout-wrapper">
<!-- Top Bar -->
<header id="page-topbar" class="isvertical-topbar">
<div class="navbar-header">
<div class="d-flex">
<!-- LOGO -->
<?php include('include/logoarea.php'); ?>
<button type="button" class="btn btn-sm px-3 font-size-24 header-item waves-effect vertical-menu-btn">
<i class="bx bx-menu align-middle"></i>
</button>
<!-- start page title -->
<div class="page-title-box align-self-center d-none d-md-block">
<h4 class="page-title mb-0">Prenotazione Classi</h4>
</div>
<!-- end page title -->
</div>
<div class="d-flex">
<?php include('include/languageselection.php'); ?>
<!-- /searh on topbar
<div class="dropdown d-inline-block">
<button type="button" class="btn header-item noti-icon"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="bx bx-search icon-sm align-middle"></i>
</button>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-end p-0">
<form class="p-2">
<div class="search-box">
<div class="position-relative">
<input type="text" class="form-control rounded bg-light border-0" placeholder="Search...">
<i class="bx bx-search search-icon"></i>
</div>
</div>
</form>
</div>
</div> -->
<?php include('include/profiletopbar.php'); ?>
</div>
</div>
</header>
<?php include('include/sidebar.php'); ?>
<header class="ishorizontal-topbar">
<div class="navbar-header">
<div class="d-flex">
</div>
</div>
<div class="topnav">
<div class="container-fluid">
<nav class="navbar navbar-light navbar-expand-lg topnav-menu">
</nav>
<div class="d-flex"></div>
<div class="topnav">
<div class="container-fluid">
<nav class="navbar navbar-light navbar-expand-lg topnav-menu"></nav>
</div>
</div>
</div>
</header>
<!-- ============================================================== -->
<!-- Start right Content here -->
<!-- ============================================================== -->
<div class="main-content">
<div class="page-content">
<div class="container-fluid">
<div class="row">
<div class="col-xl-12">
<div class="card">
<div class="card-body">
<h5>Benvenuta/o </h5>
<p>Di seguito puoi vedere lo stato delle tue prenotazioni</p>
<?php
$toprogram = $totalTickets - $passedRecords - $futureRecords - $pending - $lost;
?>
@@ -486,292 +408,193 @@ $conn->close();
</div>
<div class="alert alert-warning alert-dismissible fade show" role="alert" style="text-align: center;">
<i class="mdi mdi-alert-outline me-2"></i>
Car* Yogi, ti ricordiamo che il pacchetto 4 lezioni ha validità entro le 5 settimane dall'acquisto e il pacchetto da 12 lezioni entro il <strong>20 dicembre 2025</strong> 🙏
Car* Yogi, ti ricordiamo che il pacchetto 4 lezioni ha validità entro le 5 settimane dall'acquisto e il pacchetto da 12 lezioni entro il <strong>20 dicembre 2025</strong> 🙏
</div>
</div>
</div>
</div>
</div>
</div>
<!-- container-fluid -->
</div>
<?php if (isset($_GET['reprogram'])) { ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="mdi mdi-check-all me-2"></i>
Richiesta di riprogrammazione inviata con successo!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php } ?>
<div class="container-fluid">
<div class="row">
<div class="col-xl-12">
<div class="card">
<div class="card-body">
<div class="">
<div class="row mb-2">
<div class="col-xl-3 col-md-12">
<div class="pb-3 pb-xl-0">
<form class="email-search">
<div class="position-relative">
<h3>Lezioni Programmate</h3>
</div>
</form>
</div>
</div>
<div class="col-xl-9 col-md-12">
<div class="text-sm-end">
<?php if ($toprogram > 0) { ?>
<a href="selectorder.php">
<button type="button" class="btn btn-primary btn-rounded waves-effect waves-light mb-2 me-2" data-bs-toggle="modal" data-bs-target=".create-task">
<i class="mdi mdi-plus me-1"></i> Programma lezioni rimanenti
</button>
</a>
<?php } ?>
<a href="https://yogasoul.it/shop-completo/">
<button type="button" class="btn btn-success btn-rounded waves-effect waves-light mb-2 me-2" data-bs-toggle="modal" data-bs-target=".create-task">
<i class="mdi mdi-plus me-1"></i> Acquista Pacchetto Lezioni
</button>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<?php if (isset($_GET['reprogram'])) { ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="mdi mdi-check-all me-2"></i>
Richiesta di riprogrammazione inviata con successo!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
<!-- container-fluid -->
<?php } ?>
<div class="container-fluid">
<!-- starts cards -->
<!-- Aggiungi le frecce per la navigazione tra i mesi -->
<?php
$italianMonths = [
"January" => "Gennaio",
"February" => "Febbraio",
"March" => "Marzo",
"April" => "Aprile",
"May" => "Maggio",
"June" => "Giugno",
"July" => "Luglio",
"August" => "Agosto",
"September" => "Settembre",
"October" => "Ottobre",
"November" => "Novembre",
"December" => "Dicembre"
];
?>
<div class="month-navigation">
<a href="?prev_month=<?php echo date('Y-m', strtotime('-1 month', strtotime($currentMonthStart))); ?>" class="arrow-link">
<i class="fas fa-chevron-left fa-2x"></i>
</a>
<h2><?php echo $italianMonths[date("F", strtotime($currentMonthStart))] . ' ' . date("Y", strtotime($currentMonthStart)); ?></h2>
<a href="?next_month=<?php echo date('Y-m', strtotime('+1 month', strtotime($currentMonthStart))); ?>" class="arrow-link">
<i class="fas fa-chevron-right fa-2x"></i>
</a>
</div>
<div class="row">
<div class="col-xl-12">
<div class="card">
<div class="card-body">
<?php
$wa_startindex = 0;
if ($bookedclass->TotalRows == 0) {
echo "<p>Prenotazioni non presenti per questo mese</p>";
} else {
while (!$bookedclass->atEnd()) {
$wa_startindex = $bookedclass->Index;
?>
<?php
// Data dalla variabile $bookedclass->getColumnVal("dateschedule")
$dateschedule = $bookedclass->getColumnVal("dateschedule");
// Converti la data in un oggetto DateTime
$dateObj = new DateTime($dateschedule);
// Estrai il giorno e il mese in italiano
$dayInItalian = $dateObj->format("d");
$monthInItalian = $dateObj->format("F");
// Mappa dei nomi dei mesi in italiano
$italianMonths = [
"January" => "Gennaio",
"February" => "Febbraio",
"March" => "Marzo",
"April" => "Aprile",
"May" => "Maggio",
"June" => "Giugno",
"July" => "Luglio",
"August" => "Agosto",
"September" => "Settembre",
"October" => "Ottobre",
"November" => "Novembre",
"December" => "Dicembre"
];
// Sostituisci il nome del mese con la versione italiana
$monthInItalian = $italianMonths[$monthInItalian];
?>
<?php
// Data dalla variabile $bookedclass->getColumnVal("dateschedule")
$dateschedule = $bookedclass->getColumnVal("dateschedule");
// Converti la data in un oggetto DateTime
$dateObj = new DateTime($dateschedule);
// Formatta la data nel nuovo formato desiderato
$newDateFormat = $dateObj->format("d-m-Y H:i");
// Calculate the time difference in hours
$currentTime = new DateTime();
$classTime = new DateTime($dateschedule);
$timeDifference = $classTime->diff($currentTime);
$hoursDifference = $timeDifference->h + $timeDifference->days * 24;
// Check if the time difference is less than 6 hours
$timetocancel = $optionquery->getColumnVal("maxbeforetimecancell");
$canBeDeleted = ($hoursDifference > $timetocancel);
?>
<div class="custom-card" onclick="toggleCard(this)">
<div class="custom-date-box" style="background-color:#1ebf73">
<div class="custom-day"><?php echo $dayInItalian; ?></div>
<div class="custom-month"><?php echo $monthInItalian; ?></div>
</div>
<div class="custom-event-details" style="background-color:<?php echo ($bookedclass->getColumnVal("colorclass")); ?>">
<h2 class="custom-heading"><?php echo ($bookedclass->getColumnVal("servicename")); ?></h2>
<p class="custom-paragraph">Quando: <?php echo $newDateFormat; ?></p>
<p class="custom-paragraph">Luogo: via Valassina 62/B Seregno - Sala Contesto Yoga</p>
<div class="custom-actions">
<button class="custom-action-button" onclick="addToCalendar(this)" data-eventname="<?php echo ($bookedclass->getColumnVal("servicename")); ?>" data-eventdate="<?php echo $newDateFormat; ?>">
<i class="far fa-calendar-plus"></i> Cal
</button>
<!-- <button class="custom-action-button"><i class="fas fa-edit"></i> Riprogramma</button> -->
<?php $idbookingclass = $bookedclass->getColumnVal("idbookingclass");
$idservice = $bookedclass->getColumnVal("idservice");
?>
<?php if ($canBeDeleted) : ?>
<button class="custom-action-button" onclick="confirmDelete(<?php echo $idbookingclass; ?>, <?php echo $idservice; ?>, 'bookingpanel.php')">
<i class="fas fa-calendar-alt"></i> Riprogramma
</button>
<?php else : ?>
<button class="custom-action-button"> <i class="fas fa-exclamation-circle"></i> Non puoi riprogrammare</button>
<?php endif; ?>
</div>
<div class="">
<div class="row mb-2">
<div class="col-xl-3 col-md-12">
<div class="pb-3 pb-xl-0">
<form class="email-search">
<div class="position-relative">
<h3>Lezioni Programmate</h3>
</div>
</form>
</div>
</div>
<?php
$bookedclass->moveNext();
}
}
$bookedclass->moveFirst(); // Ritorna all'inizio del recordset
unset($wa_startindex);
unset($wa_repeatcount);
?>
<div class="col-xl-9 col-md-12">
<div class="text-sm-end">
<?php if ($toprogram > 0) { ?>
<a href="selectorder.php">
<button type="button" class="btn btn-primary btn-rounded waves-effect waves-light mb-2 me-2" data-bs-toggle="modal" data-bs-target=".create-task">
<i class="mdi mdi-plus me-1"></i> Programma lezioni rimanenti
</button>
</a>
<?php } ?>
<a href="https://yogasoul.it/shop-completo/">
<button type="button" class="btn btn-success btn-rounded waves-effect waves-light mb-2 me-2" data-bs-toggle="modal" data-bs-target=".create-task">
<i class="mdi mdi-plus me-1"></i> Acquista Pacchetto Lezioni
</button>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div><br><br>
<!-- Aggiungi altre card qui con le stesse classi -->
</div>
<div class="container-fluid">
<div class="month-navigation">
<?php
$italianMonths = [
"January" => "Gennaio",
"February" => "Febbraio",
"March" => "Marzo",
"April" => "Aprile",
"May" => "Maggio",
"June" => "Giugno",
"July" => "Luglio",
"August" => "Agosto",
"September" => "Settembre",
"October" => "Ottobre",
"November" => "Novembre",
"December" => "Dicembre"
];
?>
<a href="?prev_month=<?php echo date('Y-m', strtotime('-1 month', strtotime($currentMonthStart))); ?>" class="arrow-link">
<i class="fas fa-chevron-left fa-2x"></i>
</a>
<h2><?php echo $italianMonths[date("F", strtotime($currentMonthStart))] . ' ' . date("Y", strtotime($currentMonthStart)); ?></h2>
<a href="?next_month=<?php echo date('Y-m', strtotime('+1 month', strtotime($currentMonthStart))); ?>" class="arrow-link">
<i class="fas fa-chevron-right fa-2x"></i>
</a>
</div>
<div class="row">
<div class="col-xl-12">
<div class="card">
<div class="card-body">
<?php
$wa_startindex = 0;
if ($bookedclass->TotalRows == 0) {
echo "<p>Prenotazioni non presenti per questo mese</p>";
} else {
while (!$bookedclass->atEnd()) {
$wa_startindex = $bookedclass->Index;
$dateschedule = $bookedclass->getColumnVal("dateschedule");
$dateObj = new DateTime($dateschedule);
$dayInItalian = $dateObj->format("d");
$monthInItalian = $dateObj->format("F");
$monthInItalian = $italianMonths[$monthInItalian];
$newDateFormat = $dateObj->format("d-m-Y H:i");
<script>
function toggleCard(card) {
card.classList.toggle("expanded");
}
</script>
// Calcola se la lezione può essere riprogrammata
$currentTime = new DateTime();
$classTime = new DateTime($dateschedule);
$isSameDay = $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);
// Definisci il limite per la riprogrammazione
if ($isSameDay) {
if ($isBefore1700) {
// Lezioni prima delle 17:00: cancellazione valida fino alle 00:01 dello stesso giorno
$deadline = new DateTime($classTime->format('Y-m-d 00:01:00'));
} else {
// Lezioni alle 17:00 o dopo: cancellazione valida fino alle 12:00 dello stesso giorno
$deadline = new DateTime($classTime->format('Y-m-d 12:00:00'));
}
$canBeDeleted = $currentTime <= $deadline;
} else {
// Per lezioni in giorni futuri, la riprogrammazione è sempre consentita
$canBeDeleted = true;
}
?>
<div class="custom-card" onclick="toggleCard(this)">
<div class="custom-date-box" style="background-color:#1ebf73">
<div class="custom-day"><?php echo $dayInItalian; ?></div>
<div class="custom-month"><?php echo $monthInItalian; ?></div>
</div>
<div class="custom-event-details" style="background-color:<?php echo ($bookedclass->getColumnVal("colorclass")); ?>">
<h2 class="custom-heading"><?php echo ($bookedclass->getColumnVal("servicename")); ?></h2>
<p class="custom-paragraph">Quando: <?php echo $newDateFormat; ?></p>
<p class="custom-paragraph">Luogo: via Valassina 62/B Seregno - Sala Contesto Yoga</p>
<div class="custom-actions">
<button class="custom-action-button" onclick="addToCalendar(this)" data-eventname="<?php echo ($bookedclass->getColumnVal("servicename")); ?>" data-eventdate="<?php echo $newDateFormat; ?>">
<i class="far fa-calendar-plus"></i> Cal
</button>
<?php $idbookingclass = $bookedclass->getColumnVal("idbookingclass"); ?>
<?php $idservice = $bookedclass->getColumnVal("idservice"); ?>
<?php if ($canBeDeleted) : ?>
<button class="custom-action-button" onclick="confirmDelete(<?php echo $idbookingclass; ?>, <?php echo $idservice; ?>, 'bookingpanel.php')">
<i class="fas fa-calendar-alt"></i> Riprogramma
</button>
<?php else : ?>
<button class="custom-action-button"> <i class="fas fa-exclamation-circle"></i> Non puoi riprogrammare</button>
<?php endif; ?>
</div>
</div>
</div>
<?php
$bookedclass->moveNext();
}
}
$bookedclass->moveFirst();
unset($wa_startindex);
unset($wa_repeatcount);
?>
</div>
</div>
</div>
</div><br><br>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
<!-- End Page-content -->
<?php include('include/footer.php'); ?>
</div>
<!-- end main content-->
</div>
<!-- END layout-wrapper -->
<script>
function toggleCard(card) {
card.classList.toggle("expanded");
}
function addToCalendar(button) {
const eventName = button.getAttribute('data-eventname');
const eventDate = button.getAttribute('data-eventdate');
const googleCalendarLink = `https://www.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(eventName)}&dates=${encodeURIComponent(eventDate)}`;
window.open(googleCalendarLink, '_blank');
const outlookCalendarLink = `webcal://outlook.live.com/calendar/0/deeplink/compose?path=/calendar/action/compose&subject=${encodeURIComponent(eventName)}&startdt=${encodeURIComponent(eventDate)}`;
window.open(outlookCalendarLink, '_blank');
}
</script>
<!-- JAVASCRIPT -->
<script>
function addToCalendar(button) {
const eventName = button.getAttribute('data-eventname');
const eventDate = button.getAttribute('data-eventdate');
// Qui dovresti implementare la logica per aggiungere l'evento al calendario.
// Puoi gestire l'aggiunta sia a Google Calendar che a Outlook.
// Esempio: Aggiunta a Google Calendar (il link potrebbe variare)
const googleCalendarLink = `https://www.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(eventName)}&dates=${encodeURIComponent(eventDate)}`;
window.open(googleCalendarLink, '_blank');
// Esempio: Aggiunta a Outlook (il link potrebbe variare)
const outlookCalendarLink = `webcal://outlook.live.com/calendar/0/deeplink/compose?path=/calendar/action/compose&subject=${encodeURIComponent(eventName)}&startdt=${encodeURIComponent(eventDate)}`;
window.open(outlookCalendarLink, '_blank');
}
</script>
<script src="assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="assets/libs/metismenujs/metismenujs.min.js"></script>
<script src="assets/libs/simplebar/simplebar.min.js"></script>
<script src="assets/libs/eva-icons/eva.min.js"></script>
<script src="assets/js/app.js"></script>
<script src="assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="assets/libs/metismenujs/metismenujs.min.js"></script>
<script src="assets/libs/simplebar/simplebar.min.js"></script>
<script src="assets/libs/eva-icons/eva.min.js"></script>
<script src="assets/js/app.js"></script>
</body>
</html>
@@ -0,0 +1,36 @@
YogaSoul Autoresponder — Istruzioni rapide
COS'È
- Crea BOZZE di risposta (non invia) per le ultime X email NON LETTE in INBOX.
- Le bozze vengono salvate nella cartella Drafts/Bozze dellaccount IMAP, in thread (In-Reply-To/References) con citazione del messaggio originale.
FILE NELLA CARTELLA
- yogasoul_autoresponder.exe (oppure: email_autoresponder_yogasoul.py se lo usi con Python)
- config.json (credenziali e impostazioni)
- prompt_template.txt (testo/tono della risposta: modificabile liberamente)
- yogasoul_knowledge_base.json (dati corsi, link, ecc.)
COME SI USA (Windows, senza Python)
1) Apri la cartella “YogaSoul Autoresponder”.
2) Modifica `config.json` con:
- imap_server, email_address, email_password
- openai_api_key
- max_to_process (es. 5), throttle_seconds (es. 1.5)
3) (Opz.) Modifica `prompt_template.txt` e/o `yogasoul_knowledge_base.json`.
4) Doppio click su `yogasoul_autoresponder.exe` (oppure su `run_autoresponder.bat` se fornito).
5) Controlla in Posta le BOZZE: dovresti vedere una bozza per ogni email non letta (fino a max_to_process).
IMPOSTAZIONI UTILI (config.json)
- "max_to_process": quante email non lette processare (consigliato 5).
- "throttle_seconds": pausa tra le richieste (consigliato 12s).
- "mark_as_seen": true per segnare come lette dopo la bozza (false per lasciarle non lette).
- "preferred_draft_folder": cartella locale alternativa se il server non ha Drafts.
TROUBLESHOOTING
- Nessuna bozza: verifica credenziali IMAP/API in config.json; verifica che ci siano email UNSEEN.
- Bozze “schiacciate”: il programma converte in HTML con <p>/<br>; se serve, modifica il prompt.
- Rate limit/blocchi: alza "throttle_seconds" (es. 1.52.0) e tieni "max_to_process" basso (5).
- Cartella bozze: il programma cerca \Drafts; in fallback crea INBOX.BozzaRisposte.
SICUREZZA
- `config.json` contiene password in chiaro: conserva la cartella su un PC/utente fidato.
+11
View File
@@ -0,0 +1,11 @@
{
"imap_server": "mail.yogasoul.it",
"email_address": "info@yogasoul.it",
"email_password": "!Testolina88",
"openai_api_key": "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",
"openai_model": "gpt-3.5-turbo",
"preferred_draft_folder": "BozzaRisposte",
"mark_as_seen": true,
"throttle_seconds": 2,
"max_to_process": 5
}
@@ -13,20 +13,43 @@ from email.utils import parseaddr
from email.mime.text import MIMEText
from email.header import decode_header, make_header
from datetime import datetime
from pathlib import Path
import sys
# ==========================
# CONFIG (compila qui)
# CONFIG da config.json (accanto allo script/EXE)
# ==========================
IMAP_SERVER = "mail.yogasoul.it"
EMAIL_ADDRESS = "info@yogasoul.it"
EMAIL_PASSWORD = "!Testolina88" # <— METTI LA PASSWORD QUI
OPENAI_API_KEY = "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" # <— METTI LA OPENAI KEY QUI
KB_PATH = "yogasoul_knowledge_base.json"
OPENAI_MODEL = "gpt-3.5-turbo"
PREFERRED_DRAFT_FOLDER = "BozzaRisposte" # se non trova Drafts, crea INBOX.<questa>
MARK_AS_SEEN = True # False per lasciare le email non lette
THROTTLE_SECONDS = 0 # ritardo tra email (0 = nessuno)
MAX_TO_PROCESS = 5 # ⬅️ NUOVO: processa al massimo N email UNSEEN (ultime)
def app_dir() -> Path:
# se "frozen" (PyInstaller), usa la cartella dell'eseguibile
return Path(sys.executable).parent if getattr(sys, "frozen", False) else Path(__file__).resolve().parent
CONFIG_PATH = app_dir() / "config.json"
def load_config():
try:
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
cfg = json.load(f)
print(f"[CONFIG] Caricato: {CONFIG_PATH}")
return cfg
except Exception as e:
print(f"[ERRORE] Impossibile leggere {CONFIG_PATH}: {e}")
return {}
CFG = load_config()
# valori letti dal config (con default sensati)
IMAP_SERVER = CFG.get("imap_server", "mail.yogasoul.it")
EMAIL_ADDRESS = CFG.get("email_address", "")
EMAIL_PASSWORD = CFG.get("email_password", "")
OPENAI_API_KEY = CFG.get("openai_api_key", "")
OPENAI_MODEL = CFG.get("openai_model", "gpt-3.5-turbo")
PREFERRED_DRAFT_FOLDER = CFG.get("preferred_draft_folder", "BozzaRisposte")
MARK_AS_SEEN = bool(CFG.get("mark_as_seen", True))
THROTTLE_SECONDS = float(CFG.get("throttle_seconds", 0) or 0)
MAX_TO_PROCESS = int(CFG.get("max_to_process", 5) or 5)
# Percorsi esterni (accanto all'eseguibile)
KB_PATH = str(app_dir() / CFG.get("kb_path", "yogasoul_knowledge_base.json"))
PROMPT_PATH = str(app_dir() / CFG.get("prompt_path", "prompt_template.txt"))
# ==========================
# UTILS
@@ -72,7 +95,6 @@ def _ensure_html_blocks(s):
body = "<p>{}</p>".format(html_lib.escape(s).replace("\n", "<br>"))
return "<!doctype html><html><body>{}</body></html>".format(body)
def _make_quoted_original(body_text):
"""Crea il blocco citato del messaggio originale, safe-escaped."""
if not body_text:
@@ -84,6 +106,14 @@ def _make_quoted_original(body_text):
f"<blockquote style='margin:0 0 0 1em; padding-left:1em; border-left:3px solid #ddd'>{escaped}</blockquote>"
)
def _inject_before_body_end(html_src: str, addition: str) -> str:
"""Inserisce 'addition' prima di </body> in modo case-insensitive; se manca </body>, appende."""
m = re.search(r'</\s*body\s*>', html_src, flags=re.I)
if not m:
return html_src + addition
start = m.start()
return html_src[:start] + addition + html_src[start:]
def load_knowledge_base(path=KB_PATH):
if not os.path.isfile(path):
print(f"[ATTENZIONE] KB non trovata: {path}. Proseguo senza.")
@@ -98,20 +128,39 @@ def load_knowledge_base(path=KB_PATH):
def assert_config():
problems = []
if not isinstance(IMAP_SERVER, str) or not IMAP_SERVER.strip():
problems.append("IMAP_SERVER mancante")
problems.append("imap_server mancante in config.json")
if not isinstance(EMAIL_ADDRESS, str) or not EMAIL_ADDRESS.strip():
problems.append("EMAIL_ADDRESS mancante")
if not isinstance(EMAIL_PASSWORD, str) or not EMAIL_PASSWORD.strip() or EMAIL_PASSWORD == "INSERISCI_LA_PASSWORD":
problems.append("EMAIL_PASSWORD non impostata")
if not isinstance(OPENAI_API_KEY, str) or not OPENAI_API_KEY.strip() or OPENAI_API_KEY == "INSERISCI_OPENAI_API_KEY":
problems.append("OPENAI_API_KEY non impostata")
problems.append("email_address mancante in config.json")
if not isinstance(EMAIL_PASSWORD, str) or not EMAIL_PASSWORD.strip():
problems.append("email_password mancante in config.json")
if not isinstance(OPENAI_API_KEY, str) or not OPENAI_API_KEY.strip():
problems.append("openai_api_key mancante in config.json")
if problems:
print("[CONFIG] Correggi questi parametri prima di proseguire:")
print("[CONFIG] Correggi config.json:")
for p in problems:
print(" -", p)
return False
return True
# ==========================
# Prompt esterno
# ==========================
def load_prompt(path: str) -> str:
try:
with open(path, "r", encoding="utf-8") as f:
print(f"[PROMPT] Caricato: {path}")
return f.read()
except Exception as e:
print(f"[ATTENZIONE] Prompt non trovato o illeggibile: {path} ({e})")
return ""
def render_prompt(template: str, **vars_) -> str:
# Sostituzione semplice stile {{nome}}
out = template
for k, v in vars_.items():
out = out.replace(f"{{{{{k}}}}}", str(v))
return out
# ==========================
# IMAP helpers robusti
# ==========================
@@ -229,25 +278,33 @@ def generate_response(email_info, kb):
f"Corpo: {email_info.get('body_text','')}"
)
prompt = f"""
Sei Aurora, fondatrice di YogaSoul (www.yogasoul.it), stile zen e informale, diretto.
Rispondi in italiano, amichevole e rilassato, con emoticon yoga (🌿, 🧘‍♀️, 😊) senza esagerare.
Saluta con "Ciao {nickname}, bello sentirti!" e firma con "Namaste, Aurora - YogaSoul".
Usa la knowledge base per corsi, orari, prezzi, benefici, insegnanti.
Includi sempre il link_prenotazione specifico come <a href='link'>Iscriviti qui</a> per prenotazioni,
e il calendario <a href='https://yogasoul.it/wp-content/uploads/2025/08/Calendario-settembre-2025-2.jpg'>qui</a>.
Se non sai, scrivi: "Contattami per dettagli! 🧘‍♀️".
# Prompt da file esterno (con fallback interno)
template = load_prompt(PROMPT_PATH)
if not template:
template = (
"Sei Aurora, fondatrice di YogaSoul (www.yogasoul.it), stile zen e informale, diretto.\n"
"Rispondi in italiano, amichevole e rilassato, con emoticon yoga (🌿, 🧘‍♀️, 😊) senza esagerare.\n"
"Saluta con \"Ciao {{nickname}}, bello sentirti!\" e firma con \"Namaste, Aurora - YogaSoul\".\n"
"Usa la knowledge base per corsi, orari, prezzi, benefici, insegnanti.\n"
"Includi sempre il link_prenotazione specifico come <a href='link'>Iscriviti qui</a> per prenotazioni,\n"
"e il calendario <a href='https://yogasoul.it/wp-content/uploads/2025/08/Calendario-settembre-2025-2.jpg'>qui</a>.\n"
"Se non sai, scrivi: \"Contattami per dettagli! 🧘‍♀️\".\n\n"
"{{policy}}\n\n"
"Knowledge Base:\n"
"{{kb_json}}\n\n"
"Email ricevuta:\n"
"{{email_text}}\n\n"
"Scrivi la risposta in HTML pulito (usa <p>, <ul>/<li> se utile; niente CSS superfluo).\n"
)
{policy}
prompt = render_prompt(
template,
nickname=nickname,
policy=policy,
kb_json=kb_json,
email_text=email_text,
)
Knowledge Base:
{kb_json}
Email ricevuta:
{email_text}
Scrivi la risposta in HTML pulito (usa <p>, <ul>/<li> se utile; niente CSS superfluo).
"""
resp = client.chat.completions.create(
model=OPENAI_MODEL,
messages=[{"role": "user", "content": prompt}],
@@ -283,7 +340,7 @@ def fetch_all_unseen(mail, limit=None):
return results
if limit and limit > 0:
ids = ids[-limit:] # ⬅️ prendi SOLO le ultime N non lette
ids = ids[-limit:] # prendi SOLO le ultime N non lette
for seq_id in ids:
typ, msg_data = mail.fetch(seq_id, "(RFC822)")
@@ -335,9 +392,10 @@ def fetch_all_unseen(mail, limit=None):
def prepare_reply_mime(email_info, response_html):
"""Costruisce il MIME HTML della risposta con quote e thread headers."""
reply_body = (response_html.replace("</body>", _make_quoted_original(email_info.get("body_text","")) + "</body>")
if "</body>" in response_html.lower()
else response_html + _make_quoted_original(email_info.get("body_text","")))
reply_body = _inject_before_body_end(
response_html,
_make_quoted_original(email_info.get("body_text",""))
)
msg = MIMEText(reply_body, "html", "utf-8")
subj = email_info.get("subject") or "[Risposta automatica] - YogaSoul"
@@ -388,9 +446,23 @@ def append_draft(mail, folder, msg):
# MAIN
# ==========================
def main():
if not assert_config():
# Controllo config di base
problems = []
if not isinstance(IMAP_SERVER, str) or not IMAP_SERVER.strip():
problems.append("imap_server mancante in config.json")
if not isinstance(EMAIL_ADDRESS, str) or not EMAIL_ADDRESS.strip():
problems.append("email_address mancante in config.json")
if not isinstance(EMAIL_PASSWORD, str) or not EMAIL_PASSWORD.strip():
problems.append("email_password mancante in config.json")
if not isinstance(OPENAI_API_KEY, str) or not OPENAI_API_KEY.strip():
problems.append("openai_api_key mancante in config.json")
if problems:
print("[CONFIG] Correggi config.json:")
for p in problems:
print(" -", p)
return
# Login IMAP
try:
mail = imaplib.IMAP4_SSL(IMAP_SERVER)
mail.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
@@ -403,7 +475,7 @@ def main():
drafts_folder, delim = ensure_drafts_folder(mail)
print(f"[DEBUG] drafts_folder='{drafts_folder}' delim='{delim}'")
emails = fetch_all_unseen(mail, limit=MAX_TO_PROCESS) # ⬅️ usa il limite
emails = fetch_all_unseen(mail, limit=MAX_TO_PROCESS)
if not emails:
print("Nessuna nuova email da processare.")
mail.logout()
+17
View File
@@ -0,0 +1,17 @@
Sei Aurora, fondatrice di YogaSoul (www.yogasoul.it), stile zen e informale, diretto.
Rispondi in italiano, amichevole e rilassato, con emoticon yoga (🌿, 🧘‍♀️, 😊) senza esagerare.
Saluta con "Ciao {{nickname}}, bello sentirti!" e firma con "Namaste, Aurora - YogaSoul".
Usa la knowledge base per corsi, orari, prezzi, benefici, insegnanti.
Includi sempre il link_prenotazione specifico come <a href='link'>Iscriviti qui</a> per prenotazioni,
e il calendario <a href='https://yogasoul.it/wp-content/uploads/2025/08/Calendario-settembre-2025-2.jpg'>qui</a>.
Se non sai, scrivi: "Contattami per dettagli! 🧘‍♀️".
{{policy}}
Knowledge Base:
{{kb_json}}
Email ricevuta:
{{email_text}}
Scrivi la risposta in HTML pulito (usa <p>, <ul>/<li> se utile; niente CSS superfluo).