bodycomfortphysio.de
Leichtgewichtiges PHP-Backend für Terminbuchungen (Slots, Buchungen, Mails mit ICS) auf einem Pixelarity-Frontend.
Adminbereich mit Bootstrap 5.3.8, Login & CSRF, Slot-Generator (inkl. Urlaub/Exceptions), Physio-Workflows.
Inhalt
- Projektstruktur
- Schnellstart
- Konfiguration (
.env/cfg()) - Datenbank
- Adminbereich
- Mailversand
- Öffentliche Seiten
- CLI-Tools
- Fehlerquellen / Troubleshooting
- Build & Deployment
Projektstruktur
build/
├─ index.php # Website (Kontaktformular mit Turnstile)
├─ book.php # Buchung/Bestätigung (Pixelarity .intro--compact)
├─ includes/
│ ├─ config.php # ENV-Switch, cfg(), db()
| ├─ booking_mail.php # send_booking_mail() (HTML+Plain, ICS bei confirmed)
│ ├─ mailer.php # PHPMailer-Factory (SMTP, UTF-8, Reply-To)
| ├─ slots.php # Slot-Übersicht
│ ├─ ics.php # .ics-Generator
│ ├─ auth.php # Admin-Login (Sessions), CSRF-Token
├─ admin/
│ ├─ index.php # Dashboard (Filter, Tabellen, Aktionen)
│ ├─ login.php # Login-Formular (Bootstrap)
│ ├─ slots_tools.php # Slots generieren/sperren + Physio ad-hoc + Mobility + Einzel-Slot
│ ├─ _head_assets.php # Bootstrap/Icons/CSS/JS
│ ├─ _footer.php # Footer (davidt.cloud Logo, Hilfelink)
├─ cli/
│ ├─ generate_slots.php # ursprünglicher Generator (Logik auch im Admin)
│ ├─ test_mail.php # Mail-Test inkl. SMTP-Debug & ICS
└─ source/ # Frontend-Quellen (.kit/.scss), via Gulp → build/
Schnellstart
- DB anlegen und Schema importieren (siehe unten).
.envbefüllen.- Webserver-Docroot auf
build/zeigen lassen. - Admin-User in
staff_usersanlegen (siehe DB-Abschnitt). - Slots nach Bedarf im Admin → „Slots generieren“ erzeugen.
Konfiguration (.env / cfg())
Alle Variablen werden über includes/config.php geladen (cfg()-Helper).
Prod wird automatisch aktiviert, wenn APP_ENV=prod oder Domain *.bodycomfortphysio.de.
Beispiel .env:
# Umgebung
APP_ENV=dev # dev|prod
# DB DEV
DB_HOST_DEV=127.0.0.1
DB_PORT_DEV=3306
DB_NAME_DEV=bodycomfort
DB_USER_DEV=appuser
DB_PASS_DEV=change_me_app
# DB PROD
DB_HOST_PROD=127.0.0.1
DB_PORT_PROD=3306
DB_NAME_PROD=bodycomfort
DB_USER_PROD=appuser
DB_PASS_PROD=***secure***
# Mail
SMTP_HOST=mail.davidt.cloud
SMTP_PORT=465
SMTP_USER=noreply@bodycomfortphysio.de
SMTP_PASS=***secure***
FROM_ADDRESS=noreply@bodycomfortphysio.de
TO_ADDRESS_DEV=info@davidt.de
TO_ADDRESS_PROD=praxis@bodycomfortphysio.de
# Cloudflare Turnstile
TURNSTILE_SITE_KEY=0x4A...
TURNSTILE_SECRET=0x4A...
Verwendung im Code:
cfg('mail.from_address'), cfg('mail.to_address'), cfg('captcha.secret'), cfg('env') usw.
Datenbank
Schema-Überblick
Tabellen (Auszug):
- services:
id, name, code, duration_min, requires_approval, paid_online, price_cents, price_text - schedules:
id, service_id, weekday (0=So..6=Sa), start_time, end_time, slot_every_min, buffer_min - slots:
id, service_id, start_ts, end_ts, capacity, status('open'|'booked'|'blocked') - bookings:
id, slot_id, customer_*, status('pending'|'confirmed'|'cancelled'), payment_status, ics_uid - exceptions: Urlaub/Fortbildung je Datum (+optional service_id)
- payments: Provider-Webhooks/Belege (optional)
- staff_users: Admin-Login (
email, password_hash, role('admin'|'assistant')) - zoom_meetings: optional für Online-Termine (Mobility)
Admin-User anlegen (Beispiel):
INSERT INTO staff_users (name, email, password_hash, role)
VALUES (
'Admin',
'admin@example.com',
-- Hash per PHP: password_hash('deinpasswort', PASSWORD_DEFAULT)
'$2y$10$REPLACE_ME_HASHED',
'admin'
);
Services & Schedules (Beispiele)
Physio
code='physio',duration_min=60,requires_approval=1,paid_online=0,price_text='165 € · ca. 60 Minuten'- Kein Wochenplan – wird ad-hoc im Admin erzeugt („Physio-Slots an Tag X“).
Massage (45 min + 15 min Abstand → Start stündlich, aber nur zu bestimmten Zeiten)
SET @massage := (SELECT id FROM services WHERE code='massage');
DELETE FROM schedules WHERE service_id=@massage;
-- Mo (1): 13/14/15/17/18/19
INSERT INTO schedules (service_id, weekday, start_time, end_time, slot_every_min, buffer_min) VALUES
(@massage,1,'13:00:00','13:45:00',60,15),
(@massage,1,'14:00:00','14:45:00',60,15),
(@massage,1,'15:00:00','15:45:00',60,15),
(@massage,1,'17:00:00','17:45:00',60,15),
(@massage,1,'18:00:00','18:45:00',60,15),
(@massage,1,'19:00:00','19:45:00',60,15),
-- Di (2): 10/11/12/14/15
(@massage,2,'10:00:00','10:45:00',60,15),
(@massage,2,'11:00:00','11:45:00',60,15),
(@massage,2,'12:00:00','12:45:00',60,15),
(@massage,2,'14:00:00','14:45:00',60,15),
(@massage,2,'15:00:00','15:45:00',60,15),
-- Mi (3): 10/11/12/14/15
(@massage,3,'10:00:00','10:45:00',60,15),
(@massage,3,'11:00:00','11:45:00',60,15),
(@massage,3,'12:00:00','12:45:00',60,15),
(@massage,3,'14:00:00','14:45:00',60,15),
(@massage,3,'15:00:00','15:45:00',60,15),
-- Do (4): 13/14/15/17/18/19
(@massage,4,'13:00:00','13:45:00',60,15),
(@massage,4,'14:00:00','14:45:00',60,15),
(@massage,4,'15:00:00','15:45:00',60,15),
(@massage,4,'17:00:00','17:45:00',60,15),
(@massage,4,'18:00:00','18:45:00',60,15),
(@massage,4,'19:00:00','19:45:00',60,15),
-- Fr (5): 10/11/12/14/15/16
(@massage,5,'10:00:00','10:45:00',60,15),
(@massage,5,'11:00:00','11:45:00',60,15),
(@massage,5,'12:00:00','12:45:00',60,15),
(@massage,5,'14:00:00','14:45:00',60,15),
(@massage,5,'15:00:00','15:45:00',60,15),
(@massage,5,'16:00:00','16:45:00',60,15);
Lymphdrainage (Mo–Fr 10–20 Uhr, 45 min + 15 min Abstand → Start stündlich 10–19)
SET @lymph := (SELECT id FROM services WHERE code='lymph');
DELETE FROM schedules WHERE service_id=@lymph;
INSERT INTO schedules (service_id, weekday, start_time, end_time, slot_every_min, buffer_min) VALUES
(@lymph,1,'10:00:00','20:00:00',60,15),
(@lymph,2,'10:00:00','20:00:00',60,15),
(@lymph,3,'10:00:00','20:00:00',60,15),
(@lymph,4,'10:00:00','20:00:00',60,15),
(@lymph,5,'10:00:00','20:00:00',60,15);
-- Wenn 20:00 Start auch erlaubt sein soll: end_time auf '20:45:00' erhöhen
Mobility (Online) (Do 18:30, Kapazität 15)
SET @mobility := (SELECT id FROM services WHERE code='mobility');
DELETE FROM schedules WHERE service_id=@mobility;
INSERT INTO schedules (service_id, weekday, start_time, end_time, slot_every_min, buffer_min) VALUES
(@mobility,4,'18:30:00','19:30:00',60,0);
Capacity-Regel: automatisch 15 bei code='mobility', sonst 1.
Adminbereich
Pfad: /admin/
Login & Sicherheit
- Session-Login via
includes/auth.php(Tabellestaff_users) - CSRF-Token für alle POST-Formulare
- In DEV können bestimmte Prüfungen (z. B. Turnstile) gelockert sein (
cfg('env') !== 'prod')
Funktionen & Flows
- Filter & Quickchips (Heute/Morgen/7 Tage/30 Tage/Alle)
Status-Labels auf Deutsch: frei, gebucht, storniert, geblockt. - Buchungen
- Zu bestätigen (Physio
pending) – Bestätigen mit Mail + ICS - Stornieren mit Freitext-Begründung (Mail an Kund:in)
- Mail erneut senden (korrekt je nach Status confirmed/pending)
- Buchung bearbeiten (Name, E-Mail, Telefon)
- Reaktivieren (storniert → offen/gebucht, je Workflow)
- Zu bestätigen (Physio
- Slots
- Regelmäßig anlegen bis Zieldatum (Massage, Lymph – Plan aus
schedules) - Zeitraum sperren (Urlaub) → setzt
exceptionsund markiert offene Slots alsblocked; meldet Konflikte mit bestehendenpending/confirmedBuchungen - Physio an Tag X (ad-hoc in Steps von
duration_min) - Mobility nach Plan (im Bereich)
- Einzel-Slot anlegen (Service, Start, Ende, Status)
- Exceptions-Respekt beim Generieren (keine Slots an Ausnahme-Tagen)
- Regelmäßig anlegen bis Zieldatum (Massage, Lymph – Plan aus
- Sortierung: Slots starten ab heute, aufsteigend nach Datum/Zeit
- Navigation: Buttons „Zur Website“, „Slots generieren“, Hilfelink im Footer
Mailversand
- PHPMailer über
includes/mailer.php(SMTP SSL:465, UTF-8, quoted-printable) - Booking-Mails via
send_booking_mail():status='confirmed'→ ICS-Anhang (method=REQUEST), Betreff „Bestätigung …“status='pending'→ Eingangsbestätigung ohne ICS- Massage: Handtuch-Hinweis nur bei bestätigtem Termin
- Reply-To immer
praxis@bodycomfortphysio.de
- Kontaktformular (
index.php):- From =
noreply@…, Reply-To = Absender:in - Turnstile-Verify (
cfg('captcha.secret')); in DEV optional relaxed
- From =
Öffentliche Seiten
- index.php– Startseite
- book.php – Buchung im Pixelarity-Template (
.intro--compact), Preis-Text ausservices.price_text
CLI-Tools
cli/test_mail.php– sendet eine Test-Booking-Mail inkl. ICS. Nützlich zum SMTP-Debug.cli/generate_slots.php– anfänglicher Generator; die Funktionalität ist inzwischen im Admin verfügbar.
Fehlerquellen / Troubleshooting
- „Undefined constant …“ → überall
cfg('…')statt alter Konstanten (FROM_ADDRESS,TURNSTILE_SECRET, …). - ICS fehlt → ICS nur bei
status=confirmed. Prüfesend_booking_mail()und obisHTML(true)+multipart/mixedgesetzt ist (in Factory ok). - E-Mails kommen nicht an → SMTP-Zugangsdaten, DKIM/SPF, Absender-Domain, PHPMailer-Debug (
SMTPDebug=3). - Turnstile schlägt fehl →
cfg('captcha.secret')prüfen; in DEV percfg('env')!=='prod'deaktiviert. - Slots doppelt → DB-Constraint
UNIQUE (service_id, start_ts)undINSERT IGNOREverhindern Duplikate.
Build & Deployment
- Frontend: via
patrick-gulp-core(separates Repo) →.kit/SASS nachbuild/rendern. - Docroot: auf
build/zeigen lassen. - ENV:
.envpflegen; Prod erkennt Domain automatisch. - Datenpflege: Services/Schedules in Adminer oder per SQL; Slots über Admin erzeugen.
- Backups: DB (
bookings,slots) regelmäßig sichern; Mail-Logs optional.