Modul: Weiterbildung¶
Stand
Code-Analyse vom 2026-04-22 gegen sachpool.de, WebEdition 9.2.3, PHP 8.3.
Zweck¶
Versicherungsvermittler müssen nach §34d Abs. 9 GewO jährlich 15 Stunden berufliche Weiterbildung absolvieren (IHK-Pflicht). Das Modul:
- Verwaltet Weiterbildungs-Nachweise pro Vermittler
- Nimmt Datei-Uploads entgegen (PDF/DOC/JPG, als BLOB in MySQL!)
- Generiert IHK-konforme PDF-Nachweise (FPDF) nach § 34d Abs. 9 GewO + § 7 Abs. 1 VersVermV
- Dashboard-Widget mit aktuellem Stundenstand
Template-Struktur¶
sachpool-portal/weiterbildung/
├── frontend/
│ ├── weiterbildungskonto.tmpl (249) Haupt-UI (EasyUI-Datagrid)
│ ├── weiterbildungs-funktionen.tmpl (252) AJAX-Endpoint (9 Actions)
│ └── fpdf.tmpl (253) FPDF-Library-Wrapper
├── backend/
│ ├── weiterbildungen-verwalten.tmpl (240) Redakteurs-UI
│ └── weiterbildungs-admin-download.tmpl (273)
└── tools/
├── available-functions.tmpl (214)
├── weiterbildung-migration.tmpl (212)
└── weiterbildung-verwaiste-dokumente.tmpl (213)
Einstiegspunkt¶
| Doc-ID | Pfad | Zweck |
|---|---|---|
| — | /_login/weiterbildung/weiterbildungskonto.php |
Haupt-UI (Template 249) |
| 40158 | /_login/weiterbildung/funktionen.php |
AJAX-Endpoint (Template 252) |
| 40184 | — | FPDF-Library (Dokument-Include) |
Zusätzlich: Dashboard-Widget „Weiterbildungskonto: 0 h 0 min" verlinkt direkt auf die Hauptseite.
Technische Besonderheit: EasyUI-Frontend¶
Im Gegensatz zum Rest des Portals (Bootstrap-4) verwendet dieses Modul
jQuery EasyUI (datebox, datagrid, dialog, linkbutton, filebox).
Assets:
- easyui.css (ID 40161)
- icon.css (40163), color.css (40160)
- jquery.easyui.min.js (40164)
- easyui-lang-de.js (40162) — deutsche Lokalisierung
→ inkonsistent mit Rest der Portal-UI, siehe HANDLUNGSEMPFEHLUNGEN.md.
Datenmodell¶
Tabelle Weiterbildung¶
| Feld | Typ | Rolle |
|---|---|---|
id |
int PK | Eintrag-ID |
datum |
DATE | Datum der Weiterbildung |
anbieter |
varchar | Name des Anbieters |
thema |
varchar | Thema |
minuten |
int | Dauer in Minuten |
benutzer |
varchar | Login-Name |
benutzer_id |
int | FK auf tblWebUser.id |
ip |
varchar | IP-Adresse bei Erfassung |
Tabelle Weiterbildung_datei¶
| Feld | Typ | Rolle |
|---|---|---|
id |
int PK | |
weiterbildung_id |
int FK | Referenz auf Weiterbildung.id |
fname |
varchar | Originaldateiname |
dokument |
BLOB | Dateiinhalt binär in MySQL (!) |
Dateien als BLOB in MySQL
Uploads (PDF, DOC, JPG bis beliebige Größe) werden als BLOB in MySQL gespeichert. Keine Größenbegrenzung, kein MIME-Check. → Skalierungs- und Performance-Thema.
AJAX-Actions (Template 252)¶
Das Endpoint unterstützt 9 Actions via ?action=:
| Action | Methode | Zweck |
|---|---|---|
data |
GET | JSON-Antwort für EasyUI-Datagrid (Liste + Jahressumme) |
open |
GET | HTML-Fragment: Bearbeiten-/Neu-Formular |
save |
POST | INSERT oder UPDATE (mit optionalem File-Upload) |
delete |
GET | DELETE auf Weiterbildung + Weiterbildung_datei |
down |
GET | Datei-Download (streamt BLOB mit Content-Type) |
pdf |
GET | IHK-Nachweis-PDF (FPDF) für Zeitraum ?ab=YYYYMMDD&bis=YYYYMMDD |
askdruck |
GET | HTML-Fragment: Jahresauswahl-Formular (Spinner 2017 – aktuelles Jahr) |
konto |
GET | Formular für Mail-Benachrichtigung (Feature unvollständig — speichert nichts) |
test |
GET | Debug-Action (Dead-Code, nicht im Produktiv-Flow genutzt) |
PDF-Export (IHK-Nachweis)¶
FPDF-Subklasse generiert Nachweis-PDF mit:
- Name, Firma, Adresse (aus
tblWebUserKontakt_*-Feldern) - IHK-Registernummer
- Tabelle aller Weiterbildungen im Zeitraum: Thema + Datum/Dauer + Anbieter
- Bestätigungsformel nach § 34d Abs. 9 Satz 2 GewO i. V. m. § 7 Abs. 1 VersVermV
gez.-Zeile
Limitierungen:
- thema auf 35 Zeichen gekürzt, anbieter auf 25 Zeichen
- Mehrzeilen über explode("@", ...) — fragil, sollte wordWrap nutzen
- utf8_decode()-basiert (FPDF kann kein UTF-8) — Umlaute über ISO-8859-1
Backend-Tools¶
weiterbildungen-verwalten.tmpl (240)¶
Redakteurs-UI für Sachpool-Admins: Gesamtübersicht aller Vermittler-Weiterbildungen, massenweises Anlegen per Dateiablage, plus einzelne Bearbeitung/Löschung.
Workflow — Massen-Import:
- Redakteur legt PDFs im Import-Ordner (
<we:listdir id="39846">) ab. - Dateiname-Konvention:
<Vermittler-Kennung>_<Beschreibung>.pdf(z. B.90100-11_IDD-Schulung-Q1.pdf). Der Teil vor dem_wird als Vermittler-Kennung interpretiert und gegentblWebUser.Usernameaufgelöst. - Redakteur trägt Metadaten (Datum, Anbieter, Bezeichnung, Dauer) im Template-Formular ein und submitet.
- Pro Datei entsteht je ein
Weiterbildung- und einWeiterbildung_datei-Eintrag. Originaldatei wird als BLOB indokumentabgelegt und aus dem Import-Ordner perwe_delete()gelöscht. - Einzelne Einträge können danach über Detail-Dialoge editiert oder gelöscht werden — Bestätigung per Eintippen von „OK".
SQL-Queries (alle Template 240):
| Zweck | Query |
|---|---|
| Hauptlistung | SELECT * FROM Weiterbildung ORDER BY datum DESC |
| Kennung → User-ID | SELECT ID FROM tblWebUser WHERE Username='<kennung>' |
| Import-Insert | INSERT INTO Weiterbildung (datum, anbieter, thema, minuten, benutzer_id, benutzer, geaendert) VALUES (...) |
| Datei-Insert | INSERT INTO Weiterbildung_datei (weiterbildung_id, fname, dokument) VALUE (...) |
| Detail-Join | SELECT ... FROM Weiterbildung t1 JOIN Weiterbildung_datei t2 ON t1.id=... AND t1.id = t2.weiterbildung_id |
| Update | UPDATE Weiterbildung SET datum=..., anbieter=..., thema=..., minuten=..., benutzer=... WHERE id=... |
| Delete | DELETE FROM Weiterbildung WHERE id=... (kein Cascade auf Weiterbildung_datei!) |
Auth-Schutz: Nur <we:ifWebEdition> — prüft ausschließlich, ob irgendein
WebEdition-Login besteht. Keine Rollen-/Verantwortungsbereichs-Prüfung.
Auffälligkeiten:
UPDATEschreibtbenutzer(Anzeigename) ohnebenutzer_id— Zuordnung kann auseinanderlaufen.DELETEhinterlässt Waisen-Einträge inWeiterbildung_datei(kein CASCADE).setInterval($(".weBtnSave").hide(), 500)blendet WebEdition-Save-Buttons alle 500 ms aus (UI-Hack).- SachPoolDB-Singleton wird 7× per
getInstance()geholt und 7×close()-aufgerufen (harmlos, verwirrend).
→ Kritische Security-Funde (hartkodiertes Basic-Auth-Passwort, SQL-Injection im
Import, Stored XSS in Hauptansicht): siehe HANDLUNGSEMPFEHLUNGEN.md.
weiterbildungs-admin-download.tmpl (273)¶
Admin-Download einzelner Nachweis-PDFs über GET-Parameter record_id.
$user_check = true; // IMMER true — keine Besitzprüfung
if ($user_check) { /* Download-Logik */ }
DELETE FROM Weiterbildung WHERE id = $_GET['record_id'];
DELETE FROM Weiterbildung_datei WHERE id = $_GET['record_id']; // Bug: sollte weiterbildung_id sein
Kritisch
$user_check = true ohne Vergleich → IDOR. Jeder Redakteur / jeder mit
Session kann jede Datei herunterladen und löschen. Der DELETE trifft zusätzlich
das falsche Feld (id statt weiterbildung_id) → inkonsistente Datenbereinigung.
Siehe HANDLUNGSEMPFEHLUNGEN.md.
Tools-Ordner¶
weiterbildung-migration.tmpl(212) — einmalige Altdaten-Migration aus FastpublishCMSweiterbildung-verwaiste-dokumente.tmpl(213) — findetWeiterbildung_datei-Einträge ohne zugehörigeWeiterbildung(DB-Konsistenz-Check)available-functions.tmpl(214) — Helper-Inventar
Siehe auch¶
- Nutzer: Weiterbildung
- Cron-Jobs — plant Reminder-Mails zu Weiterbildungs-Fristen
Bekannte Altlasten¶
→ interne Datei HANDLUNGSEMPFEHLUNGEN.md im Repo-Root
- 🔴🔴🔴 Flächendeckende IDOR im AJAX-Endpoint (fast alle Actions akzeptieren
record_id/idohne Besitzprüfung) - 🔴🔴🔴 Template 273
$user_check = true— Admin-Download ohne Besitzprüfung - 🔴🔴🔴 Template 240 hartkodiertes Basic-Auth-Passwort (
vy7a1NATn93Ukbm0ypFd) im Klartext - 🔴 Template 240 SQL-Injection im Import-INSERT (POST-Werte ungeescaped)
- 🔴 Template 240 Stored XSS in Hauptansicht (DB-Werte ungeescaped in HTML)
- 🔴 SQL-Injection über
$_REQUEST['sort']/$_REQUEST['order']direkt inORDER BY(Template 252) - 🔴 Bug
action=delete: DELETE ausWeiterbildung_datei WHERE id=...stattWHERE weiterbildung_id=...— Datei-Leichen in DB - 🟡 Datei-Uploads als BLOB in MySQL (keine Größenbegrenzung, kein MIME-Check)
- 🟡 EasyUI statt Bootstrap — UI-Inkonsistenz zum Rest des Portals
- 🟡 15 DB-Instanzen (
$wedb1..$wedb14, $wedb10b) — Singleton-Antipattern - 🟡 Template 240 UPDATE ohne
benutzer_id-Sync — Anzeige und FK können auseinanderlaufen - 🟡 Template 240 DELETE ohne Cascade auf
Weiterbildung_datei - 🟢 Dead-Code
action=test, unvollständigeaction=konto - 🟢 Magic Number 2017 als Startjahr im PDF-Jahr-Spinner
- 🟢 Template 214 (
available-functions.tmpl) enthältvar_dump($_SESSION["user"]["ID"])im Produktivcode