$message
-diff --git a/public/class/SchoolMail.php b/public/class/SchoolMail.php index d1f7d31..26c466d 100644 --- a/public/class/SchoolMail.php +++ b/public/class/SchoolMail.php @@ -2,19 +2,12 @@ /** * SchoolMail - * Invio email agli allievi a nome della scuola corrente, con il template standard - * (mail/emailtemplate2.php). Se l'invio fallisce registra l'errore e ritorna false: - * non deve mai bloccare l'operazione che l'ha generata. + * Email agli allievi a nome di una scuola, con il template standard (mail/emailtemplate2.php). + * Se l'invio fallisce registra l'errore e ritorna false: non blocca mai l'operazione. * - * Uso: - * SchoolMail::toStudent($email, $firstname, 'Oggetto', [ - * 'Prima riga del messaggio.', - * 'Data e ora: 10-03-2026 18:30', - * ], 'Testo semplice alternativo'); - * - * Opzioni: - * $school riga della tabella schools (default: scuola corrente) - * $ccSchool true = copia all'email della scuola + * SchoolMail::toStudent($email, $firstname, 'Oggetto', ['riga 1', 'riga 2']); + * SchoolMail::toStudent($email, $firstname, 'Oggetto', $lines, '', $schoolRow, true); // copia alla scuola + * SchoolMail::toMany([['email'=>..,'first_name'=>..], ...], 'Oggetto', $lines); // invio di massa * * Le righe sono HTML: i dati variabili vanno passati già con htmlspecialchars(). */ @@ -35,20 +28,98 @@ class SchoolMail ?array $school = null, bool $ccSchool = false ): bool { - if ($to === '' || !filter_var($to, FILTER_VALIDATE_EMAIL)) { + if (!filter_var($to, FILTER_VALIDATE_EMAIL)) { return false; } + $school = $school ?? SchoolContext::get(); + $mail = self::mailer($school); + try { + $mail->addAddress($to); + if ($ccSchool && !empty($school['email']) && filter_var($school['email'], FILTER_VALIDATE_EMAIL)) { + $mail->addCC($school['email'], (string) $school['name']); + } + $mail->Subject = $subject; + $mail->Body = self::render($firstName, $lines, $school); + $mail->AltBody = $altText !== '' ? $altText : strip_tags(implode("\n", $lines)); + $mail->send(); + return true; + } catch (Exception $e) { + error_log('SchoolMail error (' . $to . '): ' . $mail->ErrorInfo); + return false; + } + } - $school = $school ?? SchoolContext::get(); + /** + * Invio di massa con una sola connessione SMTP. + * @param array $recipients [['email' => ..., 'first_name' => ...], ...] + * @return array{sent:int, failed:int} + */ + public static function toMany(array $recipients, string $subject, array $lines, ?array $school = null): array + { + $school = $school ?? SchoolContext::get(); + $mail = self::mailer($school); + $mail->SMTPKeepAlive = true; + $sent = 0; + $failed = 0; + + foreach ($recipients as $r) { + $to = (string) ($r['email'] ?? ''); + if (!filter_var($to, FILTER_VALIDATE_EMAIL)) { + $failed++; + continue; + } + try { + $mail->clearAddresses(); + $mail->addAddress($to); + $mail->Subject = $subject; + $mail->Body = self::render((string) ($r['first_name'] ?? ''), $lines, $school); + $mail->AltBody = strip_tags(implode("\n", $lines)); + $mail->send(); + $sent++; + } catch (Exception $e) { + error_log('SchoolMail bulk error (' . $to . '): ' . $mail->ErrorInfo); + $failed++; + $mail->getSMTPInstance()->reset(); + } + } + $mail->smtpClose(); + return ['sent' => $sent, 'failed' => $failed]; + } + + /* ------------------------------------------------------------------ */ + + private static function mailer(array $school): PHPMailer + { $schoolName = (string) ($school['name'] ?? 'YogiBook'); - $appUrl = rtrim($_ENV['APP_URL'] ?? 'https://yogibook.yogasoul.it', '/'); + $mail = new PHPMailer(true); + $mail->isSMTP(); + $mail->Host = $_ENV['MAIL_HOST']; + $mail->SMTPAuth = true; + $mail->Username = $_ENV['MAIL_USERNAME']; + $mail->Password = $_ENV['MAIL_PASSWORD']; + $mail->SMTPSecure = $_ENV['MAIL_ENCRYPTION']; + $mail->Port = (int) $_ENV['MAIL_PORT']; + $mail->CharSet = 'UTF-8'; + $mail->Timeout = 20; + $mail->isHTML(true); + $mail->setFrom($_ENV['MAIL_FROM_ADDRESS'], $schoolName ?: ($_ENV['MAIL_FROM_NAME'] ?? 'YogiBook')); + if (!empty($school['email']) && filter_var($school['email'], FILTER_VALIDATE_EMAIL)) { + $mail->addReplyTo($school['email'], $schoolName); + } + return $mail; + } + private static function render(string $firstName, array $lines, array $school): string + { + $schoolName = (string) ($school['name'] ?? 'YogiBook'); + $appUrl = rtrim($_ENV['APP_URL'] ?? 'https://yogibook.yogasoul.it', '/'); $h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8'); $p = "
"; $pe = "
"; $btn = "box-sizing: border-box; display: inline-block; text-decoration: none; text-align: center; color: #FFFFFF; background-color: #3AAEE0; border-radius: 4px; font-size: 14px;"; - $body = "Ciao " . $h($firstName) . ",
"; + $greeting = trim($firstName) !== '' ? 'Ciao ' . $h($firstName) . ',' : 'Ciao,'; + $body = "{$greeting}
"; foreach ($lines as $line) { $body .= $p . $line . $pe; } @@ -63,36 +134,6 @@ class SchoolMail . "YogiBook - " . $h($schoolName) . ""; $mailmessage1 = ''; include __DIR__ . '/../mail/emailtemplate2.php'; - - $mail = new PHPMailer(true); - try { - $mail->isSMTP(); - $mail->Host = $_ENV['MAIL_HOST']; - $mail->SMTPAuth = true; - $mail->Username = $_ENV['MAIL_USERNAME']; - $mail->Password = $_ENV['MAIL_PASSWORD']; - $mail->SMTPSecure = $_ENV['MAIL_ENCRYPTION']; - $mail->Port = (int) $_ENV['MAIL_PORT']; - $mail->CharSet = 'UTF-8'; - $mail->isHTML(true); - - $mail->setFrom($_ENV['MAIL_FROM_ADDRESS'], $schoolName ?: ($_ENV['MAIL_FROM_NAME'] ?? 'YogiBook')); - if (!empty($school['email'])) { - $mail->addReplyTo($school['email'], $schoolName); - } - $mail->addAddress($to); - if ($ccSchool && !empty($school['email']) && filter_var($school['email'], FILTER_VALIDATE_EMAIL)) { - $mail->addCC($school['email'], $schoolName); - } - - $mail->Subject = $subject; - $mail->Body = $mailmessage1 ?: $body; - $mail->AltBody = $altText !== '' ? $altText : strip_tags(implode("\n", $lines)); - $mail->send(); - return true; - } catch (Exception $e) { - error_log('SchoolMail error (' . $to . '): ' . $mail->ErrorInfo); - return false; - } + return $mailmessage1 ?: $body; } } diff --git a/public/deleteclass.php b/public/deleteclass.php index 01a568c..5e1ec5c 100644 --- a/public/deleteclass.php +++ b/public/deleteclass.php @@ -1,32 +1,37 @@ - - connect_error) { - die("Connessione fallita: " . $conn->connect_error); -} - -// Ottieni l'ID dall'URL -if (isset($_GET['id'])) { - $id = $_GET['id']; - - // Cancellazione del record dalla tabella bookingclass - $sql = "DELETE FROM bookingclass WHERE idbookingclass = $id"; - - if ($conn->query($sql) === TRUE) { - // Reindirizzamento alla pagina userpanel.php - header("Location: userpanel.php"); - exit(); - } else { - echo "Errore durante la cancellazione del record: " . $conn->error; +/** + * Rimozione di una prenotazione da parte dello staff (da situationusers.php). + * POST: id = idbookingclass. Risposta: { success, message } + */ +function jsonOut(array $payload, int $code = 200): void +{ + if (ob_get_length() !== false) { + ob_end_clean(); } -} else { - echo "ID non fornito."; + http_response_code($code); + header('Content-Type: application/json; charset=utf-8'); + echo json_encode($payload, JSON_UNESCAPED_UNICODE); + exit; } -$conn->close(); -?> +if (!SchoolContext::isStaff()) { + jsonOut(['success' => false, 'message' => 'Accesso non autorizzato.'], 403); +} + +$id = (int) ($_POST['id'] ?? 0); +if (!SchoolScope::belongs('bookingclass', $id)) { + jsonOut(['success' => false, 'message' => 'Prenotazione non trovata.'], 404); +} + +try { + $pdo = DBHandlerSelect::getInstance()->getConnection(); + $pdo->prepare("DELETE FROM bookingclass WHERE idbookingclass = ? AND idschool = ?")->execute([$id, $idschool]); + jsonOut(['success' => true]); +} catch (Throwable $ex) { + error_log('deleteclass: ' . $ex->getMessage()); + jsonOut(['success' => false, 'message' => 'Errore durante la rimozione.'], 500); +} diff --git a/public/fetch_class_details.php b/public/fetch_class_details.php index 41ab324..f7e8070 100644 --- a/public/fetch_class_details.php +++ b/public/fetch_class_details.php @@ -1,75 +1,46 @@ 'Connessione al database fallita']); - exit; -} - -// Check if id_utente is provided -if (isset($_POST['id_utente'])) { - $id_utente = intval($_POST['id_utente']); - - // Query to fetch class details with necessary fields - $sql = " - SELECT - bc.idbookingclass, - bc.bookingstart, - bc.status, - bc.lostlesson, - bc.prevbookingstart, - bc.idorder, - ob.order_date_created, - s.servicename, - s.day, - s.time - FROM bookingclass bc - LEFT JOIN orderbook ob ON bc.idorder = ob.idorderbook - LEFT JOIN service s ON bc.idservice = s.idservice - WHERE bc.iduser = $id_utente - ORDER BY bc.bookingstart DESC - "; - - $result = mysqli_query($conn, $sql); - if (!$result) { +/** + * Lezioni di un allievo nella scuola corrente (dettaglio di situationusers.php). + * POST: id_utente. Risposta: array di lezioni, oppure { error }. + */ +function jsonOut($payload, int $code = 200): void +{ + if (ob_get_length() !== false) { ob_end_clean(); - header('Content-Type: application/json'); - echo json_encode(['error' => 'Errore nella query: ' . mysqli_error($conn)]); - exit; } - - $classes = []; - - // Fetch all rows and store in an array - while ($row = mysqli_fetch_assoc($result)) { - $classes[] = $row; - } - - // Clear the output buffer, set the header, and output JSON - ob_end_clean(); - header('Content-Type: application/json'); - echo json_encode($classes); - mysqli_close($conn); + http_response_code($code); + header('Content-Type: application/json; charset=utf-8'); + echo json_encode($payload, JSON_UNESCAPED_UNICODE); exit; } -// If id_utente is not provided, return an error -ob_end_clean(); -header('Content-Type: application/json'); -echo json_encode(['error' => 'ID utente non fornito']); -mysqli_close($conn); -exit; -?> \ No newline at end of file +if (!SchoolContext::isSchoolAdmin()) { + jsonOut(['error' => 'Accesso non autorizzato.'], 403); +} + +$userId = (int) ($_POST['id_utente'] ?? 0); +if ($userId <= 0 || !SchoolScope::isMember($userId)) { + jsonOut([]); +} + +try { + $pdo = DBHandlerSelect::getInstance()->getConnection(); + $st = $pdo->prepare( + "SELECT bc.idbookingclass, bc.bookingstart, bc.status, bc.lostlesson, bc.prevbookingstart, + bc.idorder, ob.order_date_created, s.servicename, s.day, s.time + FROM bookingclass bc + LEFT JOIN orderbook ob ON bc.idorder = ob.idorderbook + LEFT JOIN service s ON bc.idservice = s.idservice + WHERE bc.iduser = ? AND bc.idschool = ? AND bc.status <> 'cancelled' + ORDER BY bc.bookingstart DESC" + ); + $st->execute([$userId, $idschool]); + jsonOut($st->fetchAll()); +} catch (Throwable $ex) { + error_log('fetch_class_details: ' . $ex->getMessage()); + jsonOut(['error' => 'Errore nel caricamento.'], 500); +} diff --git a/public/mark_lost.php b/public/mark_lost.php index b97aa59..9a24d18 100644 --- a/public/mark_lost.php +++ b/public/mark_lost.php @@ -1,54 +1,37 @@ false, 'message' => 'Connessione al database fallita']); +/** + * Segna come persa una prenotazione (da situationusers.php). + * POST: id = idbookingclass. Risposta: { success, message } + */ +function jsonOut(array $payload, int $code = 200): void +{ + if (ob_get_length() !== false) { + ob_end_clean(); + } + http_response_code($code); + header('Content-Type: application/json; charset=utf-8'); + echo json_encode($payload, JSON_UNESCAPED_UNICODE); exit; } -if (isset($_POST['id'])) { - $id = intval($_POST['id']); - - $sql = "UPDATE bookingclass SET lostlesson = 'Y' WHERE idbookingclass = ?"; - $stmt = mysqli_prepare($conn, $sql); - mysqli_stmt_bind_param($stmt, 'i', $id); - - if (mysqli_stmt_execute($stmt)) { - // Clean the output buffer before sending the response - ob_end_clean(); - echo json_encode(['success' => true]); - } else { - // Clean the output buffer before sending the response - ob_end_clean(); - echo json_encode(['success' => false, 'message' => 'Errore durante l\'aggiornamento: ' . mysqli_error($conn)]); - } - - mysqli_stmt_close($stmt); -} else { - // Clean the output buffer before sending the response - ob_end_clean(); - echo json_encode(['success' => false, 'message' => 'ID non fornito']); +if (!SchoolContext::isStaff()) { + jsonOut(['success' => false, 'message' => 'Accesso non autorizzato.'], 403); } -mysqli_close($conn); +$id = (int) ($_POST['id'] ?? 0); +if (!SchoolScope::belongs('bookingclass', $id)) { + jsonOut(['success' => false, 'message' => 'Prenotazione non trovata.'], 404); +} -// Ensure no extra output is sent -exit; \ No newline at end of file +try { + $pdo = DBHandlerSelect::getInstance()->getConnection(); + $pdo->prepare("UPDATE bookingclass SET lostlesson = 'Y' WHERE idbookingclass = ? AND idschool = ?")->execute([$id, $idschool]); + jsonOut(['success' => true]); +} catch (Throwable $ex) { + error_log('mark_lost: ' . $ex->getMessage()); + jsonOut(['success' => false, 'message' => 'Errore durante l\'aggiornamento.'], 500); +} diff --git a/public/send_email.php b/public/send_email.php index accb177..bd6d6af 100644 --- a/public/send_email.php +++ b/public/send_email.php @@ -1,160 +1,79 @@ - - - - -
-
- $message
-