From b553f7e9e924ec43ec92a06790c467277dc20022 Mon Sep 17 00:00:00 2001 From: solocla Date: Fri, 31 Jul 2026 14:34:26 +0200 Subject: [PATCH] push notification setting and update pages with PDO --- ...23_add_push_notification_to_auth_users.php | 18 + public/admin-services.php | 895 ++++++++---------- public/associate-services.php | 636 ++++++------- public/dashboard_log.txt | 66 ++ public/include/headscript.php | 95 +- public/nextdateclass.php | 174 ++-- public/orders.php | 641 ++++--------- public/userpanel.php | 352 +++---- public/userprofile.php | 720 +++++++------- public/userprofiledoc.php | 664 +++++++------ 10 files changed, 1971 insertions(+), 2290 deletions(-) create mode 100644 db/migrations/20260731123323_add_push_notification_to_auth_users.php diff --git a/db/migrations/20260731123323_add_push_notification_to_auth_users.php b/db/migrations/20260731123323_add_push_notification_to_auth_users.php new file mode 100644 index 00000000..189f0beb --- /dev/null +++ b/db/migrations/20260731123323_add_push_notification_to_auth_users.php @@ -0,0 +1,18 @@ +table('auth_users') + ->addColumn('pushnotification', 'char', [ + 'limit' => 1, + 'null' => false, + 'default' => 'Y', + 'comment' => 'Accetta notifiche push app mobile (Y/N)', + ]) + ->update(); + } +} diff --git a/public/admin-services.php b/public/admin-services.php index d2770591..3496416a 100644 --- a/public/admin-services.php +++ b/public/admin-services.php @@ -1,521 +1,468 @@ - - Action = "insert"; - $InsertQuery->Table = "service"; - $InsertQuery->bindColumn("servicename", "s", "".((isset($_POST["servicename"]))?$_POST["servicename"]:"") ."", "WA_DEFAULT"); - $InsertQuery->bindColumn("wpcatalognumber", "i", "".((isset($_POST["wpcatalognumber"]))?$_POST["wpcatalognumber"]:"") ."", "WA_DEFAULT"); - $InsertQuery->bindColumn("day", "s", "".((isset($_POST["classdayday"]))?$_POST["classdayday"]:"") ."", "WA_DEFAULT"); - $InsertQuery->bindColumn("time", "s", "".((isset($_POST["classtime"]))?$_POST["classtime"]:"") ."", "WA_DEFAULT"); - $InsertQuery->bindColumn("category", "i", "".((isset($_POST["servicecategory"]))?$_POST["servicecategory"]:"") ."", "WA_DEFAULT"); - $InsertQuery->bindColumn("maxcapacity", "i", "".((isset($_POST["maxcapacity"]))?$_POST["maxcapacity"]:"") ."", "WA_DEFAULT"); - $InsertQuery->bindColumn("colorclass", "s", "".((isset($_POST["colorclass"]))?$_POST["colorclass"]:"") ."", "WA_DEFAULT"); - $InsertQuery->bindColumn("classduration", "d", "".((isset($_POST["classduration"]))?$_POST["classduration"]:"") ."", "WA_DEFAULT"); - $InsertQuery->saveInSession(""); - $InsertQuery->execute(); - $InsertGoTo = ""; - if (function_exists("rel2abs")) $InsertGoTo = $InsertGoTo?rel2abs($InsertGoTo,dirname(__FILE__)):""; - $InsertQuery->redirect($InsertGoTo); -} -?> -setQuery("SELECT * FROM service LEFT JOIN servicecategory on service.category=servicecategory.idservicecategory"); -$servicesclass->execute(); -?> -connect_error) { - die("Connessione fallita: " . $conn->connect_error); +/** + * Connessione unica PDO (singleton). + */ +$pdo = DBHandlerSelect::getInstance()->getConnection(); + +function e($value): string +{ + return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); } -// Query per ottenere le categorie dal database -$query = "SELECT idservicecategory, namecategory FROM servicecategory"; -$result = $conn->query($query); +$message = $_GET['message'] ?? 'N'; +$insertFeedback = null; + +/* ------------------------------------------------------------------------- + * Inserimento nuova classe (POST) con prepared statement + * ---------------------------------------------------------------------- */ +if (isset($_POST['classinsert']) && $_POST['classinsert'] === 'Y') { + $servicename = trim($_POST['servicename'] ?? ''); + $wpcatalog = $_POST['wpcatalognumber'] ?? ''; + $day = trim($_POST['classdayday'] ?? ''); + $time = trim($_POST['classtime'] ?? ''); + $category = $_POST['servicecategory'] ?? ''; + $maxcapacity = $_POST['maxcapacity'] ?? ''; + $colorclass = trim($_POST['colorclass'] ?? ''); + $classduration = $_POST['classduration'] ?? ''; + + if ($servicename === '') { + $insertFeedback = ['type' => 'error', 'text' => 'La descrizione della classe è obbligatoria.']; + } else { + $sql = "INSERT INTO service + (servicename, wpcatalognumber, day, time, category, maxcapacity, colorclass, classduration) + VALUES + (:servicename, :wpcatalog, :day, :time, :category, :maxcapacity, :colorclass, :classduration)"; + $stmt = $pdo->prepare($sql); + $ok = $stmt->execute([ + ':servicename' => $servicename, + ':wpcatalog' => $wpcatalog !== '' ? (int) $wpcatalog : null, + ':day' => $day, + ':time' => $time, + ':category' => $category !== '' ? (int) $category : null, + ':maxcapacity' => $maxcapacity !== '' ? (int) $maxcapacity : null, + ':colorclass' => $colorclass, + ':classduration' => $classduration !== '' ? (float) $classduration : null, + ]); + + if ($ok) { + // Redirect per evitare re-invio del form al refresh (pattern PRG) + header('Location: ' . $_SERVER['PHP_SELF'] . '?message=inserted'); + exit; + } + $insertFeedback = ['type' => 'error', 'text' => 'Errore durante l\'inserimento della classe. Riprova.']; + } +} + +/* ------------------------------------------------------------------------- + * Categorie per il dropdown + * ---------------------------------------------------------------------- */ +$categories = $pdo->query("SELECT idservicecategory, namecategory FROM servicecategory ORDER BY namecategory")->fetchAll(); + +/* ------------------------------------------------------------------------- + * Elenco classi esistenti + * ---------------------------------------------------------------------- */ +$services = $pdo->query( + "SELECT service.*, servicecategory.namecategory + FROM service + LEFT JOIN servicecategory ON service.category = servicecategory.idservicecategory + ORDER BY service.servicename" +)->fetchAll(); ?> - - - + + -YogiBook - Prenotazioni YogaSoul - - - - + YogiBook - Inserimento e Propagazione Classi + + + - - - - - - + + + - - - - - - + + + - - - - - - - - + .admin-card { + border: none; + border-radius: 14px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06); + } - + .admin-card .card-body { + padding: 26px 30px; + } + + .section-title { + font-size: 15px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + color: #6b7280; + margin-bottom: 4px; + } + + .section-lead { + color: #9ca3af; + font-size: 14px; + margin-bottom: 22px; + } + + .form-label { + font-weight: 600; + font-size: 14px; + color: #374151; + } + + .form-control, + .form-select { + border-radius: 8px; + border: 1px solid #e2e4e9; + padding: 10px 12px; + } + + .form-control:focus, + .form-select:focus { + border-color: #1ebf73; + box-shadow: 0 0 0 3px rgba(30, 191, 115, 0.12); + } + + .btn-save { + border-radius: 8px; + padding: 10px 26px; + font-weight: 600; + } + + .class-table thead th { + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.03em; + color: #6b7280; + border-bottom: 2px solid #eef0f3; + } + + .class-table td { + vertical-align: middle; + } + + .color-dot { + display: inline-block; + width: 14px; + height: 14px; + border-radius: 50%; + border: 1px solid rgba(0, 0, 0, 0.1); + vertical-align: middle; + margin-right: 6px; + } + + .expanded-row { + display: none; + } + + .expanded-row.expanded { + display: table-row; + } + + .expanded-content { + background: #f8fbf9; + border-radius: 10px; + padding: 18px 20px; + margin: 8px 0; + } + + .action-btns .btn { + margin: 2px; + } + - - - - - - -
- - -
-
- - -
- + +
+
+
+
+
Classi esistenti
+

Propaga una classe sul calendario, associala o modificane i dettagli.

-
-
- -
-
-
- - - - -
-
-
- - -
-
-
-
-
Benvenuta/o
-

Di seguito puoi vedere lo stato delle tue prenotazioni

-
-
- - - -
- -
- -
-
- - -
-
-
-
- - -
-
-
- -
-
-
- - -
-
-
-
- - -
-
-
-
- - -
- - -
- - -
-
-
- - -
-
-
-
- - - -
- - - - -
- - - - -
- -
- - -
-
- -
- - - +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClasseGiornoOrarioDurataCategoriaAzioni
Nessuna classe presente.
+ + + + + + + + + + + + + + + + +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+ + + + + +
+
+
- - - - - +
- -
- - -
- -
-
-
-
- - - -
- - - - - - - - - - - - - - atEnd()) { - $wa_startindex = $servicesclass->Index; - ?> - - - - - - - - - - - - - moveNext(); - } - $servicesclass->moveFirst(); //return RS to the first record - unset($wa_startindex); - unset($wa_repeatcount); - ?> - -
ClasseGiornoOrarioDurataCategoriaAction
-
- - getColumnVal("servicename")); ?> - -
-
-

getColumnVal("day")); ?>

-
-

getColumnVal("time")); ?>

-
-

- - getColumnVal("classduration")); ?> -

-
-

getColumnVal("namecategory")); ?>

-
- - "> - getColumnVal("idservice"); ?> - - "> -
-
-
- - -
-
-
- - - -
-
-
-
- - - -
-
-
- - - - - - - - -
- -
-
-
-
+
-
+ + + + + - - -
- + - - - - - - - - - - - - - - - + + \ No newline at end of file diff --git a/public/associate-services.php b/public/associate-services.php index db0da018..06c28a51 100644 --- a/public/associate-services.php +++ b/public/associate-services.php @@ -1,382 +1,330 @@ - - - - - - - Action = "insert"; - $InsertQuery->Table = "associateclass"; - $InsertQuery->bindColumn("idmainservice", "i", "".((isset($_POST["idmainservice"]))?$_POST["idmainservice"]:"") ."", "WA_DEFAULT"); - $InsertQuery->bindColumn("idassociateservice", "i", "".((isset($_POST["servicelist"]))?$_POST["servicelist"]:"") ."", "WA_DEFAULT"); - $InsertQuery->saveInSession(""); - $InsertQuery->execute(); - $InsertGoTo = ""; - if (function_exists("rel2abs")) $InsertGoTo = $InsertGoTo?rel2abs($InsertGoTo,dirname(__FILE__)):""; - $InsertQuery->redirect($InsertGoTo); +require_once('include/headscript.php'); + +/** + * Associazione di classi a una classe principale. + */ +$pdo = DBHandlerSelect::getInstance()->getConnection(); + +function e($value): string +{ + return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); } -?> -setQuery("SELECT * FROM service LEFT JOIN servicecategory on service.category=servicecategory.idservicecategory WHERE service.idservice='$idmain'"); -$servicesclass->execute(); -?> -setQuery("SELECT * FROM associateclass LEFT JOIN service on service.idservice=associateclass.idassociateservice WHERE associateclass.idmainservice='$idmain'"); -$associatedclasses->execute(); -?> +// --- Determina la classe principale (da GET id o POST idservice) --- +$idmain = 0; +if (isset($_GET['id'])) { + $idmain = (int) $_GET['id']; +} +if (isset($_POST['idservice'])) { + $idmain = (int) $_POST['idservice']; +} +if (isset($_POST['idmainservice'])) { + $idmain = (int) $_POST['idmainservice']; +} + +$insertFeedback = null; + +/* ------------------------------------------------------------------------- + * Inserimento associazione (POST) + * ---------------------------------------------------------------------- */ +if (isset($_POST['formclass']) && $_POST['formclass'] === 'Y') { + $idmainservice = (int) ($_POST['idmainservice'] ?? 0); + $idassociateservice = (int) ($_POST['servicelist'] ?? 0); + + if ($idmainservice > 0 && $idassociateservice > 0) { + $stmt = $pdo->prepare( + "INSERT INTO associateclass (idmainservice, idassociateservice) + VALUES (:main, :assoc)" + ); + $ok = $stmt->execute([ + ':main' => $idmainservice, + ':assoc' => $idassociateservice, + ]); + $insertFeedback = $ok + ? ['type' => 'success', 'text' => 'Associazione aggiunta con successo.'] + : ['type' => 'error', 'text' => 'Errore durante l\'associazione. Riprova.']; + } else { + $insertFeedback = ['type' => 'error', 'text' => 'Seleziona una classe da associare.']; + } +} + +/* ------------------------------------------------------------------------- + * Dati della classe principale + * ---------------------------------------------------------------------- */ +$mainService = null; +if ($idmain > 0) { + $stmtMain = $pdo->prepare( + "SELECT service.*, servicecategory.namecategory + FROM service + LEFT JOIN servicecategory ON service.category = servicecategory.idservicecategory + WHERE service.idservice = :idmain + LIMIT 1" + ); + $stmtMain->execute([':idmain' => $idmain]); + $mainService = $stmtMain->fetch(); +} + +/* ------------------------------------------------------------------------- + * Elenco di tutte le classi (per il dropdown), escludendo la principale + * ---------------------------------------------------------------------- */ +$stmtAll = $pdo->prepare( + "SELECT idservice, servicename FROM service WHERE idservice != :idmain ORDER BY servicename" +); +$stmtAll->execute([':idmain' => $idmain]); +$allServices = $stmtAll->fetchAll(); + +/* ------------------------------------------------------------------------- + * Classi già associate alla principale + * ---------------------------------------------------------------------- */ +$stmtAssoc = $pdo->prepare( + "SELECT associateclass.idassociateclass, service.servicename + FROM associateclass + LEFT JOIN service ON service.idservice = associateclass.idassociateservice + WHERE associateclass.idmainservice = :idmain" +); +$stmtAssoc->execute([':idmain' => $idmain]); +$associatedClasses = $stmtAssoc->fetchAll(); +?> - - - + + -YogiBook - Prenotazioni YogaSoul - - - - + YogiBook - Associazione Servizi + + + - - - - + + - - - - - - + .admin-card { + border: none; + border-radius: 14px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06); + } - + .admin-card .card-body { + padding: 26px 30px; + } + + .section-title { + font-size: 15px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + color: #6b7280; + margin-bottom: 4px; + } + + .section-lead { + color: #9ca3af; + font-size: 14px; + margin-bottom: 22px; + } + + .main-service-name { + font-size: 20px; + font-weight: 700; + color: #1ebf73; + } + + .form-label { + font-weight: 600; + font-size: 14px; + color: #374151; + } + + .form-select { + border-radius: 8px; + border: 1px solid #e2e4e9; + padding: 10px 12px; + } + + .form-select:focus { + border-color: #1ebf73; + box-shadow: 0 0 0 3px rgba(30, 191, 115, 0.12); + } + + .btn-save { + border-radius: 8px; + padding: 10px 26px; + font-weight: 600; + } + + .assoc-table thead th { + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.03em; + color: #6b7280; + border-bottom: 2px solid #eef0f3; + } + + .assoc-table td { + vertical-align: middle; + } + + .empty-state { + text-align: center; + padding: 26px 10px; + color: #9ca3af; + } + + .empty-state i { + font-size: 30px; + margin-bottom: 8px; + display: block; + color: #d5d9e0; + } + - - - - - - -
- - -
-
- - -
- + +
+
+
+
+
Classi associate
+

Classi attualmente collegate a questa principale.

-
-
- -
-
-
- - - - -
-
-
- - -
-
-
-
-
Benvenuta/o
-

Servizio: getColumnVal("servicename")); ?>

-
-
-
-
- - - - -
-
-
-
- -
-
- - - +
+ + + + + + + + + + + + + + + + + + + + + +
Classe associataAzione
+
+ + Nessuna classe associata a questa principale. +
+
+ + + + + +
- - - - - +
- -
- - -
- -
-
-
-
-
- - - - - - - - - - - atEnd()) { - $wa_startindex = $associatedclasses->Index; - ?> - - - - - - - - - - - moveNext(); - } - $associatedclasses->moveFirst(); //return RS to the first record - unset($wa_startindex); - unset($wa_repeatcount); - ?> - -
Associated ClassCancel
-
- - getColumnVal("servicename")); ?> - -
-
- &id=" - -
+
-
- - - -
- - - -
- - -
- - - - - - - - - - - - - - - - - - - - + + + + + + \ No newline at end of file diff --git a/public/dashboard_log.txt b/public/dashboard_log.txt index 8eeafde5..689aa26b 100644 --- a/public/dashboard_log.txt +++ b/public/dashboard_log.txt @@ -2031,3 +2031,69 @@ Lezione aggiunta: {"idbookingclass":22,"bookingstart":"2025-10-21T18:15:00+00:00 Lezioni per idorderbook 3: 4 Order ID: 1, Lessons count: 12 Order ID: 3, Lessons count: 4 +Esecuzione dashboard: 2026-07-30 17:16:19 +Database connesso: yogibookaury +Elaborazione ordine: idorderbook = 1, order_id = 1 +Query lezioni per idorderbook 1: SELECT bc.idbookingclass, bc.bookingstart, bc.status, bc.lostlesson, bc.expirylesson, bc.idservice, bc.is_reprogrammed, s.servicename + FROM bookingclass bc + LEFT JOIN service s ON bc.idservice = s.idservice + WHERE bc.idorder = ? +Numero di lezioni trovate per idorderbook 1: 12 +Lezione aggiunta: {"idbookingclass":1,"bookingstart":"2025-09-02T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":2,"bookingstart":"2025-09-23T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":3,"bookingstart":"2025-09-30T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":4,"bookingstart":"2025-10-14T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":7,"bookingstart":"2025-10-10T18:15:00+00:00","status":"booked","lostlesson":"Y","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":8,"bookingstart":"2025-10-11T12:15:00+00:00","status":"booked","lostlesson":"Y","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":9,"bookingstart":"2025-10-11T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":10,"bookingstart":"2025-11-18T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":11,"bookingstart":"2025-11-25T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":14,"bookingstart":"2025-10-14T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":21,"bookingstart":"2025-11-24T19:30:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":41,"is_reprogrammed":"N","servicename":"Hatha Yoga"} +Lezione aggiunta: {"idbookingclass":26,"bookingstart":"2025-12-02T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"Y","servicename":"Aerial Yoga - intermedio"} +Lezioni per idorderbook 1: 12 +Elaborazione ordine: idorderbook = 3, order_id = +Query lezioni per idorderbook 3: SELECT bc.idbookingclass, bc.bookingstart, bc.status, bc.lostlesson, bc.expirylesson, bc.idservice, bc.is_reprogrammed, s.servicename + FROM bookingclass bc + LEFT JOIN service s ON bc.idservice = s.idservice + WHERE bc.idorder = ? +Numero di lezioni trovate per idorderbook 3: 4 +Lezione aggiunta: {"idbookingclass":15,"bookingstart":"2025-10-15T19:30:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":41,"is_reprogrammed":"N","servicename":"Hatha Yoga"} +Lezione aggiunta: {"idbookingclass":17,"bookingstart":"2025-11-03T19:30:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":41,"is_reprogrammed":"N","servicename":"Hatha Yoga"} +Lezione aggiunta: {"idbookingclass":18,"bookingstart":"2025-11-10T19:30:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":41,"is_reprogrammed":"N","servicename":"Hatha Yoga"} +Lezione aggiunta: {"idbookingclass":22,"bookingstart":"2025-10-21T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"Y","servicename":"Aerial Yoga - intermedio"} +Lezioni per idorderbook 3: 4 +Esecuzione dashboard: 2026-07-30 17:16:19 +Database connesso: yogibookaury +Elaborazione ordine: idorderbook = 1, order_id = 1 +Query lezioni per idorderbook 1: SELECT bc.idbookingclass, bc.bookingstart, bc.status, bc.lostlesson, bc.expirylesson, bc.idservice, bc.is_reprogrammed, s.servicename + FROM bookingclass bc + LEFT JOIN service s ON bc.idservice = s.idservice + WHERE bc.idorder = ? +Numero di lezioni trovate per idorderbook 1: 12 +Lezione aggiunta: {"idbookingclass":1,"bookingstart":"2025-09-02T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":2,"bookingstart":"2025-09-23T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":3,"bookingstart":"2025-09-30T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":4,"bookingstart":"2025-10-14T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":7,"bookingstart":"2025-10-10T18:15:00+00:00","status":"booked","lostlesson":"Y","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":8,"bookingstart":"2025-10-11T12:15:00+00:00","status":"booked","lostlesson":"Y","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":9,"bookingstart":"2025-10-11T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":10,"bookingstart":"2025-11-18T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":11,"bookingstart":"2025-11-25T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":14,"bookingstart":"2025-10-14T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"N","servicename":"Aerial Yoga - intermedio"} +Lezione aggiunta: {"idbookingclass":21,"bookingstart":"2025-11-24T19:30:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":41,"is_reprogrammed":"N","servicename":"Hatha Yoga"} +Lezione aggiunta: {"idbookingclass":26,"bookingstart":"2025-12-02T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"Y","servicename":"Aerial Yoga - intermedio"} +Lezioni per idorderbook 1: 12 +Elaborazione ordine: idorderbook = 3, order_id = +Query lezioni per idorderbook 3: SELECT bc.idbookingclass, bc.bookingstart, bc.status, bc.lostlesson, bc.expirylesson, bc.idservice, bc.is_reprogrammed, s.servicename + FROM bookingclass bc + LEFT JOIN service s ON bc.idservice = s.idservice + WHERE bc.idorder = ? +Numero di lezioni trovate per idorderbook 3: 4 +Lezione aggiunta: {"idbookingclass":15,"bookingstart":"2025-10-15T19:30:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":41,"is_reprogrammed":"N","servicename":"Hatha Yoga"} +Lezione aggiunta: {"idbookingclass":17,"bookingstart":"2025-11-03T19:30:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":41,"is_reprogrammed":"N","servicename":"Hatha Yoga"} +Lezione aggiunta: {"idbookingclass":18,"bookingstart":"2025-11-10T19:30:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":41,"is_reprogrammed":"N","servicename":"Hatha Yoga"} +Lezione aggiunta: {"idbookingclass":22,"bookingstart":"2025-10-21T18:15:00+00:00","status":"booked","lostlesson":"N","expirylesson":"N","idservice":42,"is_reprogrammed":"Y","servicename":"Aerial Yoga - intermedio"} +Lezioni per idorderbook 3: 4 +Order ID: 1, Lessons count: 12 +Order ID: 3, Lessons count: 4 diff --git a/public/include/headscript.php b/public/include/headscript.php index c6f53811..7994e7ce 100644 --- a/public/include/headscript.php +++ b/public/include/headscript.php @@ -10,21 +10,20 @@ include('../extra/auth.php'); // logged in, and in that case we redirect // the user to vanguard login page. if (! Auth::check()) { - - redirectTo('login'); -} + redirectTo('login'); +} $user = Auth::user(); -$iduserlogin=$user->present()->id; -$nameuser=$user->present()->name; -$emailuser=$user->present()->email; -$idcompany=$user->present()->idcompany; -$langid=$user->present()->langid; -$privacyacc=$user->present()->privacyaccepted; -$loginusername=$user->present()->username; -$roleuser=$user->present()->role_id; -$firstname=$user->present()->first_name; -$lastname=$user->present()->last_name; +$iduserlogin = $user->present()->id; +$nameuser = $user->present()->name; +$emailuser = $user->present()->email; +$idcompany = $user->present()->idcompany; +$langid = $user->present()->langid; +$privacyacc = $user->present()->privacyaccepted; +$loginusername = $user->present()->username; +$roleuser = $user->present()->role_id; +$firstname = $user->present()->first_name; +$lastname = $user->present()->last_name; //$user = "1"; //$iduserlogin="1"; //$idcompany="1"; @@ -34,55 +33,61 @@ $lastname=$user->present()->last_name; ?> - - - - - + + + + + - - + - @@ -98,10 +103,10 @@ $languageselection->setQuery("SELECT * FROM languages WHERE languages.active_lan $languageselection->execute(); */ ?> setQuery("SELECT avatar,id FROM auth_users WHERE auth_users.id='$iduserlogin'"); $avat->execute(); -$avatarname=$avat->getColumnVal("avatar"); +$avatarname = $avat->getColumnVal("avatar"); ?> - - - +?> \ No newline at end of file diff --git a/public/nextdateclass.php b/public/nextdateclass.php index 06c07509..e21bfd1f 100644 --- a/public/nextdateclass.php +++ b/public/nextdateclass.php @@ -1,95 +1,105 @@ - - - - - - - - - - - getConnection(); -// Mappa dei nomi dei giorni in inglese ai valori numerici dei giorni della settimana -$daysOfWeek = array( - "Sunday" => 0, - "Monday" => 1, - "Tuesday" => 2, - "Wednesday" => 3, - "Thursday" => 4, - "Friday" => 5, - "Saturday" => 6 +// --- Input dal form (con default vuoti) --- +$propagatestartdate = trim($_POST['propagatestartdate'] ?? ''); +$propagateenddate = trim($_POST['propagateenddate'] ?? ''); +$idservice = isset($_POST['idservice']) ? (int) $_POST['idservice'] : 0; +$dayclass = trim($_POST['dayclass'] ?? ''); +$timeclass = trim($_POST['timeclass'] ?? ''); +$durationtime = isset($_POST['durationtime']) ? (float) $_POST['durationtime'] : 0; + +// --- Mappa giorni settimana --- +$daysOfWeek = [ + "Sunday" => 0, + "Monday" => 1, + "Tuesday" => 2, + "Wednesday" => 3, + "Thursday" => 4, + "Friday" => 5, + "Saturday" => 6, +]; + +// --- Validazione minima: senza questi non si procede --- +if ( + $propagatestartdate === '' || $propagateenddate === '' + || $idservice <= 0 || !isset($daysOfWeek[$dayclass]) +) { + header('Location: admin-services.php?message=error'); + exit; +} + +// --- Calcolo della prima occorrenza del giorno desiderato --- +$startTs = strtotime($propagatestartdate); +$endTs = strtotime($propagateenddate); + +if ($startTs === false || $endTs === false) { + header('Location: admin-services.php?message=error'); + exit; +} + +$dayOfWeekStart = (int) date('w', $startTs); +$desiredDayValue = $daysOfWeek[$dayclass]; +$daysUntilNext = ($desiredDayValue + 7 - $dayOfWeekStart) % 7; + +$currentTs = $startTs + $daysUntilNext * 86400; +$dateEnd = date('Y-m-d', $endTs); + +// --- Statement preparati una sola volta e riusati nel loop --- +$checkClass = $pdo->prepare( + "SELECT idserviceschedule FROM serviceschedule + WHERE dateschedule = :dateschedule AND idservice = :idservice + LIMIT 1" +); +$checkDayoff = $pdo->prepare( + "SELECT iddayoff FROM dayoff WHERE dayoffdate = :dayoffdate LIMIT 1" +); +$insertSchedule = $pdo->prepare( + "INSERT INTO serviceschedule (idservice, dateschedule, scheduleday, startingtime, durationtime) + VALUES (:idservice, :dateschedule, :scheduleday, :startingtime, :durationtime)" ); -// Ottieni il valore numerico del giorno della settimana desiderato -$desiredDayValue = $daysOfWeek[$dayclass]; +$inserted = 0; -// Calcola quanti giorni mancano fino al prossimo o stesso giorno della settimana desiderato -$daysUntilNextDayClass = ($desiredDayValue + 7 - $dayOfWeek) % 7; +while (date('Y-m-d', $currentTs) <= $dateEnd) { + $currentDate = date('Y-m-d', $currentTs); + $datetimeSchedule = $currentDate . ' ' . $timeclass; -// Aggiungi il numero di giorni al timestamp della data di partenza per ottenere il primo giorno della settimana desiderato -$firstDayClassTimestamp = $nextClassDay + $daysUntilNextDayClass * 24 * 60 * 60; + // La data è già schedulata per questo servizio? + $checkClass->execute([ + ':dateschedule' => $datetimeSchedule, + ':idservice' => $idservice, + ]); + $alreadyScheduled = $checkClass->fetchColumn(); -$firstDayClassDate = date('Y-m-d', $firstDayClassTimestamp); + // È un giorno di pausa? + $checkDayoff->execute([':dayoffdate' => $currentDate]); + $isDayoff = $checkDayoff->fetchColumn(); + if (!$alreadyScheduled && !$isDayoff) { + $insertSchedule->execute([ + ':idservice' => $idservice, + ':dateschedule' => $datetimeSchedule, + ':scheduleday' => $dayclass, + ':startingtime' => $timeclass, + ':durationtime' => $durationtime, + ]); + $inserted++; + } -$datenext = $firstDayClassDate; -$datenextTimestamp=strtotime($datenext); - -$endClassDay = strtotime($propagateenddate); // Utilizza la data di propagazione come data di inizio - -$dateend = date('Y-m-d', $endClassDay); // stop date - - - -while ($datenext <= $dateend) { -// merge time with date -$datenextschedule=$datenext.' '.$timeclass; - -//query to check if present -$checkdateclass = new WA_MySQLi_RS("checkdateclass",$bkngstm,0); -$checkdateclass->setQuery("SELECT * FROM serviceschedule WHERE serviceschedule.dateschedule='$datenextschedule' AND serviceschedule.idservice='$idservice'"); -$checkdateclass->execute(); - -// Query per verificare se il giorno è un giorno di pausa -$checkdayoff = new WA_MySQLi_RS("checkdayoff", $bkngstm, 0); -$checkdayoff->setQuery("SELECT * FROM dayoff WHERE dayoffdate = '$datenext'"); -$checkdayoff->execute(); - - - -if (empty($checkdateclass->getColumnVal("idserviceschedule")) && empty($checkdayoff->getColumnVal("iddayoff"))) { -$sql = "INSERT INTO serviceschedule (idservice, dateschedule, scheduleday, startingtime, durationtime) -VALUES ($idservice, '$datenextschedule', '$dayclass', '$timeclass', $durationtime)"; -if ($conn->query($sql) === TRUE) { - echo "New record created successfully"; -} else { - echo "Error: " . $sql . "
" . $conn->error; + // Avanza di 7 giorni + $currentTs = strtotime('+7 day', $currentTs); } -} -$datenextTimestamp =strtotime("+7 day", $datenextTimestamp); -$datenext=date('Y-m-d', $datenextTimestamp); -echo $nextClassDay; -echo $datenext; -} - -header("Location: admin-services.php?message=success"); ?> \ No newline at end of file +header('Location: admin-services.php?message=success'); +exit; diff --git a/public/orders.php b/public/orders.php index 12646630..37bc75ec 100644 --- a/public/orders.php +++ b/public/orders.php @@ -1,90 +1,71 @@ getConnection(); +$iduserlogin = (int) $iduserlogin; -// Verifica se è stato inviato un modulo -if ($_SERVER["REQUEST_METHOD"] == "POST") { - if (isset($_FILES["fileToUpload"]) && $_FILES["fileToUpload"]["error"] === UPLOAD_ERR_OK) { - $conn = new mysqli($servername, $username, $password, $dbname); - if ($conn->connect_error) { - $logMessage .= "Connessione al database fallita: " . $conn->connect_error . "\n"; - file_put_contents($logFile, $logMessage, FILE_APPEND); - die("Connessione al database fallita: " . $conn->connect_error); +/* ------------------------------------------------------------------------- + * Helper per output sicuro in HTML (anti-XSS) + * ---------------------------------------------------------------------- */ +function e($value): string +{ + return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); +} + +/* ------------------------------------------------------------------------- + * 1) Ordini dell'utente + * ---------------------------------------------------------------------- */ +$sqlOrders = "SELECT o.idorderbook, o.order_id, o.idservice, o.order_date_created, + o.quantityclass, o.first_lesson_date, o.expireon, + o.maxreschedule, o.reprogrammed, + s.servicename, s.day, s.time + FROM orderbook o + LEFT JOIN service s ON o.idservice = s.idservice + WHERE o.iduser = :iduser + ORDER BY o.order_date_created DESC"; + +$stmtOrders = $pdo->prepare($sqlOrders); +$stmtOrders->execute([':iduser' => $iduserlogin]); +$orders = $stmtOrders->fetchAll(); + +/* ------------------------------------------------------------------------- + * 2) Tutte le lezioni degli ordini in UNA sola query (niente N+1) + * Poi le raggruppiamo per idorder in PHP. + * ---------------------------------------------------------------------- */ +$lessonsByOrder = []; +$orderIds = array_filter(array_map(fn($o) => (int) $o['idorderbook'], $orders)); + +if (!empty($orderIds)) { + $placeholders = implode(',', array_fill(0, count($orderIds), '?')); + $sqlLessons = "SELECT bc.idbookingclass, bc.bookingstart, bc.status, bc.lostlesson, + bc.expirylesson, bc.idservice, bc.is_reprogrammed, + bc.idorder, s.servicename + FROM bookingclass bc + LEFT JOIN service s ON bc.idservice = s.idservice + WHERE bc.idorder IN ($placeholders)"; + $stmtLessons = $pdo->prepare($sqlLessons); + $stmtLessons->execute(array_values($orderIds)); + + foreach ($stmtLessons->fetchAll() as $lesson) { + // Normalizziamo la data in formato ISO 8601 (come faceva date('c', ...)) + if (!empty($lesson['bookingstart'])) { + $lesson['bookingstart'] = date('c', strtotime($lesson['bookingstart'])); } - $iduserlogin = filter_var($_POST["iduserlogin"], FILTER_VALIDATE_INT); - $logMessage .= "ID utente ricevuto dal form: $iduserlogin\n"; - $conn->close(); - } else { - $logMessage .= "Errore caricamento file o iduserlogin non valido\n"; - file_put_contents($logFile, $logMessage, FILE_APPEND); + $lessonsByOrder[(int) $lesson['idorder']][] = $lesson; } } -// Connessione al database -$conn = new mysqli($servername, $username, $password, $dbname); -if ($conn->connect_error) { - $logMessage .= "Connessione al database fallita: " . $conn->connect_error . "\n"; - file_put_contents($logFile, $logMessage, FILE_APPEND); - die("Connessione al database fallita: " . $conn->connect_error); +// Attacchiamo a ogni ordine il suo array di lezioni +foreach ($orders as &$order) { + $oid = (int) $order['idorderbook']; + $order['lessons'] = $lessonsByOrder[$oid] ?? []; } - -$logMessage .= "Database connesso: $dbname\n"; - -// Query per selezionare i dati filtrati per iduser, inclusi maxreschedule e reprogrammed -$iduserlogin = $iduserlogin; // Sostituisci con $iduserlogin in produzione -$query = "SELECT o.idorderbook, o.order_id, o.idservice, o.order_date_created, o.quantityclass, o.first_lesson_date, o.expireon, o.maxreschedule, o.reprogrammed, s.servicename, s.day, s.time - FROM orderbook o - LEFT JOIN service s ON o.idservice = s.idservice - WHERE o.iduser = ?"; -$stmt = $conn->prepare($query); -$stmt->bind_param("i", $iduserlogin); -$stmt->execute(); -$result = $stmt->get_result(); - -$documents = array(); -while ($row = $result->fetch_assoc()) { - // Get lesson details for each order - $idorderbook = $row['idorderbook']; - $logMessage .= "Elaborazione ordine: idorderbook = $idorderbook, order_id = {$row['order_id']}\n"; - - $lesson_query = "SELECT bc.idbookingclass, bc.bookingstart, bc.status, bc.lostlesson, bc.expirylesson, bc.idservice, bc.is_reprogrammed, s.servicename - FROM bookingclass bc - LEFT JOIN service s ON bc.idservice = s.idservice - WHERE bc.idorder = ?"; - $lesson_stmt = $conn->prepare($lesson_query); - $lesson_stmt->bind_param("i", $idorderbook); - $lesson_stmt->execute(); - $lesson_result = $lesson_stmt->get_result(); - - $lessons = array(); - $logMessage .= "Query lezioni per idorderbook $idorderbook: $lesson_query\n"; - if ($lesson_result) { - $logMessage .= "Numero di lezioni trovate per idorderbook $idorderbook: " . $lesson_result->num_rows . "\n"; - while ($lesson_row = $lesson_result->fetch_assoc()) { - $lesson_row['bookingstart'] = date('c', strtotime($lesson_row['bookingstart'])); - $lessons[] = $lesson_row; - $logMessage .= "Lezione aggiunta: " . json_encode($lesson_row) . "\n"; - } - } else { - $logMessage .= "Errore nella query per idorderbook $idorderbook: " . $conn->error . "\n"; - } - $row['lessons'] = $lessons; - $documents[] = $row; - $logMessage .= "Lezioni per idorderbook $idorderbook: " . count($lessons) . "\n"; - $lesson_stmt->close(); -} -$stmt->close(); -file_put_contents($logFile, $logMessage, FILE_APPEND); +unset($order); ?> - @@ -109,47 +90,39 @@ file_put_contents($logFile, $logMessage, FILE_APPEND); dateFormat: "yy-mm-dd" }); - // Handle order click for popup - $('.order-row').click(function() { - var lessons = $(this).data('lessons'); - console.log('Lezioni ricevute:', lessons); - var total = $(this).data('total'); - var orderId = $(this).data('order-id'); - var isExpired = $(this).data('is-expired') === true; // Converti in booleano - var expireOn = $(this).data('expireon'); + // ---- Funzione unica per costruire e mostrare il popup dettagli ordine ---- + function showOrderDetails(data) { + var lessons = data.lessons || []; + var total = data.total; + var orderId = data.orderId; + var isExpired = data.isExpired === true; + var expireOn = data.expireOn; - // Calcolo delle date var now = new Date(); - // Calcolo dei conteggi - var completed = lessons.filter(l => { + var completed = lessons.filter(function(l) { var lessonDate = new Date(l.bookingstart); return (l.status === 'completed') || (l.status === 'booked' && lessonDate < now && l.lostlesson !== 'Y' && l.expirylesson !== 'Y'); }).length; - var lost = lessons.filter(l => l.lostlesson === 'Y').length; - var expired = lessons.filter(l => l.expirylesson === 'Y').length; - var booked = lessons.filter(l => { + var lost = lessons.filter(function(l) { + return l.lostlesson === 'Y'; + }).length; + var expired = lessons.filter(function(l) { + return l.expirylesson === 'Y'; + }).length; + var booked = lessons.filter(function(l) { var lessonDate = new Date(l.bookingstart); return (l.status === 'booked' && lessonDate >= now && l.lostlesson !== 'Y' && l.expirylesson !== 'Y'); }).length; var toSchedule = total - (booked + completed + lost + expired); - // Se l'ordine è scaduto, sposta le lezioni "Da Programmare" in "Scadute" + // Se l'ordine è scaduto, le "Da Programmare" diventano "Scadute" if (isExpired) { expired += toSchedule; toSchedule = 0; } - console.log({ - booked: booked, - completed: completed, - lost: lost, - expired: expired, - toSchedule: toSchedule, - total: total - }); - var expireOnFormatted = expireOn ? new Date(expireOn).toLocaleDateString('it-IT', { day: '2-digit', month: '2-digit', @@ -157,58 +130,58 @@ file_put_contents($logFile, $logMessage, FILE_APPEND); }) : 'Non specificata'; var htmlContent = ` -

- Dettagli Ordine #${orderId} - - ${isExpired ? 'Scaduto' : 'Attivo'} - -

-
-
-
Totale
-

${total}

-
-
-
Praticate
-

${completed}

-
-
-
Perse
-

${lost}

-
-
-
Scadute
-

${expired}

-
-
-
Da Programmare
-

${toSchedule}

-
-
-
-

- Il tuo ordine scadrà il ${expireOnFormatted} -

- - - - - - - - - - -`; +

+ Dettagli Ordine #${orderId} + + ${isExpired ? 'Scaduto' : 'Attivo'} + +

+
+
+
Totale
+

${total}

+
+
+
Praticate
+

${completed}

+
+
+
Perse
+

${lost}

+
+
+
Scadute
+

${expired}

+
+
+
Da Programmare
+

${toSchedule}

+
+
+
+

+ Il tuo ordine scadrà il ${expireOnFormatted} +

+
Data e OraLezioneStatoRiprogrammata
+ + + + + + + + + + `; if (lessons.length === 0) { htmlContent += ` - - - - `; + + + + `; } else { lessons.forEach(function(lesson, index) { var lessonDate = new Date(lesson.bookingstart); @@ -239,29 +212,25 @@ file_put_contents($logFile, $logMessage, FILE_APPEND); var isReprogrammedText = lesson.is_reprogrammed === 'Y' ? 'Sì' : 'No'; htmlContent += ` - - - - - - - `; + + + + + + + `; }); } htmlContent += ` - -
Data e OraLezioneStatoRiprogrammata
- Nessuna lezione trovata per questo ordine. -
+ Nessuna lezione trovata per questo ordine. +
${lessonDate.toLocaleString('it-IT', { - day: '2-digit', - month: '2-digit', - year: 'numeric', - hour: '2-digit', - minute: '2-digit' - })}${lesson.servicename} - ${statusText} - ${isReprogrammedText}
${lessonDate.toLocaleString('it-IT', { + day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' + })}${lesson.servicename ?? ''} + ${statusText} + ${isReprogrammedText}
-
- `; + + +
+ `; Swal.fire({ title: '', @@ -272,276 +241,49 @@ file_put_contents($logFile, $logMessage, FILE_APPEND); popup: 'custom-modal' } }); + } + + // Estrae i dati dalla riga e chiama la funzione unica + function detailsFromRow(row) { + showOrderDetails({ + lessons: row.data('lessons'), + total: row.data('total'), + orderId: row.data('order-id'), + isExpired: row.data('is-expired') === true, + expireOn: row.data('expireon') + }); + } + + // Click sulla riga + $('.order-row').click(function() { + detailsFromRow($(this)); }); - // Handle details button click + // Click sul bottone "Dettagli" (senza propagare alla riga) $('.details-btn').click(function(e) { e.stopPropagation(); - var row = $(this).closest('tr'); - var lessons = row.data('lessons'); - var total = row.data('total'); - var orderId = row.data('order-id'); - var isExpired = row.data('is-expired') === true; - var expireOn = row.data('expireon'); - - var now = new Date(); - - var completed = lessons.filter(l => { - var lessonDate = new Date(l.bookingstart); - return (l.status === 'completed') || - (l.status === 'booked' && lessonDate < now && l.lostlesson !== 'Y' && l.expirylesson !== 'Y'); - }).length; - var lost = lessons.filter(l => l.lostlesson === 'Y').length; - var expired = lessons.filter(l => l.expirylesson === 'Y').length; - var booked = lessons.filter(l => { - var lessonDate = new Date(l.bookingstart); - return (l.status === 'booked' && lessonDate >= now && l.lostlesson !== 'Y' && l.expirylesson !== 'Y'); - }).length; - var toSchedule = total - (booked + completed + lost + expired); - - // Se l'ordine è scaduto, sposta le lezioni "Da Programmare" in "Scadute" - if (isExpired) { - expired += toSchedule; - toSchedule = 0; - } - - var expireOnFormatted = expireOn ? new Date(expireOn).toLocaleDateString('it-IT', { - day: '2-digit', - month: '2-digit', - year: 'numeric' - }) : 'Non specificata'; - - var htmlContent = ` -

- Dettagli Ordine #${orderId} - - ${isExpired ? 'Scaduto' : 'Attivo'} - -

-
-
-
Totale
-

${total}

-
-
-
Praticate
-

${completed}

-
-
-
Perse
-

${lost}

-
-
-
Scadute
-

${expired}

-
-
-
Da Programmare
-

${toSchedule}

-
-
-
- - - - - - - - - - - `; - - if (lessons.length === 0) { - htmlContent += ` - - - - `; - } else { - lessons.forEach(function(lesson, index) { - var lessonDate = new Date(lesson.bookingstart); - var statusText = lesson.status; - var badgeClass = 'bg-primary'; - var badgeTextColor = '#fff'; - if (lesson.status === 'completed') { - badgeClass = 'bg-success'; - badgeTextColor = '#fff'; - statusText = 'Completata'; - } else if (lesson.lostlesson === 'Y') { - badgeClass = 'bg-danger'; - badgeTextColor = '#fff'; - statusText = 'Persa'; - } else if (lesson.expirylesson === 'Y') { - badgeClass = 'bg-warning'; - badgeTextColor = '#000'; - statusText = 'Scaduta'; - } else if (lesson.status === 'booked') { - if (lessonDate < now && lesson.lostlesson !== 'Y' && lesson.expirylesson !== 'Y') { - statusText = 'Completata'; - badgeClass = 'bg-success'; - badgeTextColor = '#fff'; - } else { - statusText = 'Programmata'; - } - } - var isReprogrammedText = lesson.is_reprogrammed === 'Y' ? 'Sì' : 'No'; - - htmlContent += ` - - - - - - - `; - }); - } - - htmlContent += ` - -
Data e OraLezioneStatoRiprogrammata
- Nessuna lezione trovata per questo ordine. -
${lessonDate.toLocaleString('it-IT', { - day: '2-digit', - month: '2-digit', - year: 'numeric', - hour: '2-digit', - minute: '2-digit' - })}${lesson.servicename} - ${statusText} - ${isReprogrammedText}
-
- `; - - Swal.fire({ - title: '', - html: htmlContent, - confirmButtonText: 'Chiudi', - width: '1000px', - customClass: { - popup: 'custom-modal' - } - }); + detailsFromRow($(this).closest('tr')); }); - - function confirmDelete(id, deletePageUrl) { - Swal.fire({ - title: "Sei sicuro?", - text: "Questa prenotazione verrà cancellata definitivamente! Ricordati poi di riprogrammare la tua lezione!", - icon: "warning", - showCancelButton: true, - confirmButtonColor: "#d33", - cancelButtonColor: "#3085d6", - confirmButtonText: "Sì, cancella!", - cancelButtonText: "Annulla" - }).then((result) => { - if (result.isConfirmed) { - window.location.href = `deleteclass.php?id=${id}`; - } - }); - } }); + + function confirmDelete(id, deletePageUrl) { + Swal.fire({ + title: "Sei sicuro?", + text: "Questa prenotazione verrà cancellata definitivamente! Ricordati poi di riprogrammare la tua lezione!", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#d33", + cancelButtonColor: "#3085d6", + confirmButtonText: "Sì, cancella!", + cancelButtonText: "Annulla" + }).then((result) => { + if (result.isConfirmed) { + window.location.href = `deleteclass.php?id=${id}`; + } + }); + } - - - - - + .file-chosen { + display: block; + margin-top: 10px; + font-size: 13px; + color: #6b7280; + } - + .btn-save { + border-radius: 8px; + padding: 10px 26px; + font-weight: 600; + } - -
- - -
-
- - -
-
- - - - -
-
-
- - - -
-
-
-
- - - -

Di seguito puoi compilare o aggiornare il tuo profilo

-
- -
- - - -
- - -
- - -
- - -
- - -
- -
- - - -
- - -
- - -
- - -
- - -
- - - - -
- - +
+
+ +
-
+ +
+ + +
Lascia vuoto se il documento non ha scadenza.
+
+ +
+ + + +
+ + +
- - - - +
-
- - - - - - - -
- -
- +
- - - - - - - - - - - - - - - + + + + + + \ No newline at end of file diff --git a/public/userprofiledoc.php b/public/userprofiledoc.php index 323582f6..34a658af 100644 --- a/public/userprofiledoc.php +++ b/public/userprofiledoc.php @@ -1,161 +1,94 @@ - setQuery("SELECT * FROM option"); -$optionquery->execute(); -?> -setQuery("SELECT * FROM bookingclass LEFT JOIN service on bookingclass.idservice=service.idservice LEFT JOIN serviceschedule ON bookingclass.idserviceschedule=serviceschedule.idserviceschedule WHERE bookingclass.iduser='1'"); -$bookedclass->execute(); -?> -connect_error) { - $error_message = "Connessione al database fallita: " . $conn->connect_error; - echo ""; - echo ""; - die(); - } +/** + * Connessione unica PDO (singleton). $iduserlogin dalla sessione autenticata. + */ +$pdo = DBHandlerSelect::getInstance()->getConnection(); +$iduserlogin = (int) $iduserlogin; - // Ottieni l'ID dell'utente - $iduserlogin = $_POST["iduserlogin"]; - $documentDescription = $conn->real_escape_string($_POST["documentDescription"]); - $expiryDate = $conn->real_escape_string($_POST["expiryDate"]); - $uploadedAt = date("Y-m-d"); // Data corrente per uploaded_at - $originalFileName = $_FILES["fileToUpload"]["name"]; - $fileExtension = pathinfo($originalFileName, PATHINFO_EXTENSION); - $timestamp = time(); // Timestamp corrente - $newFileName = "{$timestamp}_{$originalFileName}"; // Aggiungi timestamp al nome del file - $fileTmpName = $_FILES["fileToUpload"]["tmp_name"]; - $fileDestination = "user/document/" . $newFileName; +function e($value): string +{ + return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); +} - // Sposta il file nella cartella di destinazione - if (move_uploaded_file($fileTmpName, $fileDestination)) { - // Inserisci i dati nel database usando prepared statement - $sql = "INSERT INTO certificateuserprofile (iduser, documentdescription, filenamedocument, expirydatedocument, uploaded_at) - VALUES (?, ?, ?, ?, ?)"; - $stmt = $conn->prepare($sql); - $stmt->bind_param("issss", $iduserlogin, $documentDescription, $newFileName, $expiryDate, $uploadedAt); +$uploadFeedback = null; // ['type' => 'success'|'error', 'text' => '...'] - if ($stmt->execute()) { - echo ""; - } else { - $error_message = "Errore durante l'inserimento nel database: " . $conn->error; - echo ""; - echo ""; - } - $stmt->close(); - } else { - $error_message = "Errore nel caricamento del file."; - echo ""; - echo ""; - } +/* ------------------------------------------------------------------------- + * Upload certificato (POST) con validazione robusta + * ---------------------------------------------------------------------- */ +$allowedExt = ['pdf', 'jpg', 'jpeg', 'png']; +$allowedMime = ['application/pdf', 'image/jpeg', 'image/png']; +$maxBytes = 16 * 1024 * 1024; // 16 MB +$uploadDir = 'user/document/'; - // Chiudi la connessione al database - $conn->close(); +if ( + $_SERVER['REQUEST_METHOD'] === 'POST' + && isset($_FILES['fileToUpload']) + && $_FILES['fileToUpload']['error'] === UPLOAD_ERR_OK +) { + + $file = $_FILES['fileToUpload']; + $documentDescription = trim($_POST['documentDescription'] ?? ''); + $expiryDate = trim($_POST['expiryDate'] ?? ''); + + if ($documentDescription === '' || $expiryDate === '') { + $uploadFeedback = ['type' => 'error', 'text' => 'Descrizione e data di scadenza sono obbligatorie.']; + } elseif ($file['size'] > $maxBytes) { + $uploadFeedback = ['type' => 'error', 'text' => 'Il file supera la dimensione massima di 16 MB.']; } else { - $error_message = "Tutti i campi sono obbligatori: descrizione, data di scadenza e file."; - echo ""; - echo ""; + $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + $finfo = new finfo(FILEINFO_MIME_TYPE); + $realMime = $finfo->file($file['tmp_name']); + + if (!in_array($ext, $allowedExt, true) || !in_array($realMime, $allowedMime, true)) { + $uploadFeedback = ['type' => 'error', 'text' => 'Formato non consentito. Carica un PDF, JPG o PNG.']; + } else { + $safeName = bin2hex(random_bytes(16)) . '.' . $ext; + $destination = $uploadDir . $safeName; + + if (!is_dir($uploadDir)) { + @mkdir($uploadDir, 0755, true); + } + + if (move_uploaded_file($file['tmp_name'], $destination)) { + $sql = "INSERT INTO certificateuserprofile + (iduser, documentdescription, filenamedocument, expirydatedocument, uploaded_at) + VALUES (:iduser, :descr, :fname, :expiry, :uploaded)"; + $stmt = $pdo->prepare($sql); + $ok = $stmt->execute([ + ':iduser' => $iduserlogin, + ':descr' => $documentDescription, + ':fname' => $safeName, + ':expiry' => $expiryDate, + ':uploaded' => date('Y-m-d'), + ]); + + $uploadFeedback = $ok + ? ['type' => 'success', 'text' => 'Documento caricato correttamente.'] + : ['type' => 'error', 'text' => 'Errore nel salvataggio del documento. Riprova.']; + } else { + $uploadFeedback = ['type' => 'error', 'text' => 'Caricamento del file non riuscito. Riprova.']; + } + } } -} -?> - -connect_error) { - die("Connessione fallita: " . $conn->connect_error); +} elseif ( + $_SERVER['REQUEST_METHOD'] === 'POST' + && isset($_FILES['fileToUpload']) + && $_FILES['fileToUpload']['error'] !== UPLOAD_ERR_NO_FILE +) { + $uploadFeedback = ['type' => 'error', 'text' => 'Si è verificato un problema durante il caricamento. Riprova.']; } -// ID dell'utente per il quale vuoi filtrare gli ordini -$userid = 1; - -// Query per ottenere la somma dei ticket per ogni ordine dell'utente -$query = "SELECT iduser, idorderbook, SUM(nticket) as total_tickets - FROM orderbook - WHERE iduser = $userid - GROUP BY iduser"; - -$result = $conn->query($query); - -if (!$result) { - die("Query fallita: " . $conn->error); -} - -if ($result->num_rows > 0) { - while ($row = $result->fetch_assoc()) { - $idOrdine = $row["idorderbook"]; - $totalTickets = $row["total_tickets"]; - } -} - -$conn->close(); -?> -connect_error) { - die("Connessione al database fallita: " . $conn->connect_error); -} - -// ID dell'utente per il quale si desidera eseguire la query -$iduser = 1; // Sostituisci con l'ID utente desiderato - -// Data e ora attuali -$currentDateTime = date("Y-m-d H:i:s"); - -// Query per contare i record con data e ora passate e future -$query = "SELECT COUNT(*) AS total, - SUM(CASE WHEN serviceschedule.dateschedule <= '$currentDateTime' THEN 1 ELSE 0 END) AS passed, - SUM(CASE WHEN serviceschedule.dateschedule > '$currentDateTime' THEN 1 ELSE 0 END) AS future - FROM bookingclass - LEFT JOIN serviceschedule ON bookingclass.idserviceschedule = serviceschedule.idserviceschedule - WHERE bookingclass.iduser = $iduser"; - -$result = $conn->query($query); -if ($result) { - $row = $result->fetch_assoc(); - $totalRecords = $row['total']; - $passedRecords = $row['passed']; - $futureRecords = $row['future']; -} -// Chiusura della connessione -$conn->close(); -?> -connect_error) { - die("Connessione al database fallita: " . $conn->connect_error); -} - -// Query per selezionare i dati filtrati per iduser -$query = "SELECT * FROM certificateuserprofile WHERE iduser = $iduserlogin"; -$result = $conn->query($query); - -// Array per memorizzare i risultati -$documents = array(); - -while ($row = $result->fetch_assoc()) { - $documents[] = $row; -} - -$conn->close(); +/* ------------------------------------------------------------------------- + * Elenco documenti dell'utente + * ---------------------------------------------------------------------- */ +$stmtDocs = $pdo->prepare("SELECT * FROM certificateuserprofile WHERE iduser = :iduser ORDER BY uploaded_at DESC"); +$stmtDocs->execute([':iduser' => $iduserlogin]); +$documents = $stmtDocs->fetchAll(); ?> - + @@ -163,69 +96,37 @@ $conn->close(); - - - - - - + + -
- -
-
-
+ + + + + + + + + +
-
-
+
+
- - - -
Benvenuta/o
-

Di seguito puoi visualizzare o caricare i certificati medici di liberatoria alla pratica Yoga

+
I tuoi documenti
+

+ Ciao , qui trovi i certificati medici di liberatoria alla pratica Yoga che hai caricato. +

+
- +
- - + + - + - + - - - - - + + + + + + + + + +
Descrizione del DocumentoData di ScadenzaDescrizioneScadenza DocumentoAzioneAzione
Documento - + +
+ + Nessun documento caricato. Usa il modulo qui sotto per aggiungere il primo. +
+ + Apri + + + +
@@ -417,57 +395,65 @@ $conn->close();
- -
-
+
-
-
+
+
-
-
-
-
-
-

Carica documenti

-
-
- - -
- -
- -
- I documenti caricati sono solo a fini di sicurezza e cliccando su carica documento accetti il nostro regolamento privacy

- -
+
Carica un nuovo documento
+

Formati accettati: PDF, JPG o PNG (max 16 MB).

+ +
+
+
+
+ + +
+
+
+
+ +
-
-
+ +
+ + + +
+ +

+ I documenti caricati sono trattati solo a fini di sicurezza. Caricando il documento accetti il regolamento sulla privacy. +

+ + +
- -
- +
-
- +
- - - - - - + + + + + \ No newline at end of file