diff --git a/public/userarea/api/_bootstrap.php b/public/userarea/api/_bootstrap.php new file mode 100644 index 0000000..81e66ae --- /dev/null +++ b/public/userarea/api/_bootstrap.php @@ -0,0 +1,132 @@ +getConnection(); + +/** + * Get Authorization header (Apache/Nginx/CGI compatible) + */ +function getAuthorizationHeader(): ?string +{ + if (function_exists('getallheaders')) { + $headers = getallheaders(); + foreach ($headers as $k => $v) { + if (strtolower($k) === 'authorization') return $v; + } + } + if (!empty($_SERVER['HTTP_AUTHORIZATION'])) return $_SERVER['HTTP_AUTHORIZATION']; + if (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) return $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; + return null; +} + +/** + * Extract Bearer token + */ +function getBearerToken(): ?string +{ + $auth = getAuthorizationHeader(); + if (!$auth) return null; + + if (preg_match('/Bearer\s+(.*)$/i', $auth, $m)) { + return trim($m[1]); + } + return null; +} + +$bearer = getBearerToken(); +if (!$bearer) { + http_response_code(401); + echo json_encode(['success' => false, 'message' => 'Unauthorized (missing Bearer token).']); + exit; +} + +// Sanctum plain token format: "|" +if (strpos($bearer, '|') === false) { + http_response_code(401); + echo json_encode(['success' => false, 'message' => 'Unauthorized (invalid token format).']); + exit; +} + +[$tokenId, $plainToken] = explode('|', $bearer, 2); +$tokenId = (int)$tokenId; + +if ($tokenId <= 0 || $plainToken === '') { + http_response_code(401); + echo json_encode(['success' => false, 'message' => 'Unauthorized (invalid token).']); + exit; +} + +// --- IMPORTANT: your table name --- +$tokensTable = 'auth_personal_access_tokens'; + +// Lookup token by id +$stmt = $pdo->prepare(" + SELECT id, tokenable_id, token, expires_at + FROM {$tokensTable} + WHERE id = ? + LIMIT 1 +"); +$stmt->execute([$tokenId]); +$row = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$row) { + http_response_code(401); + echo json_encode(['success' => false, 'message' => 'Unauthorized (token not found).']); + exit; +} + +// Optional expiry check +if (!empty($row['expires_at'])) { + $now = new DateTimeImmutable('now', new DateTimeZone('Europe/Rome')); + $exp = new DateTimeImmutable($row['expires_at'], new DateTimeZone('Europe/Rome')); + if ($exp <= $now) { + http_response_code(401); + echo json_encode(['success' => false, 'message' => 'Unauthorized (token expired).']); + exit; + } +} + +// Sanctum stores SHA-256 hash of the plain token +$hash = hash('sha256', $plainToken); +if (!hash_equals((string)$row['token'], $hash)) { + http_response_code(401); + echo json_encode(['success' => false, 'message' => 'Unauthorized (token mismatch).']); + exit; +} + +$iduserlogin = (int)$row['tokenable_id']; +if ($iduserlogin <= 0) { + http_response_code(401); + echo json_encode(['success' => false, 'message' => 'Unauthorized (invalid user).']); + exit; +} + +// Update last_used_at (best effort) +try { + $pdo->prepare("UPDATE {$tokensTable} SET last_used_at = NOW() WHERE id = ?")->execute([$tokenId]); +} catch (Throwable $e) { + // ignore +} diff --git a/public/userarea/api/api_my_lessons.php b/public/userarea/api/api_my_lessons.php new file mode 100644 index 0000000..8630060 --- /dev/null +++ b/public/userarea/api/api_my_lessons.php @@ -0,0 +1,175 @@ + false, 'message' => 'Missing required parameter: school_id']); + exit; + } + + $month = $_GET['month'] ?? (new DateTimeImmutable('now', new DateTimeZone('Europe/Rome')))->format('Y-m'); + if (!preg_match('/^\d{4}-\d{2}$/', (string)$month)) { + http_response_code(422); + echo json_encode(['success' => false, 'message' => 'Invalid month format. Use YYYY-MM.']); + exit; + } + + $tz = new DateTimeZone('Europe/Rome'); + $monthDate = DateTimeImmutable::createFromFormat('Y-m', $month, $tz) ?: new DateTimeImmutable('now', $tz); + + $startOfMonth = $monthDate->format('Y-m-01'); // inclusive + $endOfMonth = $monthDate->modify('first day of next month')->format('Y-m-d'); // exclusive + $prevMonth = $monthDate->modify('-1 month')->format('Y-m'); + $nextMonth = $monthDate->modify('+1 month')->format('Y-m'); + + // --- Authorization: user must belong to that school --- + // Adjust if your authorization logic differs. + $stmt = $pdo->prepare(" + SELECT 1 + FROM user_schools + WHERE user_id = ? + AND school_id = ? + AND status = 'active' + LIMIT 1 + "); + $stmt->execute([$iduserlogin, $school_id]); + if (!$stmt->fetchColumn()) { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Forbidden: user not allowed for this school.']); + exit; + } + + // --- School info (same as your page) --- + $stmt = $pdo->prepare(" + SELECT id, name, address_street, address_city, address_province + FROM schools + WHERE id = ? + LIMIT 1 + "); + $stmt->execute([$school_id]); + $school = $stmt->fetch(PDO::FETCH_ASSOC); + if (!$school) { + http_response_code(404); + echo json_encode(['success' => false, 'message' => 'School not found.']); + exit; + } + + // --- Main query (ported from your PHP page) --- + $stmt = $pdo->prepare(" + SELECT + sb.id as booking_id, + sb.status, + sb.order_id, + cs.id as session_id, + cs.session_date, + cs.start_time, + cs.end_time, + cs.room_name, + c.name as class_name, + ct.level, + o.available_entries, + o.available_recoveries + FROM session_bookings sb + JOIN class_sessions cs ON sb.session_id = cs.id + JOIN class_types ct ON cs.class_type_id = ct.id + JOIN classes c ON ct.class_id = c.id + JOIN orders o ON sb.order_id = o.id + WHERE sb.user_id = ? + AND cs.school_id = ? + AND cs.session_date >= ? + AND cs.session_date < ? + ORDER BY cs.session_date ASC, cs.start_time ASC + "); + $stmt->execute([$iduserlogin, $school_id, $startOfMonth, $endOfMonth]); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // Better 24h rule: compare against actual lesson start datetime + $now = new DateTimeImmutable('now', $tz); + $limit = $now->modify('+24 hours'); + + $lessons = []; + foreach ($rows as $r) { + $startDt = DateTimeImmutable::createFromFormat( + 'Y-m-d H:i:s', + $r['session_date'] . ' ' . $r['start_time'], + $tz + ); + + if (!$startDt) { + // fallback if start_time is H:i + $startDt = DateTimeImmutable::createFromFormat( + 'Y-m-d H:i', + $r['session_date'] . ' ' . substr((string)$r['start_time'], 0, 5), + $tz + ); + } + + $canModify = false; + if ($r['status'] === 'booked' && $startDt instanceof DateTimeImmutable) { + $canModify = ($startDt > $limit); + } + + $lessons[] = [ + 'booking_id' => (int)$r['booking_id'], + 'status' => (string)$r['status'], + 'order_id' => (int)$r['order_id'], + + 'session' => [ + 'session_id' => (int)$r['session_id'], + 'date' => (string)$r['session_date'], + 'start_time' => (string)$r['start_time'], + 'end_time' => (string)$r['end_time'], + 'room_name' => $r['room_name'] !== null ? (string)$r['room_name'] : null, + 'start_datetime_iso' => $startDt ? $startDt->format(DateTimeInterface::ATOM) : null, + ], + + 'class' => [ + 'name' => (string)$r['class_name'], + 'level' => ($r['level'] ?? null) ? (string)$r['level'] : null, + ], + + 'wallet' => [ + 'available_entries' => (int)($r['available_entries'] ?? 0), + 'available_recoveries' => (int)($r['available_recoveries'] ?? 0), + ], + + 'can_reschedule' => $canModify, + 'can_cancel' => $canModify, + 'can_modify' => $canModify, + ]; + } + + echo json_encode([ + 'success' => true, + 'month' => $monthDate->format('Y-m'), + 'prev_month' => $prevMonth, + 'next_month' => $nextMonth, + 'school' => [ + 'id' => (int)$school['id'], + 'name' => (string)$school['name'], + 'address_street' => $school['address_street'] ?? null, + 'address_city' => $school['address_city'] ?? null, + 'address_province' => $school['address_province'] ?? null, + 'address_full' => trim( + ($school['address_street'] ?? '') . ', ' . + ($school['address_city'] ?? '') . ' ' . + ($school['address_province'] ?? '') + ), + ], + 'lessons' => $lessons + ], JSON_UNESCAPED_UNICODE); +} catch (Throwable $e) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'message' => 'Server error.', + 'error' => $e->getMessage() + ]); +} diff --git a/public/userarea/api/api_user_schools.php b/public/userarea/api/api_user_schools.php new file mode 100644 index 0000000..d378c44 --- /dev/null +++ b/public/userarea/api/api_user_schools.php @@ -0,0 +1,88 @@ +prepare("SELECT first_name, avatar FROM auth_users WHERE id = ? LIMIT 1"); + $stmt->execute([$iduserlogin]); + $user = $stmt->fetch(PDO::FETCH_ASSOC) ?: []; + + // User schools only + $stmt = $pdo->prepare(" + SELECT + s.id, + s.name, + s.logo, + s.address_street, + s.address_postal_code, + s.address_city, + s.address_province, + s.address_country + FROM user_schools us + JOIN schools s ON us.school_id = s.id + WHERE us.user_id = ? + AND us.status = 'active' + AND s.status = 'active' + ORDER BY s.name + "); + $stmt->execute([$iduserlogin]); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $schools = array_map(function (array $s): array { + $street = trim((string)($s['address_street'] ?? '')); + $zip = trim((string)($s['address_postal_code'] ?? '')); + $city = trim((string)($s['address_city'] ?? '')); + $prov = trim((string)($s['address_province'] ?? '')); + $cntry = trim((string)($s['address_country'] ?? '')); + + $line2Parts = []; + if ($zip !== '') $line2Parts[] = $zip; + if ($city !== '') $line2Parts[] = $city; + + $line2 = implode(' ', $line2Parts); + if ($prov !== '') $line2 .= ' (' . $prov . ')'; + if ($cntry !== '') $line2 .= ' - ' . $cntry; + + return [ + 'id' => (int)$s['id'], + 'name' => (string)($s['name'] ?? ''), + 'logo' => ($s['logo'] ?? null) !== null && trim((string)$s['logo']) !== '' ? (string)$s['logo'] : null, + 'address_street' => $street !== '' ? $street : null, + 'address_postal_code' => $zip !== '' ? $zip : null, + 'address_city' => $city !== '' ? $city : null, + 'address_province' => $prov !== '' ? $prov : null, + 'address_country' => $cntry !== '' ? $cntry : null, + 'address_full' => trim($street . ($street && $line2 ? ', ' : '') . $line2), + ]; + }, $rows); + + $autoSelect = (count($schools) === 1); + $selectedSchoolId = $autoSelect ? (int)$schools[0]['id'] : null; + + echo json_encode([ + 'success' => true, + 'auto_select' => $autoSelect, + 'selected_school_id' => $selectedSchoolId, + 'user' => [ + 'id' => $iduserlogin, + 'first_name' => $user['first_name'] ?? null, + 'avatar' => $user['avatar'] ?? null, + ], + 'schools' => $schools, + 'message' => empty($schools) + ? 'No active schools assigned to this user. Please contact your school.' + : null + ], JSON_UNESCAPED_UNICODE); +} catch (Throwable $e) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'message' => 'Server error.', + 'error' => $e->getMessage() + ]); +} diff --git a/public/userarea/class_type_associate.php b/public/userarea/class_type_associate.php new file mode 100644 index 0000000..79782d7 --- /dev/null +++ b/public/userarea/class_type_associate.php @@ -0,0 +1,341 @@ +getConnection(); + +/* 1) Scuola */ +$stmt = $pdo->prepare("SELECT id, name FROM schools WHERE owner_id = ? LIMIT 1"); +$stmt->execute([(int)$iduserlogin]); +$school = $stmt->fetch(PDO::FETCH_ASSOC); +if (!$school) die("Scuola non trovata."); +$school_id = (int)$school['id']; + +/* 2) class_type_id da GET */ +$class_type_id = isset($_GET['class_type_id']) ? (int)$_GET['class_type_id'] : 0; +if ($class_type_id <= 0) die("Parametro class_type_id mancante."); + +/* 3) Verifica che la variazione appartenga alla scuola + dati per header */ +$stmt = $pdo->prepare(" + SELECT + ct.id, ct.class_id, ct.level, ct.day_of_week, ct.start_time, ct.room_name, ct.status, + c.name AS class_name + FROM class_types ct + JOIN classes c ON c.id = ct.class_id + WHERE ct.id = ? AND c.school_id = ? + LIMIT 1 +"); +$stmt->execute([$class_type_id, $school_id]); +$base = $stmt->fetch(PDO::FETCH_ASSOC); +if (!$base) die("Variazione non trovata o non autorizzata."); + +/* 4) Legami già esistenti (per spuntare checkbox) */ +$stmt = $pdo->prepare(" + SELECT + CASE + WHEN class_type_id = ? THEN linked_class_type_id + ELSE class_type_id + END AS other_id + FROM class_type_links + WHERE school_id = ? + AND (class_type_id = ? OR linked_class_type_id = ?) +"); +$stmt->execute([$class_type_id, $school_id, $class_type_id, $class_type_id]); +$linkedIds = array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN)); +$linkedMap = array_flip($linkedIds); + +$feedback = ""; + +/* 5) Salvataggio */ +if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save_links') { + $selected = $_POST['links'] ?? []; + $selected_ids = array_values(array_unique(array_map('intval', $selected))); + + // rimuovo l'eventuale "se stesso" + $selected_ids = array_values(array_filter($selected_ids, fn($x) => $x > 0 && $x !== $class_type_id)); + + // Validazione: tutti devono essere della stessa scuola + if (!empty($selected_ids)) { + $in = implode(',', array_fill(0, count($selected_ids), '?')); + $params = $selected_ids; + $params[] = $school_id; + + $stmt = $pdo->prepare(" + SELECT COUNT(*) + FROM class_types ct + JOIN classes c ON c.id = ct.class_id + WHERE ct.id IN ($in) AND c.school_id = ? + "); + $stmt->execute($params); + $cnt_ok = (int)$stmt->fetchColumn(); + + if ($cnt_ok !== count($selected_ids)) { + $feedback = '
+ Selezione non valida: contiene variazioni non appartenenti alla scuola. + +
'; + } + } + + if ($feedback === "") { + try { + $pdo->beginTransaction(); + + // 1) cancello tutti i link dove compare questa variazione (da entrambi i lati) + $stmt = $pdo->prepare(" + DELETE FROM class_type_links + WHERE school_id = ? + AND (class_type_id = ? OR linked_class_type_id = ?) + "); + $stmt->execute([$school_id, $class_type_id, $class_type_id]); + + // 2) inserisco i nuovi (coppia canonica: min/max) + if (!empty($selected_ids)) { + $ins = $pdo->prepare(" + INSERT IGNORE INTO class_type_links (school_id, class_type_id, linked_class_type_id) + VALUES (?, ?, ?) + "); + + foreach ($selected_ids as $other) { + $a = min($class_type_id, $other); + $b = max($class_type_id, $other); + $ins->execute([$school_id, $a, $b]); + } + } + + $pdo->commit(); + + $feedback = '
+ Associazioni salvate! + +
'; + + // ricarico per checkbox + $stmt = $pdo->prepare(" + SELECT + CASE + WHEN class_type_id = ? THEN linked_class_type_id + ELSE class_type_id + END AS other_id + FROM class_type_links + WHERE school_id = ? + AND (class_type_id = ? OR linked_class_type_id = ?) + "); + $stmt->execute([$class_type_id, $school_id, $class_type_id, $class_type_id]); + $linkedIds = array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN)); + $linkedMap = array_flip($linkedIds); + } catch (Exception $e) { + if ($pdo->inTransaction()) $pdo->rollBack(); + $feedback = '
+ Errore salvataggio: ' . htmlspecialchars($e->getMessage()) . ' + +
'; + } + } +} + +/* 6) Lista di TUTTE le altre variazioni della scuola */ +$stmt = $pdo->prepare(" + SELECT + ct.id, + c.name AS class_name, + ct.level, + ct.day_of_week, + ct.start_time, + ct.room_name, + ct.status + FROM class_types ct + JOIN classes c ON c.id = ct.class_id + WHERE c.school_id = ? + AND ct.id <> ? + AND ct.status = 'active' + ORDER BY c.name, ct.level, ct.day_of_week, ct.start_time +"); +$stmt->execute([$school_id, $class_type_id]); +$all = $stmt->fetchAll(PDO::FETCH_ASSOC); + +function fmtLevel($lvl) +{ + return $lvl ? ucfirst($lvl) : '-'; +} +function fmtDay($d) +{ + return $d ? ucfirst($d) : '-'; +} +?> + + + + + + + Associa variazioni - <?php echo htmlspecialchars($school['name']); ?> + + + + + + +
+ + + +
+
+
+ +
+
+
+
Associazioni per riprogrammazione
+ + — + — + + + +
+
+ Dashboard +
+
+
+ + + +
+
+ +
+
+
+ +
+
+ + +
+
+
+
+ +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LinkClasseLivelloGiornoOraSala
Nessuna altra variazione disponibile.
+ > +
+
+ +
+ Annulla + +
+
+ +
+ Nota: le associazioni sono bidirezionali. Se A è associata a B, anche B risulta associata ad A. +
+ +
+
+ +
+
+
+ + +
+ + + + + + + + \ No newline at end of file diff --git a/public/userarea/future_sessions.php b/public/userarea/future_sessions.php index 7e32d99..4370b1f 100644 --- a/public/userarea/future_sessions.php +++ b/public/userarea/future_sessions.php @@ -347,6 +347,7 @@ foreach ($sessions as $s) {

Gestione completa: lista prenotati, P/C, cancellazione con avviso email.

+ Archivio ← Dashboard
diff --git a/public/userarea/school_dashboard.php b/public/userarea/school_dashboard.php index 75674ba..c0d0c31 100644 --- a/public/userarea/school_dashboard.php +++ b/public/userarea/school_dashboard.php @@ -1052,6 +1052,12 @@ $daily_sessions = $stmt->fetchAll(); data-bs-toggle="tooltip" data-bs-placement="top" title="Propaga"> Propaga + + Associa + + + '; + } + } + } + + // Send cancellation email then delete + elseif ($action === 'send_cancellation_email') { + $session_id = (int)($_POST['session_id'] ?? 0); + $body_text = $_POST['email_body'] ?? ''; + + $stmt = $pdo->prepare(" + SELECT c.name AS class_name, ct.level, cs.session_date, cs.start_time, cs.end_time + FROM class_sessions cs + JOIN class_types ct ON cs.class_type_id = ct.id + JOIN classes c ON ct.class_id = c.id + WHERE cs.id = ? AND c.school_id = ? + "); + $stmt->execute([$session_id, $school_id]); + $session = $stmt->fetch(PDO::FETCH_ASSOC); + + $class_info = $session ? ($session['class_name'] . ($session['level'] ? ' (' . ucfirst($session['level']) . ')' : '')) : ''; + $date = $session ? date('d/m/Y', strtotime($session['session_date'])) : ''; + $time = $session ? substr($session['start_time'], 0, 5) . '-' . substr($session['end_time'], 0, 5) : ''; + + $stmt = $pdo->prepare(" + SELECT au.email, au.first_name + FROM session_bookings sb + JOIN auth_users au ON sb.user_id = au.id + WHERE sb.session_id = ? + "); + $stmt->execute([$session_id]); + $recipients = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $subject = "Lezione cancellata - {$school_name}"; + $sent = 0; + + foreach ($recipients as $r) { + $personal_body = str_replace( + ['{nome}', '{classe}', '{data}', '{ora}'], + [$r['first_name'] ?: 'Gentile utente', $class_info, $date, $time], + $body_text + ); + $result = sendEmail($r['email'], $subject, $personal_body); + if (!empty($result['success'])) $sent++; + } + + $stmtDel = $pdo->prepare(" + DELETE cs FROM class_sessions cs + JOIN class_types ct ON cs.class_type_id = ct.id + JOIN classes c ON ct.class_id = c.id + WHERE cs.id = ? AND c.school_id = ? + "); + $stmtDel->execute([$session_id, $school_id]); + + $feedback = "
+ Email inviate a {$sent} partecipanti. + +
"; + } + + // Skip email then delete + elseif ($action === 'skip_cancellation_email') { + $session_id = (int)($_POST['session_id'] ?? 0); + + $stmtDel = $pdo->prepare(" + DELETE cs FROM class_sessions cs + JOIN class_types ct ON cs.class_type_id = ct.id + JOIN classes c ON ct.class_id = c.id + WHERE cs.id = ? AND c.school_id = ? + "); + $stmtDel->execute([$session_id, $school_id]); + + $feedback = "
+ Lezione cancellata (email non inviata). + +
"; + } + + // Add booking + elseif ($action === 'add_booking') { + $session_id = (int)($_POST['session_id'] ?? 0); + $user_id = (int)($_POST['user_id'] ?? 0); + + if ($session_id <= 0 || $user_id <= 0) { + $feedback = '
Dati non validi.
'; + } else { + $stmt = $pdo->prepare(" + SELECT cs.id, ct.max_capacity, + (SELECT COUNT(*) FROM session_bookings sb + WHERE sb.session_id = cs.id AND sb.status IN ('booked','attended','rescheduled') + ) AS booked_count + FROM class_sessions cs + JOIN class_types ct ON cs.class_type_id = ct.id + JOIN classes c ON ct.class_id = c.id + WHERE cs.id = ? AND c.school_id = ? + LIMIT 1 + "); + $stmt->execute([$session_id, $school_id]); + $info = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$info) { + $feedback = '
Lezione non trovata o non autorizzata.
'; + } else { + $stmt = $pdo->prepare(" + SELECT 1 + FROM user_schools + WHERE user_id = ? AND school_id = ? AND status = 'active' + LIMIT 1 + "); + $stmt->execute([$user_id, $school_id]); + $isMember = (bool)$stmt->fetchColumn(); + + if (!$isMember) { + $feedback = '
Utente non associato a questa scuola.
'; + } else { + $stmt = $pdo->prepare("SELECT 1 FROM session_bookings WHERE session_id = ? AND user_id = ? LIMIT 1"); + $stmt->execute([$session_id, $user_id]); + $already = (bool)$stmt->fetchColumn(); + + if ($already) { + $feedback = '
Questo utente è già presente nella lezione.
'; + } else { + $max = (int)($info['max_capacity'] ?? 0); + $cnt = (int)($info['booked_count'] ?? 0); + + if ($max > 0 && $cnt >= $max) { + $feedback = '
Lezione piena: capienza massima raggiunta.
'; + } else { + $stmtIns = $pdo->prepare(" + INSERT INTO session_bookings (session_id, user_id, status, created_at, updated_at) + VALUES (?, ?, 'booked', NOW(), NOW()) + "); + $stmtIns->execute([$session_id, $user_id]); + + $feedback = '
Partecipante aggiunto alla lezione!
'; + } + } + } + } + } + } + + // Mark lost + elseif ($action === 'mark_lost') { + $booking_id = (int)($_POST['booking_id'] ?? 0); + $stmt = $pdo->prepare(" + UPDATE session_bookings sb + JOIN class_sessions cs ON sb.session_id = cs.id + JOIN class_types ct ON cs.class_type_id = ct.id + JOIN classes c ON ct.class_id = c.id + SET sb.status = 'missed' + WHERE sb.id = ? AND c.school_id = ? + "); + $stmt->execute([$booking_id, $school_id]); + $feedback = '
Presenza segnata come persa.
'; + } + + // Cancel booking + elseif ($action === 'cancel_booking') { + $booking_id = (int)($_POST['booking_id'] ?? 0); + $stmt = $pdo->prepare(" + DELETE sb FROM session_bookings sb + JOIN class_sessions cs ON sb.session_id = cs.id + JOIN class_types ct ON cs.class_type_id = ct.id + JOIN classes c ON ct.class_id = c.id + WHERE sb.id = ? AND c.school_id = ? + "); + $stmt->execute([$booking_id, $school_id]); + $feedback = '
Prenotazione rimossa.
'; + } + + // Keep current month in redirect-less flow: we simply render again with the same ym from GET. +} + +// === Sessions of selected month === +$stmt = $pdo->prepare(" + SELECT + cs.id AS session_id, + cs.session_date, + cs.start_time, + cs.end_time, + c.name AS class_name, + ct.level, + ct.room_name, + ct.max_capacity, + t.first_name, + t.last_name, + (SELECT COUNT(*) + FROM session_bookings sb + WHERE sb.session_id = cs.id + AND sb.status IN ('booked','attended','rescheduled') + ) AS booked_count, + (SELECT GROUP_CONCAT( + CONCAT(sb.id,'|||',au.id,'|||',au.first_name,' ',au.last_name,'|||',au.email,'|||',COALESCE(au.phone,''),'|||',sb.status) + SEPARATOR ';;;' + ) + FROM session_bookings sb + JOIN auth_users au ON sb.user_id = au.id + WHERE sb.session_id = cs.id + AND sb.status IN ('booked','attended','rescheduled','missed') + ) AS booked_students_details + FROM class_sessions cs + JOIN class_types ct ON cs.class_type_id = ct.id + JOIN classes c ON ct.class_id = c.id + LEFT JOIN teachers t ON cs.teacher_id = t.id + WHERE c.school_id = ? + AND cs.session_date >= ? + AND cs.session_date < ? + ORDER BY cs.session_date, cs.start_time +"); +$stmt->execute([$school_id, $startDate, $endDate]); +$sessions = $stmt->fetchAll(PDO::FETCH_ASSOC); + + + +function humanMonthLabel($ym) +{ + $dt = DateTime::createFromFormat('Y-m', $ym); + if (!$dt) return $ym; + return ucfirst($dt->format('F Y')); // if locale not set, this will be English +} +?> + + + + + + + Archivio Lezioni - <?php echo htmlspecialchars($school_name); ?> + + + + + + +
+ + + +
+
+ +
+
+
+
+ Logo +
+
+

Archivio Lezioni -

+

Vedi le lezioni mese per mese con le stesse funzioni (prenotati, P/C, cancellazione con email).

+
+ +
+
+
+ + + +
+
+
+
+
Mese selezionato
+ +
+ +
+
+ + +
+ +
+
+
+
+ + +
+
+
+ Nessuna lezione trovata per il mese selezionato (). +
+
+
+ +
+
+
Archivio mese:
+ lezioni +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DataOrarioClasseLivelloSalaInsegnantePrenotatiAzioni
Non assegnato'; ?> + + + +
+
+
+
+ + +
+
+ + + + (int)($parts[0] ?? 0), + 'user_id' => (int)($parts[1] ?? 0), + 'name' => $parts[2] ?? '', + 'email' => $parts[3] ?? '', + 'phone' => $parts[4] ?? '—', + 'status' => $parts[5] ?? 'booked' + ]; + } + } + ?> + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + \ No newline at end of file diff --git a/public/userarea/user-settings.php b/public/userarea/user-settings.php new file mode 100644 index 0000000..c81bc06 --- /dev/null +++ b/public/userarea/user-settings.php @@ -0,0 +1,413 @@ +getConnection(); + +/* +|-------------------------------------------------------------------------- +| 1) Recupera la scuola (come nel tuo esempio) +|-------------------------------------------------------------------------- +*/ +$stmt = $pdo->prepare("SELECT id, name FROM schools WHERE owner_id = ? LIMIT 1"); +$stmt->execute([$iduserlogin]); +$school = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$school) { + die("Scuola non trovata."); +} + +$school_id = (int)$school['id']; + +/* +|-------------------------------------------------------------------------- +| 2) Recupera school_settings (per sapere se notifiche sono abilitate globalmente) +|-------------------------------------------------------------------------- +*/ +$stmt = $pdo->prepare("SELECT enable_notifications FROM school_settings WHERE school_id = ? LIMIT 1"); +$stmt->execute([$school_id]); +$schoolSettings = $stmt->fetch(PDO::FETCH_ASSOC); + +$school_enable_notifications = isset($schoolSettings['enable_notifications']) + ? (int)$schoolSettings['enable_notifications'] + : 1; // default ON se non esiste riga + +/* +|-------------------------------------------------------------------------- +| 3) Recupera user_settings (per questo utente in questa scuola) +|-------------------------------------------------------------------------- +*/ +$stmt = $pdo->prepare("SELECT * FROM user_settings WHERE school_id = ? AND user_id = ? LIMIT 1"); +$stmt->execute([$school_id, (int)$iduserlogin]); +$settings = $stmt->fetch(PDO::FETCH_ASSOC); + +$is_new = !$settings; + +$success_message = ""; +$error = ""; + +/* +|-------------------------------------------------------------------------- +| 4) Salvataggio POST +|-------------------------------------------------------------------------- +*/ +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // NOTA: se scuola disabilita notifiche globalmente, forzo tutto a 0 + $notifications_allowed = ($school_enable_notifications === 1); + + $notify_email = (!empty($_POST['notify_email']) && $notifications_allowed) ? 1 : 0; + $notify_whatsapp = (!empty($_POST['notify_whatsapp']) && $notifications_allowed) ? 1 : 0; + $notify_push = (!empty($_POST['notify_push']) && $notifications_allowed) ? 1 : 0; + + $notify_booking_confirm = (!empty($_POST['notify_booking_confirm']) && $notifications_allowed) ? 1 : 0; + $notify_booking_cancel = (!empty($_POST['notify_booking_cancel']) && $notifications_allowed) ? 1 : 0; + $notify_session_cancel = (!empty($_POST['notify_session_cancel']) && $notifications_allowed) ? 1 : 0; + $notify_payment_receipt = (!empty($_POST['notify_payment_receipt']) && $notifications_allowed) ? 1 : 0; + $notify_expiration_reminder = (!empty($_POST['notify_expiration_reminder']) && $notifications_allowed) ? 1 : 0; + + // MARKETING + $newsletter_opt_in = !empty($_POST['newsletter_opt_in']) ? 1 : 0; + $marketing_opt_in = !empty($_POST['marketing_opt_in']) ? 1 : 0; + + // PREFERENZE + $locale = trim($_POST['locale'] ?? 'it'); + $timezone = trim($_POST['timezone'] ?? 'Europe/Rome'); + + // whitelist minima (anti valori strani) + $allowed_locales = ['it', 'en', 'es']; + if (!in_array($locale, $allowed_locales, true)) $locale = 'it'; + if ($timezone === '') $timezone = 'Europe/Rome'; + + try { + if ($is_new) { + $stmt = $pdo->prepare(" + INSERT INTO user_settings + (school_id, user_id, + notify_email, notify_whatsapp, notify_push, + notify_booking_confirm, notify_booking_cancel, notify_session_cancel, + notify_payment_receipt, notify_expiration_reminder, + newsletter_opt_in, marketing_opt_in, + locale, timezone) + VALUES + (?, ?, + ?, ?, ?, + ?, ?, ?, + ?, ?, + ?, ?, + ?, ?) + "); + + $stmt->execute([ + $school_id, + (int)$iduserlogin, + $notify_email, + $notify_whatsapp, + $notify_push, + $notify_booking_confirm, + $notify_booking_cancel, + $notify_session_cancel, + $notify_payment_receipt, + $notify_expiration_reminder, + $newsletter_opt_in, + $marketing_opt_in, + $locale, + $timezone + ]); + + $success_message = "Impostazioni utente create con successo!"; + } else { + $stmt = $pdo->prepare(" + UPDATE user_settings SET + notify_email = ?, notify_whatsapp = ?, notify_push = ?, + notify_booking_confirm = ?, notify_booking_cancel = ?, notify_session_cancel = ?, + notify_payment_receipt = ?, notify_expiration_reminder = ?, + newsletter_opt_in = ?, marketing_opt_in = ?, + locale = ?, timezone = ? + WHERE school_id = ? AND user_id = ? + LIMIT 1 + "); + + $stmt->execute([ + $notify_email, + $notify_whatsapp, + $notify_push, + $notify_booking_confirm, + $notify_booking_cancel, + $notify_session_cancel, + $notify_payment_receipt, + $notify_expiration_reminder, + $newsletter_opt_in, + $marketing_opt_in, + $locale, + $timezone, + $school_id, + (int)$iduserlogin + ]); + + $success_message = "Impostazioni utente aggiornate con successo!"; + } + + // ricarica valori aggiornati + $stmt = $pdo->prepare("SELECT * FROM user_settings WHERE school_id = ? AND user_id = ? LIMIT 1"); + $stmt->execute([$school_id, (int)$iduserlogin]); + $settings = $stmt->fetch(PDO::FETCH_ASSOC); + $is_new = !$settings; + } catch (Exception $e) { + $error = "Errore database: " . $e->getMessage(); + } +} + +/* +|-------------------------------------------------------------------------- +| 5) Defaults (se non esiste ancora riga) +|-------------------------------------------------------------------------- +*/ +$defaults = [ + 'notify_email' => 1, + 'notify_whatsapp' => 0, + 'notify_push' => 0, + 'notify_booking_confirm' => 1, + 'notify_booking_cancel' => 1, + 'notify_session_cancel' => 1, + 'notify_payment_receipt' => 1, + 'notify_expiration_reminder' => 1, + 'newsletter_opt_in' => 0, + 'marketing_opt_in' => 0, + 'locale' => 'it', + 'timezone' => 'Europe/Rome' +]; + +$settings = $settings ?: $defaults; +?> + + + + + + + Impostazioni Utente - <?php echo htmlspecialchars($school['name']); ?> + + + + + + +
+ + + +
+
+
+
+
+ +
+
+

Impostazioni Utente

+ + + +
+ +
+ + +
+ + +
+ + + +
+ +
+ + + +
+ Le notifiche sono disattivate a livello di scuola. Le preferenze qui sotto non avranno effetto finché non vengono riattivate. +
+ + +
+ + +
Notifiche
+ +
+
+
+
+ > + +
+
Conferme, cancellazioni, promemoria.
+
+ +
+
+ > + +
+
Da attivare quando integri WA.
+
+ +
+
+ > + +
+
Da attivare quando integri app/push.
+
+
+ +
+ +
Eventi
+ +
+
+
+ > + +
+
+ +
+
+ > + +
+
+ +
+
+ > + +
+
+ +
+
+ > + +
+
+ +
+
+ > + +
+
+
+
+ +
+ + +
Newsletter e comunicazioni
+ +
+
+
+ > + +
+
Novità, eventi, contenuti.
+
+ +
+
+ > + +
+
Sconti, pacchetti speciali, promozioni.
+
+
+ +
+ + +
Preferenze
+ +
+
+ + +
+ +
+ + +
Esempio: Europe/Rome
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+
+
+
+ + +
+ + + + + \ No newline at end of file