External Context · Legacy-System-Snapshot · 2026-06-17

Projektportal-Flows im Legacy-Neos — Referenz für den Drupal-Nachbau

TL;DR — Vollständiges Inventar aller Flows des alten Neos-Projektpartnerportals (Contenance.Solcom), erstellt damit der Drupal-11-Nachbau nichts übersieht. 256 Flows über 11 Domänen, plus 13 vom Critic gefundene Lücken, die in keinem Reader auftauchten. Drei Achsen: 10 User-Journeys (end-to-end, wie der Projektpartner sie erlebt), das Flow-Inventar (atomare technische Operationen) und der Datenfluss (wohin welche Daten gehen). Kern-Portal = Projekte, Account, Alerts, Verfügbarkeit. Karriere/Personio und Donation/Sommerfest sind peripher (separate Concerns, nicht zwingend Teil des Portal-Nachbaus).
10User-Journeys
256Flows
13Lücken
76Routes
82Datenkanten

Scope & Architektur

Ein Neos/Flow-Request durchläuft fünf Stationen; eine sechste Achse (CLI/Cron) ist im Web-Request unsichtbar und beim Nachbau am leichtesten zu übersehen.

Route Routes.yaml Controller …Action() Domain / Service Model · Repo Side-Effect Mail · API · DB Fusion Rendering CLI / Cron CommandController 6. Achse: unsichtbar im Web-Request

Kern-Projektpartnerportal (Nachbau-relevant): Projekte/Suche/Merkliste, Account/Registrierung/Login, Projekt-Alerts, Verfügbarkeit/AVV, die zugehörigen Formulare und die CLI/Cron-Jobs dahinter. Peripher (eigene Concerns): Karriere-Seite mit Personio-Anbindung, Spendenaktion (Donation), Sommerfest, RSS, generische API, Admin-Control-Panel (ACP).

Abdeckung & Konfidenz — ist das wirklich alles?

Kurzfassung: Für alle Custom-Business-Flows und die Datenflüsse hohe Sicherheit. Das wurde explizit gegen den Code verifiziert — nicht angenommen.
WasStatusWie verifiziert
Contenance.Solcom (Portal-Paket) verifiziert Completeness-Critic prüfte Routes.yaml + jede *Action/*Command-Signatur + Package-Wiring. Package.php::boot() ist leer → keine versteckten Signal/Slot-Flows.
Contenance.PluginSlick (2. Custom-Paket) verifiziert Null PHP-Klassen — reines Slick-Carousel (Fusion + Templates + Assets). Trägt keinen Flow.
Packages/Sites/ verifiziert Enthält nur Contenance.Solcom (Symlink) — kein weiteres Site-Paket übersehen.
Packages/Application/ Framework Ausschließlich Neos.*-Framework + Flownative-Contrib — keine eigene Geschäftslogik.
Projekt-Datenquelle (Inbound) gepinnt Projekte kommen als XML-Datei-Drop (UTF-16, Salesforce-Export) via project:import; Referenzprojekte via HTTP-REST von www.solcom.de (referenceProject:import, SSL-Check deaktiviert).
Cron-Frequenzen Rest-Unsicherheit WELCHE Cron-Commands existieren, ist vollständig erfasst. WIE OFT sie laufen, steht in der externen Server-Crontab (nicht im Code) — beim Nachbau separat klären.
Prod-Credentials / Endpunkte Rest-Unsicherheit Integrationssysteme + Transport sind bekannt; konkrete API-Keys/URLs liegen in Configuration/Production/ (bewusst nicht in diesem Doc).
Contrib Flowpack.Neos.FrontendLogin Contrib Treibt die Login-Formular-Mechanik, in die unser LoginController einhängt. Standard-Paket; die Custom-Logik (Merkliste-Merge, Alert-Migration) liegt im analysierten Controller.

Authentifizierung & Zugriff — wer ist ab wann wie eingeloggt

Der Login läuft über das Neos-Flow-Security-Framework mit dem Provider Contenance.Solcom:Login (E-Mail/Passwort, Bcrypt). Eine Session gilt als authentifiziert genau ab dem Aufruf von LoginController::onAuthenticationSuccess() — ab dort liefert securityContext->getAccount() ein Account-Objekt mit der Rolle Neos.Neos:FrontendUser. Diese Rolle wird bei der Account-Anlage vergeben (FlowUserCreationService:74).

Gast / anonym account === null Registrierung-pending RegistrationFlow · Token 10 Tage Eingeloggter Projektpartner FrontendUser · Token authenticated Passwort-Reset-pending ResetPasswordFlow · Token 12 h E-Mail-Änderung-pending AccountChangeFlow · bleibt eingeloggt registerAction +Mail activationAction (Token-Klick) Login-Form → onAuthenticationSuccess() = "jetzt eingeloggt" logoutAction / deleteAction → Gast Passwort vergessen +Mail E-Mail ändern Token

Auth-Zustände

ZustandErkennbar anToken / TimeoutWas erlaubt
Gast / anonym$this->account === nullAlle öffentlichen Seiten: Projektliste/Suche/Detail, Gast-Merkliste (Cookie), Gast-Projekt-Alert, Bewerbung, Registrierung, Login.
Registrierung-pendingRegistrationFlow in DB, noch kein AccountAktivierungs-Token, 10 TageWie Gast; Login erst nach Token-Klick möglich.
Passwort-Reset-pendingResetPasswordFlow in DBReset-Token, 12 StundenWie Gast; setzt nach Token neues Passwort.
E-Mail-Änderung-pendingAccountChangeFlow in DBAktivierungs-Token, 10 TageBleibt voll eingeloggt mit alter E-Mail; Wechsel erst nach Token-Bestätigung.
Eingeloggter Projektpartnerauthentifiziertes UsernamePassword-Token, Provider Contenance.Solcom:LoginSession-Cookie, Inaktivität 3600 sProfil, Dokumente, persistente Merkliste & Alerts, Verfügbarkeit, Account ändern/löschen.
Beim Login werden Gast-Daten übernommen (onAuthenticationSuccess, LoginController:182–229): anonyme Merkliste (Bookmarks) wird mit der DB gemerged, und Gast-Projekt-Alerts (per E-Mail) werden auf den User umgehängt. Dieser Merge-Moment muss im Drupal-Nachbau exakt abgebildet sein.
Kein echtes 2FA. Lead2faForm ist ein DSGVO-Double-Opt-In für Marketing-Leadformulare (Kontaktformular → Bestätigungsmail → CRM), nicht ein zweiter Login-Faktor. Im gesamten Login-Pfad gibt es keinen TOTP/SMS-Schritt.

Vier getrennte Auth-Bereiche

BereichMechanismusRolle / AbsicherungDrupal-Nachbau
(a) Frontend-ProjektpartnerFlow-Provider Contenance.Solcom:LoginRolle Neos.Neos:FrontendUser; geschützte Actions nur per PHP-Check account === null → RedirectDrupal-Account + Permission; Route-Level _user_is_logged_in / _permission statt manueller Checks.
(b) ACP-AdminEigener Flow-Provider Contenance.Solcom:Acp + UserAcp-EntitycheckLoginAndRole() mit Integer-Rollen 1=Super-Admin, 2=Account-Admin, 3=Donation-ManagerEchte Drupal-Rollen mit sprechenden Namen + _permission.
(c) Internes DashboardEigenbau, kein FrameworkHardcoded-Passwort (DashboardController:107), Session-Flag solcomLoginSession; Spenden-Dashboard, nicht aktivNicht 1:1 übernehmen — Drupal-Account + dedizierte Rolle.
(d) Neos-CMS-BackendStandard-Neos /neos, /setupProvider Neos.Neos:Backend, Rollen Neos.Neos:Editor/AdministratorDrupal-Admin-UI + Redaktionsrollen.

Session-Lebenszyklus

  • @Flow\Session(autoStart=TRUE) auf loginAction, activationAction, reActivationAction.
  • Inaktivitäts-Timeout 3600 s (1 h), Session-Name Neos_Flow_Session — beides Framework-Default, kein projektspezifischer Override.
  • Session-Cookie: lifetime 0 (stirbt beim Browser-Schließen), httponly true, secure false (kein Production-Override gefunden — beim Nachbau auf secure setzen).
  • Logout invalidiert alle Tokens (parent::logoutAction()) + Hard-Redirect per header('Location:') + die().

Security-Findings — NICHT 1:1 nachbauen

Der Alt-Code enthält drei hartkodierte Secrets. Werte stehen bewusst nicht in diesem Dokument.
  • Hardcoded GitHub-Access-Token (AcpController:639, ACP-Development-Bereich) — als kompromittiert behandeln und rotieren.
  • Hardcoded Dashboard-Passwort (DashboardController:107, Vergleich per ===, kein Hashing/Rate-Limit).
  • Hardcoded AES-Schlüssel für ein Tracking-Cookie (SolcomComponent:190) — im Drupal-Nachbau aus settings.local.php/Env beziehen.
Außerdem: 22× @Flow\SkipCsrfProtection, teils auf state-mutierenden Aktionen (Account-Löschung, Admin-User-Edit, Projekt-Alert, Datei-Upload). In Drupal über FormBase-CSRF bzw. _csrf_token absichern — kein Skip-Äquivalent verwenden. Und das Policy-Anti-Pattern: alle 18 Privilege-Targets granten Neos.Flow:Everybody, der echte Schutz liegt nur im PHP — im Drupal-Nachbau über *.permissions.yml + _permission sauber deklarieren.

User-Journeys 10 End-to-End

↦ Die konkreten Eingabeformulare zu diesen Journeys (echte Felder, Pflichtangaben, Validierung) sind als originalgetreue Mockups im Schwester-Snapshot 0010 — Projektportal-Formular-Mockups dokumentiert.

Diese Ebene stitcht die atomaren technischen Flows (Detail-Sektion weiter unten) zu durchgängigen Nutzer-Reisen zusammen — so, wie ein Projektpartner sie tatsächlich erlebt. Farb-Legende der Stationen: Nutzer-Aktion System E-Mail Persistenz Entscheidung/Match.

Registrierung & Aktivierung (Double-Opt-In) Gast → Projektpartner

Registrierungs-formularabsendenRegistrationFlow +Dateien speichernAktivierungs-Mailan GastToken-Link klickenAccount + Useranlegen, RMS-Nr.Mail an bewerbung@+ an Nutzer

User Story Als interessierter Freiberufler möchte ich mich mit meinen Profildaten registrieren und meine E-Mail-Adresse bestätigen, damit ich ein aktives Projektpartner-Konto erhalte.

Akzeptanzkriterien
  • Nach dem Absenden des Registrierungsformulars erhalte ich eine Bestätigungs-E-Mail mit einem Aktivierungslink.
  • Pflichtangaben (E-Mail, Passwort, Name, Mobilnummer, Datenschutz-Zustimmung) werden vor dem Absenden geprüft.
  • Ich kann Dokumente anhängen (PDF, DOC oder DOCX, je höchstens 5 MB).
  • Der Aktivierungslink ist 10 Tage gültig; ist er abgelaufen, kann ich mir einen neuen zusenden lassen.
  • Nach Klick auf den gültigen Aktivierungslink ist mein Konto aktiv und ich kann mich einloggen.
  • Eine bereits registrierte E-Mail-Adresse wird mit einem klaren Hinweis abgelehnt.
Dev-Evals — technische Prüfung (Entwicklung)
  • Automatischer Test: Ein abgesendetes Registrierungsformular legt genau einen Pending-Registrierungs-Datensatz an und löst genau eine Aktivierungs-Mail aus (Mailversand gemockt).
  • Automatischer Test: Der Aktivierungs-Token ist nach 10 Tagen (Zeit gemockt) ungültig.
  • Automatischer Test: Eine zweite Registrierung mit bereits vorhandener E-Mail erzeugt keinen zweiten Datensatz und liefert einen „bereits vergeben“-Zustand.
  • Automatischer Test: Die Upload-Validierung lehnt Dateien >5 MB und andere Typen als PDF/DOC/DOCX ab; eine gültige Datei wird dem Profil angehängt.
  • Automatischer Test: Ein gültiger Aktivierungslink erzeugt ein aktives Konto mit Projektpartner-Rolle und vergebener RMS-Nummer und löst zwei Mails aus (intern + Nutzer).
Abnahme-Tests — funktional / Frontend (Klickpfade)
  • Formular mit gültigen Daten absenden → Erfolgshinweis; im Postfach liegt eine Mail mit Aktivierungslink.
  • Aktivierungslink klicken → Seite „Konto aktiviert“; danach ist Login mit den Daten möglich.
  • Pflichtfeld (z. B. Mobilnummer) leer lassen → Inline-Validierungsfehler, kein Absenden.
  • Datei >5 MB anhängen → verständliche Fehlermeldung.
  • Mit bereits registrierter E-Mail registrieren → Hinweis „bereits vergeben“.
Atomare Flows (Detail-Sektion unten)AccountController::registerAction AccountController::activationAction AccountController::reActivationAction
Drupal-NachbauVorhanden in solcom_projektpartner (Webform + ProjektpartnerRegistrationSubmitter). Prüfen: RMS-Nummern-Vergabe (Auto-Increment), Re-Activation-Pfad bei abgelaufenem Token, Newsletter-Opt-In nach Mailingwork.

Login (mit Übernahme von Gast-Daten) Projektpartner

Login-FormularabsendenAuthentifizierung(Flow Security)Gast-Bookmarks→ User mergenGast-Alerts →User migrierenRedirect zu Profil/ Bewerbung

User Story Als registrierter Projektpartner möchte ich mich mit E-Mail und Passwort einloggen, damit ich meine persönlichen Funktionen nutzen kann und meine als Gast gemerkten Projekte sowie Suchagenten übernommen werden.

Akzeptanzkriterien
  • Mit korrekten Zugangsdaten werde ich eingeloggt und zu meinem Profil geleitet.
  • Bei falschen Zugangsdaten erhalte ich eine Fehlermeldung und bleibe ausgeloggt.
  • Eine zuvor als Gast angelegte Merkliste wird beim Login mit meinem Konto zusammengeführt.
  • Als Gast unter meiner E-Mail angelegte Suchagenten werden auf mein Konto übernommen.
  • Nach einer Stunde Inaktivität werde ich automatisch abgemeldet.
Dev-Evals — technische Prüfung (Entwicklung)
  • Automatischer Test: Korrekte Zugangsdaten erzeugen eine authentifizierte Session; falsche Zugangsdaten nicht.
  • Automatischer Test: Beim ersten Login wird eine als Gast geführte Merkliste korrekt mit dem Konto zusammengeführt (leeres Konto übernimmt Gast-Stand, befülltes Konto behält seinen Stand).
  • Automatischer Test: Ein als Gast unter der Konto-E-Mail angelegter Suchagent wird dem Konto zugeordnet und der Gast-Datensatz entfernt.
  • Automatischer Test: Nach Ablauf des konfigurierten Inaktivitäts-Timeouts ist die Session beendet (Zeit/Config gemockt).
Abnahme-Tests — funktional / Frontend (Klickpfade)
  • Korrekt einloggen → Weiterleitung auf das Profil.
  • Falsches Passwort → Fehlermeldung, kein Zugriff auf geschützte Seiten.
  • Als Gast zwei Projekte merken, dann einloggen → Merkliste enthält beide serverseitig.
  • Als Gast Suchagent anlegen + bestätigen, dann mit gleicher E-Mail einloggen → Suchagent erscheint im Profil.
Atomare Flows (Detail-Sektion unten)LoginController::onAuthenticationSuccess LoginController::onAuthenticationFailure
Formular-Mockup↦ Login
Drupal-NachbauLogin via solcom_projektpartner (P2503-230, E-Mail-Login abgesichert). WICHTIG nicht vergessen: beim Login werden anonyme localStorage-Merkliste UND Gast-Suchagenten auf den User übernommen — das Merge-Verhalten muss nachgebaut sein.

Passwort vergessen & zurücksetzen Projektpartner

"Passwortvergessen" absendenResetPasswordFlow+ Token speichernReset-Mailan NutzerToken-Link klickenNeues Passwortsetzen

User Story Als Projektpartner, der sein Passwort vergessen hat, möchte ich es über einen E-Mail-Link zurücksetzen, damit ich wieder Zugang zu meinem Konto erhalte.

Akzeptanzkriterien
  • Nach Eingabe meiner E-Mail-Adresse erhalte ich einen Link zum Zurücksetzen (sofern ein Konto existiert).
  • Das Formular verrät aus Datenschutzgründen nicht, ob die E-Mail-Adresse registriert ist.
  • Der Reset-Link ist 12 Stunden gültig.
  • Über den Link kann ich ein neues Passwort setzen und mich anschließend damit einloggen.
Dev-Evals — technische Prüfung (Entwicklung)
  • Automatischer Test: Ein Reset wird nur bei existierendem Konto ausgelöst (genau eine Mail); bei unbekannter E-Mail kein Datensatz, aber identische UI-Antwort (keine Konto-Enumeration).
  • Automatischer Test: Der Reset-Token ist nach 12 Stunden ungültig.
  • Automatischer Test: Ein gültiger Reset setzt das neue Passwort und entwertet den Token; Login mit neuem Passwort gelingt, mit altem nicht.
Abnahme-Tests — funktional / Frontend (Klickpfade)
  • E-Mail eingeben → Hinweis „Falls Konto existiert, Mail versandt“; Reset-Mail im Postfach.
  • Reset-Link klicken, neues Passwort setzen → Login mit neuem Passwort klappt.
  • Abgelaufenen/alten Link öffnen → Hinweis „Link abgelaufen“.
Atomare Flows (Detail-Sektion unten)AccountController::passwordForgottenSendAction AccountController::passwordResetAction
Drupal-NachbauPendant ProjektpartnerMailManager::sendPasswordResetMail vorhanden. Token-Ablauf + Einmal-Verwendung prüfen.

Account-Daten ändern (Passwort / Login-E-Mail) Projektpartner (eingeloggt)

Profil-EinstellungenändernPasswort-Änderung:Mail an NutzerE-Mail-Änderung:AccountChangeFlowBestätigungs-Mailan neue AdresseToken bestätigen2 Erfolgs-Mails(alt + neu)

User Story Als eingeloggter Projektpartner möchte ich mein Passwort und meine Login-E-Mail-Adresse ändern, damit meine Zugangsdaten aktuell und sicher bleiben.

Akzeptanzkriterien
  • Eine Passwortänderung wird sofort wirksam und mir per E-Mail bestätigt.
  • Eine Änderung der Login-E-Mail wird erst nach Klick auf einen Bestätigungslink an die neue Adresse wirksam.
  • Bei einer E-Mail-Änderung werden sowohl die alte als auch die neue Adresse benachrichtigt.
  • Bis zur Bestätigung bleibe ich mit der alten Adresse vollständig eingeloggt.
Dev-Evals — technische Prüfung (Entwicklung)
  • Automatischer Test: Eine Passwortänderung wird sofort wirksam und löst genau eine Bestätigungs-Mail aus.
  • Automatischer Test: Eine E-Mail-Änderung wird erst nach Klick auf den Bestätigungslink wirksam; vorher bleibt der Login mit der alten Adresse gültig.
  • Automatischer Test: Die Bestätigung der E-Mail-Änderung löst zwei Mails aus (alte + neue Adresse).
Abnahme-Tests — funktional / Frontend (Klickpfade)
  • Passwort im Profil ändern → Bestätigungsmail; neues Passwort gilt beim nächsten Login.
  • Login-E-Mail ändern → Hinweis „Bestätigung an neue Adresse“; vor dem Klick weiter mit alter Adresse eingeloggt.
  • Bestätigungslink (neue Adresse) klicken → Login künftig nur mit neuer Adresse; beide Adressen erhalten Info-Mail.
Atomare Flows (Detail-Sektion unten)AccountController::editAccountSendAction AccountController::activationChangeAccountEmailAction
Drupal-NachbauLÜCKE: Der Login-E-Mail-Wechsel mit Doppel-Bestätigung (Mail an alte UND neue Adresse) fehlt laut Analyse im Drupal-Rebuild (P2503-226 Scope). Passwort-Änderung ist vorhanden (P2503-230).

Account löschen (Self-Service) Projektpartner (eingeloggt)

Konto-LöschunganfordernAccount + User +Dateien entfernenMail an bewerbung@+ an NutzerLogout &Bestätigungsseite

User Story Als Projektpartner möchte ich mein Konto selbst löschen können, damit meine Daten auf meinen Wunsch entfernt werden.

Akzeptanzkriterien
  • Nach meiner Bestätigung werden mein Konto und meine persönlichen Daten entfernt.
  • Auch meine hochgeladenen Dokumente werden aus dem Speicher gelöscht (DSGVO).
  • Ich werde anschließend automatisch ausgeloggt und sehe eine Bestätigungsseite.
  • Ich und SOLCOM erhalten je eine Lösch-Bestätigung per E-Mail.
Dev-Evals — technische Prüfung (Entwicklung)
  • Automatischer Test: Die Löschung entfernt Konto und personenbezogene Daten und löscht die hochgeladenen Dateien aus dem Speicher.
  • Automatischer Test: Die Löschung löst zwei Mails aus (intern + Nutzer) und beendet die Session.
  • Automatischer Test: Bestehende Suchagenten des Kontos werden konsistent behandelt (in Gast-Suchagenten überführt bzw. entfernt — Zielverhalten festlegen und prüfen).
Abnahme-Tests — funktional / Frontend (Klickpfade)
  • Konto-Löschung bestätigen → Logout + Bestätigungsseite; erneuter Login schlägt fehl.
  • Lösch-Bestätigungsmail (Nutzer) und interne SOLCOM-Mail kommen an.
  • Nach Löschung geschützte URL aufrufen → Redirect zum Login.
Atomare Flows (Detail-Sektion unten)AccountController::deleteAction LoginController::logoutAfterAccountRemovalAction
Formular-Mockupkein Eingabeformular (Aktion bzw. Listenansicht)
Drupal-NachbauAbgebildet in solcom_projektpartner (P2503-233). Prüfen: Werden auch UserFiles im Resource-Storage gelöscht (DSGVO)?

Profil & Dokumente pflegen Projektpartner (eingeloggt)

Profil-DatenbearbeitenDokument hochladen/ löschen (AJAX)UserFile imStorage ablegenCron: Änderungs-Mailan SOLCOM intern

User Story Als eingeloggter Projektpartner möchte ich meine Profildaten und Dokumente jederzeit aktualisieren, damit SOLCOM mich passend zu Projekten vermitteln kann.

Akzeptanzkriterien
  • Ich kann alle pflegbaren Profilfelder bearbeiten und speichern (Kontakt, Adresse, Stundensatz, Verfügbarkeit, Skills, Einsatzorte, Netzwerk-Links, Sprachen).
  • Ich kann Dokumente hochladen (PDF/DOC/DOCX, max. 5 MB) und einzeln wieder löschen.
  • Systemverwaltete Felder wie RMS-Nummer und Standort-Code kann ich nicht ändern.
  • Änderungen an meinem Profil werden SOLCOM intern gemeldet.
Dev-Evals — technische Prüfung (Entwicklung)
  • Automatischer Test: Das Speichern des Profils ändert ausschließlich pflegbare Felder; systemverwaltete Felder (RMS-Nummer, Standort-Code) bleiben unverändert.
  • Automatischer Test: Hochladen erzeugt einen Datei-Datensatz inkl. abgelegter Datei; Löschen entfernt beide vollständig.
  • Automatischer Test: Eine Profiländerung erzeugt die interne Benachrichtigung an SOLCOM (asynchron/Queue).
Abnahme-Tests — funktional / Frontend (Klickpfade)
  • Felder ändern + speichern → Werte sind nach Reload persistent.
  • Dokument hochladen → erscheint in der Liste; löschen → verschwindet.
  • Kein editierbares Feld für RMS-Nummer/Standort vorhanden.
Atomare Flows (Detail-Sektion unten)AccountController::editProfileSendAction AccountController::userProfileUploadAction AccountController::userProfileUploadRemoveAction UserNotificationCommandController::sendCommand
Formular-Mockup↦ Profil bearbeiten
Drupal-NachbauProfil-Pflege im Projektpartner-Portal. Nicht vergessen: die SOLCOM-interne Benachrichtigung über Profiländerungen läuft als CRON (nicht synchron) — eigener Drupal-Cron/Queue.

Projekt finden & merken (Merkliste) Gast oder Projektpartner

Projektliste:suchen / filternFilter in CookiepersistierenProjekt-DetailöffnenAuf MerklistesetzenGast: Cookie· User: DB

User Story Als Besucher oder Projektpartner möchte ich Projekte suchen, filtern und auf eine Merkliste setzen, damit ich passende Projekte wiederfinde.

Akzeptanzkriterien
  • Ich kann die Projektliste per Volltext, Einsatzort (DE/CH/A/Remote/International) und Branche filtern.
  • Meine gewählten Filter bleiben bei einem erneuten Besuch erhalten.
  • Ich kann ein Projekt aus Liste oder Detailseite auf die Merkliste setzen und wieder entfernen.
  • Als Gast wird die Merkliste lokal gehalten, als eingeloggter Nutzer dauerhaft im Konto gespeichert.
  • Ein Zähler zeigt mir jederzeit die Anzahl gemerkter Projekte.
Dev-Evals — technische Prüfung (Entwicklung)
  • Automatischer Test: Suche und Filter (Volltext, Einsatzort, Branche) liefern die erwartete Treffermenge; die gewählten Filter werden über den Besuch hinweg gespeichert.
  • Automatischer Test: Merkliste hinzufügen/entfernen/zählen funktioniert sowohl anonym (clientseitig) als auch eingeloggt (serverseitig).
  • Automatischer Test: Der Merklisten-Zähler entspricht exakt der Anzahl gemerkter Projekte.
Abnahme-Tests — funktional / Frontend (Klickpfade)
  • Nach Begriff filtern + Einsatzort „Remote“ → Liste passt; Seite neu laden → Filter weiterhin aktiv.
  • Projekt merken → Zähler +1, auf Merkliste sichtbar; entfernen → Zähler −1.
  • Als Gast merken, dann einloggen → Projekt bleibt auf der Merkliste.
Atomare Flows (Detail-Sektion unten)ProjectController::indexAction ProjectController::detailAction BookmarkController::addAction BookmarkController::indexAction
Formular-Mockupkein Eingabeformular (Aktion bzw. Listenansicht)
Drupal-NachbauVorhanden: solcom_project_offer + WatchlistController (P2503-238, backend-persistente Merkliste + anonyme localStorage-Variante). Location-Filter nutzt RMS-Codes (D1–D9, DE/CH/A/Remote) — Taxonomie-Mapping prüfen.

Auf ein Projekt bewerben Gast oder Projektpartner

Bewerbungs-Formularam Projekt öffnenFormular +Datei absendenTemp-Datei→ AnhangMail an bewerbung@(CC Projekt-Kontakt)Erfolgsseite

User Story Als interessierter Freiberufler möchte ich mich direkt auf ein Projekt bewerben und Unterlagen anhängen, damit SOLCOM meine Bewerbung mit Projektbezug erhält.

Akzeptanzkriterien
  • An der Projekt-Detailseite finde ich ein Bewerbungsformular.
  • Ich kann eine Datei anhängen (gleiche Regeln wie beim Profil-Upload).
  • Nach dem Absenden geht eine E-Mail an SOLCOM mit Kopie an den zuständigen Projekt-Kontakt.
  • Ich sehe nach erfolgreichem Absenden eine Bestätigungsseite.
Dev-Evals — technische Prüfung (Entwicklung)
  • Automatischer Test: Eine abgesendete Bewerbung löst eine Mail an SOLCOM mit Kopie an den Projekt-Kontakt und dem Datei-Anhang aus.
  • Automatischer Test: Die Upload-Validierung entspricht dem Profil-Upload (Typ/Größe); temporäre Dateien werden später automatisch aufgeräumt.
Abnahme-Tests — funktional / Frontend (Klickpfade)
  • Bewerbungsformular am Projekt öffnen, Datei anhängen, absenden → Erfolgsseite; Mail mit Anhang + CC vorhanden.
  • Ungültige Datei anhängen → Fehlermeldung, kein Versand.
Atomare Flows (Detail-Sektion unten)ProjectController::jobApplicationAction ProjectController::jobApplicationSendAction ApiController::uploadTempFileAction
Formular-Mockup↦ Projekt-Bewerbung
Drupal-NachbauPrüfen ob im Rebuild vorhanden: projekt-spezifische Bewerbung mit Datei-Upload und CC an den Salesforce-Projekt-Kontakt. Temp-File-Lifecycle (Cron-Cleanup) mitdenken.

Suchagent / Projekt-Alert & Benachrichtigung Gast oder Projektpartner

Alert mitSuchkriterien anlegenGast:Double-Opt-In-MailAlert aktiv (DB)Cron: neueProjekte matchenBenachrichtigungs-MailAbmelden perHash-Link

User Story Als Projektpartner oder Gast möchte ich einen Suchagenten mit meinen Kriterien anlegen, damit ich bei passenden neuen Projekten automatisch benachrichtigt werde.

Akzeptanzkriterien
  • Ich kann Suchkriterien speichern: Tags, auszuschließende Tags und Bundesländer.
  • Als Gast muss ich den Suchagenten per Double-Opt-In-Link in einer E-Mail bestätigen.
  • Bei neuen passenden Projekten erhalte ich eine Benachrichtigungs-E-Mail.
  • Ich kann mich jederzeit über einen Link in der E-Mail wieder abmelden.
Dev-Evals — technische Prüfung (Entwicklung)
  • Automatischer Test: Ein Suchagent speichert die Kriterien (Tags, ausgeschlossene Tags, Bundesländer); ein Gast-Suchagent wird erst nach Bestätigung des Double-Opt-In aktiv.
  • Automatischer Test: Der periodische Abgleich findet zu Kriterien passende neue Projekte und versendet je Treffer genau eine Mail ohne Doppelversand.
  • Automatischer Test: Der Abmelde-Link deaktiviert bzw. entfernt den Suchagenten.
Abnahme-Tests — funktional / Frontend (Klickpfade)
  • Suchagent mit Tags anlegen (als Gast) → Double-Opt-In-Mail; Link klicken → Agent ist aktiv.
  • Neues passendes Projekt (Testdaten) + Cron auslösen → Benachrichtigungs-Mail kommt an.
  • Abmelde-Link in der Mail klicken → keine weiteren Benachrichtigungen.
Atomare Flows (Detail-Sektion unten)ProjectAlertController::projectAlertSendAction ProjectAlertController::signInAction ProjectAlertController::signOutAction ProjectAlertCommandController::sendCommand
Drupal-NachbauVorhanden: solcom_projektpartner Suchagent (P2503-237, Alert-Mail + Migrationsconfig). Nicht vergessen: der eigentliche Match+Versand ist ein CRON-Command — und Gast-Alerts brauchen Double-Opt-In.

Verfügbarkeit melden Projektpartner (per Mail-Link)

Cron: Erinnerungs-Mailmit Hash-LinkHash-Linköffnet FormularVerfügbarkeit +optional Datei sendenIn DB speichern

User Story Als Projektpartner möchte ich meine Verfügbarkeit über einen Link aus der Erinnerungs-E-Mail melden, ohne mich einzuloggen, damit SOLCOM meinen aktuellen Status kennt.

Akzeptanzkriterien
  • Ich erhalte eine Erinnerungs-E-Mail mit einem personalisierten Link.
  • Der Link öffnet das Verfügbarkeits-Formular ohne Login.
  • Ich kann meine Verfügbarkeit angeben und optional eine Datei anhängen.
  • Nach dem Absenden wird mein Status gespeichert und SOLCOM informiert.
Dev-Evals — technische Prüfung (Entwicklung)
  • Automatischer Test: Der personalisierte Link öffnet das Formular ohne Login; das Absenden speichert die Angaben und setzt den Status auf „gemeldet“.
  • Automatischer Test: Der periodische Job legt die fälligen Datensätze an und versendet die Erinnerungs-Mails.
Abnahme-Tests — funktional / Frontend (Klickpfade)
  • Link aus der Erinnerungs-Mail öffnen → Formular erscheint ohne Login.
  • Verfügbarkeit angeben (+ optional Datei) und absenden → Bestätigung.
  • Status nach Submit = „gemeldet“; SOLCOM erhält die Info.
Atomare Flows (Detail-Sektion unten)AvailabilityController::indexAction AvailabilityController::sendAction AvailabilityCommandController::importCommand
Drupal-NachbauPrüfen ob im Rebuild vorhanden: Hash-basierter (login-freier) Verfügbarkeits-Self-Service plus der Cron, der die Erinnerungen verschickt.

Datenfluss — wohin welche Daten fließen 82 Kanten

Jede Kante aus Sicht des Portals: rein (das Portal empfängt), raus (das Portal sendet), beides (bidirektionaler Sync), intern (Persistenz in eigener DB/Storage). Beim Drupal-Nachbau ist dies die Achse mit dem höchsten Risiko stiller Auslassung.

Browser (Cookie/Session)Datei-StorageE-Mail / SMTPGoogleMailingwork (Newsletter)MySQL (Doctrine)Neos Content Reposito…PersonioRMS / SalesforceSOLCOM-Website (https…Settings.Solcom.yaml …Twitter / XSOLCOMProjektportal (Neos)━ grün: Daten rein━ orange: Daten raus┄ grau: interne Persistenz (PII)
SystemRichtungDaten / PayloadAuslöserFrequenzTransportDrupal-Nachbau
Browser (Multipart-Form-Upload) rein →
  • file binary (PDF/DOC/DOCX)
  • filename
  • MIME type
  • file size
  • CSRF token
ApiController::uploadTempFileAction() — anonymer Gast-Upload während Registrierungsformular (vor Account-Aktivierung) Web-Request (synchron) HTTP-REST multipart/form-data via $_FILES['file'] Drupal-Äquivalent: managed_file field + file.module; PII-relevant (Lebenslauf/Zertifikate von Freelancern). Im Drupal-Nachbau als temporäres verwaltetes File anlegen (file_save_upload), anschließend in eigener Tabelle als 'temporary file' vorhalten bis Registrierung abgeschlossen.
Browser (Multipart-Form-Upload, Registrierungsformular) rein →
  • file binary (PDF/DOC/DOCX)
  • filename
  • MIME type
  • file size
  • file error code
AccountController::registerAction() — Gast lädt Dokumente während der Registrierung hoch (mehrteiliges Formular, vor Account-Aktivierung) Web-Request (synchron) HTTP-REST multipart/form-data via $_FILES['--contenance_solcom-portal_account_register']['tmp_name']['uploads'] In Drupal: Webform- oder Custom-Form mit managed_file Widget; Upload im Registrierungs-Flow speichern und nach E-Mail-Bestätigung an Account koppeln. Zwischenzustand (nicht aktivierter Account) muss in Drupal explizit abgebildet werden.
Browser (Multipart-Form-Upload, authentifizierter Nutzer) rein →
  • file binary (PDF/DOC/DOCX)
  • filename
  • MIME type
  • file size
AccountController::userProfileUploadAction() — eingeloggter Projektpartner lädt Profildokument hoch (max. 5 Dateien) Web-Request (synchron) HTTP-REST multipart/form-data via $_FILES['input_file_name'] Gleiches Muster wie Gast-Upload, aber an authentifizierten User gebunden. Drupal-Nachbau: CSRF-geschützter AJAX-Endpoint mit file_validate_extensions() + file_validate_size(); Ergebnis direkt als managed file an User-Profil-Entity hängen.
Browser-Cookie ('info') — Referer-Tracking rein →
  • url_full (Base64-encoded, max. 252 Zeichen — Herkunfts-URL des Bewerbers)
CareerPersonioController::jobApplicationSendAction() — liest Cookie bei Bewerbungs-Submit; wird als custom_attribute_2722693 und custom_attribute_2722695 an Personio weitergegeben Web-Request (synchron, on-demand) Cookie In Drupal: Cookie 'info' muss beim Bewerbungsformular-Submit ausgelesen werden. Domain-Mapping-Logik (40+ Job-Portale) aus CareerPersonioController::jobApplicationSendAction() Zeilen 752-805 muss eins zu eins übernommen werden — sie bestimmt den Kanal-Label-Wert für Personio-Reporting. PII-Relevanz: MITTEL — URL kann personenbezogene Query-Parameter enthalten.
Browser-Cookie / Session (inbound read) rein →
  • searchParameter (JSON-Array)
  • location (JSON-Array)
  • business (JSON-Array)
  • zip (JSON-Array, gefiltert auf nicht-leere Einträge)
BookmarkController::indexAction() — GET-Request auf Bookmark-Übersichtsseite; Cookies werden gelesen und als Template-Variablen übergeben (Sidebar-Suchparameter wiederherstellen) Web-Request (synchron) Cookie (HTTP Cookie-Header, gelesen via CookieService::getCookie, JSON-dekodiert) Lücke: Drupal-Rebuild muss diese vier Cookies lesen und an den Bookmark-View übergeben; wahrscheinlich werden diese Cookies von einem separaten Suchformular-Controller gesetzt (nicht in diesen drei Dateien); Suchparameter-Cookie-Persistenz muss ebenfalls implementiert werden
Mailingwork (login.mailingwork.de/webservice/webservice/json/) rein →
  • email
  • listId '1'
  • result (0=nicht in Liste, 1=in Liste)
AccountController::profileAction() — beim Laden der Profilseite wird geprüft, ob die E-Mail des eingeloggten Nutzers in der Mailingwork-Liste steht (isemailinlist), um den Subscribe/Unsubscribe-Toggle korrekt darzustellen Web-Request (synchron, bei jedem Profilseitenaufruf) HTTP-REST (cURL POST) Leicht redundant mit user.field_magazine_option. Drupal-Nachbau kann auf das lokale Feld zurückgreifen, statt Mailingwork live abzufragen — Performance-Vorteil. Lücke: Konsistenz zwischen lokalem Flag und Mailingwork-Stand sicherstellen (bei externem Unsubscribe).
Personio XML Feed (https://solcom-gmbh.jobs.personio.de/xml) rein →
  • id
  • office
  • department
  • recruitingCategory
  • name
  • subcompany
  • jobDescriptions
  • employmentType
  • seniority
  • schedule
  • yearsOfExperience
  • occupation
  • occupationCategory
  • createdAt
PersonioCommandController::importCommand() — CLI-Command `./flow personio:import` Cron (Intervall nicht in Code festgelegt; muss in Cron-Tab konfiguriert werden) HTTP-REST (cURL GET, Accept: text/xml) — curlPersonioProjects() in PersonioService Entspricht dem geplanten Drupal-Modul für Stellenanzeigen-Import. Noch nicht vorhanden. Benötigt: Drupal Migrate-Source oder Custom-Service, der den XML-Feed pollt und in eine lokale Entity (Node-Type 'job_posting' o.ä.) persistiert. PII-Relevanz: keine in den Stellendaten.
SOLCOM-Website (https://www.solcom.de/asp/robots/projectReferences.xml) rein →
  • ID
  • Headline
  • Name (Kundenname)
  • Industry
  • ProjectDescription
  • Tags
CLI-Befehl `referenceProject:import` (ReferenceProjectCommandController::importCommand) holt XML via HTTP GET curl Cron (on-demand; Intervall nicht im Paket definiert) HTTP-REST (curl GET, SSL-Verifikation deaktiviert) Referenzprojekte (Kundenprojekte, nicht Freelancer-Angebote) sind ein separater Content-Typ. Im Drupal-Nachbau als `reference_project`-Node-Typ oder via Migration zu behandeln. Kein direktes Äquivalent in solcom_project_offer — eigenes Migrate-Modul erforderlich. Kein PII, aber Kundennamen sind potentiell vertraulich.
Salesforce/RMS (via Filesystem-Drop: OpenPos.xml) rein →
  • lfdNr (Projekt-ID)
  • Titel
  • Beschreibung
  • Start
  • Dauer
  • Ort
  • Stellentyp
  • Bearbeiter
  • Aktiv
  • Anforderungen
  • Zusatz
  • GULPRobot
  • aktiviert_am
  • deaktiviert_am
  • LastModified
  • LastModifiedBearbeiter
  • Region
  • Unternehmensbereich (enthält 'Salesforce' als Wert)
  • Branche
  • keinePortale
  • skills
  • tags
  • FreelancerLink (minggo.io-URL)
  • AgencyLink (minggo.io-URL)
CLI-Befehl `project:import` (ProjectCommandController::importCommand) liest die Datei Resources/Private/Import/OpenPos.xml via simplexml_load_file() Cron (on-demand / scheduled, kein Crontab im Paket gefunden; Intervall liegt außerhalb des Pakets) Dateisystem (XML-Datei, UTF-16 codiert, Filesystem-Drop durch externen Prozess — vermutlich Salesforce-Export) Im Drupal-Nachbau (solcom_project_offer) übernimmt ein Migrate-Sync-Modul diese Rolle. Die Datei OpenPos.xml entspricht dem Feed `field_sf_*`-Feldern auf dem node type `project_offer`. PII-relevant: Bearbeiter-E-Mail (z.B. a.peeke l@solcom.de) steckt im XML-Feld und landet in `contact`.
Flow PersistentResource / Filesystem (FLOW_PATH_DATA/Persistent/Resources/<sha1[0]>/<sha1[1]>/<sha1[2]>/<sha1[3]>/<sha1>) ⛁ intern
  • file binary (SHA1-adressiert)
  • sanitisierter Dateiname (alphanum + _.-)
  • MIME type
  • Dateigröße
  • creationDate
ResourceManager::importUploadedResource() — aufgerufen aus UploadService::uploadFile(), AccountController::registerAction() und AccountController::userProfileUploadAction() Web-Request (synchron), on-demand Dateisystem (PHP file_get_contents / Stream-API) Flow speichert Binärdaten als SHA1-Content-Addressed-Files unter Data/Persistent/Resources/. In Drupal: public:// oder private:// managed files (sites/default/files/). PII-kritisch: Lebensläufe/Zertifikate müssen in private:// liegen, nicht public://. Entspricht Drupal FileInterface + ManagedFile-Widget.
Flow-Session (PHP-Session-Storage) ⛁ intern
  • projects (array von Projekt-IDs/Strings — Lesezeichen des anonymen/eingeloggten Besuchers)
Bookmark::addProject() — aufgerufen bei Klick auf Merken-Button; @Flow\Session(autoStart=true) Web-Request (synchron) Session Bookmark ist @Flow\Scope('session') — kein Doctrine-Entity, kein DB-Persist; im Drupal-Rebuild als PHP-Session oder Drupal-TempStore abgebildet; für angemeldete User im Drupal-Rebuild per Watchlist-Node-Reference persistiert
Flow-Session (server-side) ⛁ intern
  • Bookmark.projects (Array von Project-UUIDs, max. 7 Einträge)
BookmarkController::addAction() / removeAction() / removeAllAction() — AJAX-Requests vom Browser; Bookmark-Model ist @Flow\Scope('session'), wird server-seitig in Flow-Session persistiert Web-Request (synchron, on-demand per AJAX) Session (Neos Flow Session-Storage, server-seitig; Client hat nur Session-Cookie-ID) In Drupal: $_SESSION oder tempstore (PrivateTempStore) für anonyme/authentifizierte User; max. 7 Bookmarks ist Business-Regel die übernommen werden muss
MySQL-Tabelle: contenance_solcom_domain_model_accountchangeflow ⛁ intern
  • oldEmail
  • newEmail
  • activationToken (30-Zeichen-Zufallsstring)
  • activationTokenValidUntil (datetime)
  • creationDate (datetime)
E-Mail-Adresse-Ändern-Formular-Submit (zweistufige Bestätigung) Web-Request (synchron) DB-Doctrine Im Drupal-Rebuild als solcom_projektpartner AccountChangeFlow abgebildet (bereits implementiert in P2503-230); speichert old+new E-Mail transient bis Bestätigung — PII-Retention-Window beachten
MySQL-Tabelle: contenance_solcom_domain_model_availability ⛁ intern
  • staffId (PersonalNr des SOLCOM-Mitarbeiters)
  • projectPartner (Projektnummer/RMS-Referenz)
  • type (Mailtyp integer)
  • email (E-Mail-Adresse des Projektpartners)
  • salutation (Anredezeile)
  • staff (Name SOLCOM-Mitarbeiter)
  • editor (Bearbeiter-Name)
  • hash
  • creationDate
  • status (1=Mail gesendet, 2=PP hat geantwortet)
Verfügbarkeitsabfrage durch SOLCOM-Mitarbeiter (CRM-seitig initiiert) Web-Request (synchron) / on-demand DB-Doctrine Kein direktes Äquivalent im aktuellen Drupal-Rebuild erkennbar; enthält PII beider Seiten (PP-E-Mail + Mitarbeiter-Name/ID); bei Migration prüfen ob als Custom-Entity oder als Webform-Submission abzubilden
MySQL-Tabelle: contenance_solcom_domain_model_donation ⛁ intern
  • salutation
  • personalAddress
  • title
  • firstname
  • lastname
  • email (Empfänger/Projektpartner)
  • salesPersonalAddress
  • salesName (Name des SOLCOM-Sales-Mitarbeiters)
  • salesEmail (E-Mail des SOLCOM-Sales-Mitarbeiters)
  • mailStatus (0–4 Fortschritts-Integer)
  • donationStatus (bool)
  • creationDate
  • spendDate
  • donationOrganisation FK
Spendenformular-Submit (SOLCOM-internes Kampagnen-Feature) Web-Request (synchron) DB-Doctrine Einmaliges Kampagnen-Feature; kein Äquivalent im aktuellen Drupal-MVP; enthält PII beider Seiten (Projektpartner + Sales-Mitarbeiter); bei Drupal-Migration: Post-MVP oder als Webform-Submission mit Consent-Flag; salesEmail ist interne Mitarbeiter-E-Mail
MySQL-Tabelle: contenance_solcom_domain_model_lead2faform ⛁ intern
  • token (Zufalls-Token — verknüpft Lead mit 2FA-Opt-in)
  • accepted (bool)
  • acceptedDate (datetime, nullable)
2FA-/Lead-Formular-Consent-Klick Web-Request (synchron) DB-Doctrine Kein direktes Äquivalent erkennbar; token allein ist kein PII, aber Verknüpfung zum Lead impliziert personenbezogenen Kontext; im Drupal-Rebuild als Consent-Log-Feld am User-Profil oder als Webform-Submission abzubilden
MySQL-Tabelle: contenance_solcom_domain_model_projectalert ⛁ intern
  • tags (array — Suchfilter-Kriterien)
  • noTags (array — Ausschluss-Tags)
  • states (array — Projektstatus-Filter)
  • hash
  • active (bool)
  • user FK → contenance_solcom_domain_model_user (nullable)
  • guest FK → contenance_solcom_domain_model_projectalertguest (nullable, cascade all)
Projektbenachrichtigung-Einrichten durch registrierten User oder Gast Web-Request (synchron); Benachrichtigungs-Versand via Cron DB-Doctrine Im Drupal-Rebuild als solcom_project_offer Watchlist-Modul abgedeckt; user-FK ist PII-Verknüpfung; guest-Zweig enthält eigenständige PII (→ ProjectAlertGuest-Kante)
MySQL-Tabelle: contenance_solcom_domain_model_projectalertguest ⛁ intern
  • gender
  • firstName
  • lastName
  • email
Projektbenachrichtigung-Einrichten durch nicht-registrierten Gast Web-Request (synchron) DB-Doctrine Gast-PII ohne Account-Binding; im Drupal-Rebuild prüfen ob als anonyme Webform-Submission oder eigene Entity abzubilden; DSGVO-Lösch-Mechanismus fehlt in Neos-Quelle
MySQL-Tabelle: contenance_solcom_domain_model_registrationflow ⛁ intern
  • email
  • encryptedPassword (bcrypt-Hash)
  • notified (bool)
  • attributes (json_array — enthält firstName, lastName und weitere Registrierungsfelder)
  • activationToken (30-Zeichen-Zufallsstring)
  • activationTokenValidUntil (datetime)
Registrierungs-Formular-Submit (vor Account-Aktivierung) Web-Request (synchron) DB-Doctrine In Drupal durch solcom_projektpartner ProjektpartnerRegistrationSubmitter ersetzt; kein persistenter Flow-Zwischenspeicher — Token-basierte Aktivierung per E-Mail; Lücke: attributes-JSON enthält Registrierungsdaten, die im Drupal-Rebuild explizit gemappt werden müssen
MySQL-Tabelle: contenance_solcom_domain_model_resetpasswordflow ⛁ intern
  • email
  • resetPasswordToken (30-Zeichen-Zufallsstring)
  • resetPasswordTokenValidUntil (datetime)
Passwort-vergessen-Formular-Submit Web-Request (synchron) DB-Doctrine In Drupal durch Core-eigenen Passwort-Reset-Flow (user/password) abgedeckt; kein Custom-Entity nötig
MySQL-Tabelle: contenance_solcom_domain_model_sommerfestperson ⛁ intern
  • gender
  • firstname
  • lastname
  • email
  • qrCode
  • identification
  • status (0=kein Eintrag, 1=Eintrag vorhanden)
  • creationDate
  • submitDate
Sommerfest-Anmeldeformular-Submit Web-Request (synchron) DB-Doctrine Einmaliges Event-Feature; kein Äquivalent im aktuellen Drupal-MVP geplant; enthält vollständige personenbezogene Kontaktdaten inkl. QR-Code-Verknüpfung; bei Migration auf Post-MVP schieben oder als Webform-Modul-Submission abbilden
MySQL-Tabelle: contenance_solcom_domain_model_user ⛁ intern
  • email (NotNull, unique via account FK)
  • title
  • salutation
  • gender
  • firstName
  • lastName
  • hourlyrate
  • birthday
  • company
  • phone
  • mobile
  • website
  • streetAndNumber
  • zip
  • city
  • country
  • countryAll
  • countryA
  • countryCh
  • countryDe
  • countryInternational
  • available
  • location
  • networkGulpId
  • networkFreelancer
  • networkXing
  • networkFreelancermap
  • professionalEmphases
  • magazine
  • skills (text, max 1000)
  • applicationText (text, max 2500)
  • rmsNumber
  • languages (array/json)
  • bookmarks (array/json)
  • policyOption (bool)
  • magazineOption (bool)
  • lastAccountEditDate (datetime)
  • account FK → neos_flow_security_account
Registrierung, Profil-Update, Account-Anlage Web-Request (synchron) DB-Doctrine Drupal-Entsprechung: user-Entity + zugehörige Profilfelder als Custom-Fields (field_rms_number, field_skills, field_available etc.); PII-Felder vollständig vorhanden laut NeosProjektpartnerUser-Migration; bookmarks-Array im Drupal-Watchlist-Modul als eigene Entität gelöst
MySQL/Doctrine ORM (Neos-Datenbank, Tabelle contenance_solcom_domain_model_project) ⛁ intern
  • id (lfdNr)
  • nodeId
  • creationDate
  • title
  • description
  • start
  • duration
  • location
  • type
  • contact
  • active
  • requirements
  • additional
  • gulp
  • activated
  • deactivated
  • modified
  • modifiedContact
  • state
  • division
  • business
  • noPortal
  • skills
  • tags
  • freelancerLink
  • agencyLink
ProjectCommandController::createProject() bei neuer lfdNr; ::updateProject() wenn LastModified im XML neuer als in DB; ::removeProject() bei fehlender lfdNr im aktuellen XML-Lauf Cron (synchron mit import-Command) DB-Doctrine (Neos Flow PersistenceManager::persistAll()) Drupal-Äquivalent: node type `project_offer` mit Feldern field_sf_lfd_nr, field_sf_title, etc. (bereits implementiert in solcom_project_offer). Lücke: `noPortal`-Flag (keinePortale) steuert, ob ein Projekt im Portal erscheint — muss als Boolean-Feld (field_sf_no_portal) im Drupal-Node vorhanden sein und im View-Query ausgewertet werden.
MySQL/Doctrine ORM (Tabelle contenance_solcom_domain_model_rms) ⛁ intern
  • counter (Integer, autoinkrement bei jeder Neuregistrierung)
FlowUserCreationService::createUserFromRegistrationFlow() bei erfolgreicher Registrierung eines neuen Projektpartners; UtilityCommandController::setCounterCommand() / createCounterCommand() / setCounterToUsersCommand() für Admin-Operationen Web-Request (Registrierung) + on-demand CLI DB-Doctrine (Neos Flow PersistenceManager) Im Drupal-Nachbau (solcom_projektpartner) bereits als `field_rms_number` auf dem User-Entity implementiert. Der Counter selbst muss als Singleton-Config (State-Tabelle oder Drupal State API) migriert werden. Startwert laut Code: 121212. PII-Relevanz: RMS-Nummer ist interner Identifier, aber indirekt personenbezogen (verknüpft mit User-Account).
MySQL/Doctrine ORM (Tabelle contenance_solcom_domain_model_user, Feld rms_number) ⛁ intern
  • rmsNumber (String, entspricht dem aktuellen RMS-Counter-Wert zum Zeitpunkt der Registrierung)
FlowUserCreationService::createUserFromRegistrationFlow() — direkt nach Anlage des User-Objekts wird der aktuelle Counter-Wert als rmsNumber gesetzt und der Counter inkrementiert Web-Request (einmalig bei Nutzer-Registrierung) DB-Doctrine Bereits in Drupal migriert als `field_rms_number` (solcom_projektpartner-Modul). Wichtig: die Zuweisung ist atomar (read-then-increment) — bei parallelen Requests Race-Condition-Risiko in der Legacy-Implementierung; im Drupal-Rebuild via Database-Transaction oder Sequence absichern.
MySQL/Doctrine ORM — lokale PersonioProject-Entity (Cache-Tabelle) ⛁ intern
  • id (Personio-ID)
  • subcompany
  • office
  • department
  • recruitingCategory
  • name
  • jobDescriptions (JSON/Array)
  • employmentType
  • seniority
  • schedule
  • yearsOfExperience
  • occupation
  • occupationCategory
  • createdAt
  • nodeId (Neos-Content-Repository-UUID)
  • creationDate
PersonioCommandController::importCommand() — create/modify/remove je nach Diff gegen Live-Feed Cron (sync mit XML-Fetch) DB-Doctrine (PersonioProjectRepository add/update/remove) In Drupal: node-basierte oder custom-entity-basierte lokale Cache-Tabelle für Personio-Stellenanzeigen. Schlüssel-Feld ist die numerische Personio-ID ('id'). jobDescriptions ist ein Array (serialisiert), in Drupal als JSON-Feld oder Paragraph-Entity abzubilden. nodeId-Äquivalent entfällt (Neos-spezifisch).
MySQL/Doctrine — Tabelle contenance_solcom_domain_model_temporaryfile ⛁ intern
  • UUID (identifier)
  • type ('guest')
  • creationDate (DateTime)
  • originalResource FK → neos_flow_resourcemanagement_persistentresource
UploadService::uploadFile() — nach erfolgreichem importUploadedResource(); Lifecycle: bleibt bis zum manuellen Cleanup über SolcomResourcesCommandController::cleanCommand() Web-Request (synchron) beim Upload; Cleanup on-demand (Cron/manuell) DB-Doctrine (Neos Flow ORM) Kein direktes Drupal-Core-Äquivalent für 'temporäre Datei mit explizitem DB-Record'. In Drupal: status=0 in file_managed (temporäre Dateien werden nach 6h von file_cron() gelöscht). Lücke: Legacy hält TemporaryFile unbegrenzt — Drupal-Nachbau braucht expliziten Cleanup-Cron (hook_cron) oder temporäre Files mit TTL.
MySQL/Doctrine — Tabelle contenance_solcom_domain_model_userfile (JOINED Inheritance) ⛁ intern
  • UUID (identifier)
  • title (sanitisierter Dateiname)
  • creationDate (DateTime)
  • shouldNotSendAsNotification (bool)
  • originalResource FK
  • user FK → User (nullable)
  • registrationFlow FK → RegistrationFlow (nullable)
AccountController::registerAction() (Registrierung, FK auf RegistrationFlow), AccountController::userProfileUploadAction() (eingeloggter Nutzer, FK auf User). Max. 5 Dateien pro User. Web-Request (synchron) DB-Doctrine (Neos Flow ORM) In Drupal als eigenes Content Entity 'user_document' oder als field_file auf einem Custom-Profile-Entity modellieren. Wichtig: shouldNotSendAsNotification muss als Boolean-Feld erhalten bleiben (steuert tägliche Notification-E-Mail). FK auf RegistrationFlow wird nach Account-Aktivierung auf User umgeschrieben — in Drupal entsprechend im Hook nach Account-Aktivierung migrieren. PII: Lebensläufe, Zertifikate.
MySQL/Doctrine — neos_flow_resourcemanagement_persistentresource (Flow Core-Tabelle) ⛁ intern
  • sha1 (Content-Hash)
  • filename (sanitisiert)
  • mediaType
  • collectionName
  • fileSize
ResourceManager::importUploadedResource() — wird von UploadService und AccountController aufgerufen; PersistentResource ist das zentrale Binär-Registry-Objekt, auf das UserFile und TemporaryFile per FK zeigen Web-Request (synchron) beim Upload; Cleanup via SolcomResourcesCommandController::cleanCommand() (on-demand/manuell) DB-Doctrine (Neos Flow ORM) Entspricht Drupal-Kern-Tabelle file_managed (fid, uri, filename, filemime, filesize, status). Cascade-Delete: UserFile hat cascade={'all'} auf originalResource — beim Löschen eines UserFile wird auch der PersistentResource-Eintrag und die Datei auf dem Filesystem entfernt. In Drupal: file_usage_delete() + file_delete() korrekt aufrufen.
Neos Content Repository (NodeData, Workspace 'live') ⛁ intern
  • title
  • titleOverride
  • metaDescription
  • contentForSearch (description+requirements+skills+tags)
  • uriPathSegment (title-lfdNr)
  • canonicalLink
  • openGraphType
  • openGraphTitle
  • openGraphDescription
  • openGraphImage
  • projectid (Doctrine UUID des Project-Objekts)
  • NodeType: Contenance.Solcom:Document.Portal.Project
ProjectCommandController::createProjectNode() bei Neuanlage; removeProjectNode() bei Löschung; updateProjectNode() ist implementiert aber auskommentiert (nicht aktiv) Cron (synchron mit import-Command) DB-Doctrine (Neos ContentRepository NodeDataRepository) Im Drupal-Nachbau keine 1:1-Entsprechung nötig: Drupal-Nodes sind bereits die kanonischen Entitäten. `contentForSearch` (Volltextfeld) wird in Drupal durch Search-API-Indexierung ersetzt. `projectid`-Referenz entfällt (Node = Entity). Relevant: `uriPathSegment`-Logik → Drupal pathauto mit Muster `/fuer-freiberufler/projektliste/[title]-[lfdNr]`.
Neos Content Repository (NodeData, Workspace 'live', NodeType Contenance.Solcom:Document.ReferenceProject) ⛁ intern
  • title (Headline)
  • titleOverride
  • metaDescription
  • uriPathSegment (Headline-ID)
  • description (ProjectDescription, nl2br)
  • customer (Name)
  • branche (Industry)
  • words (Tags)
  • NodeType: Contenance.Solcom:Document.ReferenceProject
ReferenceProjectCommandController::createNodes() nach erfolgreichem XML-Fetch; pruneCommand() löscht alle bestehenden Nodes vor Re-Import Cron (synchron mit referenceProject:import) DB-Doctrine (Neos ContentRepository) Im Drupal-Nachbau eigener Node-Typ nötig. Wichtig: pruneCommand + createNodes ist ein vollständiger Replace-Zyklus (kein Delta-Import) — im Drupal-Rebuild als Truncate+Reimport oder via Migrate-Rollback+Run abbilden.
Settings.Solcom.yaml (interne Konfiguration) ⛁ intern
  • mailingworks.username ('Geopard')
  • mailingworks.password ('Ben1ist_Super!')
  • mailingworks.listId ('1')
Applikationsstart / Service-Injection (injectSettings) einmalig (pro Request-Lifecycle) Dateisystem (Flow-Settings-Injection) Credentials NICHT in VCS übernehmen. Drupal-Nachbau: settings.php / .env mit MAILINGWORK_USERNAME, MAILINGWORK_PASSWORD, MAILINGWORK_LIST_ID. listId='1' entspricht Liste 'SOLCOM Freiberufler Magazin' — diese ID im Drupal-Config-Objekt hinterlegen.
Browser (authentifizierter Download / Inline-Anzeige) → raus
  • file binary (via SHA1)
  • filename
  • MIME type
  • Content-Length
ApiController::getpersistentfileAction() — eingeloggter Nutzer ruft /api/getpersistentfile?file=<sha1/path> auf; Authentifizierung wird vor Auslieferung geprüft Web-Request (synchron) HTTP (header() + echo; kein Framework-Response-Objekt — direktes PHP-Streaming) Sicherheitslücke im Legacy-Code: sha1 wird aus URL-Parameter extrahiert, jede bekannte SHA1 ist abrufbar (kein User-Scope-Check). Drupal-Nachbau muss private:// + hook_file_download() mit explizitem Owner/Role-Check implementieren. PII-kritisch.
Browser-Cookie → raus
  • info.time
  • info.url_full (base64-encoded HTTP_REFERER or utm_source value)
SolcomComponent::handle() — jeder eingehende HTTP-Request auf karriere.solcom.de mit gesetztem HTTP_REFERER oder utm_source-Query-Param; externer Referrer überschreibt bestehenden Cookie nur wenn er nicht von solcom.de-Domains stammt Web-Request (synchron, jeder Page-Load) Cookie (Set-Cookie HTTP-Header, Laufzeit +1 Tag, JSON-codiert) Lücke: Drupal hat keinen Äquivalent; muss als custom EventSubscriber (KernelEvents::REQUEST) oder Middleware neu gebaut werden; PII-Relevant: URL kann personenbezogene Suchanfragen enthalten
Browser-Cookie → raus
  • utm_time.time
  • utm_time.utm_source
  • utm_time.utm_sourcename
  • utm_time.utm_medium
SolcomComponent::handle() — Request mit utm_source + utm_medium + utm_campaign=Jobposting Query-Params; bekannte utm_source (freelancercheck.de, gulp.de, jooble.org, xing.com, projektwerk.com, job.trovit.com); Cookie wird nach 24h gelöscht Web-Request (synchron) Cookie (Set-Cookie, Laufzeit +1 Tag, JSON-codiert) Lücke: kein Äquivalent; custom Middleware oder EventSubscriber nötig; utm_sourcename ist normalisierter Plattformname (Enum-artiger Wert), muss Mapping aus getUtmSourceName() übernommen werden
Browser-Cookie → raus
  • utm_measurement.time
  • utm_measurement.utm_source
  • utm_measurement.utm_medium
  • utm_measurement.utm_campaign (optional)
SolcomComponent::handle() — Request mit utm_source + utm_medium Query-Params (unabhängig von utm_campaign); @deprecated laut Code-Kommentar; Cookie wird nach 24h gelöscht Web-Request (synchron) Cookie (Set-Cookie, Laufzeit +1 Tag, JSON-codiert) Deprecated in Legacy — für Drupal-Rebuild evaluieren ob Measurement Protocol noch aktiv genutzt wird; wenn ja: custom Middleware; wenn nein: weglassen
Browser-Cookie → raus
  • utm_ads.time
  • utm_ads.utm_campaign (444=XING, 555=LinkedIn, 666=Bing, 333=Google)
SolcomComponent::handle() — Request mit utm_campaign in {444,555,666} ODER gid=googleadsfreelancer Query-Param; Cookie wird nach 24h gelöscht Web-Request (synchron) Cookie (Set-Cookie, Laufzeit +1 Tag, JSON-codiert) Lücke: Werbeplattform-Tracking-Cookie; custom Middleware; Kampagnen-ID-Mapping (numerische Codes → Plattformnamen) muss ins Drupal-Äquivalent übernommen werden
Browser-Cookie → raus
  • sol_act (AES-128-CBC-verschlüsselt, base64): time, msclkid (Microsoft Ads Click-ID), gclid (Google Ads Click-ID)
SolcomComponent::handle() — Request mit msclkid oder gclid Query-Param (Enhanced Conversion Tracking); Cookie wird nach 24h gelöscht; Verschlüsselungskey '«redigiert»' hardcoded Web-Request (synchron) Cookie (Set-Cookie, Laufzeit +1 Tag, AES-128-CBC-verschlüsselt + base64) PII-kritisch: msclkid/gclid sind nutzerbezogene Ad-Click-IDs; hardcodierter Schlüssel muss in Drupal-Config/Secrets-Management; Drupal-Rebuild als custom EventSubscriber; Verschlüsselung beibehalten oder durch httpOnly+Secure-Cookie ersetzen
Google Analytics Measurement Protocol (UA-17264541-1 / UA-17264541-4) → raus
  • v (protocol version=1)
  • t (hit type: event)
  • tid (UA-17264541-1 Projektportal / UA-17264541-4 Karriere)
  • cid (timestamp als Client-ID)
  • ec (event category: Projektbewerbung / Karriere Bewerbung / B2B Anfrage / Account Manager Karriere Bewerbung / MP-Variante)
  • ea (event action: Bewerbungsart + guestOrUser)
  • el (Protocol - transactionId)
  • ti (transaction ID, inkrementeller Zähler aus Statistics-Entity)
  • pa (product action: purchase)
  • pr1id / pr2id... (project.getId())
  • pr1nm / pr2nm... (project.getTitle())
  • pr1ca / pr2ca... (Projektportal / Karriere/<office>)
  • geoid (DE)
  • cs / cm / cn (utm_source, utm_medium, utm_campaign aus utm_measurement-Cookie)
ProjectController->sendAction (Bewerbung abschicken), CareerPersonioController->sendAction (Karrierebewerbung abschicken), FormsController->sendForm (B2B Kontaktformular), LandingpageFinisher->executeFinisher (Landingpage-Formular) Web-Request (synchron, server-side cURL im Request-Thread) HTTP-REST (cURL POST zu http://www.google-analytics.com/collect) Lücke — noch nicht implementiert. In Drupal: Measurement Protocol Calls oder serverseitiges GA4 Event API bei Bewerbungs-Submission; UTM-Cookie-Auslesen via custom Service. PII-Relevanz: transactionId, projectId/-name, UTM-Daten. UA ist deprecated → auf GA4 Measurement Protocol migrieren.
Google Fonts (fonts.googleapis.com) → raus
  • Font-Familie: Open+Sans (300,400,600,700)
  • Font-Familie: Lato (300,300italic,400,400italic,700,700italic,900) — nur ACP-Vendor-Asset
  • Font-Familie: Raleway (300,400,600) — nur Dashboard-Template
Jeder Seitenaufruf (HTML <link rel=stylesheet> im <head>) Web-Request (synchron, beim Seitenload) HTTP-REST (CSS-Request an fonts.googleapis.com, nachfolgende Font-Datei-Requests an fonts.gstatic.com) Bekannt und abgedeckt — in Drupal-Neuaufbau prüfen ob Fonts self-hosted werden sollen (DSGVO-konform) oder ob der CDN-Link beibehalten wird. byte_theme nutzt ggf. bereits eigene Font-Einbindung. Kein API-Key erforderlich.
Google Maps JavaScript API (key: AIzaSyBz3SQLqUNyB_nvdm4E1FortzlH3oWRId8) → raus
  • API Key: AIzaSyBz3SQLqUNyB_nvdm4E1FortzlH3oWRId8
  • lat (Breitengrad aus Neos-Node-Property)
  • lng (Längengrad aus Neos-Node-Property)
  • title (Marker-Titel aus Neos-Node-Property)
  • node.identifier (Map-Container-ID)
Seitenaufruf einer Seite mit Contenance.Solcom:Map-ContentElement, NACHDEM der Benutzer Google-Maps-Cookies im Cookie-Consent-Banner akzeptiert hat; Cookie-Consent-Callback lädt maps.google.com/maps/api/js dynamisch nach Web-Request (on-demand, client-side nach Cookie-Consent) HTTP-REST (dynamisch geladenes JS-Bundle von maps.google.com) Lücke — Google Maps API Key muss in Drupal-Konfiguration (Umgebungsvariable / settings.php) hinterlegt werden. Cookie-Consent-Integration mit Drupal EU Cookie Compliance oder ähnlichem Modul. API Key ist im Legacy-Code im Klartext in Script.js — in Drupal als Config-Secret ablegen. PII-Relevanz: IP-Adresse wird an Google übermittelt.
Google Tag Manager (GTM-ND5SFNH) / Google Analytics (client-side dataLayer) → raus
  • GTM-Container-ID: GTM-ND5SFNH
  • dataLayer events: Pageview, Impression, productClick, addToCart, checkout, transaction, purchase, begin_checkout, view_item
  • projekt-id (Projekt-Nummer aus DOM)
  • projektname, projectcategory, projectposition, projectlist
  • Bewerbungsart (User / Gast)
  • transactionId (inkrementeller Zähler)
  • Karriere-Felder: item_standort, item_anstellung, item_starttermin, item_erfahrung
  • Kontaktformular-Felder: sha256_email_address, sha256_phone_number, msclkid, gclid (aus data-Attributen im HTML)
  • Events: Anmeldung Projektalert, Nutzerregistrierung durchgeführt, Userlogin, B2B Anfrage abgeschickt, Karriere Lead, Rückruf Anfrage, Projektsuche durchgeführt + Suchbegriff + Trefferzahl
Jeder Seitenaufruf (Pageview/Impression automatisch); Click-Events auf Bewerbungs-CTA, Projektdetail-Links; Form-Submission-Success-Pages (DOM-Flag-Klassen wie .dataLayerPurchase, .dataLayerTransactionCareer, .contactformsendsucc) Web-Request (client-side JavaScript, synchron beim Seitenload / on-demand bei User-Interaktion) HTTP-REST (GTM JS-Snippet lädt gtm.js von googletagmanager.com; dataLayer-Pushes werden von GTM an GA/Ads weitergeleitet) Lücke — GTM-Snippet und dataLayer-Pushes müssen 1:1 in Drupal reproduziert werden. Kandidaten: Drupal Google Tag-Modul (contrib) für GTM-Snippet; custom JS in byte_theme für dataLayer-Pushes auf Projekt-/Karriere-Seiten. sha256_email/phone sind PII-relevant. msclkid/gclid = Conversion-Tracking für Ads.
Mailingwork (login.mailingwork.de/webservice/webservice/json/) → raus
  • title (Herr/Frau)
  • lastName
  • firstName
  • email
  • optinSetupId (=listId '1', list: 'SOLCOM Freiberufler Magazin')
AccountController::registerAction() — Nutzer registriert sich und setzt magazine='true' im Registrierungsformular (Double-Opt-In via optinrecipient-Endpoint) Web-Request (synchron) HTTP-REST (cURL POST, application/x-www-form-urlencoded), credentials username='Geopard'/password in Settings.Solcom.yaml PII-relevant (Vorname, Nachname, E-Mail). Im Drupal-Nachbau: ProjektpartnerRegistrationSubmitter oder entsprechender EventSubscriber nach erfolgreicher Registrierung; Opt-In-Checkbox im Registrierungsformular muss erhalten bleiben. Mailingwork-Credentials in .env / Drupal-Config (nicht in VCS). Lücke: Double-Opt-In-Flow (optinrecipient) muss nachgebaut werden.
Mailingwork (login.mailingwork.de/webservice/webservice/json/) → raus
  • title (Herr/Frau)
  • lastName
  • firstName
  • email
  • optinSetupId (=listId '1')
AccountController::addMagazineAction() — eingeloggter Nutzer abonniert nachträglich das Freiberufler-Magazin über die Profilseite Web-Request (synchron) HTTP-REST (cURL POST) PII-relevant. Drupal-Nachbau: eigener Route/Controller-Action im Projektpartner-Profil. Nach erfolgreichem API-Call: user.field_magazine_option = TRUE setzen (analog User::setMagazineOption). Vorhanden als ProjektpartnerMailManager-Modul — Mailingwork-Subscribe-Methode fehlt noch.
Mailingwork (login.mailingwork.de/webservice/webservice/json/) → raus
  • email
  • listId '1'
  • recipientId (intern via getrecipientidbyemail lookup)
AccountController::removeMagazineAction() — eingeloggter Nutzer kündigt Magazin-Abo über die Profilseite (deleterecipientbyid nach ID-Lookup) Web-Request (synchron) HTTP-REST (cURL POST, zwei sequenzielle Calls: getrecipientidbyemail + deleterecipientbyid) PII-relevant (Löschung). Drupal-Nachbau muss zwei API-Calls kapseln. Nach Erfolg: user.field_magazine_option = FALSE. Lücke: kein Unsubscribe-Flow im aktuellen solcom_projektpartner-Modul.
Mailingwork (login.mailingwork.de/webservice/webservice/json/) → raus
  • title (Herr/Frau)
  • lastName
  • firstName
  • email
  • optinSetupId (=listId '1')
ProjectAlertController::projectAlertSendAction() — Nutzer (eingeloggt oder Gast) legt Projekt-Alert an und setzt newsletter='1' im Formular Web-Request (synchron) HTTP-REST (cURL POST) PII-relevant. Drupal-Nachbau: beim Erstellen eines Projekt-Alerts (WatchlistController oder dedizierter ProjectAlertController) Newsletter-Checkbox auswerten und ggf. subscribe aufrufen. Eingeloggte Nutzer erhalten zusätzlich user.field_magazine_option = TRUE.
Mailingwork (login.mailingwork.de/webservice/webservice/json/) → raus
  • title (salutation)
  • lastName (lastname)
  • firstName (firstname)
  • email
  • optinSetupId (=listId '1')
EmailFinisher::executeInternal() — Neos-Form-Finisher; wird nach Absenden eines beliebigen Neos-Forms (vermutlich Lead-Formular / Kontaktformular) ausgeführt, das mit diesem Finisher konfiguriert ist Web-Request (synchron) HTTP-REST (cURL POST) PII-relevant. Drupal-Nachbau: Webform-Handler oder Custom Submit Handler für das entsprechende Lead-/Kontaktformular. Kein magazine-Guard — jeder Form-Submit trägt ein. Prüfen, ob das Formular im Drupal-Rebuild existiert (Lead-Formular via Webform-Modul?). Lücke: unklar, welche Neos-Form-Instanzen diesen Finisher nutzen — alle betroffenen Formulare inventarisieren.
Mailingwork (login.mailingwork.de/webservice/webservice/json/) → raus
  • title (Herr/Frau)
  • lastName (formFields.lastname)
  • firstName (formFields.firstname)
  • email (formFields.email)
  • optinSetupId (=listId '1')
ProjectController::jobApplicationSendAction() — Nutzer bewirbt sich auf ein Projekt und setzt magazine='true' im Bewerbungsformular Web-Request (synchron) HTTP-REST (cURL POST) PII-relevant. Drupal-Nachbau: beim Einreichen einer Projektbewerbung (falls nachgebaut) Newsletter-Checkbox mitführen. Bewerbungsformular ist im aktuellen Drupal-Scope (solcom_project_offer) nicht vollständig vorhanden — Lücke erfassen.
MySQL-Tabelle / Doctrine-Entity (User) → raus
  • user.bookmarks (Array von Project-UUIDs — Sync aus Session bei Login)
BookmarkController::addAction() — wenn $account vorhanden (eingeloggter User): Session-Bookmarks werden in user.bookmarks geschrieben + persistenceManager->persistAll() Web-Request (synchron, on-demand per AJAX) DB-Doctrine (Neos Flow PersistenceManager::persistAll) Gast-zu-Login-Migration: Session-Bookmarks werden bei addAction eines eingeloggten Users in die DB migriert; Drupal-Rebuild muss diesen Sync-Trigger in der Watchlist-Implementierung abbilden (bei Login: Session-Watchlist → user field_bookmarks); PII: Project-UUIDs ohne direkten Personenbezug, aber über User-Entity verknüpft
MySQL-Tabelle / Doctrine-Entity (User) → raus
  • user.bookmarks (aktualisiertes Array nach Entfernen eines Projekts)
BookmarkController::removeAction() — wenn $account vorhanden: Bookmark-UUID wird aus user.bookmarks entfernt + persistenceManager->persistAll() Web-Request (synchron, on-demand per AJAX) DB-Doctrine (Neos Flow PersistenceManager::persistAll) Symmetrisch zu addAction; Drupal-Rebuild: removeAction auf Watchlist-Entity; Session und DB werden synchron gehalten
MySQL-Tabelle / Doctrine-Entity (User) → raus
  • user.bookmarks = [] (leeres Array)
BookmarkController::removeAllAction() — wenn $account vorhanden: alle Bookmarks aus user.bookmarks gelöscht + persistenceManager->persistAll() Web-Request (synchron, on-demand per AJAX) DB-Doctrine (Neos Flow PersistenceManager::persistAll) Bulk-Clear; Drupal: deleteAll auf Watchlist-Einträge für den User; Session wird ebenfalls geleert (bookmark.removeAllProjects())
Personio API Auth (https://api.personio.de/v1/auth) → raus
  • client_id
  • client_secret
PersonioService::personioApiAuth() — OAuth-Token-Beschaffung (Methode existiert, aber im aktuellen Code-Pfad nicht aufgerufen; recruitingApiKey wird direkt genutzt) on-demand (latent/unused im aktuellen Flow) HTTP-REST (cURL POST, X-Personio-App-ID: SOLCOM_CAREER_PORTAL) Auth-Methode vorhanden, wird im aktuellen Code jedoch nicht im Bewerbungs-Flow verwendet (Bearer-Token hardcoded aus Settings). In Drupal-Rebuild dokumentieren aber nicht aktiv implementieren, solange recruitingApiKey ausreicht. Config: clientid NTUwNjg3YWI5MGE2OTYxYmU5Yjc5NmQ2, secret NTYyNGIzNjI0NDFjY2ZmNDIwZmNlOTFlOTExNDFjMWVlNTBj.
Personio Recruiting API (https://api.personio.de/v1/recruiting/applications) → raus
  • job_position_id (Personio-ID)
  • first_name
  • last_name
  • email
  • application_date
  • attributes[gender]
  • attributes[phone]
  • attributes[available_from]
  • attributes[salary_expectations]
  • attributes[custom_attribute_2722696] (Talentpool-Opt-in: Ja/Nein)
  • attributes[custom_attribute_2722693] (Referer-URL/Herkunft)
  • attributes[custom_attribute_2722695] (Kanal-Label aus Domain-Mapping)
  • files[uuid, original_filename, category]
CareerPersonioController::jobApplicationSendAction() — Bewerbungsformular-Submit durch Bewerber (nach erfolgreichem Datei-Upload) Web-Request (synchron, on-demand) HTTP-REST (cURL POST application/json, Authorization: Bearer recruitingApiKey, X-Personio-App-ID: SOLCOM_CAREER_PORTAL, X-Company-ID: 78231) Kern-Outbound-Edge. In Drupal: nach Webform-Submit gegen personioApiSendApplication() posten. PII-Relevanz: KRITISCH — Bewerberdaten (Name, E-Mail, Telefon, Gehaltswunsch, Verfügbarkeitsdatum, Gender). Referer-URL aus 'info'-Cookie wird als Herkunfts-Tracking mitgeschickt (custom_attribute_2722693/2722695). API-Key identisch mit Upload-Endpoint.
Personio Recruiting API (https://api.personio.de/v1/recruiting/applications/documents) → raus
  • file (Binary/multipart)
  • original_filename
  • uuid (response)
  • mimetype
  • extension
  • size (response)
CareerPersonioController::jobApplicationSendAction() — Bewerbungsformular-Submit durch Bewerber; handleUploads() pro Datei (max. 3 Dateien: cv, cover-letter, certificate, other) Web-Request (synchron, on-demand) HTTP-REST (cURL POST multipart/form-data, Authorization: Bearer recruitingApiKey, X-Personio-App-ID: SOLCOM_CAREER_PORTAL, X-Company-ID: 78231) In Drupal: Bewerbungsformular (Webform oder Custom Form) muss Datei-Upload-Felder (bis zu 3) gegen diesen Endpoint senden. API-Key aus Settings.yaml (recruitingApiKey: c1510ffd84a8d1356b70). PII-Relevanz: HOCH — Bewerber-Dokumente (Lebenslauf, Anschreiben, Arbeitszeugnis). Temporäre Dateien werden in der TemporaryFile-Entity zwischengespeichert, vor dem API-Upload lokal gehalten.
SMTP/Mail-Empfänger (bewerbung@solcom.de) → raus
  • file binary als Swift_Attachment
  • filename
  • MIME type
  • alle UserFile-Binaries des Users
AccountController (privat): sendMailToSolcom() — beim Abschluss der Registrierung (Account-Aktivierung); alle UserFiles des Users werden als E-Mail-Anhänge mitgesendet Web-Request (synchron, bei Account-Aktivierung) SMTP via Neos\SwiftMailer\Message (Swift_Attachment::newInstance + getStream()) Drupal-Nachbau: hook_user_insert / HookEventSubscriber auf AccountEvents::ACCOUNT_ACTIVATED; alle managed files des Users als E-Mail-Anhang via \Drupal\Core\Mail\MailManager. PII: Lebensläufe gehen an interne SOLCOM-Adresse.
SMTP/Mail-Empfänger (bewerbung@solcom.de) — tägliche Änderungs-Notification → raus
  • file binary als Swift_Attachment (nur Dateien von gestern, shouldNotSendAsNotification=false)
  • filename
  • MIME type
UserNotificationCommandController::notifyCommand() — Cron-Job sendet täglich E-Mail an bewerbung@solcom.de für jeden User mit Profil-Änderungen; neue Dokumente (creationDate = gestern, shouldNotSendAsNotification=false) werden als Anhänge mitgesendet Cron (täglich) SMTP via Neos\SwiftMailer\Message (Swift_Attachment::newInstance + stream_get_contents($resource->getStream())) Drupal-Nachbau: Drupal Queue + hook_cron oder Scheduled Task (Ultimate Cron); shouldNotSendAsNotification als Boolean auf dem user_document-Entity. Datumsprüfung creationDate=gestern muss beibehalten werden (verhindert Doppel-Versand). PII: Neu-Uploads gehen täglich raus.
SMTP/Mail-Empfänger: Bewerber (formFields.email) + BCC: bewerbung@solcom.de → raus
  • formFields.firstname
  • formFields.lastname
  • projectObj.id (mehrere möglich)
  • signature=info@solcom.de
ProjectController::jobApplicationSendAction — Eingangsbestätigung nach erfolgreichem Versand der Bewerbungsmail Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/ApplicationConfirmation.html; Betreff: 'Eingangsbestätigung / Projekt-Nummer: [IDs]'; From: no-reply@solcom.de; Lücke in Drupal WatchlistController: Bestätigungs-Mail noch nicht implementiert; PII: E-Mail + Name
SMTP/Mail-Empfänger: Entwickler/Admins (rehme@geopard-digital.de + admin@mxp.de + solcom.de-Mitarbeiter) → raus
  • errorInModuleName
  • bodyText
  • now (Zeitstempel)
MailerService::sendAlert — wird aus beliebigem Service/Controller bei kritischem Fehler aufgerufen Web-Request (synchron) oder Cron (on-demand bei Fehler) SMTP (Neos\SwiftMailer\Message) Template Mail/Error/GeneralError.html; Betreff: '***CRITICAL*** / ***INFO*** SOLCOM Webseiten - Error: [Modul]'; From: no-reply@solcom.de; Drupal: Watchdog / Alert-Modul statt Custom-Mailer; hardcodierte Empfängerliste enthält externe Dienstleister-Adressen
SMTP/Mail-Empfänger: Formular-Absender (formValues.email) — Allgemeine Formulare → raus
  • formValues.lastname (für Anrede)
  • currentDate
  • sessionid
NormalEmailFinisher::executeInternal — beliebiges Neos-Form mit NormalEmailFinisher-Konfiguration wird abgesendet Web-Request (synchron) SMTP (Neos\SwiftMailer\Message über MailerService) Template Mail/Forms/ThanksForEmailSubmission.html; Betreff: 'Bestätigung: Wir haben Ihre Anfrage erhalten.'; From: no-reply@solcom.de; generischer Dankeschön-Flow, in Drupal über Webform-Bestätigungs-E-Mail abbildbar; PII: E-Mail + Name
SMTP/Mail-Empfänger: Nutzer (accountChangeFlow.newEmail + accountChangeFlow.oldEmail) — zwei separate Mails → raus
  • accountChangeFlow.newEmail
  • accountChangeFlow.oldEmail
  • user.fullName
  • signature=info@solcom.de
AccountController::activationChangeAccountEmailAction — Nutzer klickt Bestätigungslink für E-Mail-Änderung Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Templates Mail/Account/AccountChangeSuccessMailtoUser.html (an neue Adresse) + AccountChangeSuccessMailtoUserOld.html (an alte Adresse); Betreff jeweils: 'Bestätigung: Ihre Login-E-Mail-Adresse im SOLCOM Projektportal wurde geändert'; Lücke in Drupal; PII: beide E-Mail-Adressen
SMTP/Mail-Empfänger: Nutzer (accountChangeFlow.newEmail) → raus
  • accountChangeFlow.newEmail
  • accountChangeFlow.activationToken
  • activationUrl
  • user.fullName
  • signature=info@solcom.de
AccountController::editAccountSendAction — Nutzer beantragt E-Mail-Änderung im Profil Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Account/AccountChangeMailtoUser.html; Betreff: 'Änderung Ihrer Login-E-Mail-Adresse im SOLCOM Projektportal'; Lücke in Drupal-Rebuild: dieser Flow fehlt noch (P2503-226 scope); PII: alte + neue E-Mail
SMTP/Mail-Empfänger: Nutzer (accountEmailAddress) → raus
  • user.firstName
  • user.lastName
  • signature=info@solcom.de
AccountController::deleteAction — gleichzeitig mit der internen Lösch-Benachrichtigung Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Account/DeleteNoticeForPP.html; Betreff: 'Bestätigung: Löschung SOLCOM Nutzerkonto'; From: no-reply@solcom.de; PII: Name + E-Mail
SMTP/Mail-Empfänger: Nutzer (availability.email) → raus
  • availability.firstName
  • availability.lastName
  • availability.email
  • availability.staffId
  • pppid
  • signature=projektpartner@solcom.de
AvailabilityController::sendAction — Eingangsbestätigung nach erfolgreichem Versand der Verfügbarkeits-Mail Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Availability/Confirmation.html; Betreff: 'Eingangsbestätigung'; From: no-reply@solcom.de; Drupal-Lücke: Verfügbarkeits-Formular-Bestätigung fehlt; PII: E-Mail + Name + RMS-Nr.
SMTP/Mail-Empfänger: Nutzer (registrationFlow.email) → raus
  • activationToken
  • activationUrl
  • salutation
  • lastName
  • title
  • signature=info@solcom.de
AccountController::registerAction — Nutzer füllt Registrierungsformular aus und klickt Absenden Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Entspricht ProjektpartnerMailManager::sendRegistrationMail() in solcom_projektpartner; Template Mail/Account/Registration.html → Drupal-Template noch anlegen; From: no-reply@solcom.de, To: Nutzer-E-Mail, Betreff: 'Bestätigung Ihrer Registrierung im SOLCOM Projektportal'; PII: E-Mail + Name
SMTP/Mail-Empfänger: Nutzer (registrationFlow.email) — Bewerbung mit Kontoanlage → raus
  • activationToken
  • activationUrl
  • lastName
  • salutation
  • title
  • signature=info@solcom.de
ProjectController::jobApplicationSendAction — Gast bewirbt sich und legt dabei gleichzeitig Konto an Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Account/Registration.html (gleicher Template-Typ wie Standalone-Registrierung); Betreff: 'Bestätigung Ihrer Registrierung'; Drupal: kombinierter Flow (Bewerbung + Registrierung) fehlt noch; PII: E-Mail + Name
SMTP/Mail-Empfänger: Nutzer (registrationFlow.email) — Token abgelaufen, Erneut-Senden → raus
  • activationToken
  • activationUrl
  • salutation
  • lastName
  • title
  • signature=info@solcom.de
AccountController::reActivationAction — Nutzer ruft Aktivierungs-URL mit abgelaufenem Token auf Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Gleicher Mail-Typ wie Registrierungs-E-Mail; Template Mail/Account/Registration.html; Drupal: ProjektpartnerMailManager – resend-Pfad; PII: E-Mail + Name
SMTP/Mail-Empfänger: Nutzer (resetPasswordFlow.email) → raus
  • resetPasswordToken
  • resetPasswordUrl
  • signature=info@solcom.de
AccountController::passwordForgottenSendAction — Nutzer sendet Passwort-vergessen-Formular Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Account/PasswordForgotten.html; Betreff: 'Zurücksetzen Ihres Passwortes / SOLCOM Projektportal'; Drupal: solcom_projektpartner hat eigenen ProjektpartnerMailManager::sendPasswordResetMail(); PII: E-Mail + Token
SMTP/Mail-Empfänger: Nutzer (user.email) → raus
  • user.firstName
  • user.lastName
  • signature=info@solcom.de
AccountController::activationAction — direkt nach Account-Anlage (gleicher Web-Request) Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Account/RegistrationUserNotification.html; From: no-reply@solcom.de, Betreff: 'Ihre Aktivierung war erfolgreich'; Drupal-Pendant: ProjektpartnerMailManager::sendActivationSuccessMail(); PII: E-Mail + Name
SMTP/Mail-Empfänger: Nutzer (user.email) → raus
  • user.firstName
  • user.lastName
  • signature=info@solcom.de
AccountController::editAccountSendAction — Nutzer ändert Passwort im Profil Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Account/AccountChangePasswordMailtoUser.html; Betreff: 'Ihre Passwortänderung im SOLCOM Projektportal'; From: no-reply@solcom.de; Drupal-Pendant in solcom_projektpartner vorhanden (P2503-230); PII: Name + E-Mail
SMTP/Mail-Empfänger: Nutzer/Gast (user.email / guest.email) — Projekt-Alert → raus
  • user.firstName
  • user.lastName
  • tags
  • states
  • projects (Liste: title, link)
  • hash
  • projectAlert.id
  • unsubscribeLink
  • signature=projektalert@solcom.de
ProjectAlertCommandController::sendCommand — Cron-Job sendet neue Projekt-Matches an alle aktiven Alert-Abonnenten Cron (täglich, Intervall per Cron-Konfiguration) SMTP (Neos\SwiftMailer\Message über MailerService) Template Mail/ProjectAlert/ProjectAlert.html; Betreff: 'SOLCOM Projekt Alert'; From: no-reply@solcom.de; Drupal-Lücke: ProjectAlert-Cron-Flow noch nicht in solcom_project_offer abgebildet (F-scope offen); PII: E-Mail + Name + Suchkritierien
SMTP/Mail-Empfänger: bewerbung@solcom.de → raus
  • formFields (alle Registrierungsattribute)
  • formattedSkills
  • rmsNumber (pppid)
  • currentDate
  • sessionid
  • filenames
  • file-Attachments (UserFiles)
AccountController::activationAction — Nutzer klickt Aktivierungs-Link im E-Mail-Postfach, Token gültig, Account wird angelegt Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Account/RegistrationNotification.html (20 KB, vollständiges Bewerbungsprofil); To: bewerbung@solcom.de; Anhänge = hochgeladene Profilunterlagen; entspricht dem internen Benachrichtigungs-Flow in solcom_projektpartner ProjektpartnerRegistrationSubmitter; PII hoch
SMTP/Mail-Empfänger: bewerbung@solcom.de → raus
  • user.firstName
  • user.lastName
  • user.email
  • loginemail
  • signature
AccountController::deleteAction — Nutzer löscht sein Konto selbst Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Account/Delete.html; Betreff: 'Nutzerkonto Löschung'; From: info@solcom.de; In Drupal abgebildet in solcom_projektpartner (P2503-233 Account-Löschung); PII: Name + E-Mail
SMTP/Mail-Empfänger: bewerbung@solcom.de (+ CC: projectObj.contact) → raus
  • formFields (salutation, firstname, lastname, email, phone, mobile, birthday, company, street, zip, city, country, hourlyrate, availability, skills, applicationText, languages, gulp, freelancer, xing, freelancermap)
  • projectObj.id
  • projectObj.title
  • filenames
  • file-Attachments
  • pppid
  • accountCreated
  • currentDate
  • utm_sourcename/utm_campaign
ProjectController::jobApplicationSendAction — Nutzer/Gast sendet Projektbewerbung; eine Mail pro Projekt Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Application.html; Betreff: 'Bewerbung über [Quelle] / Projekt-Nummer: [ID] / [Titel]'; From: no-reply@solcom.de; In Drupal: WatchlistController / solcom_project_offer; PII hoch (vollständiges Bewerberprofil + Dokumente)
SMTP/Mail-Empfänger: bewerbung@solcom.de (ACP-Admin-Bereich) → raus
  • user.firstName
  • user.lastName
  • user.email
  • acpUser
  • currentDate
  • sessionid
AcpController::removeUserAction — ACP-Administrator löscht Nutzerkonto manuell Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Acp/AccountDelete.html; Betreff: 'SOLCOM Konsole - Account-Löschung durchgeführt'; From: no-reply@solcom.de; Drupal: kein direktes Äquivalent (ACP = Admin-Interface, in Drupal über Standard-Admin gelöst); PII: Name + E-Mail
SMTP/Mail-Empfänger: bewerbung@solcom.de — Profil-Aktualisierungen → raus
  • user.rmsNumber
  • user.firstName
  • user.lastName
  • changes (propertyName, old, new)
  • filenames
  • file-Attachments (neu hochgeladene Dokumente vom Vortag)
UserNotificationCommandController::sendCommand — Cron-Job aggregiert alle Profil-Änderungen und sendet täglich eine Sammlung Cron (täglich) SMTP (Neos\SwiftMailer\Message) Template Mail/UserNotification/Notification.html; Betreff: 'SOLCOM PPP-ID [RMS-Nr.] hat seine Daten aktualisiert'; From: no-reply@solcom.de; Drupal-Lücke: kein Äquivalent in solcom_projektpartner – Profil-Änderungs-Benachrichtigung per Cron fehlt; PII hoch
SMTP/Mail-Empfänger: crm@solcom.de + BCC: K.Staritz@solcom.de, info@solcom.de → raus
  • formValues (Anfrage-Felder: name, company, email, phone, message)
  • currentDate
  • sessionid
  • file-Attachments
FormsController::sendMailtoSolcom — Nutzer sendet Anfrageformular (Projektanfrage CRM) Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Forms/SendProject.html; Betreff: 'Anfrageformular - Anfrage übermitteln'; From: no-reply@solcom.de; PII: Name + E-Mail + Projektdaten
SMTP/Mail-Empfänger: crm@solcom.de — 2FA-Bestätigung → raus
  • email
  • token
FormsController::activate2faAction — Nutzer klickt 2FA-Bestätigungslink in E-Mail Web-Request (synchron) SMTP (Neos\SwiftMailer\Message über MailerService) Template Mail/2FA/Accept.html (Pfad im Code: Mail/Lead2faForm/Accept.html); Betreff: 'AVV Bestätigung ([E-Mail])'; From: no-reply@solcom.de; Drupal-Lücke: 2FA-Flow für AVV-Bestätigung fehlt; PII: E-Mail
SMTP/Mail-Empfänger: karriere@solcom.de → raus
  • formValues (firstname, lastname, email, phone, company, message, utm-/performance-Cookies)
  • currentDate
  • sessionid
  • file-Attachment (Bewerbungsunterlage)
FormsController::sendCareerLandingpageMailtoSolcom — Nutzer sendet Karriere-Landingpage-Bewerbungsformular Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Forms/SendCareerLandingpage.html; Betreff: 'Karriere Bewerbungsformular - Bewerbung'; From: no-reply@solcom.de; Kein Portal-Scope (Karriereseite); PII: Name + E-Mail + Datei
SMTP/Mail-Empfänger: karriere@solcom.de → raus
  • formValues (firstname, lastname, email, message, Termin-Felder)
  • currentDate
  • sessionid
FormsController::sendCareerApplicationMailtoSolcom — Nutzer sendet Karriere-Terminbestätigungs-Formular Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Forms/SendCareerApplication.html; Betreff: 'Terminbestätigung von [Vorname] [Nachname]'; From: no-reply@solcom.de; Kein Portal-Scope; PII: Name + E-Mail
SMTP/Mail-Empfänger: projektpartner@solcom.de → raus
  • availability.staffId
  • availability.projectPartner
  • date
  • availability (alle Felder)
  • sessionid
  • message
  • file-Attachments (bis zu 4 Dokumente)
AvailabilityController::sendAction — eingeloggter/nicht eingeloggter PP sendet Verfügbarkeits-Formular Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template Mail/Availability/Solcom.html; Betreff: 'Aktualisierung RMS Nr. [ID] – [Name] / Verfügbar ab: [Datum]'; From: no-reply@solcom.de; Kein direktes Drupal-Äquivalent (Verfügbarkeits-Flow = eigener Scope); PII: Name + RMS-Nr. + Verfügbarkeitsdatum
SMTP/Mail-Empfänger: recipientAddress (aus Form-Config) — Allgemeine Formulare → raus
  • subject (aus Form-Config)
  • recipientAddress
  • senderAddress
  • replyToAddress
  • carbonCopyAddress
  • blindCarbonCopyAddress
  • formValues (alle Felder)
  • performanceCodeGoogle
  • performanceCodeBing
NormalEmailFinisher::executeInternal — zweite Mail (an SOLCOM-Empfänger) nach der Nutzer-Bestätigung Web-Request (synchron) SMTP (Neos\SwiftMailer\Message) Template konfigurierbar (templatePathAndFilename in Form-YAML); Empfänger je nach Form-Konfiguration; Drupal: Webform-EmailHandler; PII abhängig von Formular-Feldern
Twitter/X API (OAuth 1.0a) → raus
  • consumerKey: xYJ2EoFMMjL5n1Eu6oc9g (hardcoded)
  • consumerSecret: s1qtbwFCZvNOQWndSBG7BrQI9Sz00ZckHcBYldraI (hardcoded)
  • accessToken: 492335992-anrUvaAzb39l4p1YdHVEmfNAsB7YobjpPryZCkzI (hardcoded)
  • accessTokenSecret: VylvKsOtoUoGKMUnpua7igPZm6C87sXxj9rCj7xMFI (hardcoded)
  • Tweet-Text: project.getTitle() + Ort: project.getState() + Start: project.getStart() + project.getType() + project.getBusiness() + #Job #Stellenangebot #beruf + https://solcom.de/t/<project.getId()>
Cron-Command project:importAndProcess (ProjectCommandController->importAndProcessCommand) — AUSKOMMENTIERT: $this->twitterService->tweetProject() ist im Code vorhanden aber commented out; kein aktiver Aufruf in Produktion Cron (war vorgesehen bei Projekt-Import; aktuell deaktiviert) HTTP-REST (DG\Twitter\Twitter-Library, OAuth 1.0a POST zu api.twitter.com/1.1/statuses/update.json) Niedrige Priorität — Twitter-Integration war bereits deaktiviert (auskommentiert). Im Drupal-Neuaufbau nicht zwingend zu implementieren. Falls doch: Twitter/X API v2 (OAuth 2.0), keine Legacy-Credentials übernehmen. Credentials im Klartext im Code sind ein Sicherheitsproblem — müssen rotiert werden. Kein PII im Tweet-Text.

Datenmodell & Felder — was wird gespeichert, was ist pflegbar

Feld-Ebene der Kern-Entities. Die Spalte Pflegbar? unterscheidet, ob der Projektpartner ein Feld selbst über das Profil-/Formular-UI setzt (pflegbar) oder ob es system-/importseitig verwaltet wird (System) — z. B. die RMS-Nummer, der Standort-Code oder alle Projekt-Felder.

User (Projektpartner-Profil) 40 Felder · 31 pflegbar

FeldTypBedeutungPflegbar?Beleg
email string E-Mail-Adresse (zugleich Login-Identifier) pflegbar User.php:36 / FlowUserCreationService.php:81 setEmail / RegisterForm.html:155 / EditProfile.html:96
title string Akademischer Titel (z. B. Dr., Prof.) pflegbar User.php:42 / FlowUserCreationService.php:127 setTitle / RegisterForm.html:59 / EditProfile.html:36
salutation string Anrede-Kuerzel ('m' = Herr, 'f' = Frau) pflegbar User.php:48 / FlowUserCreationService.php:133 setSalutation / RegisterForm.html:79 / EditProfile.html:44
gender string Geschlecht - in FlowUserCreationService identisch mit salutation gesetzt ('m'/'f') pflegbar User.php:54 / FlowUserCreationService.php:83 setGender(attributes['salutation'])
firstName string Vorname pflegbar User.php:60 / FlowUserCreationService.php:86 setFirstName / RegisterForm.html:89 / EditProfile.html:50
lastName string Nachname pflegbar User.php:66 / FlowUserCreationService.php:91 setLastName / RegisterForm.html:99 / EditProfile.html:54
hourlyrate string Stundensatz in EUR (nur Ziffern, pattern ^[0-9]*$) pflegbar User.php:72 / FlowUserCreationService.php:176 setHourlyrate / RegisterForm.html:440 / EditProfile.html:143
birthday string Geburtsdatum (ISO-Format Y-m-d, im Formular als dd.mm.yyyy per Datepicker) pflegbar User.php:78 / FlowUserCreationService.php:98 setBirthday(attributes['inputBirth']['date']) / RegisterForm.html:108 / EditProfile.html:59
company string Firmenname / Selbststaendigen-Bezeichnung pflegbar User.php:84 / FlowUserCreationService.php:136 setCompany / RegisterForm.html:125 / EditProfile.html:71
phone string Telefonnummer (Festnetz) pflegbar User.php:90 / FlowUserCreationService.php:141 setPhone / RegisterForm.html:138 / EditProfile.html:82
mobile string Mobilnummer (pattern fuer Ziffern/Plus/Klammern, Pflichtfeld bei Registrierung) pflegbar User.php:96 / FlowUserCreationService.php:146 setMobile / RegisterForm.html:144 / EditProfile.html:89
website string Eigene Webseite (URL mit https?:// Pattern), default '' pflegbar User.php:101 / FlowUserCreationService.php:166 setWebsite / RegisterForm.html:164 / EditProfile.html:101
streetAndNumber string Strasse und Hausnummer pflegbar User.php:107 / FlowUserCreationService.php:151 setStreetAndNumber(attributes['street']) / RegisterForm.html:172 / EditProfile.html:107
zip string Postleitzahl pflegbar User.php:113 / FlowUserCreationService.php:156 setZip / RegisterForm.html:182 / EditProfile.html:115
city string Wohnort pflegbar User.php:119 / FlowUserCreationService.php:161 setCity / RegisterForm.html:191 / EditProfile.html:121
country string Wohnsitz-Land (Select aus Country-Liste) pflegbar User.php:125 / FlowUserCreationService.php:171 setCountry / RegisterForm.html:201 / EditProfile.html:130
countryAll string Einsatzort-Flag 'Alle Einsatzorte' (Checkbox, gespeicherter Wert 'ALL') pflegbar User.php:131 / FlowUserCreationService.php:110 setCountryAll / RegisterForm.html:401 / EditProfile.html:156
countryA string Einsatzort Oesterreich (Checkbox, Wert 'A') pflegbar User.php:137 / FlowUserCreationService.php:113 setCountryA / RegisterForm.html:408 / EditProfile.html:160
countryCh string Einsatzort Schweiz (Checkbox, Wert 'CH') pflegbar User.php:143 / FlowUserCreationService.php:116 setCountryCh / RegisterForm.html:415 / EditProfile.html:163
countryDe string Einsatzort Deutschland (Checkbox, Wert 'DE') pflegbar User.php:149 / FlowUserCreationService.php:119 setCountryDe / RegisterForm.html:420 / EditProfile.html:166
countryInternational string Einsatzort Andere Laender (Checkbox, Wert 'International') pflegbar User.php:155 / FlowUserCreationService.php:122 setCountryInternational / RegisterForm.html:427 / EditProfile.html:169
available string Verfuegbarkeitsdatum (aus Datepicker, intern als date-String persistiert) pflegbar User.php:161 / FlowUserCreationService.php:104 setAvailable(attributes['availability']['date']) / RegisterForm.html:447 / EditProfile.html:148
location string Standort-Code aus RMS (D1-D9, DE, CH, A, Remote) - kein UI-Feld im Profil-Formular System User.php:167 / kein setLocation-Aufruf in registerAction / editProfileSendAction
networkGulpId string GULP-Profil-ID oder GULP-URL pflegbar User.php:173 / FlowUserCreationService.php:181 setNetworkGulpId(attributes['gulp']) / RegisterForm.html:464 / EditProfile.html:179
networkFreelancer string Freelance.de-Profil-URL pflegbar User.php:179 / FlowUserCreationService.php:186 setNetworkFreelancer(attributes['freelancer']) / RegisterForm.html:471 / EditProfile.html:184
networkXing string XING-Profil-URL pflegbar User.php:185 / FlowUserCreationService.php:191 setNetworkXing(attributes['xing']) / RegisterForm.html:478 / EditProfile.html:189
networkFreelancermap string Freelancermap-Profil-URL pflegbar User.php:191 / FlowUserCreationService.php:196 setNetworkFreelancermap(attributes['freelancermap']) / RegisterForm.html:484 / EditProfile.html:194
professionalEmphases string Fachliche Schwerpunkte (Freitext) - kein Formular-Feld, weder in Register noch in EditProfile System User.php:197 / kein set-Aufruf in FlowUserCreationService oder AccountController
magazine string Mailingworks-Newsletter-Status aus Registrierungsflow ('true'/'false' als String) pflegbar User.php:203 / FlowUserCreationService.php:125 setMagazine(attributes['magazine']) / RegisterForm.html:376 Checkbox
skills string Freitext-Skills (max. 500 Zeichen Validierung, ORM-Column length=1000) pflegbar User.php:210 / FlowUserCreationService.php:201 setSkills / RegisterForm.html:518 / EditProfile.html:208
applicationText string Bewerbungstext / Anschreiben (max. 2000 Zeichen, ORM-Column length=2500) pflegbar User.php:217 / FlowUserCreationService.php:209 setApplicationText / RegisterForm.html:531 / EditProfile.html:230
rmsNumber string RMS-interne Projektpartner-ID (PPP-ID), auto-inkrementiert aus RMS-Counter System User.php:223 / FlowUserCreationService.php:229 setRmsNumber(rmsCounter)
languages array Sprachenliste (Mehrfachauswahl, serialisiertes Array in DB) pflegbar User.php:229 / FlowUserCreationService.php:206 setLanguages / RegisterForm.html:507 / EditProfile.html:218
account \Neos\Flow\Security\Account Verknuepftes Flow-Security-Account-Objekt (OneToOne; traegt AccountIdentifier = E-Mail und Credentials) System User.php:235 / FlowUserCreationService.php:78 setAccount
bookmarks array Gemerkte Projekte (Array von Projekt-Identifiers, ueber Bookmark-Service intern gepflegt) System User.php:248 / kein Formular-Feld
policyOption boolean Datenschutz-Zustimmung (default false) - im RegisterForm als Pflicht-Checkbox vorhanden, aber nicht ueber FlowUserCreationService in User persistiert System User.php:253 / RegisterForm policy/policy2-Checkboxes nur Formular-Pflichtfelder
magazineOption boolean Mailingworks-Newsletter aktiv (true/false; via addMagazineAction/removeMagazineAction gesetzt) System User.php:258 / AccountController.php:942 setMagazineOption(true) / AccountController.php:965 setMagazineOption(false)
lastAccountEditDate \DateTime Zeitstempel der letzten Kontoaenderung System User.php:263 / FlowUserCreationService.php:80 setLastAccountEditDate(new DateTime)
userFiles Collection<UserFile> Hochgeladene Dokumente (OneToMany-Relation) pflegbar User.php:22 / FlowUserCreationService.php:224 setUserFiles / RegisterForm Upload-Felder
userUpdateNotifications Collection<UserUpdateNotification> Interne Benachrichtigungen ueber Profilaenderungen (OneToMany, nur systemseitig) System User.php:28
Zwei parallele Registrierungs-Services: FlowUserCreationService (produktiv, mappt alle Formular-Attribute auf User-Properties) und NeosUserCreationService (Neos-Backend, mappt nur firstName/lastName/email). Einsatzort-Felder (countryAll/A/Ch/De/International) sind Checkbox-Flags mit festen Wert-Strings, kein Enum. salutation-Werte: 'm' (Herr) / 'f' (Frau). gender wird redundant aus salutation gesetzt. birthday im Formular: dd.mm.yyyy (Datepicker), persistiert als Y-m-d-String. location (Standort-Code D1-D9, DE/CH/A/Remote) hat keinen Formular-Eingang - fuer RMS-Sync reserviert.

Project 27 Felder · 0 pflegbar

FeldTypBedeutungPflegbar?Beleg
id string Externe Projekt-ID aus RMS/Salesforce System Project.php:23
nodeId string Neos-CMS-Content-Node-UUID (von id getrennt) System Project.php:27
creationDate \DateTime Erstellungsdatum des Projekts System Project.php:33
title string Projekttitel System Project.php:38
description string Projektbeschreibung (ORM type=text) System Project.php:43
start string Projektstart (String-Datum) System Project.php:49
duration string Projektlaufzeit / Dauer System Project.php:54
location string Einsatzort des Projekts System Project.php:59
type string Projekttyp System Project.php:64
contact string SOLCOM-Ansprechpartner System Project.php:69
active string Aktiv-Status des Projekts (String-Flag) System Project.php:74
requirements string Anforderungen an den Kandidaten (ORM type=text) System Project.php:80
additional string Zusaetzliche Informationen zum Projekt (ORM type=text) System Project.php:85
gulp string GULP-Referenz-ID oder Link System Project.php:91
activated \DateTime Datum der Aktivierung im Portal System Project.php:96
deactivated \DateTime Datum der Deaktivierung System Project.php:101
modified \DateTime Letzte Aenderungszeit System Project.php:106
modifiedContact string Wer das Projekt zuletzt geaendert hat System Project.php:111
state string Bundesland / Region des Projekts System Project.php:116
division string SOLCOM-interne Abteilung / Division System Project.php:121
business string Geschaeftsbereich / Branche System Project.php:126
noPortal string Flag: Projekt nicht im Portal anzeigen System Project.php:131
skills string Geforderte Skills (ORM type=text) System Project.php:137
tags string Suchbegriffe / Tags fuer das Projekt (ORM type=text) System Project.php:143
freelancerLink string Link zum Freelance.de-Inserat System Project.php:148
agencyLink string Link fuer Agenturen System Project.php:153
notifications Collection<ProjectAlertNotification> Versendete Alert-Benachrichtigungen zu diesem Projekt (OneToMany) System Project.php:159
Rein systemseitig befuellte Entitaet - alle Felder kommen aus dem RMS/Salesforce-Import, kein Endnutzer-Formular. id = externer RMS-Key, nodeId = Neos-Content-Node-UUID (zwei verschiedene IDs).

ProjectAlert 8 Felder · 3 pflegbar

FeldTypBedeutungPflegbar?Beleg
tags array Tags / Suchbegriffe fuer die Alert-Suche (NotEmpty-Pflicht) pflegbar ProjectAlert.php:21
noTags array Ausgeschlossene Tags (NOT-Filter) pflegbar ProjectAlert.php:27
states array Bundesland-Filter fuer den Alert pflegbar ProjectAlert.php:32
hash string Eindeutiger Hash zur Identifikation des Alerts (NotEmpty-Pflicht) System ProjectAlert.php:37
notifications Collection<ProjectAlertNotification> Versendete Benachrichtigungen zu diesem Alert (OneToMany) System ProjectAlert.php:43
user User Verknuepfter eingeloggter Nutzer (OneToOne, nullable - entweder user oder guest) System ProjectAlert.php:50
guest ProjectAlertGuest Verknuepfter Gast-Datensatz (OneToOne, nullable - entweder user oder guest) System ProjectAlert.php:56
active boolean Alert aktiv (default false) System ProjectAlert.php:62
Ein Alert gehoert entweder einem eingeloggten User oder einem Guest (nie beiden gleichzeitig). Bei Account-Loeschung wird user auf null gesetzt und stattdessen ein ProjectAlertGuest-Datensatz angelegt (AccountController.php:506-513).

ProjectAlertGuest 4 Felder · 4 pflegbar

FeldTypBedeutungPflegbar?Beleg
gender string Anrede-Kuerzel (NotEmpty-Pflicht; Werte analog User.salutation) pflegbar ProjectAlertGuest.php:19
firstName string Vorname (NotEmpty-Pflicht) pflegbar ProjectAlertGuest.php:25
lastName string Nachname (NotEmpty-Pflicht) pflegbar ProjectAlertGuest.php:31
email string E-Mail-Adresse (NotEmpty + EmailAddress-Validator) pflegbar ProjectAlertGuest.php:37
Minimalentitaet fuer Gaeste ohne Account. Wird bei Account-Loeschung aus dem bestehenden User-Objekt befuellt (AccountController.php:506-509). Kein eigenes Passwort, kein Account-Link.

Availability 11 Felder · 0 pflegbar

FeldTypBedeutungPflegbar?Beleg
id string Datensatz-ID aus dem RMS (Dataset-Feld ID) System Availability.php:19
staffId string Personalnummer des SOLCOM-Mitarbeiters (Dataset PersonalNr) System Availability.php:25
projectPartner string Projektnummer / Projektpartner-Kennung (Dataset Projektnummer) System Availability.php:31
type integer Mail-Typ aus RMS (Dataset Mailtyp) System Availability.php:37
email string E-Mail-Adresse des Projektpartners (Dataset EmailAdresse) System Availability.php:43
salutation string Anredezeile aus RMS (Dataset Anredezeile) System Availability.php:49
staff string Name des SOLCOM-Mitarbeiters (Dataset Mitarbeiter) System Availability.php:55
editor string Bearbeiter-Name (Dataset Bearbeiter) System Availability.php:61
hash string Eindeutiger Hash fuer den Availability-Link System Availability.php:66
creationDate \DateTime Erstellungsdatum des Datensatzes System Availability.php:71
status integer Workflow-Status: 1 = E-Mail an PP gesendet; 2 = PP hat Formular ausgefuellt und E-Mail an SOLCOM gesendet System Availability.php:79
Kein direktes Nutzer-Formular. Datensaetze kommen aus einem RMS-Export. Workflow: RMS schreibt Datensatz (status=1) -> PP empfaengt E-Mail mit Hash-Link -> fuellt Formular aus -> status=2. Kein FK auf User-Entitaet.

UserFile 6 Felder · 1 pflegbar

FeldTypBedeutungPflegbar?Beleg
title string Dateiname (aus Original-Filename + Datum konstruiert, z. B. 'Lebenslauf_2024-06-17.pdf') pflegbar UserFile.php:24 / AccountController.php:829 setTitle(newfileName)
shouldNotSendAsNotification boolean Flag: Datei nicht als Anhang in Benachrichtigungs-E-Mail versenden (default false) System UserFile.php:30
creationDate \DateTime Upload-Zeitstempel System UserFile.php:35 / AccountController.php:830 setCreationDate(new DateTime)
originalResource \Neos\Flow\ResourceManagement\PersistentResource Neos-Flow-Ressource-Objekt (OneToOne, cascade all) - enthaelt Dateiinhalt, SHA1, MIME-Typ System UserFile.php:39 / AccountController.php:831 setOriginalResource
user User Verknuepfter User (ManyToOne, nullable - null waehrend RegistrationFlow-Phase) System UserFile.php:47 / FlowUserCreationService.php:219 setUser
registrationFlow RegistrationFlow Temporaere Verknuepfung zum RegistrationFlow vor Account-Aktivierung (ManyToOne, nullable) System UserFile.php:53 / AccountController.php:828 setRegistrationFlow
Erlaubte MIME-Types: application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document (docx), application/msword (doc). Maximale Dateigroesse: 5 MB (5242880 Bytes). Minimale Dateigroesse: 1 KB (1024 Bytes). Dateiname wird bereinigt: nur [a-zA-Z0-9_.-] erlaubt (preg_replace in UploadService). Waehrend der Registrierung liegt die Datei erst am RegistrationFlow; nach Aktivierung wird user gesetzt und registrationFlow auf null.

Kritische Lücken & Verifikations-Punkte für den Nachbau 13 Funde

Der Completeness-Critic (Opus) hat Routes.yaml, alle *Action- und *Command-Signaturen sowie das Package-Wiring gegen die erfassten Flows geprüft. Folgende Flows tauchten in keinem Reader-Cluster auf — sie sind die wahrscheinlichsten Kandidaten, beim Drupal-Nachbau übersehen zu werden.
Übersehener FlowBeleg-DateiEvidence
Projekt-Alerts komplett löschen (Prune)Classes/Command/ProjectAlertCommandController.php:110public function pruneCommand() — nur ::sendCommand und ::importAlertsCommand sind in cli-commands erfasst, der Prune fehlt
Projekt-Alert-Notifications löschen (Prune)Classes/Command/ProjectAlertCommandController.php:116public function pruneNotificationsCommand() — nicht in der erfassten Liste
Verfügbarkeits-Datensätze komplett löschen (Prune)Classes/Command/AvailabilityCommandController.php:307public function pruneCommand() — availability-dashboard-avv erfasst nur importCommand und initialImportCommand, der Prune fehlt
Spendenaktion: Staff-Daten löschen (Prune)Classes/Command/DonationCommandController.php:251public function pruneStaffCommand() — donation-sommerfest-misc erfasst nur den Import-Trio importStaff/importOrganisations/importDonations, die drei Prunes fehlen
Spendenaktion: Organisationen löschen (Prune)Classes/Command/DonationCommandController.php:339public function pruneOrganisationsCommand() — nicht einzeln erfasst
Spendenaktion: Donations löschen (Prune)Classes/Command/DonationCommandController.php:438public function pruneDonationsCommand() — nicht einzeln erfasst
Statistik-Counter (Bewerbungszähler) neu anlegen/zurücksetzenClasses/Command/UtilityCommandController.php:103public function addStatisticsProjectAppilcationCounterCommand() — löscht alle Statistics und legt einen neuen mit Counter=0 an; nicht in cli-commands erfasst
RMS-Counter-Entität initial anlegen (Startnummer 121212)Classes/Command/UtilityCommandController.php:200public function createCounterCommand() — erfasst sind nur setCounter und setCounterToUsers, das initiale Anlegen des RMS-Counters fehlt
Alle RMS-Counter löschenClasses/Command/UtilityCommandController.php:214public function removeAllCommand() — rmsRepository->removeAll(); nicht in cli-commands erfasst
Projektpartner-Accounts auflisten (CLI-Report)Classes/Command/SolcomUserCommandController.php:446public function listAccountsCommand() — gibt Tabelle aller Accounts mit Provider/Rollen aus; nicht erfasst
Projektpartner-User auflisten (CLI-Report)Classes/Command/SolcomUserCommandController.php:474public function listUsersCommand() — nicht erfasst
ACP-Admin-User auflisten (CLI-Report)Classes/Command/AcpUserCommandController.php:98public function listCommand() — gibt Rollen-Matrix + ACP-User-Tabelle aus; erfasst sind nur create und remove des ACP-Users
Volltext-Suche AJAX (IndexedSearch Typeahead, mehrsprachig)Classes/Controller/IndexedSearchController.php:183public function searchResultAjaxAction($language) — eigener AJAX-Such-Flow parallel zu ProjectController::ajaxSearchAction und SearchController::ajaxSearchAction; erfasst ist bei IndexedSearch nur die nicht-AJAX searchResultAction

Dedup-Hinweise (Render-Geschwister, nicht doppelt zählen)

  • FormsController::careerApplicationFormAction + careerApplicationFormSuccessAction sind die Anzeige-/Erfolgs-Phase des erfassten submitCareerApplicationFormAction — beim Merge als Render-Geschwister derselben Karriere-Terminbestätigung dedupen, nicht als neue Flows
  • FormsController::submitProjectFormAction + projectFormSuccessAction sind Anzeige-/Erfolgs-Phase des erfassten submitProjectFormSendAction (Projekt-Einreichungsformular)
  • FormsController::careerLandingpageFormAction + careerLandingpageFormSuccessAction sind Anzeige-/Erfolgs-Phase des erfassten submitCareerLandingpageFormAction
  • AvailabilityController::formResultAction ist die Ergebnis-/Status-Anzeige nach dem erfassten AvailabilityController::sendAction (Verfügbarkeit melden) — kein eigener Flow
  • IndexedSearchController::indexAction + indexAjaxAction sind reine Such-Box-Render-Helfer (prepareSearchBox) zum erfassten searchResultAction — Render-Phase, kein eigener fachlicher Flow

Critic-Notizen (Phantom-Endpoints, Scope-Bestätigung)

PHANTOM (captured-but-nonexistent, Gegenteil von missing): Routes.yaml Zeile 474-478 mappt 'api/removetempfile' -> ApiController::removeTempFile, aber ApiController hat KEINE removeTempFileAction (nur getpersistentfile, projectDetailCompact, projectDetail, getpersistentfile, sendRecallToSolcom, uploadTempFile, countSearchResults). Die erfasste Liste (routing-security + donation-sommerfest-misc) führt 'Route: Temporäre Datei entfernen [ApiController::removeTempFileAction]' als Flow — dieser Controller-Entrypoint existiert im untersuchten Code nicht; beim Drupal-Nachbau ist die Route ein toter Endpoint oder die Logik liegt woanders (vermutlich JS-seitig oder die Route ist obsolet). Sollte beim Merge als verifizierungsbedürftig markiert werden.

NICHT-FACHLICH (bewusst NICHT als missing gelistet): testCommand erscheint 3x (AvailabilityCommandController:98, UserNotificationCommandController:80, UtilityCommandController:296) — reine Dev-Test-Stubs, keine Business-Flows. DonationController::noaccessAction (Zeile 238) ist ein toter Endpoint (nur var_dump('Nope! Hier gehts nicht weiter.')).

SCOPE-BESTÄTIGUNG: Package.php::boot() ist leer ('// nothing yet') — KEIN Signal/Slot-Wiring im Package vorhanden, also keine versteckten Event-getriebenen Flows. Cron-Scheduling ist externe Wiring (crontab/Settings), nicht im Code; die Command-Enumeration IST bereits die vollständige Cron-Flow-Population.

DonationCommandController-Diagnose-Commands (setEmailStatusCommand, findByEmailCommand, checkVmMatchingCommand, statusCommand) sind durch den erfassten Sammel-Flow 'Cron — Status-Verwaltung und Diagnose' abgedeckt; die Send-Mail-Commands (sendStartmailMA/PP, sendInfomailFirst/Second, sendEndmailSolcom/PP, sendAfterMail) durch 'Cron — Mailing-Kampagne'. Diese habe ich daher NICHT als missing gelistet — nur die 3 Prune-Commands fielen aus beiden Sammel-Flows heraus.

Flow-Inventar nach Domäne

Jede Zeile ist ein Flow; aufklappbar je Flow: Schritte, berührte Domain-Objekte, Side-Effects, Dateien. Die Spalte Drupal-Nachbau trägt den Hinweis des Analyse-Agents, ob/wo das im Drupal-Stack schon existiert.

Projekte — Liste, Suche, Detail, Merkliste Kern 22 Flows

FlowAuslöserEntrypointZugriffDrupal-Nachbau
Projekt-Import (XML → Datenbank + CMS-Node) CLI-Command: `./flow project:import` ProjectCommandController::importCommand CLI-only, keine HTTP-Authentifizierung In Drupal: WatchlistController/solcom_project_offer übernimmt bereits Teile davon. Die XML-Import-Logik (RMS-Export OpenPos.xml als Datenquelle) muss als Migrate-Quelle oder Feeds-Modul nachgebaut werden. Felder id, title, description, start, duration, location, type, contact, active, requirements, additional, gulp, activated, deactivated, modified, modifiedContact, state (Region/PLZ-Codes), division, business, noPortal, skills, tags, freelancerLink, agencyLink müssen als Drupal-Felder vorhanden sein. WICHTIG: state enthält codierten Regionsschlüssel (DE, CH, A, D1-D9, Remote, International, DACH, PLZ-Präfixe) — entspricht field_sf_* im bestehenden Drupal-Modul.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • CLI-Command wird aufgerufen (z.B. via Cron)
  • Liest OpenPos.xml aus Resources/Private/Import/ (Dateiname konfigurierbar via Settings.Solcom.yaml import.filename)
  • Parsed XML mit SimpleXML; jeder Knoten enthält: lfdNr, Titel, Beschreibung, Start, Dauer, Ort, Stellentyp, Bearbeiter, Aktiv, Anforderungen, Zusatz, GULPRobot, aktiviert_am, deaktiviert_am, LastModified, LastModifiedBearbeiter, Region, Unternehmensbereich, Branche, keinePortale, skills, tags, FreelancerLink, AgencyLink
  • Pro Projekt: findOneById aus ProjectRepository — wenn nicht vorhanden → createProject(), wenn vorhanden → updateProject() (nur wenn XML-Modified > DB-Modified)
  • createProject(): erstellt Project-Entity, legt Neos-ContentNode vom Typ Contenance.Solcom:Document.Portal.Project unterhalb des konfigurierten projectNodeIdentifier-Nodes an, befüllt SEO-Properties (title, metaDescription, openGraphType/-Title/-Description, canonicalLink), speichert nodeId im Project-Objekt
  • Am Ende: alle DB-Projekte, die nicht mehr in XML sind, werden entfernt (removeProject), dabei werden auch User-Bookmarks bereinigt
  • persistenceManager->persistAll()
  • Logger-Eintrag: Anzahl verarbeiteter Projekte
Domain-Objekte
Side-Effects
  • DB-Write: Project-Entity anlegen oder aktualisieren
  • DB-Write: Neos-ContentNode (Document.Portal.Project) mit SEO-Feldern anlegen
  • DB-Write: User-Bookmarks bereinigen wenn Projekte gelöscht werden
  • Dateisystem-Read: OpenPos.xml
DateienClasses/Command/ProjectCommandController.php, Classes/Domain/Model/Project.php, Classes/Domain/Repository/ProjectRepository.php, Configuration/Settings.Solcom.yaml
Projekt-Prune (alle Projekte löschen) CLI-Command: `./flow project:prune` ProjectCommandController::pruneCommand CLI-only Entspricht einem drush-Kommando zum Bereinigen aller importierten Projektangebote. Kann in Drupal als Drush-Command oder Migrate-Rollback implementiert werden. Lücke: Drupal-Nachbau sollte Watchlist-Einträge beim Löschen ebenfalls bereinigen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Setzt alle User-Bookmarks auf leeres Array und speichert
  • Löscht alle Project-Entities aus der Datenbank
  • Löscht alle Document.Portal.Project Neos-Nodes unterhalb des projectNodeIdentifier-Parent-Nodes
  • persistenceManager->persistAll()
  • Logger-Eintrag
Domain-Objekte
Side-Effects
  • DB-Write: alle Projects gelöscht
  • DB-Write: alle User-Bookmarks geleert
  • DB-Write: alle Neos-CMS-Nodes vom Typ Document.Portal.Project entfernt
DateienClasses/Command/ProjectCommandController.php, Classes/Domain/Repository/ProjectRepository.php
Projektliste anzeigen (Hauptseite Projektportal) URL/Route: {node}/page-{currentPage} → ProjectController::indexAction (Plugin-Route Projektangebote-Seite) ProjectController::indexAction Gast (öffentlich zugänglich) In Drupal bereits als WatchlistController + Views-basierte Projektliste vorhanden (solcom_project_offer). Cookie-basiertes Suchparamter-Persistenz muss in Drupal als URL-basierter Filter oder Session-Storage nachgebaut werden. WICHTIG: Location-Filter arbeitet mit RMS-Codes (D1-D9 = PLZ-Regionen, DE, CH, A, Remote, International) — muss in Drupal-Taxonomy oder Filterfeld korrekt abgebildet sein.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Liest searchArguments aus HTTP-Request oder aus dem Plugin-Namespace des Parent-Requests (verschiedene Plugin-Namespaces werden abgefragt: mainsearch, projectalert, bookmark, profile, detail, login)
  • Liest Suchparameter aus Request-Arguments mit Cookie-Fallback (1-Tag-Cookie): searchParameter, zip, location (ALL/International/Remote/DACH/A/CH/DE), business
  • Ruft SearchService::search() auf → ProjectRepository::findBySearchString() mit LIKE-Queries auf title, description, tags, id, additional, location, state, requirements, business
  • Wenn keine Suchparameter: zeigt alle Projekte (findAll() bei findAll-Flag gesetzt)
  • Baut Share-Link aus aktueller URI + search + location Parametern
  • Weist Template zu: location, zip, business, businessOptions (aus getBusinessOptions()), itemsPerPage, projectCounter, maximumNumberOfLinks, searchString, searchResults, shareDetails
  • persistenceManager->persistAll()
Domain-Objekte
Side-Effects
  • Session/Cookie: schreibt searchParameter, location, zip, paginationItemsPerPage Cookies (Ablauf +1 Tag)
DateienClasses/Controller/ProjectController.php, Classes/Domain/Service/SearchService.php, Classes/Domain/Repository/ProjectRepository.php, Configuration/Routes.yaml, Configuration/Settings.Solcom.yaml
Projekt-Landingpage nach Technologie oder Branche URL/Route: {node}/page{currentPage} → ProjectController::landingpageAction (Plugin-Route Landingpage) ProjectController::landingpageAction Gast (öffentlich zugänglich) Entspricht Technology/Business Landing Pages im Drupal-Nachbau. Diese werden als gefilterte Projektlisten-Views pro Technologie- oder Branchen-Taxon realisiert. TechnologyDataSource und BusinessDataSource liefern die Editor-Auswahllisten für diese Nodes.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Liest DocumentNode-Property 'product' (Array von Tags/Technologien) und 'title' aus dem Neos-CMS-Node
  • Wenn NodeType Contenance.Solcom:Portal.Project.Technology: ruft ProjectRepository::findByTagsAndSkills(tags) auf (LIKE-Query auf tags-Feld)
  • Wenn NodeType Contenance.Solcom:Portal.Project.Business: ruft ProjectRepository::findByBusinesses(tags) auf (LIKE-Query auf business-Feld)
  • Weist Template zu: projectCounter, searchResults, itemsPerPage, maximumNumberOfLinks, tagTitle, headerContent, footerContent
Domain-Objekte
Side-Effects
DateienClasses/Controller/ProjectController.php, Classes/Domain/Repository/ProjectRepository.php, Classes/DataSource/TechnologyDataSource.php, Classes/DataSource/BusinessDataSource.php
Projekt-Detailseite anzeigen URL/Route: Node vom Typ Contenance.Solcom:Document.Portal.Project → ProjectController::detailAction ProjectController::detailAction Gast (öffentlich zugänglich) In Drupal bereits als node--project_offer.html.twig + hook_node_view vorhanden. Skill-zu-Technology-Mapping muss als Taxonomy-Relation nachgebaut werden. Aktuell: Skill-String-Matching gegen CMS-Node-Properties — in Drupal besser als Term-Referenz-Feld realisieren.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Liest documentNode aus internem Request-Argument __documentNode
  • Liest projectid-Property vom Neos-Node
  • Ruft ProjectRepository::findByIdentifier(projectId) auf
  • Wenn project.skills vorhanden: durchsucht alle Contenance.Solcom:Portal.Project.Technology-Nodes nach passenden Technologien (Skill-Matching, case-insensitive)
  • Liest aktuelle Suchparameter aus Cookies (searchParameter, location, business, zip) für Sidebar-Anzeige
  • Weist Template zu: businessOptions, technology (zugehöriger Technology-Node), project, request
Domain-Objekte
Side-Effects
DateienClasses/Controller/ProjectController.php, Classes/Domain/Model/Project.php, Classes/Domain/Repository/ProjectRepository.php
AJAX-Suche im Projektportal (Typeahead) URL/Route: GET api/search → ProjectController::ajaxSearchAction (@SkipCsrfProtection) ProjectController::ajaxSearchAction Gast (öffentlich zugänglich, CSRF-Schutz deaktiviert) In Drupal als Search API Autocomplete oder JSON:API-Endpoint mit Views-Ergebnis nachbauen. Limit von 3 Treffern konfigurierbar. WICHTIG: URL-Generierung erfolgte über Neos-Node-Lookup — in Drupal als Drupal-URL zum node/NID realisieren.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Empfängt searchString und additionalParams via GET
  • Setzt Limit aus Settings (limitAjaxSearchResults = 3)
  • Ruft SearchService::search() auf → findBySearchString()
  • Pro gefundenem Projekt: ruft Neos-ContentNode via FlowQuery auf, generiert URL aus Node
  • Gibt JSON-Array zurück: [{id, title, link}]
Domain-Objekte
Side-Effects
DateienClasses/Controller/ProjectController.php, Classes/Domain/Service/SearchService.php, Configuration/Routes.yaml
AJAX-Suche im Haupt-Navigationsmenü (siteweite Suche) URL/Route: GET api/mainsearch → SearchController::ajaxSearchAction (@SkipCsrfProtection) SearchController::ajaxSearchAction Gast (öffentlich zugänglich, CSRF-Schutz deaktiviert) In Drupal als Search API Autocomplete mit multiple-Index (Projektangebote + Seiten) nachbauen. color-Kategorie als UI-Hinweis nutzen. Lücke: Der Neos NodeSearchService sucht in allen Properties mit searchable-Flag — in Drupal müssen die korrespondierenden Search-API-Felder explizit konfiguriert werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Empfängt searchString via GET
  • Ruft Neos NodeSearchService::findByProperties() auf (sucht in allen Nodes mit searchable=TRUE Properties, Case-Insensitive uppercase)
  • Kategorisiert Treffer nach NodeType: Document.Portal.Project → topic='Projektangebot' + color='portal'; Document.Page / Document.ReferenceProject → topic='Unternehmen' + color='site'; Career/Portal.Project.Business/Technology werden nicht ausgegeben
  • De-dupliziert nach URL
  • Sortiert Ergebnis nach topic (descending)
  • Gibt JSON-Array zurück: [{title, topic, link, color}]
Domain-Objekte
Side-Effects
DateienClasses/Controller/SearchController.php, Configuration/Routes.yaml
Volltext-Suche (IndexedSearch, Blog/Karriere-Suche) URL/Route: Plugin-Widget auf Neos-Seite → IndexedSearchController::searchResultAction (Form-Submit mit searchParameter) IndexedSearchController::searchResultAction Gast (öffentlich zugänglich) Entspricht der siteweiten Volltextsuche. In Drupal als Search API Views-Page mit Highlighting nachbauen. Kontextsensitivität (career vs. Unternehmen) über separaten Index oder Content-Type-Filter realisieren. AJAX-Variante (searchResultAjaxAction) liefert maximal 10 Treffer als JSON für Autocomplete.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Liest searchParameter aus POST-Body (Plugin-Namespace --contenance_solcom-blog_indexedsearch)
  • Ruft IndexedSearchService::search() auf: NodeSearchService::findByProperties() case-insensitive, kontextsensitiv nach sitecontext-Property (career oder leer)
  • Pro gefundenem Node: bestimmt documentNode via FlowQuery closest('[instanceof Contenance.Solcom:Document]')
  • Kategorisiert: Document.Page/ReferenceProject → filterWebsite=Unternehmen (oder Karriere bei searchContext=career); Portal.Project.Business/Technology → filterWebsite=Projektportal; Document.Portal.Project → filterPortal=Projektportal; Career.Project → filterCareer=Offene Stelle
  • Extrahiert findString mit Highlight-Kontext (max 20 Wörter um Treffer herum)
  • De-dupliziert nach documentNode-Identifier, nimmt besseren findString
  • Weist Template zu: searchResults, searchParameter, searchCount, searchContext
Domain-Objekte
Side-Effects
DateienClasses/Controller/IndexedSearchController.php, Classes/Domain/Service/IndexedSearchService.php
Businesses-Übersicht (Branchen-Navigation) Plugin-Request → ProjectController::businessesAction (Neos-Plugin auf Übersichtsseite) ProjectController::businessesAction Gast (öffentlich zugänglich) In Drupal als View mit aggregierter Zählung pro Business-Taxonomy-Term realisieren. BusinessDataSource liefert die verfügbaren Branchen für den Neos-Editor-Inspector.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Findet alle Neos-Nodes vom Typ Contenance.Solcom:Portal.Project.Business
  • Pro Node: liest product-Array (Branchen-Tags), zählt Projekte via ProjectRepository::countByBusinesses()
  • Filtert Nodes mit 0 Treffern heraus
  • Weist Template zu: businesses [{title, counter, node}]
Domain-Objekte
Side-Effects
DateienClasses/Controller/ProjectController.php, Classes/Domain/Repository/ProjectRepository.php, Classes/DataSource/BusinessDataSource.php
Technologies-Übersicht (Technologie-Navigation) Plugin-Request → ProjectController::technologiesAction (Neos-Plugin auf Übersichtsseite) ProjectController::technologiesAction Gast (öffentlich zugänglich) In Drupal als View mit aggregierter Zählung pro Technologie-Taxonomy-Term. TechnologyDataSource liefert Skills aus allen Projekten für Editor-Auswahl.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Findet alle Neos-Nodes vom Typ Contenance.Solcom:Portal.Project.Technology
  • Pro Node: liest product-Array (Skills/Technologien), sucht Projekte via ProjectRepository::findByTagsAndSkills()
  • Weist Template zu: technologies [{title, subTitle, counter, node}]
Domain-Objekte
Side-Effects
DateienClasses/Controller/ProjectController.php, Classes/Domain/Repository/ProjectRepository.php, Classes/DataSource/TechnologyDataSource.php
Projekt Twitter-Redirect (Kurz-URL) URL/Route: GET t/{id} → ProjectController::twitterRedirectAction ProjectController::twitterRedirectAction Gast (öffentlich zugänglich) In Drupal als Redirect-Modul-Eintrag oder custom Route mit node/NID-Lookup realisieren. Lücke: Wird genutzt für Twitter/X-Sharing-URLs — in Drupal-Nachbau prüfen ob Social-Sharing-URLs weiter genutzt werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Liest id aus URL-Parameter
  • Sucht Projekt via ProjectRepository::findOneById(id)
  • Wenn gefunden: sucht zugehörigen Neos-Node via FlowQuery (Typ Document.Portal.Project, Filter nach projectid)
  • Leitet zu Projekt-URL weiter
  • Wenn nicht gefunden: Redirect zu /de/projektportal/404
Domain-Objekte
Side-Effects
  • HTTP-Redirect
DateienClasses/Controller/ProjectController.php, Configuration/Routes.yaml
Hauptsuche-Widget rendern (Projektzähler + Link zur Ergebnisseite) Plugin-Request → ProjectController::mainSearchAction ProjectController::mainSearchAction Gast (öffentlich zugänglich) Kleines Widget, das die Gesamtanzahl aktiver Projekte und einen Link zur Suchergebnis-Seite anzeigt. In Drupal als Block mit Views-Count oder custom Twig-Snippet.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Zählt alle Projekte in DB via ProjectRepository::countAll()
  • Sucht searchResultPage-Node aus Settings via FlowQuery
  • Weist Template zu: searchResultNode, projectCounter
Domain-Objekte
Side-Effects
DateienClasses/Controller/ProjectController.php
Bookmark hinzufügen (Merkliste) URL/Route: GET api/bookmark/add/{project} → BookmarkController::addAction BookmarkController::addAction Gast und eingeloggter Projektpartner In Drupal bereits als WatchlistController (solcom_project_offer) vorhanden. Limit von 7 Einträgen muss dort enforced sein. WICHTIG: Dual-Storage (Session + DB) — in Drupal Session für Gäste, DB-Feld für eingeloggte Nutzer; Session-Merge beim Login muss implementiert sein.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Empfängt Project-Objekt via Route-Part (ObjectType-Mapping auf Contenance\Solcom\Domain\Model\Project)
  • Prüft aktuellen Bookmark-Count: Maximum 7 Einträge
  • Wenn < 7: Bookmark::addProject(projectId) — speichert UUID in session-scoped Bookmark-Objekt
  • Wenn eingeloggt: liest User via UserRepository, fügt projectId auch zum User.bookmarks-Array hinzu, persistiert
  • Gibt JSON zurück: {title, text, status} — status 1=hinzugefügt, 2=bereits vorhanden, 3=Maximum erreicht
Domain-Objekte
Side-Effects
  • Session-Write: Bookmark-Objekt (Flow\Scope=session)
  • DB-Write (wenn eingeloggt): User.bookmarks-Array aktualisieren
DateienClasses/Controller/BookmarkController.php, Classes/Domain/Model/Bookmark.php, Configuration/Routes.yaml
Bookmark entfernen (Merkliste) URL/Route: GET api/bookmark/remove/{project} → BookmarkController::removeAction BookmarkController::removeAction Gast und eingeloggter Projektpartner In Drupal WatchlistController bereits vorhanden. Symmetrisch zum Hinzufügen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Empfängt Project-Objekt via Route-Part
  • Ruft Bookmark::removeProject(projectId) auf (entfernt aus Session-Array)
  • Wenn eingeloggt: entfernt projectId aus User.bookmarks-Array, persistiert
  • Gibt JSON true/false zurück
Domain-Objekte
Side-Effects
  • Session-Write: Bookmark aus Session entfernen
  • DB-Write (wenn eingeloggt): User.bookmarks-Array aktualisieren
DateienClasses/Controller/BookmarkController.php, Classes/Domain/Model/Bookmark.php, Configuration/Routes.yaml
Alle Bookmarks löschen (Merkliste leeren) URL/Route: GET api/bookmark/removeall → BookmarkController::removeAllAction BookmarkController::removeAllAction Gast und eingeloggter Projektpartner In Drupal WatchlistController bereits vorhanden. Wird auch nach erfolgreicher Bewerbung automatisch aufgerufen (jobApplicationSendAction leert Bookmarks).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Ruft Bookmark::removeAllProjects() auf (leert Session-Array)
  • Wenn eingeloggt: setzt User.bookmarks auf leeres Array, persistiert
  • Gibt JSON '0'/'1' zurück
Domain-Objekte
Side-Effects
  • Session-Write: alle Bookmarks aus Session entfernen
  • DB-Write (wenn eingeloggt): User.bookmarks leeren
DateienClasses/Controller/BookmarkController.php, Classes/Domain/Model/Bookmark.php, Configuration/Routes.yaml
Bookmark-Anzahl abfragen (Badge-Counter) URL/Route: GET api/bookmark/count → BookmarkController::countAction BookmarkController::countAction Gast und eingeloggter Projektpartner In Drupal WatchlistController bereits vorhanden. Wird per AJAX für Merklisten-Badge-Counter in der Navigation aufgerufen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Ruft Bookmark::getTotal() auf
  • Gibt Anzahl als String zurück
Domain-Objekte
Side-Effects
DateienClasses/Controller/BookmarkController.php, Classes/Domain/Model/Bookmark.php, Configuration/Routes.yaml
Merkliste anzeigen (Bookmark-Übersicht) Plugin-Request → BookmarkController::indexAction (Neos-Plugin auf Merklisten-Seite, Session autoStart) BookmarkController::indexAction Gast und eingeloggter Projektpartner (Session autoStart) In Drupal WatchlistController (solcom_project_offer) bereits vorhanden. Automatisches Bereinigen nicht mehr existenter Projekte muss ebenfalls implementiert sein.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Ruft Bookmark::getProjects() auf (Session-Array der UUIDs)
  • Pro UUID: ProjectRepository::findByIdentifier(); wenn Projekt nicht mehr existiert → automatisch aus Bookmark entfernen
  • Liest aktuelle Suchparameter aus Cookies (searchParameter, location, business, zip) für Sidebar
  • Weist Template zu: businessOptions, projects (Array von Project-Objekten), counter
Domain-Objekte
Side-Effects
  • Session-Write: veraltete Bookmarks werden automatisch bereinigt wenn Projekt nicht mehr existiert
DateienClasses/Controller/BookmarkController.php, Classes/Domain/Model/Bookmark.php, Classes/Domain/Repository/ProjectRepository.php
Projekt-Bewerbungsformular anzeigen URL/Route: {node}_{project} (Projektportal Application Route) → ProjectController::jobApplicationAction ProjectController::jobApplicationAction Gast und eingeloggter Projektpartner In Drupal als Webform oder Custom Form mit Conditional Logic nachbauen. Dual-Mode (Gast/User) mit Vorausfüllen aus Profil ist kritisch. Mehrfach-Bewerbung aus Watchlist (mehrere Projekte gleichzeitig) muss explizit unterstützt werden — Lücke im Drupal-Nachbau prüfen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Liest Projekt-Identifier aus Route (einzelnes Projekt) oder aus Plugin-Namespace contenance_solcom-jobapplication (Mehrfach-Bewerbung aus Bookmark-Liste)
  • Sammelt Projekt-Objekte (title, id, projectuuid) für alle Bewerbungsprojekte
  • Wenn eingeloggt: liest User-Daten (Uploads, Sprachen) und befüllt Formular vor
  • Liest formValues für Validierungsfehler-Rückgabe
  • Weist Template zu: projects, countryOptions, languageOptions, documentNode, containerGuest/containerUser, user, loggedIn, userUploads
Domain-Objekte
Side-Effects
DateienClasses/Controller/ProjectController.php, Configuration/Routes.yaml
Projekt-Bewerbung absenden Form-Submit POST → ProjectController::jobApplicationSendAction ProjectController::jobApplicationSendAction Gast und eingeloggter Projektpartner Zentraler kritischer Flow. In Drupal als Webform-Submission + Custom SubmissionHandler nachbauen. WICHTIGE LÜCKEN: (1) Dual-Mode Registrierung (Konto anlegen beim Bewerben) muss als Webform-Finisher realisiert werden; (2) Mehrfach-Bewerbung (mehrere Projekte gleichzeitig) — je Projekt eine separate Mail; (3) UTM-Cookie-Tracking für Bewerbungs-Herkunft; (4) Filesystem-Sicherung der Bewerbungen (DSGVO-Aspekt); (5) Google Measurement Protocol; (6) Profile-Update während Bewerbung.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Liest $_POST[--contenance_solcom-portal_project_jobapplication]
  • Validiert E-Mail (FILTER_VALIDATE_EMAIL); bei Magazin-Opt-In: Mailingwork-Subscription via mailingworkService
  • Verarbeitet bis zu 4 Datei-Uploads (Felder new-uploadfield00-03): sucht in TemporaryFileRepository oder UserFileRepository
  • Wenn nicht eingeloggt und Passwort angegeben: erstellt RegistrationFlow-Objekt, speichert verschlüsselt, sendet Aktivierungs-E-Mail an Bewerber (Template Mail/Account/Registration.html)
  • Wenn eingeloggt und changeProfileInformation=true: aktualisiert User-Profil via saveProfileInformations()
  • Wenn eingeloggt: löscht UserUpdateNotifications, speichert neue Uploads als UserFile-Objekte
  • Pro Bewerbungsprojekt: rendert Application.html-Template (StandaloneView), speichert als bewerbung.html + Dokumente im Filesystem unter FLOW_PATH_DATA/Bewerbungen/YYYY-MM-DD/
  • Sendet Bewerbungs-Mail an bewerbung@solcom.de (CC: project.contact wenn vorhanden), mit Dateianhängen; Betreff enthält UTM-Source aus Cookie wenn vorhanden
  • Sendet Eingangsbestätigungs-Mail an Bewerber (Template Mail/ApplicationConfirmation.html)
  • Google Analytics Measurement Protocol: sendet Hit via GoogleService (nur ohne _ga-Cookie)
  • Löscht Session-Bookmarks (Bookmark::removeAllProjects())
  • Bereinigt temporäre Dateien
  • Redirect zu jobApplicationSuccess mit projectCollection, transactionId, error
Domain-Objekte
Side-Effects
  • Mail an bewerbung@solcom.de (Bewerbung) + CC an project.contact
  • Mail an Bewerber (Eingangsbestätigung)
  • Mail an Bewerber (Registrierungsbestätigung wenn neues Konto angelegt)
  • DB-Write: RegistrationFlow wenn neues Konto
  • DB-Write: UserFile-Objekte (Uploads)
  • DB-Write: User-Profil Update
  • DB-Write: UserUpdateNotifications löschen
  • Filesystem-Write: Bewerbung als HTML + Dokumente unter FLOW_PATH_DATA/Bewerbungen/
  • Mailingwork-API: Newsletter-Opt-In wenn magazine=true
  • Google Analytics Measurement Protocol: Hit senden
  • Session-Write: Bookmarks leeren
DateienClasses/Controller/ProjectController.php, Classes/Domain/Model/Bookmark.php, Classes/Domain/Repository/ProjectRepository.php, Configuration/Routes.yaml, Configuration/Settings.Solcom.yaml
Bewerbungs-Erfolgsseite anzeigen Redirect nach jobApplicationSendAction → ProjectController::jobApplicationSuccessAction ProjectController::jobApplicationSuccessAction Gast und eingeloggter Projektpartner In Drupal als Webform-Confirmation-Page oder Redirect-URL nach erfolgreicher Submission. transactionId wird für Google Analytics E-Commerce-Tracking genutzt.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Empfängt projectCollection, transactionId (Google Analytics), error
  • Wenn eingeloggt: setzt isUser=true
  • Weist Template zu: error, projectCollection, transactionId
Domain-Objekte
Side-Effects
DateienClasses/Controller/ProjectController.php
Business-DataSource für Neos-Editor (Branchen-Auswahlliste) Neos-Backend Editor ruft DataSource via AJAX auf (identifier: contenance-solcom-business) BusinessDataSource::getData Neos-Backend-Redakteur (Admin) In Drupal nicht direkt relevant — entspricht einem dynamischen Taxonomy-Vocabulary das aus Projekt-Daten befüllt wird. In Drupal als Taxonomy-Term-Autocomplete oder Select-Feld mit Dynamic Entity Reference lösen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Liest bestehende Werte aus Node-Property 'product'
  • Durchsucht alle Projekte in DB via ProjectRepository::findAll()
  • Sammelt unique business-Werte (toLowerCase für Vergleich, Original für Label)
  • Gibt Options-Array [{label, value}] zurück für Inspector-Dropdown
Domain-Objekte
Side-Effects
DateienClasses/DataSource/BusinessDataSource.php, Classes/Domain/Repository/ProjectRepository.php
Technology-DataSource für Neos-Editor (Skills-Auswahlliste) Neos-Backend Editor ruft DataSource via AJAX auf (identifier: contenance-solcom-technology) TechnologyDataSource::getData Neos-Backend-Redakteur (Admin) Entspricht einer dynamischen Skill/Technologie-Taxonomy aus Projekt-Daten. In Drupal als Term-Autocomplete oder konfiguriertes Select-Feld. Tags-Feld ist im Code auskommentiert — nur skills werden extrahiert.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Liest bestehende Werte aus Node-Property 'product'
  • Durchsucht alle Projekte in DB via ProjectRepository::findAll()
  • Extrahiert skills-Feld (komma-separiert) und explodiert es
  • Sammelt unique skill-Werte (toLowerCase für Vergleich, Original für Label)
  • Gibt Options-Array [{label, value}] zurück
Domain-Objekte
Side-Effects
DateienClasses/DataSource/TechnologyDataSource.php, Classes/Domain/Repository/ProjectRepository.php

Account — Registrierung, Login, Passwort, 2FA Kern 19 Flows

FlowAuslöserEntrypointZugriffDrupal-Nachbau
Projektpartner-Registrierung (Formular + Double-Opt-In) Form-Submit POST auf AccountController::registerAction (Plugin-Route contenance_solcom-portal_account_register) AccountController::registerAction Gast (keine Authentifizierung erforderlich; @Flow\SkipCsrfProtection nicht gesetzt — CSRF-Schutz aktiv) In Drupal via Webform + Custom Submit Handler (ProjektpartnerRegistrationSubmitter) bereits vorhanden (solcom_projektpartner Modul). Doppelter Token-Timeout-Wert aus Settings muss übernommen werden. Mailingwork-Integration als separater Service-Call. UserFile-Upload-Limit (5 Dateien, 5 MB) prüfen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Gast ruft Registrierungsformular auf (registerFormAction rendert Formular mit Länder- und Sprach-Optionen)
  • Gast sendet Formular mit E-Mail, Passwort, Profildaten und optionalen Datei-Uploads
  • registerAction prüft, ob die E-Mail bereits als Neos-Account existiert (AccountRepository::findByAccountIdentifierAndAuthenticationProviderName); wenn ja → Status 'emailInUse'
  • Neues RegistrationFlow-Objekt wird erstellt: PasswordDto gesetzt, Attribute übernommen, Passwort verschlüsselt (storeEncryptedPassword), zufälliger 30-Zeichen-Aktivierungstoken mit konfigurierbarem Timeout generiert (initializeObject)
  • Hochgeladene Dateien (PDF/DOC/DOCX, max. 5 MB je Datei) werden via UploadService validiert (checkUploadError, checkMimeType, checkFileSize), als PersistentResource importiert und als UserFile-Objekte an den RegistrationFlow angehängt
  • Bereits vorhandene RegistrationFlows für dieselbe E-Mail werden gelöscht (Idempotenz-Guard)
  • RegistrationFlow wird in der Datenbank persistiert (RegistrationFlowRepository::add)
  • Optional: Wenn Nutzer Newsletter gewählt hat, wird E-Mail über MailingworkService in Mailingwork-Liste eingetragen
  • Aktivierungs-E-Mail an den Gast gesendet (Template Mail/Account/Registration.html) mit tokenbasiertem Bestätigungslink (Node-UUID 771bde22-...) und Anrede/Name
  • Formular zeigt Status 'ok' mit RegistrationFlow-Daten an
Domain-Objekte
Side-Effects
  • DB-Write: RegistrationFlow angelegt
  • DB-Write: UserFile-Objekte angelegt
  • DB-Write: Alte RegistrationFlows für gleiche E-Mail gelöscht
  • Mail an Gast: Bestätigung Ihrer Registrierung im SOLCOM Projektportal (from: no-reply@solcom.de)
  • Externe API: Mailingwork addEmailToMailingworks (nur wenn Magazine-Option gesetzt)
DateienClasses/Controller/AccountController.php, Classes/Domain/Model/RegistrationFlow.php, Classes/Domain/Model/PasswordDto.php, Classes/Domain/Service/Flow/FlowUserCreationService.php, Classes/Domain/Service/UserCreationServiceInterface.php
Account-Aktivierung per E-Mail-Token (Double-Opt-In Abschluss) GET-Request auf Aktivierungs-Seite mit URL-Parameter ?token=<30-Zeichen-String> (Node-UUID 771bde22-c56e-4062-aa9b-d63017551f4e) AccountController::activationAction Gast (Token-Link aus E-Mail; @Flow\Session(autoStart=TRUE)) Im Drupal-Modul solcom_projektpartner als Aktivierungs-Endpoint vorhanden. RMS-Nummer-Logik (Auto-Increment-Counter via Rms-Entity) muss geprüft werden. Token-Ablauf-Logik (configurable Timeout) muss 1:1 abgebildet sein. Zwei Bestätigungs-Mails (Solcom intern + Nutzer) sind Pflicht.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Seite wird mit ?token-Parameter aufgerufen; Controller liest Token aus Parent-Request-Arguments
  • RegistrationFlowRepository::findOneByActivationToken sucht passenden Flow
  • Wenn kein Flow gefunden: Token-Status 2 (NOT FOUND)
  • Wenn Flow gefunden und Token abgelaufen (hasValidActivationToken = false): Token-Status 3 (EXPIRED)
  • Wenn Flow gefunden und Token gültig und Account bereits existiert (AccountRepository::findByAccountIdentifierAndAuthenticationProviderName): Token-Status 4 (ALREADY EXISTING)
  • Wenn Flow gefunden, Token gültig, kein Account: UserCreationServiceInterface::createUserAndAccount erstellt Neos-Account (Rolle Neos.Neos:FrontendUser, Provider Contenance.Solcom:Login) und User-Entity mit allen Profildaten aus RegistrationFlow; RMS-Nummer wird aus Rms-Counter zugeteilt und Counter incrementiert
  • Dateianhänge aus RegistrationFlow werden auf User übertragen (UserFile.registrationFlow = null, UserFile.user = user)
  • Benachrichtigungs-Mail an SOLCOM (bewerbung@solcom.de) gesendet (Template RegistrationNotification.html) mit allen Formular-Feldern, RMS-Nummer, Datei-Anhängen und formatierten Skills
  • Bestätigungs-Mail an neuen Nutzer gesendet (Template RegistrationUserNotification.html)
  • RegistrationFlow wird aus DB entfernt (whitelisted)
  • Token-Status 1 (OK) wird an Template übergeben
  • Wenn kein Token in Request und Frontend: Redirect zur Projektübersicht
Domain-Objekte
Side-Effects
  • DB-Write: User und Account angelegt (UserRepository::add, AccountRepository implizit via Cascade)
  • DB-Write: UserFile-Objekte auf User verschoben
  • DB-Write: RegistrationFlow gelöscht
  • DB-Write: Rms-Counter incrementiert
  • Mail an bewerbung@solcom.de: Neue Registrierung mit PPP-ID (with attachments)
  • Mail an neuen Nutzer: Aktivierung erfolgreich
DateienClasses/Controller/AccountController.php, Classes/Domain/Model/RegistrationFlow.php, Classes/Domain/Service/Flow/FlowUserCreationService.php, Classes/Domain/Service/Neos/NeosUserCreationService.php
Token-Erneuerung nach abgelaufenem Registrierungs-Token (Re-Activation) GET-Request auf Re-Aktivierungs-Seite mit URL-Parameter ?token=<abgelaufener-Token> AccountController::reActivationAction Gast (Token-Link aus abgelaufener E-Mail; @Flow\Session(autoStart=TRUE)) Lücke in Drupal prüfen: Dieser Re-Activation-Endpoint existiert möglicherweise noch nicht. Notwendig, wenn Token-Timeout kürzer als typische Nutzer-Reaktionszeit.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Seite wird mit ?token-Parameter aufgerufen
  • RegistrationFlowRepository::findOneByActivationToken sucht passenden Flow
  • Wenn kein Flow gefunden: Redirect zur Projektübersicht
  • Wenn Flow gefunden und Token noch gültig: Redirect zur Projektübersicht (erneute Aktivierung unnötig)
  • Wenn Flow gefunden und Token abgelaufen: generateActivationToken() erstellt neuen Token mit neuem Timeout-Zeitstempel
  • RegistrationFlow wird mit neuem Token in DB aktualisiert
  • Neue Aktivierungs-E-Mail wird an Gast gesendet (Template Mail/Account/Registration.html) mit neuem Aktivierungslink
Domain-Objekte
Side-Effects
  • DB-Write: RegistrationFlow mit neuem Token aktualisiert
  • Mail an Gast: Neue Bestätigung Ihrer Registrierung im SOLCOM Projektportal (from: no-reply@solcom.de)
DateienClasses/Controller/AccountController.php, Classes/Domain/Model/RegistrationFlow.php
Login (Neos Flow AbstractAuthenticationController) Form-Submit POST auf LoginController (Neos Flow UsernamePassword-Token; AbstractAuthenticationController::authenticateAction) LoginController::onAuthenticationSuccess / LoginController::onAuthenticationFailure Gast (Authentifizierung ist der Auslöser; @Flow\SkipCsrfProtection gesetzt) Drupal-Login via Standard-Auth-System vorhanden. Besonderheit: Bookmark-Sync Session↔DB und Gast-Alert-Migration müssen in hook_user_login implementiert werden. Status-Parameter für Fehlermeldungen (AuthFailure, AuthFailureButExistingAccount) brauchen eigene Drupal-Drupal-Logik.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Nutzer öffnet Login-Seite: loginAction rendert Formular, liest Cookie-basierte Suchparameter (searchParameter, location, business, zip) und Branchen-Optionen aus Neos-Nodes
  • Neos Flow Security Framework verarbeitet UsernamePassword-Token (Provider Contenance.Solcom:Login)
  • Bei Erfolg: onAuthenticationSuccess aufgerufen; Account und User-Entity werden geladen
  • Bookmark-Synchronisation: Wenn User Bookmarks in DB hat, werden Session-Bookmarks überschrieben; sonst werden Session-Bookmarks in User-Entity gespeichert
  • Gast-Projektalert-Migration: Wenn kein UserProjectAlert vorhanden, werden vorhandene GuestProjectAlerts (by E-Mail) auf User umgehängt (Guest-Referenz entfernt, User-Referenz gesetzt, GuestProjectAlert gelöscht)
  • Wenn referierende Anfrage ein JobApplication-Plugin-Argument enthält, wird zum HTTP_REFERER zurückgeleitet
  • Andernfalls: Redirect zu Account::profile mit status=AuthSuccess
  • Wenn User-Entity null (Account ohne User): Redirect zu login mit status=AuthFailure
  • Bei Fehler: onAuthenticationFailure aufgerufen; wenn E-Mail-Adresse in RegistrationFlow vorhanden (noch nicht aktiviertes Konto): Status 'AuthFailureButExistingAccount'; sonst Status 'AuthFailure'
Domain-Objekte
Side-Effects
  • DB-Write: User-Bookmarks ggf. aktualisiert
  • DB-Write: GuestProjectAlerts auf User umgehängt und GuestProjectAlert-Entitäten gelöscht
  • Session: Neos Flow Security-Session gestartet (@Flow\Session(autoStart=TRUE))
  • Redirect nach Login zu Profil-Seite
DateienClasses/Controller/LoginController.php, Classes/Domain/Repository/UserRepository.php
Logout GET/POST auf LoginController::logoutAction (URL /de/projektportal/login/auth/logout) LoginController::logoutAction Eingeloggter Projektpartner (@Flow\SkipCsrfProtection gesetzt) Drupal-Standard-Logout vorhanden. Besonderheit: Redirect-Ziel und der spezifische Logout-URL-Pfad müssen konfiguriert sein. Kein Cookie-Cleanup nötig (Flow-Session-basiert).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • AbstractAuthenticationController::logoutAction beendet alle aktiven Security-Tokens (Neos Flow Security invalidiert Session)
  • Direkter HTTP-Header-Redirect (302/303) zu /de/projektportal/login/auth/logout mit Status-Parameter ?status=Logout
  • HTML-Meta-Refresh als Fallback eingebettet
Domain-Objekte
Side-Effects
  • Session: Alle aktiven Authentication-Tokens werden invalidiert
  • HTTP-Redirect: /de/projektportal/login/auth/logout
DateienClasses/Controller/LoginController.php
Logout nach Account-Löschung Intern nach AccountController::deleteAction: Redirect zu LoginController::logoutAfterAccountRemovalAction LoginController::logoutAfterAccountRemovalAction Eingeloggter Projektpartner (wird direkt nach deleteAction aufgerufen; @Flow\SkipCsrfProtection gesetzt) Drupal-Standard-Logout nach Account-Löschung vorhanden. Spezifische Zielseite (Bestätigungsseite 'account-loeschung') muss als Drupal-Seite existieren.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • AbstractAuthenticationController::logoutAction beendet alle aktiven Security-Tokens
  • Direkter HTTP-Header-Redirect (302/303) zu /de/projektportal/mein-profil/account-loeschung
  • HTML-Meta-Refresh als Fallback
Domain-Objekte
Side-Effects
  • Session: Alle aktiven Authentication-Tokens werden invalidiert
  • HTTP-Redirect: /de/projektportal/mein-profil/account-loeschung (Bestätigungsseite)
DateienClasses/Controller/LoginController.php, Classes/Controller/AccountController.php
Passwort vergessen / Reset-Link anfordern Form-Submit POST: AccountController::passwordForgottenSendAction mit E-Mail-Adresse AccountController::passwordForgottenSendAction Gast (nur wenn nicht eingeloggt; @Flow\SkipCsrfProtection gesetzt) Drupal hat Standard-Passwort-Reset. Besonderheit: Fallback-Nachricht wenn RegistrationFlow (noch nicht aktiviertes Konto) existiert muss implementiert werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Nutzer öffnet Passwort-Vergessen-Seite (passwordForgottenAction); wenn bereits eingeloggt: Redirect zur Profil-Seite
  • Nutzer sendet E-Mail-Adresse ab
  • Validierung: E-Mail muss gültiges Format haben
  • AccountRepository::findActiveByAccountIdentifierAndAuthenticationProviderName prüft ob aktiver Account existiert
  • Wenn Account existiert: Alle vorhandenen ResetPasswordFlows für diese E-Mail werden gelöscht
  • Neuer ResetPasswordFlow wird erstellt mit zufälligem 30-Zeichen-Token und konfigurierbarem Timeout (generateResetPasswordToken)
  • Password-Reset-E-Mail an Nutzer gesendet (Template Mail/Account/PasswordForgotten.html) mit tokenbasiertem Reset-Link (Node-UUID c7dfad8f-d99a-4b93-9aa8-88dab0324813)
  • ResetPasswordFlow in DB persistiert
  • Wenn Account nicht existiert aber RegistrationFlow vorhanden (noch nicht aktiviert): 'registerFlowExisting' Flag gesetzt im View
  • Wenn weder Account noch RegistrationFlow: 'resetPasswordFlow' = false im View
Domain-Objekte
Side-Effects
  • DB-Write: Alte ResetPasswordFlows für E-Mail gelöscht
  • DB-Write: Neuer ResetPasswordFlow angelegt
  • Mail an Nutzer: Zurücksetzen Ihres Passwortes (from: no-reply@solcom.de)
DateienClasses/Controller/AccountController.php, Classes/Domain/Model/ResetPasswordFlow.php
Passwort-Reset per Token (neues Passwort setzen) GET mit ?token-Parameter auf passwordResetAction; dann Form-Submit POST auf passwordResetSendAction AccountController::passwordResetAction / AccountController::passwordResetSendAction Gast (Token-Link aus E-Mail; @Flow\SkipCsrfProtection gesetzt für passwordForgottenAction) Drupal hat Standard-Passwort-Reset. lastAccountEditDate-Tracking bei Reset muss im Custom-Code nachgezogen werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Nutzer öffnet Reset-Link aus E-Mail (?token=...)
  • passwordResetAction prüft Token: nicht gefunden → Status 'tokenNotFound'; abgelaufen (hasValidResetPasswordToken = false) → Status 'tokenTimeout'; kein Token im Request → Status 'tokenNotGiven'
  • Bei gültigem Token: ResetPasswordFlow wird an Template übergeben
  • Nutzer gibt neues Passwort und Bestätigung ein (PasswordDto-Validation via CustomPasswordDtoValidator)
  • passwordResetSendAction: Account wird via E-Mail aus ResetPasswordFlow geladen
  • Neues verschlüsseltes Passwort wird via account::setCredentialsSource in Account gesetzt
  • User::lastAccountEditDate wird auf aktuelles Datum gesetzt
  • ResetPasswordFlow wird aus DB entfernt
  • View gibt Status 'success' aus
Domain-Objekte
Side-Effects
  • DB-Write: Account-Credentials aktualisiert
  • DB-Write: User.lastAccountEditDate aktualisiert
  • DB-Write: ResetPasswordFlow gelöscht
DateienClasses/Controller/AccountController.php, Classes/Domain/Model/ResetPasswordFlow.php, Classes/Domain/Model/PasswordDto.php
Account-Daten ändern (Passwort und/oder Login-E-Mail) Form-Submit POST auf AccountController::editAccountSendAction (eingeloggter Nutzer im Profil-Bereich) AccountController::editAccountSendAction Eingeloggter Projektpartner (account !== null; Identitäts-Check via account === user.account) Im Drupal-Modul solcom_projektpartner teilweise vorhanden. Wichtig: E-Mail-Änderung läuft über separaten Double-Opt-In-Flow (AccountChangeFlow) — nicht sofort; das muss im Drupal-Nachbau ebenso zweistufig sein.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Nutzer öffnet Account-Bearbeitungs-Formular (editAccountAction; zeigt aktuellen User-State)
  • Nutzer sendet Formular mit optionalem neuem Passwort und/oder neuer E-Mail (beide Felder plus Bestätigung)
  • Identitäts-Prüfung: account muss zu user.account gehören
  • Validierung: E-Mail-Felder müssen übereinstimmen; Passwort-Felder müssen übereinstimmen; fieldMatchError wenn nicht
  • E-Mail-Änderung: Neue E-Mail darf nicht als Account oder RegistrationFlow bereits existieren; wenn frei: Alten AccountChangeFlow löschen, neuen AccountChangeFlow mit altem und neuem E-Mail-Wert und Token erstellen, Änderungs-E-Mail an neue Adresse senden
  • Passwort-Änderung: account::setCredentialsSource mit neu gehashetem Passwort (HashService::hashPassword), account aktualisiert, Bestätigungs-Mail an Nutzer-E-Mail gesendet
  • Redirect zu Account::profile mit kombinierten Status-Codes (accountChangedEmail, accountChangedPassword, accountChangedPasswordAndEmail, noAccountChange, couldNotChangeAccountEmailBecauseItsExisting, fieldMatchError)
Domain-Objekte
Side-Effects
  • DB-Write: AccountChangeFlow angelegt (bei E-Mail-Änderung)
  • DB-Write: Account.credentialsSource aktualisiert (bei Passwort-Änderung)
  • Mail an neue E-Mail-Adresse: Änderung Ihrer Login-E-Mail-Adresse (from: no-reply@solcom.de) mit Bestätigungslink
  • Mail an aktuelle Nutzer-E-Mail: Ihre Passwortänderung im SOLCOM Projektportal (from: no-reply@solcom.de)
DateienClasses/Controller/AccountController.php, Classes/Domain/Model/AccountChangeFlow.php, Classes/Domain/Model/PasswordDto.php
Login-E-Mail-Änderung bestätigen (AccountChangeFlow-Token) GET-Request auf Aktivierungs-Seite mit ?token-Parameter (Node-UUID 8a18013e-39e8-47e7-a313-7a0569039935) AccountController::activationChangeAccountEmailAction Gast/Nutzer (Token-Link aus E-Mail; kein Login-Check — Token ist der Auth-Faktor) Im Drupal-Modul solcom_projektpartner prüfen ob Double-Opt-In für E-Mail-Änderung implementiert ist. Zwei Bestätigungs-Mails (neue UND alte Adresse) sind Pflicht-Feature das leicht übersehen wird.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Nutzer klickt auf Bestätigungslink aus Änderungs-E-Mail
  • AccountChangeFlowRepository::findOneByActivationToken sucht passenden Flow
  • Wenn nicht gefunden: Token-Status 2 (NOT FOUND)
  • Wenn gefunden und Token abgelaufen (hasValidActivationToken = false): Token-Status 3 (EXPIRED)
  • Wenn gültig: Account-Identifier wird via accountRepository::findActiveByAccountIdentifierAndAuthenticationProviderName geladen und auf neue E-Mail gesetzt (account::setAccountIdentifier)
  • Zwei Bestätigungs-Mails werden gesendet: eine an neue Adresse, eine an alte Adresse
  • AccountChangeFlow wird gelöscht
  • Token-Status 1 (OK)
Domain-Objekte
Side-Effects
  • DB-Write: Account.accountIdentifier auf neue E-Mail aktualisiert
  • DB-Write: AccountChangeFlow gelöscht
  • Mail an neue E-Mail: Bestätigung Login-E-Mail-Adresse geändert (Template AccountChangeSuccessMailtoUser.html)
  • Mail an alte E-Mail: Bestätigung Login-E-Mail-Adresse geändert (Template AccountChangeSuccessMailtoUserOld.html)
DateienClasses/Controller/AccountController.php, Classes/Domain/Model/AccountChangeFlow.php
Account-Löschung (Self-Service) POST/GET auf AccountController::deleteAction (eingeloggter Nutzer im Profil-Bereich) AccountController::deleteAction Eingeloggter Projektpartner (account !== null; @Flow\SkipCsrfProtection gesetzt) Im Drupal-Modul solcom_projektpartner P2503-233 implementiert. Besonders wichtig: ProjectAlert-Konvertierung zu GuestProjectAlert bei Löschung (damit Jobalerts weiterhin für die E-Mail aktiv bleiben). Zwei Mails (intern + Nutzer) Pflicht.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Nutzer fordert Account-Löschung an
  • Laufender AccountChangeFlow für diesen Account wird gelöscht (Cleanup)
  • ProjectAlert-Handling: Wenn ein UserProjectAlert existiert, wird geprüft ob die E-Mail bereits als GuestProjectAlert vorhanden ist; wenn nicht: neuer GuestProjectAlert mit Nutzerdaten erstellt, ProjectAlert.user = null, ProjectAlert.guest = neuer Guest; wenn E-Mail schon als Guest existiert: ProjectAlert.user = null (kein neuer Guest)
  • User-Entity wird aus UserRepository entfernt (cascade: Account wird ebenfalls gelöscht)
  • Benachrichtigungs-Mail an SOLCOM (bewerbung@solcom.de) gesendet (Template Delete.html) mit User-Daten
  • Bestätigungs-Mail an Nutzer gesendet (Template DeleteNoticeForPP.html)
  • persistAll
  • Redirect zu LoginController::logoutAfterAccountRemovalAction (Session-Invalidierung + Redirect zur Bestätigungsseite)
Domain-Objekte
Side-Effects
  • DB-Write: AccountChangeFlow gelöscht
  • DB-Write: User und Account entfernt (via cascade)
  • DB-Write: ProjectAlert ggf. auf GuestProjectAlert umgehängt
  • DB-Write: Neuer GuestProjectAlert ggf. angelegt
  • Mail an bewerbung@solcom.de: Nutzerkonto Löschung
  • Mail an Nutzer: Bestätigung Löschung SOLCOM Nutzerkonto
  • Session: Invalidiert via logoutAfterAccountRemovalAction
DateienClasses/Controller/AccountController.php, Classes/Controller/LoginController.php
Profil-Daten bearbeiten (editProfile) Form-Submit POST auf AccountController::editProfileSendAction (eingeloggter Nutzer) AccountController::editProfileSendAction Eingeloggter Projektpartner (account !== null; Identitäts-Check) Im Drupal-Modul solcom_projektpartner prüfen. UserUpdateNotification-Tracking (welche Felder geändert wurden) ist eine interne Audit-Log-Funktion — ob sie in Drupal gebraucht wird, muss geklärt werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Nutzer öffnet Profil-Bearbeitungs-Formular (editProfileAction; zeigt aktuellen User-State mit Länder- und Sprach-Optionen)
  • Nutzer sendet geänderte Profildaten (Name, Adresse, Skills, Verfügbarkeit, Stundensatz, Freelancer-Netzwerke, etc.)
  • Identitäts-Prüfung: account muss zu user.account gehören
  • addProfileNotification: Alten User-State aus Doctrine UnitOfWork abrufen, Felder vergleichen, geänderte Felder als UserUpdateNotification-Entitäten persistieren (für interne Änderungs-Tracking)
  • UserRepository::update persistiert geänderten User
  • Redirect zu Account::profile mit status='profileChanged'
Domain-Objekte
Side-Effects
  • DB-Write: User-Entity aktualisiert (alle Profil-Felder)
  • DB-Write: UserUpdateNotification-Entitäten für geänderte Felder angelegt
DateienClasses/Controller/AccountController.php, Classes/Domain/Model/User.php
Profil-Dokument hochladen (AJAX) AJAX POST auf AccountController::userProfileUploadAction (eingeloggter Nutzer, multipart/form-data) AccountController::userProfileUploadAction Eingeloggter Projektpartner (account !== null; @Flow\SkipCsrfProtection gesetzt) Im Drupal-Modul solcom_projektpartner prüfen. Limit 5 Dateien und MIME-Type-Whitelist müssen konfiguriert sein. Dateiname-Bereinigung via preg_replace muss nachgebaut werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Nutzer lädt Datei im Profil-Bereich hoch
  • Prüfung: Nutzer hat maximal 4 bereits hochgeladene Dokumente (userFilesCount < 5)
  • UploadService-Validierung: checkUploadError, checkMimeType (PDF/DOC/DOCX per finfo), checkFileSize
  • MIME-Type-Whitelist (application/pdf, application/msword, .docx) und Größenlimit (5 MB)
  • ResourceManager::importUploadedResource importiert Datei, Dateiname wird auf alphanumerische Zeichen bereinigt (preg_replace)
  • UserFile-Entität mit Titel, Erstellungsdatum und PersistentResource erstellt
  • Datei wird User zugeordnet (user.addUserFile)
  • JSON-Response mit Status, Dateiname, fileId und imageUrl (persistentResource-URI)
Domain-Objekte
Side-Effects
  • DB-Write: UserFile-Entität angelegt
  • DB-Write: User.userFiles aktualisiert
  • Datei-Upload: Datei in Neos Flow ResourceManager gespeichert
DateienClasses/Controller/AccountController.php, Classes/Domain/Model/User.php
Profil-Dokument löschen (AJAX) AJAX POST/GET auf AccountController::userProfileUploadRemoveAction mit id-Parameter (eingeloggter Nutzer) AccountController::userProfileUploadRemoveAction Eingeloggter Projektpartner (account !== null; Eigentümer-Check via userFile.user === user; @Flow\SkipCsrfProtection gesetzt) Eigentümer-Check ist wichtig — sicherstellen dass Nutzer nur eigene Dateien löschen kann.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Nutzer sendet Lösch-Request mit UserFile-Identifier
  • UserFileRepository::findByIdentifier lädt UserFile
  • Identitäts-Check: userFile.user muss der eingeloggte User sein
  • user.removeUserFile, UserRepository::update, UserFileRepository::remove
  • JSON-Response mit Status
Domain-Objekte
Side-Effects
  • DB-Write: UserFile-Entität gelöscht
  • DB-Write: User.userFiles aktualisiert
DateienClasses/Controller/AccountController.php
Newsletter/Magazin-Opt-In Form-Submit POST auf AccountController::addMagazineAction oder während der Registrierung (registerAction) AccountController::addMagazineAction Eingeloggter Projektpartner (account !== null; Identitäts-Check) Mailingwork-Integration als externer Service vorhanden. Separate Opt-In-Aktion (nicht nur bei Registrierung) muss im Drupal-Profil vorhanden sein.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Eingeloggter Nutzer aktiviert Newsletter-Option im Profil
  • Identitäts-Prüfung: account muss zu user.account gehören
  • Anrede wird aus user.salutation ('m'/'f') zu 'Herr'/'Frau' konvertiert
  • MailingworkService::addEmailToMailingworks mit title, lastName, firstName, email aufgerufen
  • Bei Erfolg: user.magazineOption = true, User aktualisiert, Redirect zu Profile mit status='profileMagazinAdd'
  • Bei Fehler: Redirect zu Profile ohne Status
Domain-Objekte
Side-Effects
  • DB-Write: User.magazineOption auf true gesetzt
  • Externe API: Mailingwork addEmailToMailingworks
DateienClasses/Controller/AccountController.php, Classes/Domain/Model/User.php
Newsletter/Magazin-Opt-Out Form-Submit POST auf AccountController::removeMagazineAction (eingeloggter Nutzer) AccountController::removeMagazineAction Eingeloggter Projektpartner (account !== null; Identitäts-Check) Symmetrie zu addMagazine muss gewährleistet sein. Mailingwork-Remove-Aufruf ist Pflicht.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Eingeloggter Nutzer deaktiviert Newsletter-Option im Profil
  • Identitäts-Prüfung: account muss zu user.account gehören
  • MailingworkService::removeEmailFromMailingworks mit user.email aufgerufen
  • Bei Erfolg: user.magazineOption = false, User aktualisiert, Redirect zu Profile mit status='profileMagazinRemove'
  • Bei Fehler: Redirect zu Profile ohne Status
Domain-Objekte
Side-Effects
  • DB-Write: User.magazineOption auf false gesetzt
  • Externe API: Mailingwork removeEmailFromMailingworks
DateienClasses/Controller/AccountController.php
E-Mail-Verfügbarkeit prüfen (AJAX) AJAX GET/POST auf AccountController::emailcheckAction mit email-Parameter AccountController::emailcheckAction Gast (kein Auth-Check — öffentliche AJAX-Endpoint) Einfacher AJAX-Endpoint für Live-Validierung im Registrierungsformular. Muss im Drupal-Registrierungsformular als Callback vorhanden sein.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Aufruf mit E-Mail-Parameter (z.B. während Registrierungs-Formular-Eingabe)
  • AccountRepository::findByAccountIdentifierAndAuthenticationProviderName prüft ob E-Mail bereits als Account existiert
  • Gibt '0' (nicht vorhanden) oder '1' (vorhanden) als Plain-Text zurück
Domain-Objekte
Side-Effects
DateienClasses/Controller/AccountController.php
Dashboard-Login (internes Admin-Dashboard mit Hardcoded-Passwort) Form-Submit POST auf DashboardController::authenticateAction mit password-Parameter DashboardController::authenticateAction Jeder mit Zugang zur Dashboard-URL (kein IP-Filter, kein CSRF-Schutz sichtbar — sicherheitskritisch!) Dieses Dashboard (Spendenstatistik-Tool) scheint eine interne Seite ohne Verbindung zum Projektpartnerportal zu sein. Im Drupal-Nachbau wahrscheinlich nicht relevant — klären ob es migriert werden muss. Das Hardcoded-Passwort ist ein Sicherheitsproblem.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Nutzer sendet Passwort an authenticateAction
  • Passwort wird mit Hardcoded-String '«redigiert»' verglichen (kein Hashing!)
  • Bei Treffer: Session-Variable 'solcomLoginSession' = true (Neos Flow SessionInterface::putData)
  • Redirect zu DashboardController::indexAction
  • indexAction prüft Session-Variable und rendert Dashboard mit Spendenstatistiken (Donation/Organisation/Staff)
  • logoutAction: Session-Variable auf null setzen, Redirect zu index
Domain-Objekte
Side-Effects
  • Session: 'solcomLoginSession' in Flow-Session gesetzt
DateienClasses/Controller/DashboardController.php
2FA-Formular-Entität (Lead2faForm) Kein direkter Controller-Endpoint gefunden — Lead2faForm ist eine persistierbare Entität (@Flow\Entity) mit Token, accepted-Flag und acceptedDate Lead2faForm (Domain Model — kein Controller-Entrypoint in den untersuchten Dateien) Unbekannt (kein Controller-Endpoint in den untersuchten Dateien) Lücke: Kein Controller-Endpoint für Lead2faForm in den zugewiesenen Dateien gefunden. Es ist unklar ob diese 2FA für den Projektpartner-Login gilt oder für einen anderen Flow (z.B. Lead-Generierung). Muss durch Analyse anderer Controller (LeadController, FormFinisher) ergänzt werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Lead2faForm-Entität speichert: token (String), accepted (boolean, default false), acceptedDate (DateTime, nullable)
  • Die Entität wird vermutlich in einem nicht untersuchten Controller (z.B. LeadController oder einem Formular-Finisher) verwendet
  • Typischer Flow: 2FA-Token wird generiert und in Lead2faForm gespeichert; nach Bestätigung durch Nutzer wird accepted=true und acceptedDate gesetzt
Domain-Objekte
Side-Effects
  • DB-Write: Lead2faForm-Entität wird angelegt/aktualisiert (via nicht identifiziertem Controller)
DateienClasses/Domain/Model/Lead2faForm.php

Projekt-Alerts / Suchagent Kern 10 Flows

FlowAuslöserEntrypointZugriffDrupal-Nachbau
Projekt-Alert-Formular anzeigen GET-Request auf Neos-Seite mit NodeType Contenance.Solcom:Portal.Project.ProjectAlert (z.B. /de/projektportal/projektalert) ProjectAlertController::projectAlertAction Everybody (Policy: Contenance.Solcom:ProjectAlert GRANT für Neos.Flow:Everybody) Drupal-Seite mit einem Webform oder Custom Form Block. Zwei Varianten: Gast (mit Kontaktdaten-Feldern + Double-Opt-In) und eingeloggter Projektpartner (ohne Kontaktdaten, direktes Speichern/Aktualisieren). Cookie-Prefill aus Suchparametern muss nachgebaut werden. Multi-Statement-Tag-Logik (bis zu 4 OR-Gruppen mit je bis zu 4 AND-Tags) ist die komplexeste Eigenschaft.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Fusion rendert den Plugin-Node (Contenance.Solcom:Portal.Project.ProjectAlert) und delegiert an ProjectAlertController::projectAlertAction
  • Controller liest aktiven User aus UserRepository (via aktuellem Account)
  • CookieService liest gespeicherte Suchparameter aus Cookies (searchParameter, location, business, zip) für Vorausfüllung
  • Technology- und Business-Optionen werden aus dem Content-Repository geladen (getTechnologyOptions/getBusinessOptions)
  • Falls User eingeloggt: ProjectAlertRepository::findByUser() prüft ob bereits ein Alert existiert
  • Existierende Tags (tags, noTags) und Regionsfilter (states/locations) werden aus dem Alert-Objekt extrahiert und für das Template aufbereitet
  • View-Template ProjectAlert/ProjectAlert.html rendert das Formular: für Gäste mit Kontaktdaten-Feldern, für eingeloggte User ohne; Schlagwort-Felder (tags1-4, tagsNot1-2), Regions-Checkboxen, optional Newsletter-Checkbox
  • Button-Label ist 'Aktualisieren' wenn Alert bereits vorhanden, sonst 'Jetzt Anmelden'
Domain-Objekte
  • ProjectAlert
  • ProjectAlertGuest
  • ProjectAlertRepository
  • ProjectAlertGuestRepository
Side-Effects
DateienClasses/Controller/ProjectAlertController.php, Resources/Private/Fusion/Content/Portal/Project/ProjectAlert.fusion, Resources/Private/Templates/ProjectAlert/ProjectAlert.html, Configuration/Policy.yaml
Projekt-Alert anlegen (Gast, Double-Opt-In) POST Form-Submit auf action='projectAlertSend', controller='ProjectAlert' — ohne aktive Session (Gast-Benutzer) ProjectAlertController::projectAlertSendAction Everybody (kein Login erforderlich) Kernflow für Gast-Alerts. Double-Opt-In ist Pflicht (Datenschutz-Text im Template bestätigt: 10-Tage-Frist). Der Hash-Token muss sicher generiert und in der DB gespeichert werden. Drupal: Custom Entity oder Webform Submission + Custom Submission Handler + SwiftMailer/Symfony Mailer. Mailingwork-Integration ist optional/Legacy.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Formularwerte werden ausgelesen: email, gender, firstname, lastname plus tags1-4, tagsNot1-2, states/location[], states/zip[], newsletter, policy
  • Prüfung ob E-Mail bereits als Guest-Alert existiert (ProjectAlertGuestRepository::countByEmail) oder als User-Alert (UserRepository::findOneByEmail + ProjectAlertRepository::findByUser)
  • Falls E-Mail neu: optionaler Mailingwork-Beitritt falls newsletter==1 (mailingworkService::addEmailToMailingworks mit Anrede/Name/Email)
  • Aktivierungslink wird erstellt: Neos-Content-Node mit der konfigurierten Node-ID (settings.projectAlert.activation) wird per FlowQuery gesucht, absolute URL daraus generiert
  • Neues ProjectAlertGuest-Objekt wird erstellt (gender, firstName, lastName, email) und in ProjectAlertGuestRepository persistiert
  • Neues ProjectAlert-Objekt wird erstellt: guest gesetzt, tags (bis zu 4 OR-Statement-Gruppen, je bis zu 4 AND-Tags) gesetzt, noTags (bis zu 2 Ausschluss-Gruppen) gesetzt, states/locations gesetzt, UUID-Hash generiert, active=FALSE
  • ProjectAlert wird in ProjectAlertRepository persistiert
  • Double-Opt-In-Mail wird versendet: Template Mail/ProjectAlert/SignIn.html, Betreff 'Anmeldebestätigung - SOLCOM Projekt Alert', Absender no-reply@solcom.de, Empfänger eingegebene E-Mail, enthält Aktivierungslink mit hash-Parameter
  • View zeigt status='success' und Hinweis auf Bestätigungs-E-Mail
Domain-Objekte
  • ProjectAlert
  • ProjectAlertGuest
  • ProjectAlertRepository
  • ProjectAlertGuestRepository
Side-Effects
  • DB-Write: ProjectAlertGuest (neu)
  • DB-Write: ProjectAlert (neu, active=FALSE)
  • E-Mail: Aktivierungsbestätigung an Gast (no-reply@solcom.de → Gast-E-Mail)
  • Externe API: Mailingwork (optional, falls newsletter==1)
DateienClasses/Controller/ProjectAlertController.php, Classes/Domain/Model/ProjectAlert.php, Classes/Domain/Model/ProjectAlertGuest.php, Resources/Private/Templates/ProjectAlert/ProjectAlertSend.html, Resources/Private/Templates/Mail/ProjectAlert/SignIn.html
Projekt-Alert anlegen (eingeloggter Projektpartner) POST Form-Submit auf action='projectAlertSend', controller='ProjectAlert' — mit aktiver Login-Session ProjectAlertController::projectAlertSendAction Everybody (Policy GRANT) — aber faktisch nur für eingeloggte Nutzer relevant, da E-Mail aus Account kommt Ähnlich wie Gast-Flow, aber verknüpft mit dem Drupal-User-Account statt einem Guest-Datensatz. Da eingeloggt, ist Double-Opt-In (activate per E-Mail-Link) dennoch Pflicht — nicht automatisch aktiv. In Drupal: Alert-Entity mit user_id-Referenz anlegen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Formularwerte werden ausgelesen; da account!=null werden email/gender/name vom User-Objekt geholt (kein Gast-Formular)
  • Prüfung ob User bereits einen Alert hat (ProjectAlertRepository::findByUser): falls noch keiner vorhanden → Neuanlage
  • Neues ProjectAlert-Objekt: user gesetzt, tags (bis zu 4 OR-Gruppen), noTags (bis zu 2 Ausschlussgruppen), states/locations, UUID-Hash, active=FALSE
  • ProjectAlert wird persistiert
  • Double-Opt-In-Mail wird versendet (identisch zum Gast-Flow): Template Mail/ProjectAlert/SignIn.html, enthält Aktivierungslink
  • View zeigt status='success'
Domain-Objekte
  • ProjectAlert
  • ProjectAlertRepository
  • UserRepository
Side-Effects
  • DB-Write: ProjectAlert (neu, active=FALSE, user gesetzt)
  • E-Mail: Aktivierungsbestätigung an Account-E-Mail (no-reply@solcom.de → User-E-Mail)
DateienClasses/Controller/ProjectAlertController.php, Classes/Domain/Model/ProjectAlert.php, Resources/Private/Templates/ProjectAlert/ProjectAlertSend.html, Resources/Private/Templates/Mail/ProjectAlert/SignIn.html
Projekt-Alert aktualisieren (eingeloggter Projektpartner) POST Form-Submit auf action='projectAlertSend', controller='ProjectAlert' — eingeloggter User mit bereits bestehendem Alert ProjectAlertController::projectAlertSendAction Eingeloggter Projektpartner (account!=null erforderlich, sonst status='errorLogin') Update-Flow: Kein neues Double-Opt-In — Alert bleibt aktiv. Nach Update sofortiger Matching-Lauf. In Drupal: Alert-Entity updaten + sofort send() aufrufen. Wichtig: keine erneute Aktivierungsmail.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • System erkennt: User hat bereits Alert (ProjectAlertRepository::findByUser gibt Ergebnis zurück) und account!=null
  • Bestehender Alert wird geladen (existingUserProjectAlert = userAlreadyExists[0])
  • Tags (tags1-4), noTags (tagsNot1-2) und states/locations werden aus den Formulardaten neu gesetzt und auf dem bestehenden Alert-Objekt gespeichert
  • ProjectAlertRepository::update wird aufgerufen
  • ProjectAlertService::send wird sofort aufgerufen — passende neue Projekte werden gesucht und ggf. per E-Mail versandt
  • View zeigt status='update'
Domain-Objekte
  • ProjectAlert
  • ProjectAlertRepository
  • ProjectAlertNotification
  • ProjectAlertNotificationRepository
Side-Effects
  • DB-Write: ProjectAlert (Update tags/noTags/states)
  • DB-Write: ProjectAlertNotification (neue Notifications für neu matchende Projekte)
  • E-Mail: Sofortiger Versand von Matching-Projekten an User falls neue passende Projekte existieren
DateienClasses/Controller/ProjectAlertController.php, Classes/Domain/Service/ProjectAlertService.php, Resources/Private/Templates/ProjectAlert/ProjectAlertSend.html
Projekt-Alert aktivieren (Double-Opt-In per E-Mail-Link) GET-Request auf Neos-Seite mit NodeType Contenance.Solcom:Portal.ProjectAlert.SignIn, URL enthält Query-Parameter hash=<uuid> ProjectAlertController::signInAction Everybody — kein Login erforderlich, Sicherheit nur über geheimen UUID-Hash Standard Double-Opt-In Bestätigungs-Seite. In Drupal als Custom Route implementieren die den Hash-Token validiert und die Alert-Entity auf active setzt. Danach sofort Matching-Lauf anstossen. Lücke: Kein Token-Timeout implementiert (anders als bei Account-Aktivierung mit 10-Tage-Frist — die Frist steht nur im Info-Text, wird nicht technisch erzwungen).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • hash-Parameter wird aus dem HTTP-Request gelesen
  • ProjectAlertRepository::findOneByHash(hash) sucht den zugehörigen Alert
  • Falls Alert gefunden: projectAlert->setActive(TRUE), ProjectAlertRepository::update, persistAll()
  • ProjectAlertService::send($projectAlert) wird sofort aufgerufen — findet alle bereits passenden Projekte und sendet Benachrichtigungs-E-Mail
  • View zeigt status=TRUE (Erfolgsmeldung) oder implizit Fehlermeldung bei ungültigem/bereits verwendetem Hash
Domain-Objekte
  • ProjectAlert
  • ProjectAlertRepository
  • ProjectAlertNotification
  • ProjectAlertNotificationRepository
Side-Effects
  • DB-Write: ProjectAlert (active=TRUE)
  • DB-Write: ProjectAlertNotification (für alle matchenden Projekte)
  • E-Mail: Sofortige Benachrichtigung mit passenden Projekten (falls vorhanden)
DateienClasses/Controller/ProjectAlertController.php, Classes/Domain/Service/ProjectAlertService.php, Resources/Private/Fusion/Content/Portal/Project/ProjectAlert.fusion, Resources/Private/Templates/ProjectAlert/SignIn.html
Projekt-Alert abmelden per Hash-Link (Gast und User) GET-Request auf Neos-Seite mit NodeType Contenance.Solcom:Portal.ProjectAlert.SignOut, URL enthält Query-Parameter hash=<uuid> ProjectAlertController::signOutAction Everybody — kein Login erforderlich, Sicherheit nur über geheimen UUID-Hash Abmelde-Seite via Hash-Link (aus E-Mails). In Drupal als Custom Route. Beim Löschen des Alerts müssen abhängige Notifications und ggf. Guest-Datensatz mitgelöscht werden (Cascade Delete). Die 'hash=noemail'-Variante zeigt ein E-Mail-Eingabeformular als Fallback.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • hash-Parameter wird aus HTTP-Request gelesen
  • Falls hash='noemail': View zeigt E-Mail-Eingabeformular (für Nutzer die keinen direkten Hash-Link haben), status noemail=TRUE
  • Falls hash gültig: ProjectAlertRepository::findOneByHash(hash) lädt den Alert
  • ProjectAlertRepository::remove($projectAlert) löscht den Alert (inklusive zugehörigem Guest-Objekt dank orphanRemoval/cascade=all)
  • persistAll()
  • View zeigt status=TRUE (Erfolgsmeldung 'erfolgreich abgemeldet') oder Fehlermeldung bei bereits gelöschtem/ungültigem Hash
Domain-Objekte
  • ProjectAlert
  • ProjectAlertGuest
  • ProjectAlertNotification
  • ProjectAlertRepository
Side-Effects
  • DB-Delete: ProjectAlert
  • DB-Delete: ProjectAlertGuest (cascade/orphanRemoval bei Gast-Alerts)
  • DB-Delete: ProjectAlertNotification (cascade=all auf notifications Collection)
DateienClasses/Controller/ProjectAlertController.php, Resources/Private/Templates/ProjectAlert/SignOut.html
Projekt-Alert Abmeldung per E-Mail-Eingabe (Gast-Fallback) POST Form-Submit auf action='signOutGuest', controller='ProjectAlert' — Nutzer hat keinen Hash-Link, gibt E-Mail ein (aus SignOut.html bei hash=noemail) ProjectAlertController::signOutGuestAction Everybody Fallback-Flow wenn User E-Mail-Link nicht mehr hat. Lookup über E-Mail → Hash → Redirect auf Hash-basierten Abmelde-Flow. Wichtig: Keine Confirmation-E-Mail vor Löschung — direkter Redirect. In Drupal als separaten Form-Submit-Handler implementieren.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • E-Mail-Adresse wird aus POST-Daten gelesen ($_POST['--contenance_solcom-portal_projectalert_signout']['email'])
  • E-Mail wird per FILTER_VALIDATE_EMAIL validiert
  • Suche ob ein Neos-Account mit dieser E-Mail existiert (AccountRepository::findByAccountIdentifierAndAuthenticationProviderName)
  • Falls Account gefunden: User-Objekt laden → ProjectAlertRepository::findOneByUser → Redirect auf /de/projektportal/projektalert/abmeldung?hash=<hash> (führt signOutAction aus)
  • Falls kein Account: ProjectAlertGuestRepository::findOneByEmail → ProjectAlertRepository::findOneByGuest → Redirect auf Abmelde-URL mit Hash
  • Falls E-Mail ungültig oder kein Alert gefunden: Redirect auf /de/projektportal/projektalert/abmeldung?hash=noemail (zeigt erneut das Formular)
Domain-Objekte
  • ProjectAlert
  • ProjectAlertGuest
  • ProjectAlertGuestRepository
  • ProjectAlertRepository
Side-Effects
  • HTTP-Redirect zu signOutAction (eigentliches Löschen dort)
DateienClasses/Controller/ProjectAlertController.php, Resources/Private/Templates/ProjectAlert/SignOut.html
Projekt-Alert aus Profil löschen (eingeloggter User via AJAX) POST-Request auf Route api/profile/projectalert/remove (AccountController::removeProfileProjectAlertAction), AJAX-Call aus Profil-Seite mit Parameter id=<alert-identifier> AccountController::removeProfileProjectAlertAction Eingeloggter Projektpartner (account!=null Pflicht) AJAX-Endpunkt im Profil. In Drupal als JSON:API-Endpoint oder Custom Controller Route. Access Control muss sicherstellen dass User nur eigene Alerts löschen kann (im Neos-Code fehlt diese Prüfung — Security-Lücke: jeder eingeloggte User kann jeden Alert per ID löschen).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Prüfung ob account!=null (eingeloggter User)
  • id-Parameter aus Request auslesen
  • ProjectAlertRepository::findByIdentifier(id) lädt den Alert
  • ProjectAlertRepository::remove($projectAlert) löscht den Alert
  • persistAll()
  • JSON-Response: {status: 'success', title: '...', message: '...'} oder {status: 'error', ...}
Domain-Objekte
  • ProjectAlert
  • ProjectAlertRepository
Side-Effects
  • DB-Delete: ProjectAlert (inkl. Notifications via cascade)
DateienClasses/Controller/AccountController.php, Configuration/Routes.yaml
Cron: Projekt-Alert-Benachrichtigungen versenden CLI-Command: ./flow projectalert:send (ProjectAlertCommandController::sendCommand) — typisch via Cronjob ProjectAlertCommandController::sendCommand CLI (kein Web-Zugriff) — nur über Server-Shell / Cron ausführbar Kernstück des Alert-Systems. In Drupal als Drupal Queue Worker oder Scheduled Task (cron hook_cron + Queue). ProjectRepository-Query-Logik (findProjectsForProjectAlertSending mit verschachtelter AND/OR-Tag-Logik auf Salesforce-Projekt-Daten) muss vollständig nachgebaut werden — ist die komplexeste Komponente. Notification-Deduplication über ProjectAlertNotification-Entity ist essenziell um Duplikate zu verhindern.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • ProjectAlertRepository::findByActive(TRUE) lädt alle aktiven Alerts
  • ProjectAlertService::send($projectAlertQueryResult) wird mit dem gesamten QueryResult aufgerufen
  • Für jeden aktiven Alert: Tags, noTags, states aus Alert-Objekt lesen; User oder Guest ermitteln
  • ProjectRepository::findProjectsForProjectAlertSending(tags, noTags, states) führt Tag-basierte Suche mit AND/OR-Logik gegen Projekt-Datenbank durch
  • Für jedes gefundene Projekt: ProjectAlertNotificationRepository::countByAlertAndProject prüft ob Notification bereits existiert
  • Falls noch nicht benachrichtigt: Neues ProjectAlertNotification-Objekt anlegen (alert + project), persistieren; Projekt-URL via FlowQuery+LinkingService generieren
  • Falls neue Notifications vorhanden: Fluid-Template Mail/ProjectAlert/ProjectAlert.html rendern mit user, tags (formatierter AND/OR-String), states (formatierter Gebiets-String), projects (mit Links), hash
  • E-Mail versenden (no-reply@solcom.de → Empfänger, Betreff 'SOLCOM Projekt Alert')
  • sendCounter wird erhöht; CLI-Output mit Anzahl versandter Alerts
Domain-Objekte
  • ProjectAlert
  • ProjectAlertNotification
  • ProjectAlertRepository
  • ProjectAlertNotificationRepository
Side-Effects
  • DB-Write: ProjectAlertNotification (je Projekt pro Alert, dedupliziert)
  • E-Mail: Benachrichtigungs-E-Mail mit matchenden Projekten (no-reply@solcom.de → Alert-Inhaber)
DateienClasses/Command/ProjectAlertCommandController.php, Classes/Domain/Service/ProjectAlertService.php, Classes/Domain/Model/ProjectAlertNotification.php, Classes/Domain/Repository/ProjectAlertNotificationRepository.php, Resources/Private/Templates/Mail/ProjectAlert/ProjectAlert.html
Legacy-Import: Projekt-Alerts aus CSV migrieren CLI-Command: ./flow projectalert:importalerts <start> <stop> (ProjectAlertCommandController::importAlertsCommand) ProjectAlertCommandController::importAlertsCommand CLI — nur für Admins/Devs Einmaliger Migrations-Import (Legacy). In Drupal bereits im Migrate-Framework abgebildet (solcom_projektpartner-Modul). Wichtig: Notification-Baseline beim Import setzen verhindert dass bei der ersten Cron-Ausführung alle historischen Projekte als 'neu' gelten. Dieses Verhalten muss im Drupal-Migrate-Prozess repliziert werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • CSV-Datei SOLCOM_Projekt_Alerts.csv wird aus Resources/Private/Import/ geladen
  • Rows <start> bis <stop> werden verarbeitet (Batching-Parameter)
  • Pro Row: E-Mail validieren, Surename/Forename/Salutation/EMailAddress/Activated aus CSV-Spalten extrahieren
  • Prüfung ob Account mit E-Mail bereits existiert (AccountRepository) → User-Alert oder Gast-Alert
  • Prüfung auf Duplikate: wenn Alert für User/Guest bereits existiert → createAlert=false
  • CSV-Spalten 5-15 (SearchWord01-06 + Operatoren) werden in Tags-Struktur (AND/OR-Gruppen, max. 4 Statements) konvertiert
  • CSV-Spalten 16-18 (SearchNotWord01-02 + Operator) werden in noTags-Struktur konvertiert
  • CSV-Spalte 19 (PostalCodeFilter) wird in states/locations konvertiert
  • ProjectAlert mit setActive($activated) anlegen — Import behält den originalen Aktivierungsstatus
  • Für alle vorhandenen Projekte werden sofort ProjectAlertNotification-Datensätze angelegt (markiert alle bestehenden Projekte als 'bereits gesehen')
  • CLI-Output: [IMPORT] Success: created / not created / [ERROR] E-Mail invalid
Domain-Objekte
  • ProjectAlert
  • ProjectAlertGuest
  • ProjectAlertNotification
  • ProjectAlertRepository
  • ProjectAlertGuestRepository
  • ProjectAlertNotificationRepository
Side-Effects
  • DB-Write: ProjectAlertGuest (ggf. neu)
  • DB-Write: ProjectAlert (neu)
  • DB-Write: ProjectAlertNotification (für ALLE bestehenden Projekte als initiale Baseline)
DateienClasses/Command/ProjectAlertCommandController.php, Classes/Domain/Model/ProjectAlert.php, Classes/Domain/Model/ProjectAlertGuest.php, Classes/Domain/Model/ProjectAlertNotification.php

Verfügbarkeit, Dashboard, AVV Kern 12 Flows

FlowAuslöserEntrypointZugriffDrupal-Nachbau
Verfügbarkeit-Formular anzeigen (hash-basiert) GET-Request auf Neos-Page mit Plugin Contenance.Solcom:Portal.Availability — URL-Parameter ?hash=<uuid> AvailabilityController::indexAction Kein Login erforderlich — Zugang nur über geheimen hash-Link (per E-Mail versandt); kein Session-Guard Lücke: Drupal-Nachbau benötigt einen tokengeschützten Landingpage-Pfad (z.B. custom route mit UUID-Parameter) für anonyme Nutzer. Availability-Entity muss als Custom-Entity angelegt werden (id, staffId, projectPartner, type, email, salutation, staff, editor, hash, creationDate, status).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Neos rendert die Page, das Fusion-Plugin Contenance.Solcom:Portal.Availability ruft AvailabilityController::indexAction auf
  • Controller liest URL-Argument 'hash' aus dem HTTP-Request
  • AvailabilityRepository::findOneByHash($hash) sucht den Datensatz in der DB
  • Falls Datensatz gefunden: View-Variablen 'availability', 'formValues', 'error' werden gesetzt
  • BusinessOptions (Branchen-Optionen) werden aus Settings geladen und an View übergeben
  • Template Resources/Private/Templates/Availability/Index.html rendert das Formular mit Name, Anrede, vorausgefüllten Feldern aus dem Availability-Objekt
Domain-Objekte
  • Availability
  • AvailabilityRepository
Side-Effects
DateienClasses/Controller/AvailabilityController.php, Classes/Domain/Model/Availability.php, Classes/Domain/Repository/AvailabilityRepository.php, Resources/Private/Fusion/Content/Portal/Availability.fusion, Resources/Private/Templates/Availability/Index.html
Verfügbarkeit melden (Formular-Submit mit optionalem Datei-Upload) POST-Submit des Verfügbarkeitsformulars an AvailabilityController::sendAction (Neos-Plugin auf der Availability-Page) AvailabilityController::sendAction Kein Login erforderlich — Formular ist über hash-Link zugänglich (anonymer Gast) Lücke: In Drupal als Webform oder Custom-Form-Submit implementieren. Wichtig: kein DB-Status-Update nach Submit (Neos-Legacy-Verhalten). Datei-Persistenz-Pfad muss angepasst werden (kein FLOW_PATH_DATA). E-Mail-Versand via Drupal Mail API. MIME-Whitelist (pdf/docx/doc) und 5-MB-Limit sind zu übernehmen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Controller liest POST-Daten: id, available (Datum), message, policy-Checkbox; Datei-Uploads aus $_FILES
  • Validierung: policy-Checkbox muss gesetzt sein; Datum muss gültiger Timestamp sein — sonst Redirect zurück zu indexAction mit error='noDate'
  • AvailabilityRepository::findByIdentifier($id) lädt den Datensatz
  • Datei-Uploads: bis zu 4 Dateien; jede wird via UploadService auf Fehler, MIME-Type (nur pdf/docx/doc), Dateigröße (1 KB–5 MB) geprüft — bei Fehler Redirect zurück mit error='uploadFileSize' oder 'uploadFailed'
  • Tagesordner unter FLOW_PATH_DATA/Verfuegbarkeit/<Y-m-d>/<Y-m-d_H-i-s>_verfuegbarkeit/ wird angelegt; HTML-Body und Dateien werden dort gespeichert
  • Fluid-Template Mail/Availability/Solcom.html wird gerendert (mit Availability-Objekt, Datum, Nachricht, sessionId, ggf. RMS-Nummer des Users)
  • E-Mail an projektpartner@solcom.de wird versendet (Betreff: 'Aktualisierung RMS Nr. X – Name / Verfügbar ab: Datum'), Dateien als Anhänge
  • Falls Availability eine E-Mail-Adresse hat: Bestätigungs-E-Mail an den Projektpartner via Mail/Availability/Confirmation.html
  • Redirect zu formResultAction mit status=true (Erfolg) oder status=false (Fehler beim Versand)
Domain-Objekte
  • Availability
  • AvailabilityRepository
  • UploadService
Side-Effects
  • Datei-Schreib-Operation: HTML-Body + Upload-Dokumente in FLOW_PATH_DATA/Verfuegbarkeit/<datum>/
  • E-Mail an projektpartner@solcom.de (intern)
  • Bestätigungs-E-Mail an den Projektpartner (extern, optional)
  • Kein DB-Write — der Availability-Datensatz wird nicht aktualisiert (status bleibt 1)
DateienClasses/Controller/AvailabilityController.php, Classes/Domain/Service/UploadService.php, Resources/Private/Templates/Mail/Availability/Solcom.html, Resources/Private/Templates/Mail/Availability/Confirmation.html, Resources/Private/Templates/Availability/FormResult.html
Verfügbarkeits-Erinnerungsmail versenden (Cron-Import) CLI-Command: ./flow availability:import (liest Aktualisierungsanfrage.xml aus Resources/Private/Import/) AvailabilityCommandController::importCommand CLI-only (kein Web-Zugang); nur von Systemadministratoren ausführbar Lücke: In Drupal als Drush-Command oder Cron-Job (QueueWorker) implementieren. XML-Import-Logik muss nachgebaut werden. 4 E-Mail-Templates je Mailtyp müssen in Drupal Mail-System überführt werden. Hash-Link-Generierung muss reproduziert werden (UUID als URL-Token).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • XML-Datei Aktualisierungsanfrage.xml wird eingelesen (Felder: ID, PersonalNr, Projektpartner, Mailtyp, EmailAdresse, Anredezeile, Mitarbeiter, Bearbeiter)
  • RFC822-E-Mail-Validierung für jede Zeile; ungültige Adressen werden übersprungen (Log-Eintrag)
  • Pro Eintrag: AvailabilityRepository::findByStaffId() prüft ob Datensatz bereits existiert
  • Neuer Datensatz: Availability-Objekt wird angelegt, UUIDv4-Hash generiert, status=1 gesetzt, persistiert
  • Bestehender Datensatz: wird mit neuen Werten (alle Felder außer staffId/hash) aktualisiert
  • Je nach Mailtyp (1–4) wird ein typenspezifisches Fluid-Template ausgewählt: Partner.html (Typ 1, 'Ihr Profil für unsere Projekte'), Agent.html (Typ 2, 'Ihr Mitarbeiterprofil'), WithoutContactPerson.html (Typ 3, 'Sind Sie bald verfügbar?'), WithContactPerson.html (Typ 4, 'Ist Ihr Mitarbeiter bald verfügbar?')
  • E-Mail mit individualisiertem Link (Neos-Node-URL + hash) wird an den Projektpartner versendet; 2 Sekunden sleep zwischen den Versendungen
  • Abschließender Log-Eintrag mit Anzahl neuer und aktualisierter Einträge
Domain-Objekte
  • Availability
  • AvailabilityRepository
Side-Effects
  • DB-Write: Availability-Entitäten werden angelegt oder aktualisiert
  • E-Mail an Projektpartner (extern) — 4 verschiedene Vorlagen je nach Mailtyp
  • Log-Schreiboperation via CommandLoggerInterface
DateienClasses/Command/AvailabilityCommandController.php, Classes/Domain/Model/Availability.php, Classes/Domain/Repository/AvailabilityRepository.php, Resources/Private/Templates/Mail/Availability/Partner.html, Resources/Private/Templates/Mail/Availability/Agent.html, Resources/Private/Templates/Mail/Availability/WithoutContactPerson.html, Resources/Private/Templates/Mail/Availability/WithContactPerson.html
Verfügbarkeits-Erstimport aus CSV (einmalig) CLI-Command: ./flow availability:initialImport --start <n> --stop <m> (liest AktualisierungsanfrageImport.csv) AvailabilityCommandController::initialImportCommand CLI-only; einmaliger Migrations-Command Nicht für Drupal-Nachbau relevant — einmaliger Migrations-Seed-Import, wird durch Drupal-Migration (Migrate-API) ersetzt.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • CSV-Datei AktualisierungsanfrageImport.csv (pipe-delimitiert) wird gelesen (Felder: ID|PersonalNr|Projektpartner|Mailtyp|EmailAdresse|Anredezeile|Mitarbeiter|Bearbeiter|EmailVersendet|EmailVersendetAm|IdentifizierungsGUID)
  • Nur Zeilen zwischen start und stop-Index werden importiert (Range-basierter Import)
  • Pro Zeile wird ein Availability-Objekt erstellt, hash aus Spalte 10 (IdentifizierungsGUID) übernommen, status=1 gesetzt
  • Objekte werden in AvailabilityRepository gespeichert (kein Duplikat-Check, kein E-Mail-Versand)
Domain-Objekte
  • Availability
  • AvailabilityRepository
Side-Effects
  • DB-Write: Availability-Entitäten werden angelegt (bulk)
DateienClasses/Command/AvailabilityCommandController.php
AVV Opt-In (Double-Opt-In-Bestätigung per Link) GET-Request auf Neos-Page mit Plugin Contenance.Solcom:AvvOptIn — URL-Parameter ?token=<32-stelliger-Hash> AvvController::optInAction Kein Login erforderlich — Zugang über personalisierten Token-Link in E-Mail (anonymer Klick) In Drupal als anonymer Callback-Endpunkt mit Token-Validation implementieren. Lead2faForm-Entität muss als Custom-Entity angelegt werden (token, accepted, acceptedDate). AVV-Bestätigungs-E-Mail an CRM-Adresse muss implementiert werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Token wird aus internem Fusion-Argument '__token' gelesen (aus request.arguments.token via Fusion-Prototype AvvOptIn)
  • Validierung: Token muss vorhanden und exakt 32 Zeichen lang sein — sonst error='580993452' oder '438979234'
  • LeadFormService::optIn($token) wird aufgerufen
  • Lead2faFormRepository::findOneByToken($token) sucht den Datensatz
  • Falls Datensatz gefunden und noch nicht akzeptiert: accepted=true, acceptedDate=jetzt gesetzt, Datensatz aktualisiert
  • LeadFormService::sendAccepted(): E-Mail 'AVV Bestätigung' an CRM@solcom.de wird versendet (Template Mail/Lead2faForm/Accepted.html)
  • PersistenceManager::persistAll() schreibt alles in die DB
  • Template Resources/Private/Templates/Avv/OptIn.html rendert Erfolgs- oder Fehlermeldung
Domain-Objekte
  • Lead2faForm
  • Lead2faFormRepository
  • LeadFormService
Side-Effects
  • DB-Write: Lead2faForm.accepted=true, acceptedDate=jetzt
  • E-Mail an CRM@solcom.de (intern, Betreff: 'AVV Bestätigung (<token>)')
DateienClasses/Controller/AvvController.php, Classes/Domain/Service/LeadFormService.php, Classes/Domain/Model/Lead2faForm.php, Classes/Domain/Repository/Lead2faFormRepository.php, Resources/Private/Fusion/Content/Avv.fusion, Resources/Private/Templates/Avv/OptIn.html, Resources/Private/Templates/Mail/Lead2faForm/Accepted.html
AVV Opt-Out (Widerruf der Einwilligung per Link) GET-Request auf Neos-Page mit Plugin Contenance.Solcom:AvvOptOut — URL-Parameter ?token=<32-stelliger-Hash> AvvController::optOutAction Kein Login erforderlich — Zugang über personalisierten Token-Link in E-Mail Analoge Implementierung zu Opt-In (s.o.). Kein E-Mail-Versand beim Opt-Out (Legacy-Entscheidung). Drupal-Nachbau sollte prüfen, ob eine Benachrichtigung an CRM@solcom.de gewünscht ist.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Token wird aus internem Fusion-Argument '__token' gelesen
  • Validierung: Token muss vorhanden sein — sonst error='438979234'
  • LeadFormService::optOut($token) wird aufgerufen
  • Lead2faFormRepository::findOneByToken($token) sucht den Datensatz
  • Falls Datensatz gefunden und aktuell accepted=true: accepted=false, acceptedDate=jetzt gesetzt, Datensatz aktualisiert
  • PersistenceManager::persistAll() schreibt alles in die DB
  • Template Resources/Private/Templates/Avv/OptOut.html rendert Bestätigung (kein E-Mail-Versand vorgesehen, laut TODO-Kommentar im Code)
Domain-Objekte
  • Lead2faForm
  • Lead2faFormRepository
  • LeadFormService
Side-Effects
  • DB-Write: Lead2faForm.accepted=false, acceptedDate=jetzt
DateienClasses/Controller/AvvController.php, Classes/Domain/Service/LeadFormService.php, Classes/Domain/Model/Lead2faForm.php, Classes/Domain/Repository/Lead2faFormRepository.php, Resources/Private/Fusion/Content/Avv.fusion, Resources/Private/Templates/Avv/OptOut.html
Lead-Formular Eingang mit AVV Double-Opt-In Formular-Submit eines Lead-Formulars (Landingpage o.ä.) — LeadFormService::addEntry() wird aus einem Controller aufgerufen LeadFormService::addEntry Gast — kein Login erforderlich (Landingpage-Formular) In Drupal als Webform-Handler oder Custom-Form-Submit implementieren. Double-Opt-In-Mechanismus (Lead2faForm) muss als separate Entity nachgebaut werden. Zwei E-Mail-Templates (AcceptRequest + Lead) müssen migriert werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • E-Mail-Adresse aus formValues wird extrahiert; SHA256-Hash (mit Salt) als eindeutiger Token generiert
  • Lead2faFormRepository prüft ob bereits ein Eintrag mit diesem Token existiert
  • Neuer Lead: Lead2faForm-Objekt wird angelegt (accepted=false), in Repository gespeichert
  • AVV-Accept-Request E-Mail wird an den Interessenten gesendet (Template Mail/Lead2faForm/AcceptRequest.html, Betreff: 'Bestätigen Sie Ihr Einverständnis') mit Opt-In-Link
  • Lead-Daten werden intern an CRM@solcom.de + F.Letsche@solcom.de gesendet (Template Mail/Lead2faForm/Lead.html, Betreff: 'Leadformular <formTitle>')
  • Bestehender Lead, noch nicht akzeptiert: erneut Accept-Request E-Mail + Lead-E-Mail an SOLCOM intern
  • Bestehender Lead, bereits akzeptiert: nur Lead-E-Mail an SOLCOM intern (kein Accept-Request mehr)
Domain-Objekte
  • Lead2faForm
  • Lead2faFormRepository
  • LeadFormService
Side-Effects
  • DB-Write: Lead2faForm-Eintrag angelegt (neu) oder bereits vorhanden (kein Update)
  • E-Mail an Interessenten (extern): AVV-Bestätigungsanfrage mit Opt-In-Link
  • E-Mail an CRM@solcom.de + F.Letsche@solcom.de (intern): Lead-Daten
DateienClasses/Domain/Service/LeadFormService.php, Classes/Domain/Model/Lead2faForm.php, Classes/Domain/Repository/Lead2faFormRepository.php, Resources/Private/Templates/Mail/Lead2faForm/AcceptRequest.html, Resources/Private/Templates/Mail/Lead2faForm/Lead.html
Temporärer Datei-Upload (Gast / Registrierungsformular) POST-Request auf Route api/uploadtempfile (ApiController::uploadTempFileAction), CSRF-Token erforderlich ApiController::uploadTempFileAction Gast (kein Login, aber CSRF-Token-Pflicht) — Kontext: Registrierungsformulare und Bewerbungsformulare In Drupal als AJAX-Upload-Endpoint implementieren (z.B. Managed File + CSRF-geschützter Route). TemporaryFile-Konzept muss nachgebaut werden (z.B. via Drupal Temporary Files API mit 40-Minuten-TTL, s. Cron-Flow unten).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • CSRF-Token aus Request wird validiert — bei Ungültigkeit wird kein Upload durchgeführt
  • Prüfung ob genau eine Datei übergeben wurde
  • UploadService::checkUploadError(): PHP-Upload-Fehler-Code prüfen
  • UploadService::checkMimeType(): MIME-Type via mime_content_type() prüfen (erlaubt: pdf, docx, doc)
  • UploadService::checkFileSize(): Größe prüfen (1 KB–5 MB)
  • UploadService::uploadFile(): Datei via ResourceManager::importUploadedResource() in Neos-Ressourcen-System importieren, TemporaryFile-Objekt anlegen (type='guest'), in TemporaryFileRepository speichern
  • JSON-Response mit error, filename, message (UUID des TemporaryFile) zurückgeben
Domain-Objekte
  • TemporaryFile
  • TemporaryFileRepository
  • UploadService
Side-Effects
  • DB-Write: TemporaryFile-Entität wird angelegt
  • Datei-Import in Neos ResourceManager (persistente Ressource)
DateienClasses/Controller/ApiController.php, Classes/Domain/Service/UploadService.php, Classes/Domain/Model/TemporaryFile.php, Classes/Domain/Repository/TemporaryFileRepository.php, Configuration/Routes.yaml
Profil-Dokument hochladen (eingeloggter Projektpartner) POST-Request auf Route api/profile/file/upload (AccountController::userProfileUploadAction), @SkipCsrfProtection AccountController::userProfileUploadAction Eingeloggter Projektpartner (Session-Account-Pflicht) In Drupal als AJAX-Upload via Managed Files implementieren. UserFile-Entity existiert bereits als Drupal-Entität (oder File-Field am User). Limit von 4 Dateien muss serverseitig durchgesetzt werden. Bereits teilweise vorhanden in solcom_projektpartner-Modul prüfen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Session-Account wird geprüft — kein Account: keine Aktion
  • User-Objekt via UserRepository::findOneByAccount() laden
  • Anzahl bestehender UserFiles prüfen: maximal 4 Dokumente erlaubt (Fehlermeldung: 'Sie können maximal 4 Dokumente hochladen!')
  • PHP-Upload-Fehler prüfen (error === 0)
  • UploadService::checkUploadError(), checkMimeType(), checkFileSize() prüfen (MIME: pdf/docx/doc, Größe: max 5 MB)
  • ResourceManager::importUploadedResource(): Datei importieren, Dateiname bereinigen (nur alphanumerisch + _.-)
  • UserFile-Objekt anlegen (user, title, creationDate, originalResource), via UserFileRepository persistieren
  • User.addUserFile() und UserRepository::update() — Datei dem User zuordnen
  • PersistenceManager::persistAll()
  • JSON-Response mit status, filename, fileId, imageUrl (public resource URL)
Domain-Objekte
  • UserFile
  • UserFileRepository
  • UploadService
Side-Effects
  • DB-Write: UserFile-Entität angelegt, User.userFiles aktualisiert
  • Datei-Import in Neos ResourceManager
DateienClasses/Controller/AccountController.php, Classes/Domain/Model/UserFile.php, Classes/Domain/Repository/UserFileRepository.php, Classes/Domain/Service/UploadService.php, Configuration/Routes.yaml
Profil-Dokument löschen (eingeloggter Projektpartner) POST-Request auf Route api/profile/file/remove (AccountController::userProfileUploadRemoveAction) mit Parameter id=<UserFile-UUID> AccountController::userProfileUploadRemoveAction Eingeloggter Projektpartner — Ownership-Check (User darf nur eigene Dateien löschen) In Drupal als AJAX-Delete-Endpoint implementieren. Ownership-Check muss explizit implementiert werden. Bereits teilweise vorhanden in solcom_projektpartner prüfen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Session-Account wird geprüft — kein Account: keine Aktion
  • UserFile via UserFileRepository::findByIdentifier($id) laden
  • Eigentümer-Check: UserFile.user muss dem aktuell eingeloggten User entsprechen (Zugriffsschutz)
  • User.removeUserFile(), UserRepository::update(), UserFileRepository::remove()
  • PersistenceManager::persistAll()
  • JSON-Response mit status='success' oder Fehlermeldung
Domain-Objekte
  • UserFile
  • UserFileRepository
Side-Effects
  • DB-Write: UserFile-Entität gelöscht, User.userFiles aktualisiert
DateienClasses/Controller/AccountController.php, Classes/Domain/Model/UserFile.php, Classes/Domain/Repository/UserFileRepository.php, Configuration/Routes.yaml
Cron: Temporäre Dateien aufräumen (Verfügbarkeits- und Bewerbungs-Uploads) CLI-Command: ./flow utility:removeTempFiles (Cron-Job, typischerweise stündlich) UtilityCommandController::removeTempFilesCommand CLI-only (Cron-Job) In Drupal als Cron-Job (hook_cron) oder QueueWorker implementieren. Drupal Temporary Files API (file_cron()) übernimmt die grundlegende Temp-File-Bereinigung; zusätzlich muss der Dateisystem-Ordner-Cleanup (Bewerbungen/Verfügbarkeit) nachgebaut werden. 40-Minuten-TTL für TemporaryFile sowie 30-Tage-TTL für Filesystem-Ordner müssen konfigurierbar sein.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • TemporaryFileRepository::findAll() lädt alle temporären Datei-Einträge
  • Für jeden Eintrag: creationDate mit (now - 40 Minuten) vergleichen
  • Ist Eintrag älter als 40 Minuten: originalResource auf null setzen, Datensatz updaten, persistieren
  • Prüfen ob Resource bereits an einen UserFile-Eintrag gebunden ist (via UserFileRepository::findOneByOriginalResource) — wenn ja: Resource NICHT löschen (Fix für TT-23796)
  • Andernfalls: ResourceRepository::remove() löscht die Datei, TemporaryFileRepository::remove() löscht den Eintrag
  • Verzeichnis FLOW_PATH_DATA/Bewerbungen/ scannen: Unterordner älter als 30 Tage werden rekursiv gelöscht
  • Verzeichnis FLOW_PATH_DATA/Verfuegbarkeit/ scannen: Unterordner älter als 30 Tage werden rekursiv gelöscht
Domain-Objekte
  • TemporaryFile
  • TemporaryFileRepository
  • UserFile
  • UserFileRepository
Side-Effects
  • DB-Delete: TemporaryFile-Einträge und ggf. zugehörige PersistentResources
  • Filesystem: Bewerbungs- und Verfügbarkeitsordner älter als 30 Tage werden gelöscht
DateienClasses/Command/UtilityCommandController.php, Classes/Domain/Model/TemporaryFile.php, Classes/Domain/Repository/TemporaryFileRepository.php, Classes/Domain/Repository/UserFileRepository.php
Cron: Profil-Änderungsbenachrichtigungen an SOLCOM intern versenden CLI-Command: ./flow usernotification:send (Cron-Job, typischerweise täglich) UserNotificationCommandController::sendCommand CLI-only (Cron-Job) In Drupal als Cron-Job oder QueueWorker implementieren. UserUpdateNotification-Konzept (Änderungs-Queue) muss als Custom-Entity nachgebaut werden, sofern nicht bereits über Drupal-User-History-Mechanismen abgedeckt. Besonderheit: Datei-Anhänge nur für Uploads vom Vortag und nur wenn shouldNotSendAsNotification=false.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • UserUpdateNotificationRepository::findAll() lädt alle ausstehenden Änderungsbenachrichtigungen
  • Änderungen werden nach User-UUID gruppiert (ein E-Mail-Versand pro User)
  • Pro User: Fluid-Template Mail/UserNotification/Notification.html wird gerendert (user, changes)
  • UserFiles des Users werden geprüft: Dateien vom gestrigen Tag mit shouldNotSendAsNotification=false werden als Anhänge beigefügt
  • E-Mail an bewerbung@solcom.de mit Betreff 'SOLCOM PPP-ID <rmsNumber> hat seine Daten aktualisiert'
  • Nach erfolgreichem Versand: UserUpdateNotificationRepository::removeAll() leert die Queue
  • PersistenceManager::persistAll()
Domain-Objekte
  • UserFile
  • UserFileRepository
Side-Effects
  • E-Mail an bewerbung@solcom.de (intern) mit Profil-Änderungen und ggf. Datei-Anhängen
  • DB-Delete: UserUpdateNotification-Queue wird geleert
DateienClasses/Command/UserNotificationCommandController.php, Resources/Private/Templates/Mail/UserNotification/Notification.html

Formulare & Finisher Kern 17 Flows

FlowAuslöserEntrypointZugriffDrupal-Nachbau
Allgemeines Kontaktformular (Contact.yaml) Form-Submit des Neos-Formulars mit identifier 'Contact' (Startseite / allgemeine Kontaktseite) NormalEmailFinisher::executeInternal Gast (kein Login erforderlich) Drupal Webform-Modul mit Custom-Submit-Handler. Double-Opt-In-Logik (Lead2faForm-Tabelle) fehlt in Drupal noch komplett – eigene Entity + DOI-Mechanik erforderlich. Pabbly-Webhook-Integration muss als Webform-Handler nachgebaut werden. Enhanced Conversion Tracking (sol_act-Cookie-Entschlüsselung) ist eine Besonderheit.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Besucher füllt Contact.yaml aus (Anrede, Vorname, Nachname, Firma, Telefon optional, E-Mail, Nachricht, Datenschutz-Checkbox).
  • Neos Form Framework validiert Pflichtfelder (NotEmpty, EmailAddress).
  • NormalEmailFinisher rendert Fluid-Template resource://Contenance.Solcom/Private/Templates/Mail/Forms/ThanksForEmailSubmission.html und schickt Bestätigungs-E-Mail an Absender (no-reply@solcom.de → {email}).
  • NormalEmailFinisher schickt Benachrichtigungs-E-Mail (Plaintext-Template Form/Finisher/Contact.txt) an crm@solcom.de, CC F.Letsche@solcom.de, Subject 'Kontaktanfrage'.
  • NormalEmailFinisher sendet Lead-Daten via cURL-POST an Pabbly-Webhook (https://connect.pabbly.com/...), inklusive gclid/msclkid aus URL-Parameter oder Cookie, sowie Cookie-Consent-Status.
  • Wenn enable2fa=true: LeadFormService::addEntry() wird aufgerufen – prüft ob E-Mail bereits in Lead2faFormRepository vorhanden.
  • Wenn neuer Lead: Lead2faForm-Objekt mit SHA256-Token (32 Zeichen) wird persistiert, Double-Opt-In-Mail ('Bestätigen Sie Ihr Einverständnis') geht an Einreichenden, Lead-Info-Mail geht an CRM@solcom.de (CC F.Letsche@solcom.de).
  • Wenn bestehender Lead und noch nicht akzeptiert: erneute Opt-In-Mail + Lead-Mail.
  • Wenn bestehender Lead und bereits akzeptiert: nur Lead-Mail an CRM.
  • CustomMessageFinisher rendert Erfolgs-HTML-Div (#Contact, CSS-Klasse 'contactformsendsucc') in die Response; kein Seiten-Redirect, Inline-Replacement im Formular-Container.
  • enhancedConversionTracking=true: Sol-act-Cookie wird entschlüsselt (AES-128-CBC), msclkid/gclid sowie SHA1-gehashte E-Mail und Telefon werden als data-Attribute in die Erfolgs-Div eingebettet.
Domain-Objekte
Side-Effects
  • E-Mail an Einreichenden (Bestätigung, via MailerService)
  • E-Mail an crm@solcom.de + CC F.Letsche@solcom.de (Benachrichtigung)
  • cURL-POST an Pabbly-Webhook (Lead-Daten inkl. Click-IDs)
  • DB-Write: Lead2faForm-Eintrag in Flow-Persistence (neu oder update)
  • E-Mail Double-Opt-In-Request an Einreichenden (bei neuem/unakzeptiertem Lead)
  • E-Mail Lead-Info an CRM@solcom.de
DateienResources/Private/Form/Contact.yaml, Classes/Finishers/NormalEmailFinisher.php, Classes/Finishers/CustomMessageFinisher.php, Classes/Domain/Service/LeadFormService.php, Configuration/Settings.Form.yaml
Landingpage-Kontaktformular DE (ContactLandingpage.yaml) Form-Submit des Neos-Formulars mit identifier 'ContactLandingpage' (Marketing-Landingpages) LandingpageFinisher::executeInternal Gast (kein Login erforderlich) Drupal-Pendant fehlt. LandingpageFinisher hat KEINE eigene E-Mail an den Einreichenden (im Gegensatz zu NormalEmailFinisher). Google-Measurement-Protocol-Integration und Pabbly-Webhook sind spezifische Seiteneffekte die nachgebaut werden müssen. Irrelevant-Email-Logik (Freemailer-Domain-Erkennung) muss als Webform-Condition abgebildet werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Besucher füllt ContactLandingpage.yaml aus (Anrede, Vorname, Nachname, Firma, Telefon optional, E-Mail, Nachricht, Datenschutz-Checkbox).
  • Neos Form Framework validiert Pflichtfelder.
  • LandingpageFinisher liest HTTP-Request (Parent-Request-Kette), extrahiert gclid/msclkid aus URL-Parametern oder Cookies (_gcl_aw, _uetmsclkid).
  • LandingpageFinisher sendet cURL-POST an Pabbly-Webhook mit Lead-Daten (Vorname, Nachname, Firma, E-Mail, Telefon, Referer, Cookie-Consent-Status, Click-IDs).
  • Wenn Cookie 'cookieconsentCategory' den Wert 'all', 'media,marketing' oder 'marketing' hat: dataCookie='akzeptiert', sonst 'abgelehnt'.
  • Wenn _ga-Cookie NICHT gesetzt: GoogleService::sendB2BContactLandingPageHit() (Google Measurement Protocol Hit).
  • LeadFormService::addEntry() mit formValues (ohne subject-Parameter, formTitle='Landingpage') wird aufgerufen – gleicher DOI-Flow wie bei Contact.
  • CustomMessageFinisher rendert Erfolgs-HTML (irrelevantEmails=true: bei Freemailer-Domains wie gmail.com/web.de wird CSS-Klasse auf 'contactformIrell' umgeschaltet – kein DataLayer-Conversion-Event).
  • enhancedConversionTracking=true: sol_act-Cookie wird entschlüsselt, data-Attribute in Erfolgs-Div eingebettet.
Domain-Objekte
Side-Effects
  • cURL-POST an Pabbly-Webhook (Lead-Daten inkl. Click-IDs, Cookie-Consent)
  • Google Measurement Protocol Hit (nur wenn kein _ga-Cookie)
  • DB-Write: Lead2faForm (via LeadFormService)
  • E-Mail Double-Opt-In-Request an Einreichenden (via LeadFormService)
  • E-Mail Lead-Info an CRM@solcom.de (via LeadFormService)
DateienResources/Private/Form/ContactLandingpage.yaml, Classes/Finishers/LandingpageFinisher.php, Classes/Finishers/CustomMessageFinisher.php, Classes/Domain/Service/LeadFormService.php
Landingpage-Kontaktformular NL (ContactLandingpageNiederlande.yaml) Form-Submit des Neos-Formulars mit identifier 'ContactLandingpageNiederlande' (niederländischsprachige Landingpages) LandingpageFinisher::executeInternal Gast (kein Login erforderlich) Sprachvariante von ContactLandingpage. In Drupal als separate Webform-Variante oder als übersetztes Webform realisierbar. Niederlande-Salutation-Optionen müssen übernommen werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Identischer Flow wie ContactLandingpage.yaml, jedoch mit niederländischer UI-Sprache (Aanhef, Voornaam, Achternaam, Bedrijf, Telefoon, E-mail).
  • Subject der Lead-Mail: 'Anfrageformular Landingpage (Niederlande)'.
  • Salutation-Optionen erweitert um 'Divers' und 'Sonstige/Liever niet zeggen'.
  • Alle Seiteneffekte identisch mit ContactLandingpage.
Domain-Objekte
Side-Effects
  • cURL-POST an Pabbly-Webhook
  • Google Measurement Protocol Hit (nur wenn kein _ga-Cookie)
  • DB-Write: Lead2faForm
  • E-Mail Double-Opt-In an Einreichenden
  • E-Mail Lead-Info an CRM@solcom.de
DateienResources/Private/Form/ContactLandingpageNiederlande.yaml, Classes/Finishers/LandingpageFinisher.php, Classes/Finishers/CustomMessageFinisher.php, Classes/Domain/Service/LeadFormService.php
Rückruf-Formular Landingpage DE (CallBack.yaml) Form-Submit des Neos-Formulars mit identifier 'CallBack' (Landingpage-Rückrufanfrage) NormalEmailFinisher::executeInternal Gast (kein Login erforderlich) In Drupal als Webform mit NormalEmailFinisher-äquivalentem Custom-Handler. Unterschied zu ContactLandingpage: es wird zusätzlich eine Bestätigungs-Mail an den Einreichenden geschickt. DOI-Logik und Pabbly-Integration analog zu Contact.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Besucher füllt CallBack.yaml aus (Anrede, Vorname, Nachname, Firma, Telefon Pflicht, E-Mail, Datenschutz-Checkbox, sourceurl HiddenField).
  • Neos Form Framework validiert Pflichtfelder.
  • NormalEmailFinisher: Bestätigungs-E-Mail an Einreichenden (ThanksForEmailSubmission.html).
  • NormalEmailFinisher: Benachrichtigungs-E-Mail (Plaintext-Template CallbackSolcom.txt) an crm@solcom.de, CC F.Letsche@solcom.de, Subject 'Anfrage Rückruf Landingpage'.
  • cURL-POST an Pabbly-Webhook mit Lead-Daten und Click-IDs.
  • enable2fa=true: LeadFormService::addEntry() – DOI-Flow (gleich wie Contact).
  • CustomMessageFinisher: Erfolgs-HTML mit CSS-Klasse 'landingpageformsendsucc'; bei Freemailer-Domain Umschaltung auf 'landingpageformsendIrell'; enhancedConversionTracking=true.
Domain-Objekte
Side-Effects
  • E-Mail Bestätigung an Einreichenden
  • E-Mail Benachrichtigung an crm@solcom.de + CC F.Letsche@solcom.de
  • cURL-POST an Pabbly-Webhook
  • DB-Write: Lead2faForm
  • E-Mail Double-Opt-In an Einreichenden
  • E-Mail Lead-Info an CRM@solcom.de
DateienResources/Private/Form/CallBack.yaml, Classes/Finishers/NormalEmailFinisher.php, Classes/Finishers/CustomMessageFinisher.php, Classes/Domain/Service/LeadFormService.php
Rückruf-Formular Landingpage NL (CallBackNiederlande.yaml) Form-Submit des Neos-Formulars mit identifier 'CallBackNiederlande' (niederländischsprachige Rückruf-Landingpage) NormalEmailFinisher::executeInternal Gast (kein Login erforderlich) Sprachvariante von CallBack. Salutation um Divers/Sonstige erweitert. Sonst identisch.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Identischer Flow wie CallBack.yaml, jedoch mit niederländischer UI-Sprache.
  • Subject der Benachrichtigungs-Mail: 'Anfrage Rückruf Landingpage (Niederlande)'.
  • enable2fa=true, enhancedConversionTracking=true, irrelevantEmails=true – alle Seiteneffekte identisch.
Domain-Objekte
Side-Effects
  • E-Mail Bestätigung an Einreichenden
  • E-Mail Benachrichtigung an crm@solcom.de + CC F.Letsche@solcom.de
  • cURL-POST an Pabbly-Webhook
  • DB-Write: Lead2faForm
  • E-Mail Double-Opt-In an Einreichenden
  • E-Mail Lead-Info an CRM@solcom.de
DateienResources/Private/Form/CallBackNiederlande.yaml, Classes/Finishers/NormalEmailFinisher.php, Classes/Finishers/CustomMessageFinisher.php, Classes/Domain/Service/LeadFormService.php
Rückruf-Formular Website (CallbackSolcom.yaml) Form-Submit des Neos-Formulars mit identifier 'CallbackSolcom' (allgemeines Website-Rückrufformular, nicht Landingpage) NormalEmailFinisher::executeInternal Gast (kein Login erforderlich) Besonderheit: kein E-Mail-Feld im Formular, aber enable2fa=true gesetzt – wahrscheinlich Konfigurations-Bug der in Drupal nicht reproduziert werden sollte. Das Formular erfasst Datum und Zeitraum (DatePicker + Zeitfelder) – das muss in Drupal als Date/Time-Kombination abgebildet werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Besucher füllt CallbackSolcom.yaml aus (Name, Firma, Telefon, Datum-Picker, Zeitraum Von/Bis, Datenschutz-Checkbox).
  • Kein E-Mail-Feld – E-Mail des Einreichenden wird NICHT erfasst.
  • NormalEmailFinisher: Bestätigungs-E-Mail (ThanksForEmailSubmission.html) – Adresse für Bestätigungsmail fehlt, da kein email-Feld; Logik schlägt ggf. fehl.
  • NormalEmailFinisher: Benachrichtigungs-E-Mail (Plaintext-Template CallbackSolcom.txt) an crm@solcom.de, CC F.Letsche@solcom.de, Subject 'Anfrage Rückrufformular Webseite'.
  • cURL-POST an Pabbly-Webhook (E-Mail-Feld leer).
  • enable2fa=true: LeadFormService::addEntry() schlägt fehl da kein 'email'-Key in formValues vorhanden (PHP-Notice zu erwartendem Wert).
  • CustomMessageFinisher: Erfolgs-HTML mit CSS-Klasse 'contactformsendsucc'; irrelevantEmails=true (kein Effekt da kein email-Feld); enhancedConversionTracking=true.
Domain-Objekte
Side-Effects
  • E-Mail Benachrichtigung an crm@solcom.de + CC F.Letsche@solcom.de
  • cURL-POST an Pabbly-Webhook (mit leerem E-Mail-Feld)
  • DB-Write-Versuch: Lead2faForm (fehleranfällig ohne E-Mail)
DateienResources/Private/Form/CallbackSolcom.yaml, Classes/Finishers/NormalEmailFinisher.php, Classes/Finishers/CustomMessageFinisher.php, Classes/Domain/Service/LeadFormService.php
Anfrageformular (mehrstufig) Website (ContactPages.yaml) Form-Submit des Neos-Formulars mit identifier 'ContactPages' (mehrstufiges B2B-Anfrageformular auf Kontaktseiten, 3 Sections: Kontakt / Anforderungen optional / Zeit+Budget optional) NormalEmailFinisher::executeInternal Gast (kein Login erforderlich) Drupal Webform muss die 3-stufige Tab-Navigation (DivSection-Elemente) nachbauen. MultipleSelectPicker für Sprachen ist ein Custom-Element. Datei-Upload-Felder (bis zu 3) mit Typ-Validierung. Confirmation-Finisher ohne ECT/irrelevantEmails-Logik. Mehrstufiges Layout ist Besonderheit.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Besucher füllt ContactPages.yaml aus: Pflicht (Anrede, Vorname, Nachname, Firma, Abteilung, Telefon, E-Mail, Kontaktart Radio, Nachricht, Datenschutz); Optional (Must-have/Nice-to-have Kriterien, Sprachen MultiPicker, Branchenerfahrung, PLZ/Ort, Startdatum, Enddatum, Verlängerung, Auslastung, Preis, Feedbackdatum); bis zu 3 Datei-Uploads (pdf/doc/docx).
  • attachAllPersistentResources=true: alle hochgeladenen Dateien werden als E-Mail-Anhänge übergeben.
  • NormalEmailFinisher: Bestätigungs-E-Mail an Einreichenden (ThanksForEmailSubmission.html).
  • NormalEmailFinisher: Benachrichtigungs-E-Mail (Plaintext-Template ContactPages.txt) an crm@solcom.de, CC F.Letsche@solcom.de, Subject 'Anfrageformular Webseite'; Attachments.
  • cURL-POST an Pabbly-Webhook.
  • enable2fa=true: LeadFormService::addEntry() – DOI-Flow.
  • Statt CustomMessageFinisher: Standard Neos.Form:Confirmation-Finisher mit statischem Erfolgs-HTML (#ContactPages, Link 'Zurück' zu /de/fuer-kunden/projekt-einreichen).
Domain-Objekte
Side-Effects
  • E-Mail Bestätigung an Einreichenden
  • E-Mail Benachrichtigung an crm@solcom.de + CC F.Letsche@solcom.de (mit Datei-Anhängen)
  • cURL-POST an Pabbly-Webhook
  • DB-Write: Lead2faForm
  • E-Mail Double-Opt-In an Einreichenden
  • E-Mail Lead-Info an CRM@solcom.de
  • Datei-Upload: bis zu 3 Dateien (pdf/doc/docx) als E-Mail-Anhänge verarbeitet
DateienResources/Private/Form/ContactPages.yaml, Classes/Finishers/NormalEmailFinisher.php, Classes/Domain/Service/LeadFormService.php
Freiberufler-Magazin Newsletter-Anmeldung (FreelancerMagazine.yaml) Form-Submit des Neos-Formulars mit identifier 'FreelancerMagazine' (Newsletter-Abo für das Freiberufler-Magazin) EmailFinisher::executeInternal (registriert als Contenance.Solcom:CustomFinisher) Gast (kein Login erforderlich) Einziges Formular mit Mailingwork-Integration statt Pabbly. In Drupal als Webform mit Custom Mailingwork-Submit-Handler. Die MailingworkService-Klasse muss analysiert werden (Cluster liegt außerhalb dieser Analyse). Kein DOI über Lead2faForm – DOI läuft über Mailingwork-intern.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Besucher füllt FreelancerMagazine.yaml aus (Anrede, Titel optional, Vorname, Nachname, E-Mail, Datenschutz-Checkbox mit DOI-Erklärungstext).
  • EmailFinisher::executeInternal extrahiert salutation/lastname/firstname/email aus formValues.
  • MailingworkService::addEmailToMailingworks() wird aufgerufen – sendet Kontaktdaten an Mailingwork-API (E-Mail-Marketing-Plattform) um Adresse einzutragen.
  • KEIN direkter E-Mail-Versand durch den Finisher selbst – Mailingwork übernimmt den DOI-Prozess.
  • Neos.Form:Confirmation-Finisher zeigt statisches Erfolgs-HTML (#FreelancerMagazine) mit Hinweis auf Bestätigungs-E-Mail via Mailingwork.
Domain-Objekte
Side-Effects
  • Externe API: Mailingwork::addEmailToMailingworks() (E-Mail-Marketing-Plattform, Adresseintragung)
DateienResources/Private/Form/FreelancerMagazine.yaml, Classes/Finishers/EmailFinisher.php, Configuration/Settings.Form.yaml
Gummibären-Bestellformular (Gummibear.yaml) Form-Submit des Neos-Formulars mit identifier 'Gummibear' (SOLCOM Gummibären-Aktion /de/gibgummi/) NormalEmailFinisher::executeInternal Gast (kein Login erforderlich) Adressen-Felder (Straße, PLZ, Ort) sind Besonderheit. E-Mail geht an Einkaufsorganisation (nicht CRM). HTML-Format statt Plaintext. Kein DOI. Kein CC. Kann in Drupal als einfaches Webform ohne DOI-Handler abgebildet werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Besucher füllt Gummibear.yaml aus (Firma, Abteilung, Anrede, Vorname, Nachname, E-Mail, Straße+Nr, PLZ, Ort, Gummibärchen-Typ Dropdown: klassisch/vegan, Nachricht, Datenschutz-Checkbox).
  • NormalEmailFinisher: Bestätigungs-E-Mail an Einreichenden (ThanksForEmailSubmission.html).
  • NormalEmailFinisher: Benachrichtigungs-E-Mail (HTML-Template Gummibear.html, kein Plaintext) an einkauforganisation@solcom.de, Sender info@solcom.de, ReplyTo no-reply@solcom.de, Subject 'Gib Gummi - Zucker für Ihr Business'.
  • cURL-POST an Pabbly-Webhook.
  • enable2fa ist NICHT gesetzt – kein DOI-Flow.
  • Neos.Form:Confirmation-Finisher: Erfolgs-HTML (#Gummibear, Link 'Zurück' zu /de/gibgummi/#Gummibear).
Domain-Objekte
Side-Effects
  • E-Mail Bestätigung an Einreichenden
  • E-Mail HTML-Benachrichtigung an einkauforganisation@solcom.de
  • cURL-POST an Pabbly-Webhook
DateienResources/Private/Form/Gummibear.yaml, Classes/Finishers/NormalEmailFinisher.php
Presseservice-Anmeldung (PressService.yaml) Form-Submit des Neos-Formulars mit identifier 'PressService' (Anmeldung für Presseverteilerliste) NormalEmailFinisher::executeInternal Gast (kein Login erforderlich) Pressefelder (Redaktion, Ressort) sind spezifisch. Kein DOI. E-Mail-Empfänger ist info@ statt crm@. Der 'Zurück'-Link im Erfolgs-HTML zeigt auf die falsche Seite (Satisfaction statt PressService) – in Drupal korrigieren.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Besucher füllt PressService.yaml aus (Anrede, Vorname, Nachname, Redaktion optional, Ressort optional, Telefon optional, Fax optional, E-Mail, Sonstige Nachricht optional, Datenschutz-Checkbox).
  • NormalEmailFinisher: Bestätigungs-E-Mail an Einreichenden (ThanksForEmailSubmission.html).
  • NormalEmailFinisher: Benachrichtigungs-E-Mail (Plaintext PressService.txt) an info@solcom.de, Sender info@solcom.de, ReplyTo info@solcom.de, Subject 'Presseservice'.
  • cURL-POST an Pabbly-Webhook.
  • enable2fa ist NICHT gesetzt – kein DOI-Flow.
  • Neos.Form:Confirmation-Finisher: Erfolgs-HTML (#Gummibear CSS-ID, Link 'Zurück' zu /de/kontakt/feedbackformular/#Satisfaction – vermutlich Copy-Paste-Fehler im Template).
Domain-Objekte
Side-Effects
  • E-Mail Bestätigung an Einreichenden
  • E-Mail Benachrichtigung an info@solcom.de
  • cURL-POST an Pabbly-Webhook
DateienResources/Private/Form/PressService.yaml, Classes/Finishers/NormalEmailFinisher.php
Zufriedenheits-/Feedbackformular (Satisfaction.yaml) Form-Submit des Neos-Formulars mit identifier 'Satisfaction' (Feedbackformular unter /de/kontakt/feedbackformular/) NormalEmailFinisher::executeInternal Gast (kein Login erforderlich) Zweifaches CC (bewerbung@ und F.Letsche@). Vorname/Nachname optional. DOI-Flow aktiv. In Drupal als Webform mit NormalEmailFinisher-Äquivalent und DOI-Handler.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Besucher füllt Satisfaction.yaml aus (Vorname optional, Nachname optional, E-Mail Pflicht, Kommentar/Feedback Pflicht, Datenschutz-Checkbox).
  • NormalEmailFinisher: Bestätigungs-E-Mail an Einreichenden (ThanksForEmailSubmission.html).
  • NormalEmailFinisher: Benachrichtigungs-E-Mail (Plaintext Satisfaction.txt) an crm@solcom.de, CC bewerbung@solcom.de + F.Letsche@solcom.de, Subject 'SOLCOM Feedbackformular'.
  • cURL-POST an Pabbly-Webhook.
  • enable2fa=true: LeadFormService::addEntry() – DOI-Flow.
  • Neos.Form:Confirmation-Finisher: Erfolgs-HTML (#Satisfaction, Link 'Zurück' zu /de/kontakt/feedbackformular/#Satisfaction).
Domain-Objekte
Side-Effects
  • E-Mail Bestätigung an Einreichenden
  • E-Mail Benachrichtigung an crm@solcom.de + CC bewerbung@solcom.de + F.Letsche@solcom.de
  • cURL-POST an Pabbly-Webhook
  • DB-Write: Lead2faForm
  • E-Mail Double-Opt-In an Einreichenden
  • E-Mail Lead-Info an CRM@solcom.de
DateienResources/Private/Form/Satisfaction.yaml, Classes/Finishers/NormalEmailFinisher.php, Classes/Domain/Service/LeadFormService.php
Lead Double-Opt-In Bestätigung (2FA-Aktivierung) Benutzer klickt Bestätigungslink in der DOI-E-Mail; URL: FormsController::activate2faAction (GET-Request mit ?token=<32-Zeichen-Token>&email=<E-Mail>) FormsController::activate2faAction Gast – Token im Link ist einziger Zugangsschutz Dieser Flow ist das Gegenstück zu LeadFormService::addEntry(). In Drupal fehlt die komplette DOI/2FA-Infrastruktur. Muss als separater Route-Handler mit Token-Validierung gebaut werden. Achtung: der Flow im FormsController und der in LeadFormService::optIn() sind PARALLEL vorhanden und tun leicht Unterschiedliches (FormsController sendet nur AVV-Mail an CRM; optIn() sendet zusätzlich sendAccepted()-Mail).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Benutzer öffnet Bestätigungslink aus DOI-E-Mail.
  • Controller validiert Token-Länge (exakt 32 Zeichen).
  • Wenn Token ungültig (!=32): Fehler-Code 1001 wird an View übergeben.
  • Wenn Token korrekt: Lead2faFormRepository::findOneByToken() sucht den Eintrag.
  • Wenn Token nicht gefunden: Fehler-Code 1002 wird an View übergeben.
  • Wenn Token gefunden: lead2faForm->setAccepted(true) wird gesetzt, Lead2faFormRepository::update() persistiert die Änderung.
  • AVV-Bestätigungs-E-Mail (Template 2FA/Accept.html) wird an CRM@solcom.de gesendet (kein Empfänger-Lookup aus Lead, direkt crm@solcom.de), Subject 'AVV Bestätigung ({email})'.
  • View zeigt Erfolgs- oder Fehler-Meldung.
Domain-Objekte
Side-Effects
  • DB-Write: Lead2faForm.accepted=true (via Flow-Persistence)
  • E-Mail AVV-Bestätigung an crm@solcom.de (via MailerService)
DateienClasses/Controller/FormsController.php, Classes/Domain/Service/LeadFormService.php
Lead DOI Opt-In via LeadFormService::optIn() Programmatischer Aufruf von LeadFormService::optIn(token) (z.B. aus einem separaten Route-Handler der nicht in FormsController liegt) LeadFormService::optIn Interner Service-Aufruf – kein direkter HTTP-Zugang ohne aufrufenden Controller Zu klären ob dieser Pfad tatsächlich von einem Web-Route oder nur intern getriggert wird. LeadFormService::optOut() existiert ebenfalls (setzt accepted=false, sendet keine Mail). Beide Methoden sind für Drupal-Rebuild relevant.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Token wird übergeben.
  • Lead2faFormRepository::findOneByToken() sucht den Eintrag.
  • Wenn nicht gefunden: false zurückgeben.
  • Wenn gefunden und noch nicht akzeptiert: lead2faForm->setAccepted(true), setAcceptedDate(now), update() persistiert.
  • sendAccepted() schickt AVV-Bestätigungs-E-Mail (Template Lead2faForm/Accepted.html) an CRM@solcom.de, Subject 'AVV Bestätigung ({token})'.
  • true zurückgeben.
  • Wenn bereits akzeptiert: false zurückgeben (kein erneutes Senden).
Domain-Objekte
Side-Effects
  • DB-Write: Lead2faForm.accepted=true, acceptedDate=now
  • E-Mail AVV-Bestätigung an CRM@solcom.de
DateienClasses/Domain/Service/LeadFormService.php
Karriere-Bewerbungsformular Terminbestätigung (FormsController) Form-Submit des Custom-Action-Controllers; POST zu FormsController::submitCareerApplicationFormAction (nicht Neos-Form-Framework, sondern direktes PHP-Form) FormsController::submitCareerApplicationFormAction Gast – kein Login-Check sichtbar; wahrscheinlich wird dieses Formular nur nach erfolgreichem Bewerbungsgespräch-Prozess angezeigt Kein Neos-Form-Framework, sondern direktes PHP-Formular mit eigenem Controller. In Drupal als Webform oder Custom Form API. Kein Datei-Upload. Kein DOI. Nur interner Benachrichtigungs-Mail-Flow.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • POST-Daten werden aus $_POST['--contenance_solcom-careerapplicationfrom'] gelesen.
  • Wenn POST-Key nicht vorhanden: Redirect zu careerApplicationForm mit error=true.
  • sendCareerApplicationMailtoSolcom() wird aufgerufen: rendert Template Mail/Forms/SendCareerApplication.html mit Fluid.
  • E-Mail an karriere@solcom.de geht ab (From: no-reply@solcom.de / 'SOLCOM Karriere'), Subject 'Terminbestätigung von {Vorname} {Nachname}'.
  • Wenn E-Mail-Versand erfolgreich: Redirect zu careerApplicationFormSuccess (error=false, formValues übergeben).
  • Wenn E-Mail-Versand fehlschlägt: Redirect zurück zu careerApplicationForm mit formValues und Fehlermeldung 'Fehler.'
Domain-Objekte
Side-Effects
  • E-Mail Terminbestätigung an karriere@solcom.de (via MailerService)
DateienClasses/Controller/FormsController.php
Projekt-Einreichungsformular (FormsController) Form-Submit; POST zu FormsController::submitProjectFormSendAction (direktes PHP-Form, nicht Neos-Form-Framework) FormsController::submitProjectFormSendAction Gast – kein Login-Check im Controller sichtbar Direktes PHP-Formular mit eigenem Controller – in Drupal als Webform oder Custom Form API. BCC-Empfänger (K.Staritz, info@) unterscheiden sich von anderen Formularen. Bis zu 4 Datei-Anhänge mit MIME-Type-Validierung via UploadService. Google-MP-Integration bei fehlendem _ga-Cookie. Kein DOI, keine Bestätigungs-Mail an Einreichenden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • POST-Daten werden aus $_POST['--contenance_solcom-submitprojectform'] gelesen.
  • Wenn POST-Key nicht vorhanden: Redirect zu submitProjectForm mit error=true.
  • Wenn _ga-Cookie NICHT in Request-Headers: GoogleService::sendB2BContactHit() (Google Measurement Protocol Hit).
  • sendMailtoSolcom() wird aufgerufen: rendert Template Mail/Forms/SendProject.html mit Fluid.
  • Datei-Uploads werden aus $_FILES['--contenance_solcom-submitprojectform']['name']['uploads'] iteriert (bis maximal 4 Dateien).
  • Jede Datei wird via UploadService auf Fehler, MIME-Type und Dateigröße geprüft; valide Dateien als Swift_Attachment zur E-Mail hinzugefügt.
  • E-Mail an crm@solcom.de (BCC K.Staritz@solcom.de + info@solcom.de), Subject 'Anfrageformular - Anfrage übermitteln'.
  • Wenn E-Mail-Versand erfolgreich: Redirect zu projectFormSuccess (error=false).
  • Wenn E-Mail-Versand fehlschlägt: Redirect zurück zu submitProjectForm mit formValues und Fehlermeldung.
Domain-Objekte
Side-Effects
  • Google Measurement Protocol Hit (nur wenn kein _ga-Cookie)
  • E-Mail Benachrichtigung an crm@solcom.de + BCC K.Staritz@solcom.de, info@solcom.de (mit bis zu 4 Datei-Anhängen)
DateienClasses/Controller/FormsController.php
Karriere-Landingpage Bewerbungsformular (FormsController) Form-Submit; POST zu FormsController::submitCareerLandingpageFormAction (direktes PHP-Form, nicht Neos-Form-Framework) FormsController::submitCareerLandingpageFormAction Gast – kein Login-Check; Landingpage-Formular für externe Bewerber Direktes PHP-Formular. Einziger Datei-Upload (Lebenslauf, Pflichtfeld). Performance-Tracking-Cookie-Extraktion (Google+Bing) muss in Drupal-Submit-Handler nachgebaut werden. policyNode UUID wird über Personio-Settings als Link zur Datenschutzseite in die View übergeben – dieser UUID-Lookup entfällt in Drupal.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Request-Arguments werden geprüft; wenn keine Arguments: Redirect zurück mit Fehlermeldung.
  • Server-seitige Validierung: policy=1 Pflicht, salutation Pflicht, firstname Pflicht, lastname Pflicht, location Pflicht, uploads nicht leer Pflicht.
  • Wenn Validierungsfehler: Redirect zurück zu careerLandingpageForm mit formValues und spezifischer Fehlermeldung.
  • sendCareerLandingpageMailtoSolcom() wird aufgerufen: rendert Template Mail/Forms/SendCareerLandingpage.html.
  • Performance-Tracking: _gcl_aw-Cookie wird geprüft (Google Click ID), _uetuid-Cookie wird geprüft (Bing Performance Code) – beide werden zur E-Mail hinzugefügt.
  • Datei-Upload: uploads[0] wird geprüft via UploadService (checkUploadError, checkMimeType, checkFileSize); wenn valide wird Datei als Swift_Attachment hinzugefügt (Dateiname mit Datum-Suffix).
  • E-Mail an karriere@solcom.de (From no-reply@solcom.de), Subject 'Karriere Bewerbungsformular - Bewerbung'.
  • Redirect zu careerLandingpageFormSuccess (error=false).
Domain-Objekte
Side-Effects
  • E-Mail Bewerbung an karriere@solcom.de (mit Lebenslauf-Anhang), inkl. Google/Bing Performance-Tracking-Codes
DateienClasses/Controller/FormsController.php
iCal-Download für Bewerbungsgespräch-Termin GET-Request zu FormsController::createicalAction mit formValues (Datum + Uhrzeit aus Bewerbungsgespräch-Bestätigungs-Flow) FormsController::createicalAction Gast – Link wird vermutlich aus Bestätigungs-E-Mail heraus aufgerufen Einfacher File-Download-Endpoint. In Drupal als Custom Route mit Response-Objekt implementieren. Die formValues (userdate/timeFrom) werden als URL-Parameter übergeben. Kein Datenbankzugriff. Cache-Header sind aggressiv (2 Jahre) – in Drupal mit max-age konfigurieren.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Controller empfängt formValues mit userdate.date und timeFrom.
  • Berechnet DTSTART (Terminzeit minus 1 Stunde in UTC) und DTEND (Terminzeit plus 2 Stunden in UTC).
  • Generiert iCal-String mit VEVENT: EventName 'SOLCOM Bewerbungsgespräch', Location 'SOLCOM GmbH (in Präsenz/virtuell...)', VALARM-Reminder (10080 Minuten = 7 Tage vorher).
  • Setzt HTTP-Header: Content-Type text/calendar, Content-Disposition inline filename='{EventName}.ics', Cache-Control public max-age=63070512, Expires +2 Jahre.
  • Gibt iCal-Datei-Inhalt direkt aus und stoppt die PHP-Ausführung mit die().
Domain-Objekte
Side-Effects
  • Datei-Download: .ics-Kalender-Datei wird direkt an Browser geliefert
DateienClasses/Controller/FormsController.php

CLI / Cron — Background-Jobs Kern 22 Flows

FlowAuslöserEntrypointZugriffDrupal-Nachbau
Projekt-XML-Import (Cron) CLI-Command: `./flow project:import` — typisch via Cron ProjectCommandController::importCommand Admin / Server-seitige Cron-Ausführung (kein Web-Request) Zentraler Import-Cron. In Drupal via solcom_project_offer Migrate-Sync ersetzt (Salesforce-API statt XML). Achten: das XML enthält keinePortale-Flag und GULPRobot-Feld — prüfen, ob diese Felder im Drupal-Migrationsziel vorhanden sind. Node-Erstellung in Neos entfällt (Drupal hat eigene node-Entities). Bookmark-Bereinigung entspricht dem Watchlist-Cleanup im Drupal-WatchlistController.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Settings-Option 'debug' prüfen; bei debug=true nur Dump-Ausgabe, kein DB-Write
  • XML-Datei aus Resources/Private/Import/<filename aus Settings> laden (simplexml_load_file)
  • Für jedes Projekt-Element im XML: Felder mappen (lfdNr, Titel, Beschreibung, Start, Dauer, Ort, Stellentyp, Bearbeiter, Aktiv, Anforderungen, Zusatz, GULPRobot, aktiviert_am, deaktiviert_am, LastModified, LastModifiedBearbeiter, Region, Unternehmensbereich, Branche, keinePortale, skills, tags, FreelancerLink, AgencyLink)
  • ProjectRepository::findOneById prüfen, ob Projekt bereits existiert
  • Falls neu: createProject() — Doctrine-Entity anlegen, Neos-ContentRepository-Node vom Typ 'Contenance.Solcom:Document.Portal.Project' unter dem konfigurierten Parent-Node erstellen, Node-Properties setzen (title, contentForSearch, titleOverride, metaDescription, openGraphType/-Title/-Description/-Image, uriPathSegment), nodeId am Entity speichern, persistAll()
  • Falls vorhanden und LastModified neuer: updateProject() — Entity-Felder aktualisieren, persistAll(), Log-Eintrag
  • Nach allen XML-Einträgen: alle DB-Projekte, die nicht mehr im XML vorhanden sind, via removeProject() löschen — dabei Neos-Node entfernen, Bookmarks aller User bereinigen, persistAll()
  • Logger-Eintrag '[IMPORT PROJECTS] - N projects handled successfully.'
  • Ausgabe auf Konsole
Domain-Objekte
Side-Effects
  • DB-Writes: Project-Entity (create/update/delete), User-Entity (Bookmark-Bereinigung)
  • Neos ContentRepository: Nodes vom Typ Contenance.Solcom:Document.Portal.Project anlegen/löschen im live-Workspace
  • Neos ContentRepository: Container- und Portal.Project.Detail-Child-Nodes anlegen
  • CommandLogger-Eintrag
  • Filesystem-Lesezugriff auf XML-Importdatei
DateienClasses/Command/ProjectCommandController.php, Classes/Domain/Model/Project.php, Classes/Domain/Repository/ProjectRepository.php, Classes/Domain/Repository/UserRepository.php, Configuration/Settings.yaml
Projekte komplett löschen (Prune) CLI-Command: `./flow project:prune` ProjectCommandController::pruneCommand Admin / Server-seitige Ausführung Entspricht einem Hard-Reset des Projekt-Imports. In Drupal gibt es dafür 'drush migrate:rollback'. Kein direktes Äquivalent als eigenständiger Drush-Command vorhanden — Drupal-Nachbau sollte einen solchen Rollback-Befehl bereitstellen oder via Migrate-Rollback abbilden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Alle User laden, deren Bookmarks auf leeres Array setzen, UserRepository::update, persistAll()
  • Alle Projekte aus ProjectRepository laden und entfernen
  • Alle Neos ContentRepository-Nodes vom Typ Contenance.Solcom:Document.Portal.Project unter dem Parent-Node entfernen
  • persistAll()
  • Logger-Eintrag '[PRUNE PROJECTS] - N projects pruned.'
  • Ausgabe auf Konsole
Domain-Objekte
Side-Effects
  • DB-Writes: alle Project-Entities gelöscht, alle User-Bookmarks geleert
  • Neos ContentRepository: alle Projekt-Nodes im live-Workspace gelöscht
  • CommandLogger-Eintrag
DateienClasses/Command/ProjectCommandController.php, Classes/Domain/Repository/ProjectRepository.php, Classes/Domain/Repository/UserRepository.php
User-Änderungs-Benachrichtigungen versenden (Cron) CLI-Command: `./flow usernotification:send` — typisch via Cron (täglich) UserNotificationCommandController::sendCommand Admin / Server-seitige Cron-Ausführung Kritischer interner Benachrichtigungs-Flow: Wenn ein Projektpartner sein Profil ändert, landen Änderungs-Notifications in der DB und werden täglich per Cron gebündelt an das SOLCOM-Recruiting (bewerbung@solcom.de) gesendet — inklusive neu hochgeladener Unterlagen als Anhang. In Drupal via ProjektpartnerMailManager/ProjektpartnerRegistrationSubmitter teilweise vorhanden, aber das gebündelte tägliche Digest-Pattern und die Datei-Anhang-Logik müssen explizit geprüft werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • UserUpdateNotificationRepository::findAll() laden — alle ausstehenden Änderungsbenachrichtigungen
  • Notifications nach User-UUID gruppieren (usersChanges-Array)
  • Für jeden betroffenen User: Fluid-StandaloneView mit Template 'Mail/UserNotification/Notification.html' rendern, User + geänderte Felder (propertyName, alte/neue Werte, Zeitstempel) assignen
  • Alle UserFiles des Users prüfen: Dateien vom gestrigen Tag (creationDate = gestern) und shouldNotSendAsNotification===false als Swift_Attachment anhängen und Dateinamen für Mail-Body sammeln
  • Mail an bewerbung@solcom.de senden: From=no-reply@solcom.de, Subject='SOLCOM PPP-ID <rmsNumber> hat seine Daten aktualisiert', Body=HTML
  • Nach Versand: UserUpdateNotificationRepository::removeAll(), persistAll()
  • Ausgabe auf Konsole
Domain-Objekte
Side-Effects
  • E-Mail-Versand via SwiftMailer an bewerbung@solcom.de mit User-Profildaten und Datei-Anhängen
  • DB-Write: alle UserUpdateNotification-Einträge werden nach Versand gelöscht
  • Filesystem-Zugriff: Datei-Streams der UserFiles für Anhänge
DateienClasses/Command/UserNotificationCommandController.php, Classes/Domain/Model/UserUpdateNotification.php, Classes/Domain/Repository/UserUpdateNotificationRepository.php, Resources/Private/Templates/Mail/UserNotification/Notification.html
UserUpdateNotification-Einträge löschen (Prune) CLI-Command: `./flow usernotification:prune` UserNotificationCommandController::pruneCommand Admin / Server-seitige Ausführung Wartungs-Command zum Leeren der Notification-Queue ohne Versand. Kein Äquivalent in Drupal vorhanden — als Drush-Command einplanen wenn User-Notification-Queue in Drupal implementiert wird.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • UserUpdateNotificationRepository::removeAll()
  • persistAll()
Domain-Objekte
Side-Effects
  • DB-Write: alle UserUpdateNotification-Einträge gelöscht
DateienClasses/Command/UserNotificationCommandController.php, Classes/Domain/Repository/UserUpdateNotificationRepository.php
Ausstehende Registrierungen benachrichtigen (Cron) CLI-Command: `./flow solcomuser:notifypendingaccounts` — typisch via Cron SolcomUserCommandController::notifyPendingAccountsCommand Admin / Server-seitige Cron-Ausführung Wichtige Erinnerungs-Mail für nicht-aktivierte Konten, 5 Tage vor Ablauf des Aktivierungstokens. In Drupal via ProjektpartnerMailManager vorhanden — prüfen, ob der 5-Tages-Schwellwert und das notified-Flag korrekt implementiert sind. Aktivierungstoken-Gültigkeit in Neos = 10 Tage (autoRemoveUsers löscht nach Ablauf).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • RegistrationFlowRepository::findAll() laden
  • Für jeden RegistrationFlow: activationTokenValidUntil - 5 Tage berechnen
  • Falls dieser Schwellwert < heute UND notified === false:
  • Fluid-StandaloneView mit Template 'Mail/Account/RegistrationNotify.html' rendern
  • Aktivierungs-Node via ContentRepository (UUID 771bde22-c56e-4062-aa9b-d63017551f4e) suchen und URL bauen (?token=...)
  • Mail senden: From=no-reply@solcom.de, To=E-Mail-Adresse des RegistrationFlows, Subject='Erinnerung: Aktivierung Ihres Nutzerkontos im SOLCOM Projektportal', Body=HTML mit Aktivierungslink, Anrede, Nachname
  • RegistrationFlow::setNotified(true) setzen, Repository::update()
Domain-Objekte
Side-Effects
  • E-Mail-Versand via MailerService an die Registrierungs-E-Mail-Adresse
  • DB-Write: RegistrationFlow.notified = true
  • Neos ContentRepository-Zugriff für URL-Generierung
DateienClasses/Command/SolcomUserCommandController.php, Classes/Domain/Repository/RegistrationFlowRepository.php, Resources/Private/Templates/Mail/Account/RegistrationNotify.html
Abgelaufene Registrierungen automatisch löschen (Cron) CLI-Command: `./flow solcomuser:autoremoveusers` — typisch via Cron SolcomUserCommandController::autoRemoveUsersCommand Admin / Server-seitige Cron-Ausführung Garbage-Collection für nicht-aktivierte Registrierungen. Aktivierungstoken haben eine Gültigkeitsdauer von 10 Tagen. In Drupal prüfen, ob abgelaufene RegistrationFlow-Äquivalente automatisch bereinigt werden — dies ist ein stiller Hintergrund-Flow ohne User-Feedback.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • RegistrationFlowRepository::findAll() laden
  • Für jeden RegistrationFlow prüfen: activationTokenValidUntil < jetzt
  • Falls ja: RegistrationFlowRepository::remove(registrationFlow)
Domain-Objekte
Side-Effects
  • DB-Write: abgelaufene RegistrationFlow-Einträge gelöscht
DateienClasses/Command/SolcomUserCommandController.php, Classes/Domain/Repository/RegistrationFlowRepository.php
Projektpartner-Konto per CLI anlegen CLI-Command: `./flow solcomuser:create <username> <password> [--additionalAttributes]` SolcomUserCommandController::createCommand Admin / Server-seitige Ausführung Ermöglicht das Anlegen von Projektpartnern ohne Web-Registrierung — nützlich für Daten-Migration und Support. In Drupal entspricht dies dem drush user:create plus manuelle Rollenzuweisung. Kein dedizierter Solcom-Drush-Command für diesen Flow vorhanden — als Lücke markieren.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • additionalAttributes-String parsen (Semikolon-getrennte key:value-Paare)
  • PasswordDto und RegistrationFlow-Entity anlegen, E-Mail + Passwort + Attribute setzen
  • Bereits existierende RegistrationFlows für diese E-Mail entfernen
  • storeEncryptedPassword() aufrufen
  • RegistrationFlowRepository::add(), persistAll()
  • activateRegistrationCommand(username) direkt aufrufen — überspringt E-Mail-Aktivierung
  • userCreationService::createUserAndAccount(registrationFlow), RegistrationFlow entfernen
Domain-Objekte
Side-Effects
  • DB-Write: User-Entity und Account-Entity angelegt
  • DB-Write: RegistrationFlow temporär angelegt und sofort wieder entfernt
  • Kein E-Mail-Versand (direkter Bypass des Aktivierungsflows)
DateienClasses/Command/SolcomUserCommandController.php, Classes/Domain/Repository/RegistrationFlowRepository.php, Classes/Domain/Service/UserCreationServiceInterface.php
Konto-Aktivierung per CLI (Bypass E-Mail) CLI-Command: `./flow solcomuser:activateregistration <username>` SolcomUserCommandController::activateRegistrationCommand Admin / Server-seitige Ausführung Direkter Aktivierungs-Bypass ohne Token-Validierung. Equivalent könnte in Drupal via drush user:unblock o.ä. abgebildet werden, aber das Registrierungsflow-Modell unterscheidet sich — im Drupal-Nachbau ist der Flow über ProjektpartnerRegistrationSubmitter implementiert.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • RegistrationFlowRepository::findOneByEmail(username) laden
  • Falls nicht gefunden: Fehlerausgabe und quit(1)
  • userCreationService::createUserAndAccount(registrationFlow)
  • RegistrationFlowRepository::remove(registrationFlow)
Domain-Objekte
Side-Effects
  • DB-Write: User-Entity und Account-Entity angelegt
  • DB-Write: RegistrationFlow gelöscht
DateienClasses/Command/SolcomUserCommandController.php, Classes/Domain/Service/UserCreationServiceInterface.php, Classes/Domain/Repository/RegistrationFlowRepository.php
Passwort per CLI ändern CLI-Command: `./flow solcomuser:setpassword <username> <password> [--authenticationProvider]` SolcomUserCommandController::setPasswordCommand Admin / Server-seitige Ausführung Standard-Drush-Äquivalent: `drush user:password <name> <password>`. In Drupal bereits nativ verfügbar — kein Custom-Command nötig.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Im Neos-Kontext: Dispatch an Neos UserCommandController::setPassword
  • Sonst: AccountRepository::findByAccountIdentifierAndAuthenticationProviderName
  • Falls nicht gefunden: Fehlerausgabe und quit(1)
  • hashService::hashPassword(password)
  • account::setCredentialsSource(encrypted), AccountRepository::update(account)
Domain-Objekte
Side-Effects
  • DB-Write: Account.credentialsSource aktualisiert
DateienClasses/Command/SolcomUserCommandController.php, Classes/Domain/Repository/UserRepository.php
Projektpartner-Konto per CLI löschen (by E-Mail) CLI-Command: `./flow solcomuser:remove <username>` SolcomUserCommandController::removeCommand Admin / Server-seitige Ausführung Beim Löschen wird der Projekt-Alert des Users auf ein Guest-Objekt migriert, damit der Alert-Empfang erhalten bleibt. Dieses Muster muss im Drupal-Nachbau explizit abgebildet werden — einfaches drush user:cancel reicht nicht. Prüfen: Drupal-Modul solcom_projektpartner hat removeUserData-ähnliche Logik?
Schritte, Domain-Objekte & Side-Effects
Schritte
  • UserRepository::findOneByEmail(username)
  • Falls nicht gefunden: Fehlerausgabe und quit(1)
  • removeUserData(user) aufrufen:
  • - ProjectAlert des Users suchen; falls vorhanden: Guest-Objekt erstellen (falls E-Mail noch nicht in ProjectAlertGuestRepository) und Alert auf Guest umschreiben, oder Alert-User auf null setzen
  • - UserRepository::remove(user), AccountRepository::remove(account), persistAll()
Domain-Objekte
Side-Effects
  • DB-Write: User-Entity und Account-Entity gelöscht
  • DB-Write: ProjectAlert wird auf Guest umgeschrieben oder User-Referenz entfernt
  • Optional DB-Write: neues ProjectAlertGuest-Objekt angelegt
DateienClasses/Command/SolcomUserCommandController.php, Classes/Domain/Repository/UserRepository.php, Classes/Domain/Repository/ProjectAlertRepository.php, Classes/Domain/Repository/ProjectAlertGuestRepository.php
Projektpartner-Konto per CLI löschen (by RMS-Nummer) CLI-Command: `./flow solcomuser:removebyrms <rms>` SolcomUserCommandController::removeByRmsCommand Admin / Server-seitige Ausführung Variante des Lösch-Flows für den Fall, dass nur die interne RMS-Nummer bekannt ist (z.B. Import/Migrationskontext). In Drupal fehlt dieses Lookup-Muster — bei der Migration prüfen, ob RMS-Nummern als User-Feld vorhanden sind.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • UserRepository::findOneByRmsNumber(rms)
  • Falls nicht gefunden: Fehlerausgabe und quit(1)
  • removeUserData(user) — identische Logik wie removeCommand (Alert-Migration, Löschung)
Domain-Objekte
Side-Effects
  • DB-Write: User-Entity und Account-Entity gelöscht
  • DB-Write: ProjectAlert Guest-Migration wie bei removeCommand
DateienClasses/Command/SolcomUserCommandController.php, Classes/Domain/Repository/UserRepository.php
Konten im Lösch-Haltebereich bereinigen (Cron) CLI-Command: `./flow solcomuser:removeuersonhold` SolcomUserCommandController::removeUsersOnHoldCommand Admin / Server-seitige Cron-Ausführung Dieser Flow implementiert eine zweistufige Löschung: Konten werden zuerst auf den Provider 'Contenance.Solcom:Removed' umgestellt (Soft-Delete) und dann per Cron endgültig gelöscht. In Drupal entspricht dies user_status=0 + späteres Löschen. Die Provider-Name-Mechanik als Soft-Delete-Flag ist Neos-spezifisch — Drupal-Äquivalent muss über ein anderes Flag (Field / Status) abgebildet werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • AccountRepository::findByAuthenticationProviderName('Contenance.Solcom:Removed') — alle Konten mit dem Provider 'Removed'
  • Für jedes Account: UserRepository::findOneByAccount(account)
  • removeUserData(user) aufrufen (Alert-Migration + Löschung)
Domain-Objekte
Side-Effects
  • DB-Write: Alle 'on hold' User-Entities und Account-Entities gelöscht
  • DB-Write: ProjectAlert Guest-Migration für jeden betroffenen User
DateienClasses/Command/SolcomUserCommandController.php, Classes/Domain/Repository/UserRepository.php
Temporäre Dateien bereinigen (Cron) CLI-Command: `./flow utility:removetempfiles` — typisch via Cron UtilityCommandController::removeTempFilesCommand Admin / Server-seitige Cron-Ausführung Wichtiger Housekeeping-Cron für Upload-Artefakte. In Drupal sind temporäre Dateien via managed_file / file_usage abgebildet. Die 40-Minuten-Schwelle für DB-Einträge und die 30-Tage-Schwelle für Dateisystem-Ordner müssen als Drupal-Cron-Hook oder Drush-Command implementiert werden. Bewerbungsordner-Struktur (FLOW_PATH_DATA/Bewerbungen/YYYY-MM-DD/) muss im Drupal-Äquivalent nachvollzogen werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • TemporaryFileRepository::findAll() laden
  • Für jede TemporaryFile: Erstellungsdatum prüfen — falls älter als 40 Minuten:
  • - originalResource auf null setzen, TemporaryFileRepository::update()
  • - Falls Resource nicht bereits einem UserFile zugeordnet: ResourceRepository::remove(resource), persistAll()
  • - TemporaryFileRepository::remove(temporaryFile), persistAll()
  • Bewerbungen-Ordner (FLOW_PATH_DATA/Bewerbungen/) durchsuchen: Unterordner älter als 30 Tage per deleteDirectory() löschen
  • Verfuegbarkeit-Ordner (FLOW_PATH_DATA/Verfuegbarkeit/) durchsuchen: Unterordner älter als 30 Tage per deleteDirectory() löschen
  • Ausgabe auf Konsole
Domain-Objekte
Side-Effects
  • DB-Write: TemporaryFile-Einträge gelöscht, Resource-Referenzen auf null gesetzt
  • DB-Write: PersistentResource-Einträge gelöscht (falls nicht User-gebunden)
  • Filesystem-Schreibzugriff: Ordner und Dateien unter FLOW_PATH_DATA/Bewerbungen/ und FLOW_PATH_DATA/Verfuegbarkeit/ gelöscht
DateienClasses/Command/UtilityCommandController.php, Classes/Domain/Repository/TemporaryFileRepository.php, Classes/Domain/Repository/UserFileRepository.php
Cloudflare-Cache leeren CLI-Command: `./flow utility:clearcfcache` UtilityCommandController::clearCFCacheCommand Admin / Server-seitige Ausführung In Drupal wird Cloudflare-Cache-Purge typisch via Cloudflare-Modul (drupal/cloudflare) oder Purge-Modul (drupal/purge) abgehandelt. Der hardcodierte API-Token im Code ist ein Security-Smell — in Drupal-Konfiguration als Settings/Environment-Variable auslagern. Zone-ID und Token müssen migriert werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Cloudflare Zone-ID und Bearer-Token aus hardcodierten Konstanten lesen
  • HTTP POST an https://api.cloudflare.com/client/v4/zones/<zoneId>/purge_cache mit Payload {purge_everything:true}
  • Ergebnis ausgeben
Domain-Objekte
Side-Effects
  • Externe API-Aufruf: Cloudflare purge_cache (alle Cache-Einträge der Zone gelöscht)
DateienClasses/Command/UtilityCommandController.php
RMS-Zähler setzen CLI-Command: `./flow utility:setcounter <counter>` UtilityCommandController::setCounterCommand Admin / Server-seitige Ausführung RMS-Nummern sind interne SOLCOM-IDs für Projektpartner. In Drupal ist das RMS-Feld als User-Feld field_rms_number implementiert. Ein Auto-Inkrement-Zähler-Entity wie in Neos existiert in Drupal nicht nativ — prüfen, ob dieser noch benötigt wird oder ob RMS-Nummern nun aus Salesforce/Personio synchronisiert werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • RmsRepository::findActive() — aktiven Zähler laden
  • Falls gefunden: counter-Wert setzen, RmsRepository::update(), persistAll()
  • Logger-Eintrag '[RMS COUNTER] - Counter updated: N.'
Domain-Objekte
Side-Effects
  • DB-Write: Rms-Entity counter-Wert aktualisiert
  • CommandLogger-Eintrag
DateienClasses/Command/UtilityCommandController.php, Classes/Domain/Repository/RmsRepository.php, Classes/Domain/Model/Rms.php
RMS-Nummern an Nutzer ohne RMS vergeben CLI-Command: `./flow utility:setcountertousers` UtilityCommandController::setCounterToUsersCommand Admin / Server-seitige Ausführung Migrations-Hilfsmittel: einmalig ausgeführt um bestehenden Usern RMS-Nummern zuzuweisen. In Drupal bei der Neos-Migration relevant — prüfen ob alle Nutzer im Migrate-Prozess bereits RMS-Nummern erhalten. Ansonsten als einmaligen Post-Migration-Drush-Command implementieren.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • UserRepository::findAll() laden
  • Für jeden User ohne RMS-Nummer: RmsRepository::findActive() Zähler laden, aktuellen Zählerstand als RMS-Nummer setzen
  • User updaten, Zähler inkrementieren (setCounterUp()), persistAll()
  • Logger-Eintrag '[RMS] - Add Counter N to User email.'
Domain-Objekte
Side-Effects
  • DB-Write: User.rmsNumber gesetzt für alle User ohne RMS
  • DB-Write: Rms.counter inkrementiert
  • CommandLogger-Einträge
DateienClasses/Command/UtilityCommandController.php, Classes/Domain/Repository/UserRepository.php, Classes/Domain/Repository/RmsRepository.php
Referenzprojekte aus SOLCOM-Website importieren (Cron) CLI-Command: `./flow referenceproject:import` — typisch via Cron ReferenceProjectCommandController::importCommand Admin / Server-seitige Cron-Ausführung ACHTUNG: Der Import macht keinen Diff — er fügt immer neue Nodes hinzu ohne bestehende zu prüfen oder zu aktualisieren. Typischerweise wird daher pruneCommand() vor importCommand() ausgeführt. In Drupal muss dieser Import-Cron als Migrate-Source implementiert werden mit korrekte Update/Delete-Mechanik. Ziel-Node-Typ ist 'Contenance.Solcom:Document.ReferenceProject' mit CTA-Hardcoding — Inhalte der CTA-Box müssen als configurable Template in Drupal abgebildet werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • XML-Feed von https://www.solcom.de/asp/robots/projectReferences.xml via cURL laden
  • XML parsen (mb_convert_encoding auf UTF-8, simplexml_load_string, JSON-Encode/Decode)
  • Für jedes Referenzprojekt: id, headline, name (Kunde), industry, description (nl2br), tags extrahieren
  • createNodes(referenceprojects) aufrufen:
  • - Parent-Node via konfigurierten UUID aus Settings laden
  • - Für jedes Referenzprojekt: ContentRepository-Node vom Typ 'Contenance.Solcom:Document.ReferenceProject' anlegen
  • - Node-Properties setzen: title, titleOverride, metaDescription, uriPathSegment (headline+id)
  • - TeaserImage-Child-Node mit Standardbild und Standardtext anlegen
  • - Container-Node mit TwoColumn-Layout anlegen
  • - ReferenceProjectContainer-Node mit description, customer, branche, words anlegen
  • - HTML-Node mit hardcodiertem CTA-Box-HTML anlegen (Anfrage/Projekte-finden)
Domain-Objekte
Side-Effects
  • Externe API-Aufruf: cURL GET https://www.solcom.de/asp/robots/projectReferences.xml
  • Neos ContentRepository: neue Referenzprojekt-Nodes im live-Workspace angelegt (kein Prune/Update-Zyklus — es werden immer neue Nodes hinzugefügt, kein Diff!)
DateienClasses/Command/ReferenceProjectCommandController.php
Referenzprojekte komplett löschen (Prune) CLI-Command: `./flow referenceproject:prune` ReferenceProjectCommandController::pruneCommand Admin / Server-seitige Ausführung Wird typisch vor dem importCommand ausgeführt um einen sauberen Re-Import zu ermöglichen. In Drupal via migrate:rollback abbilden. Kein DB-Entity-Pendant wie bei Projekten — Referenzprojekte existieren nur als CMS-Nodes.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Parent-Node für Referenzprojekte via UUID aus Settings laden
  • Alle Child-Nodes vom Typ 'Contenance.Solcom:Document.ReferenceProject' laden
  • Jeden Node entfernen, persistAll() nach jedem Löschvorgang
Domain-Objekte
Side-Effects
  • Neos ContentRepository: alle Referenzprojekt-Nodes im live-Workspace gelöscht
DateienClasses/Command/ReferenceProjectCommandController.php
ACP-Admin-Konto per CLI anlegen CLI-Command: `./flow acpuser:create <userid> <password> <name> <role>` AcpUserCommandController::createCommand Admin / Server-seitige Ausführung ACP = Administration Control Panel für SOLCOM-interne Nutzer (kein Projektpartner-Account). Drei Rollen: Admin, Nutzerkonten-Verwaltung, Spendenaktion. In Drupal entspricht dies Drupal-Rollen mit unterschiedlichen Permissions. Kein direktes Äquivalent als Entitätstyp UserAcp — Drupal-Nutzer mit Rollen admin/user-manager/donation-manager abbilden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Account-Entity anlegen: accountIdentifier=userid, Passwort via hashService hashen, AuthenticationProvider='Contenance.Solcom:Acp' setzen
  • Rolle 'Neos.Neos:FrontendUser' zuweisen
  • UserAcp-Entity anlegen: account, role (1=Admin/2=Nutzerkonten/3=Spendenaktion), name
  • UserAcpRepository::add(userAcp), persistAll()
Domain-Objekte
Side-Effects
  • DB-Write: Account-Entity und UserAcp-Entity angelegt
DateienClasses/Command/AcpUserCommandController.php, Classes/Domain/Model/UserAcp.php, Classes/Domain/Repository/UserAcpRepository.php
ACP-Admin-Konto per CLI löschen CLI-Command: `./flow acpuser:remove <name>` AcpUserCommandController::removeCommand Admin / Server-seitige Ausführung Standard-Drush-Äquivalent: drush user:cancel. Kein Custom-Command nötig falls UserAcp in Drupal als regulärer User mit Rollen abgebildet wird.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • UserAcpRepository::findOneByName(name)
  • Falls nicht gefunden: Fehlerausgabe und quit(1)
  • UserAcpRepository::remove(user), AccountRepository::remove(account), persistAll()
Domain-Objekte
Side-Effects
  • DB-Write: UserAcp-Entity und Account-Entity gelöscht
DateienClasses/Command/AcpUserCommandController.php, Classes/Domain/Repository/UserAcpRepository.php
lastAccountEditDate für alle User backfüllen CLI-Command: `./flow solcomuser:setlastaccounteditdate` — einmalige Migration SolcomUserCommandController::setLastAccountEditDateCommand Admin / Server-seitige Ausführung (einmaliger Migrations-Command) Einmaliger Post-Update-Befehl (Kommentar im Code: 'TODO: Remove this command after update and initial use'). Für Drupal-Migration irrelevant, da lastAccountEditDate beim Import direkt gesetzt werden kann. Kein permanenter Cron-Job.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • UserRepository::findAll() laden
  • Für jeden User: lastAccountEditDate vs. account.creationDate vergleichen
  • Falls lastAccountEditDate < creationDate: user.setLastAccountEditDate(creationDate), UserRepository::update(), persistAll()
Domain-Objekte
Side-Effects
  • DB-Write: User.lastAccountEditDate für betroffene User aktualisiert
DateienClasses/Command/SolcomUserCommandController.php, Classes/Domain/Repository/UserRepository.php
Ressourcen-Datenbank bereinigen (interaktiv) CLI-Command: `./flow solcomresources:clean` SolcomResourcesCommandController::cleanCommand Admin / interaktive Server-Wartung Drupal-Äquivalent: drush file:delete-unused oder das Media-Modul mit managed_file-Cleanup. In Drupal gibt es file_managed-Einträge mit usage-Tracking. Dieser interaktive Command ist eher ein Wartungs-Tool als ein regulärer Cron-Job — in Drupal via Admin-UI (Media-Modul) oder drush-Script abbilden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • ResourceRepository::findAllIterator() — alle PersistentResource-Einträge iterieren
  • Für jede Resource: resource.getStream() prüfen — falls kein gültiger Stream: als 'broken' markieren
  • Falls Neos.Media aktiv: zugehörige Assets und Thumbnails für jede broken Resource suchen
  • Interaktiv fragen: 'Broken UserFiles aus früheren Releases prüfen und fixen?'
  • - Falls ja: UserFileRepository::findByOriginalResource(resource) — gefundene UserFiles entfernen, persistAll()
  • Interaktiv fragen: 'Alle broken Resources und zugehörige Assets löschen?'
  • - Falls ja: TemporaryFiles für broken Resources entfernen; ResourceRepository::remove(resource); Assets und Thumbnails entfernen; persistAll()
Domain-Objekte
Side-Effects
  • DB-Write (bei Bestätigung): PersistentResource-Einträge, UserFile-Einträge, Asset-Einträge, Thumbnail-Einträge gelöscht
  • Interaktive Konsoleneingabe erforderlich (kein vollautomatischer Cron-Lauf möglich)
DateienClasses/Command/SolcomResourcesCommandController.php, Classes/Domain/Repository/UserFileRepository.php, Classes/Domain/Repository/TemporaryFileRepository.php

Services & Integrationen Querschnitt 17 Flows

FlowAuslöserEntrypointZugriffDrupal-Nachbau
E-Mail-Versand via MailerService (generisch) Interner PHP-Aufruf durch Controller, Finisher oder Command; kein eigener HTTP-Endpoint MailerService::send(array $mailerProperties) Nur intern erreichbar (kein HTTP-Endpoint); aufgerufen von Controller-Aktionen und Finishern Drupal-Äquivalent: hook_mail() + \Drupal::service('plugin.manager.mail')->mail() oder Symfony Mailer. Der MailerService ist ein zentraler Wrapper – Drupal hat keinen direkten Gegenbaustein; der Aufbau erfolgt als Service-Klasse mit ProjektpartnerMailManager (bereits in Arbeit in web/modules/custom/solcom_projektpartner/src/Service/ProjektpartnerMailManager.php).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Aufrufer befüllt $mailerProperties-Array (mailerFrom, mailerTo, mailerSubject, body, type, optional: mailerCc, mailerBcc, replyTo, attachments)
  • MailerService::send() baut ein Neos\SwiftMailer\Message-Objekt auf
  • Debug-Modus-Check: bei $debug=true werden alle Empfänger auf debugMail-Adresse umgeleitet
  • Attachments werden aus Flow-PersistentResource-Objekten als Swift_Attachment angehängt
  • Blacklist-Check: Absender-Domain wird gegen Liste ('example') geprüft – bei Match wird true ohne Senden zurückgegeben
  • Neos SwiftMailer sendet die Mail, Rückgabe: boolean
Domain-Objekte
Side-Effects
  • E-Mail-Versand via Neos SwiftMailer (SMTP-Konfiguration in Flow-Settings)
  • Kein DB-Write
DateienClasses/Domain/Service/MailerService.php, Classes/Finishers/NormalEmailFinisher.php, Classes/Controller/ProjectController.php, Classes/Controller/AccountController.php
System-Alert-Mail bei kritischen Fehlern Interner PHP-Aufruf bei Fehler in Google-API, Twitter-API oder anderen kritischen Subsystemen MailerService::sendAlert(string $errorInModuleName, string $bodyText, bool $info = false) Nur intern; kein HTTP-Endpoint Drupal: watchdog + optional hook_watchdog / Monolog + Alert-Mail über Admin-Notification-Module. Feste Empfängerliste sollte in Drupal-Config (settings.php oder Config-API) ausgelagert werden statt hardcoded.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Fester Empfänger-Verteiler wird aufgebaut (8 Adressen: Entwickler, Admin, SOLCOM-Stakeholder)
  • Fluid-Template Resources/Private/Templates/Mail/Error/GeneralError.html wird gerendert
  • Betreff enthält Severity-Prefix ***CRITICAL*** oder ***INFO***
  • MailerService::send() wird mit den aufgebauten Mailer-Properties aufgerufen
Domain-Objekte
Side-Effects
  • E-Mail an bis zu 8 fixe Empfänger
  • Keine DB-Writes
DateienClasses/Domain/Service/MailerService.php, Classes/Domain/Service/GoogleService.php, Classes/Domain/Service/TwitterService.php
Mailingwork-Newsletter-Anmeldung Verschiedene Trigger: (1) Projekt-Bewerbungsformular (ProjectController), (2) Registrierung (AccountController::activateAction), (3) Neos-Form-Submit mit EmailFinisher, (4) Projekt-Alert-Aktivierung (ProjectAlertController) MailingworkService::addEmailToMailingworks(array $fieldParameter) Kein direkter HTTP-Endpoint; wird als Nebeneffekt von Login/Registrierung/Formular-Submit ausgelöst In Drupal noch nicht implementiert. Benötigt: HTTP-Client-Service (GuzzleHttp), Mailingwork-API-Credentials in Settings, Integration in Registrierungs- und Formular-Submit-Hooks. Kandidat für eigenen Service in solcom_projektpartner.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • isEmailInList() prüft via cURL-POST gegen https://login.mailingwork.de/webservice/webservice/json/isemailinlist ob E-Mail bereits im Verteiler ist
  • Wenn E-Mail NICHT in Liste: createRecipient() ruft optinrecipient-Endpoint mit Feldern title, lastName, firstName, email (field-IDs 1,3,4,11) auf
  • Mailingwork sendet Double-Opt-In-E-Mail an Empfänger
  • Rückgabe: true bei erfolgreicher Anmeldung, false wenn E-Mail bereits existiert oder Fehler
Domain-Objekte
Side-Effects
  • POST-Request an login.mailingwork.de (externer SaaS-Dienst)
  • Empfänger wird in Mailingwork-Liste aufgenommen (listId aus Settings)
  • Mailingwork versendet Double-Opt-In-Mail
DateienClasses/Domain/Service/MailingworkService.php, Classes/Controller/ProjectController.php, Classes/Controller/AccountController.php, Classes/Controller/ProjectAlertController.php, Classes/Finishers/EmailFinisher.php, Classes/Command/UtilityCommandController.php
Mailingwork-Newsletter-Abmeldung AccountController-Aktion beim Löschen des Benutzerkontos (deleteAccountAction o.ä.) oder explizitem Newsletter-Opt-out MailingworkService::removeEmailFromMailingworks(string $email) Intern; durch Account-Delete-Flow getriggert Muss beim Konto-Lösch-Flow in Drupal mitimplementiert werden. Der Konto-Lösch-Flow ist bereits in web/modules/custom/solcom_projektpartner vorhanden (P2503-233), Mailingwork-Abmeldung fehlt noch.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • isEmailInList() prüft ob E-Mail im Verteiler ist
  • Wenn vorhanden: getRecipientIdByEmail() ruft getrecipientidbyemail-Endpoint ab
  • deleteRecipientById() ruft deleterecipientbyid-Endpoint mit der ermittelten Recipient-ID auf
  • Rückgabe: true bei Erfolg, false wenn E-Mail nicht in Liste
Domain-Objekte
Side-Effects
  • DELETE-Äquivalent-Aufruf gegen Mailingwork-API
  • Empfänger wird aus Mailingwork-Liste entfernt
DateienClasses/Domain/Service/MailingworkService.php, Classes/Controller/AccountController.php
Google Analytics Measurement Protocol – Projektbewerbungs-Event Form-Submit der Projektbewerbung in ProjectController (jobApplicationAction o.ä.) GoogleService::sendProjectApplicationHit(mixed $projects, string $guestOrUser, mixed $request, bool $mp = false) Intern; kein HTTP-Endpoint Google Analytics UA ist deprecated (seit 01.07.2023 keine Daten mehr). In Drupal-Nachbau durch GA4 Measurement Protocol oder serverseitiges GTM ersetzen. Lücke: noch kein Äquivalent in Drupal vorhanden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • StatisticsRepository liefert aktuellen Transaktionszähler (getProjectAppilcationCounter())
  • CookieService liest utm_measurement-Cookie aus und ergänzt UTM-Parameter (cs, cm, cn) wenn vorhanden
  • POST-Payload wird aufgebaut: v=1, t=event, tid=UA-17264541-1, ec='Projektbewerbung', ea='3 - {User/Gast}', pr{n}id/nm/ca für alle beworbenen Projekte
  • Bei mp=true: Payload ohne Produkt-Daten, ec='MP - Projektbewerbung'
  • cURL-POST an http://www.google-analytics.com/collect
  • Bei Fehler: MailerService::sendAlert() mit Meldung über Google-API-Fehler
Domain-Objekte
Side-Effects
  • POST an Google Analytics Measurement Protocol (UA-17264541-1)
  • DB-Read auf StatisticsRepository
  • Bei Fehler: Alert-Mail an Entwickler
DateienClasses/Domain/Service/GoogleService.php, Classes/Controller/ProjectController.php
Google Analytics Measurement Protocol – Karrierebewerbungs-Event Form-Submit der Karriere-Bewerbung in CareerPersonioController GoogleService::sendCareerApplicationHit(PersonioProject $careerproject, mixed $request, bool $mp = false) Intern; kein HTTP-Endpoint Gleiche Deprecation wie Projektbewerbungs-Event. Karriere-Seite wird über Personio-Integration abgebildet; Analytics-Tracking fehlt in Drupal noch.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • StatisticsRepository liefert aktuellen Career-Transaktionszähler
  • CookieService liest utm_measurement-Cookie aus
  • POST-Payload: tid=UA-17264541-4 (Karriere-Property), ec='Karriere Bewerbung', pr1id/nm/ca aus PersonioProject
  • cURL-POST an http://www.google-analytics.com/collect
  • Bei Fehler: Alert-Mail
Domain-Objekte
Side-Effects
  • POST an Google Analytics Measurement Protocol (UA-17264541-4, Karriere-Property)
  • DB-Read auf StatisticsRepository
DateienClasses/Domain/Service/GoogleService.php, Classes/Controller/CareerPersonioController.php
Google Analytics Measurement Protocol – B2B-Kontaktformular-Event Form-Submit des B2B-Kontaktformulars (FormsController) oder Landingpage-Formular (LandingpageFinisher) GoogleService::sendB2BContactHit(string $sessionId, mixed $request) / GoogleService::sendB2BContactLandingPageHit(string $sessionId, mixed $request) Intern; durch Formular-Finisher getriggert Lücke in Drupal. B2B-Kontaktformulare werden in Drupal via Webform abgebildet; GA4-Server-Side-Tracking fehlt.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Zufällige Session-ID wird übergeben (rand(100000, 999999))
  • UTM-Parameter aus utm_measurement-Cookie werden hinzugefügt
  • POST-Payload: tid=UA-17264541-1, ec='B2B Anfrage', ea='2 - Formular ausgefüllt'
  • Bei LandingpageFinisher: nur wenn kein _ga-Cookie gesetzt ist
  • cURL-POST an Google Analytics
Domain-Objekte
Side-Effects
  • POST an Google Analytics Measurement Protocol
DateienClasses/Domain/Service/GoogleService.php, Classes/Controller/FormsController.php, Classes/Finishers/LandingpageFinisher.php
UTM/Tracking-Cookie-Verwaltung pro HTTP-Request Jeder eingehende HTTP-Request (SolcomComponent implementiert ComponentInterface und wird in der Flow-HTTP-Pipeline ausgeführt) SolcomComponent::handle(ComponentContext $componentContext) Läuft für alle HTTP-Requests (kein Auth-Check); ausschließlich Cookie-Manipulation In Drupal als EventSubscriber auf KernelEvents::REQUEST implementieren. Die Cookie-Logik (sol_act mit AES-Verschlüsselung, utm_time mit Jobposting-Source-Mapping) ist fachlich komplex und noch nicht in Drupal vorhanden. Besonders der sol_act-Cookie (Google/Bing Click-ID) ist relevant für Conversion-Tracking.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Query-Parameter werden aus dem HTTP-Request gelesen
  • Referer-Tracking (nur karriere.solcom.de): HTTP_REFERER wird als info-Cookie (base64-encoded, 24h) gespeichert, außer bei internen SOLCOM-Domains
  • UTM Jobposting-Tracking: utm_source+utm_medium+utm_campaign=Jobposting → utm_time-Cookie mit Source-Name (FREELANCERCHECK, GULP, JOOBLE, XING, PROJEKTWERK, TROVIT) setzen; Cookie nach 24h invalidieren
  • Enhanced Conversion Tracking: gclid (Google Ads) oder msclkid (Microsoft Ads) → AES-128-CBC-verschlüsselt im sol_act-Cookie speichern; nach 24h invalidieren
  • Measurement Protocol (deprecated): utm_source+utm_medium → utm_measurement-Cookie; nach 24h invalidieren
  • Ad-Campaign-Tracking: utm_campaign 444/555/666/gid=googleadsfreelancer → utm_ads-Cookie; nach 24h invalidieren
Domain-Objekte
Side-Effects
  • Cookies setzen/löschen: utm_time, sol_act, utm_measurement, utm_ads, info
  • Kein DB-Write
  • Kein externer API-Aufruf
DateienClasses/Http/SolcomComponent.php, Classes/Domain/Service/CookieService.php
Twitter-Projekt-Post bei neuen Projekten CLI-Command (ProjectCommandController), aufgerufen bei Projekt-Import (Cron) TwitterService::tweetProject(mixed $twitter, Project $project) CLI-Only (Command Controller); nicht über HTTP erreichbar Twitter-API v1 ist seit 2023 kostenpflichtig/deprecated. Der Aufruf ist bereits auskommentiert – kein Rebuild notwendig. Falls Social-Media-Sharing gewünscht: separate Lösung nötig.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • TwitterService::connect() baut OAuth-Verbindung mit hardcodierten Consumer-Key/Secret und Access-Token auf
  • Authentifizierung via $twitter->authenticate() – bei Fehler: Alert-Mail
  • Tweet-Text wird zusammengesetzt: Titel | Ort: State | Start | Type [| Business] | #Job #Stellenangebot #beruf | https://solcom.de/t/{id}
  • TwitterService::send() ruft $twitter->send($tweet) auf
  • Hinweis: der tweetProject-Aufruf in ProjectCommandController ist auskommentiert (// $this->twitterService->tweetProject(...))
Domain-Objekte
Side-Effects
  • POST an Twitter API v1 (via DG\Twitter-Library)
  • Bei Auth-Fehler: Alert-Mail an Entwickler
DateienClasses/Domain/Service/TwitterService.php, Classes/Command/ProjectCommandController.php
Cloudflare-Cache-Purge via CLI CLI-Command: php flow utility:clearCFCache UtilityCommandController::clearCFCacheCommand() CLI-Only; kein HTTP-Endpoint In Drupal kann Cloudflare-Integration über das Drupal-Cloudflare-Modul oder eigene drush-Commands abgebildet werden. Credentials sollten aus settings.php kommen statt hardcoded.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Hardcodierte Zone-ID und Bearer-Token werden verwendet
  • cURL-POST an https://api.cloudflare.com/client/v4/zones/{zoneId}/purge_cache mit Payload {"purge_everything":true}
  • Ergebnis wird auf der Konsole ausgegeben (Success: result.id)
  • Bei Fehler: Fehlermeldung auf Konsole
Domain-Objekte
Side-Effects
  • Cloudflare CDN-Cache für gesamte Zone wird geleert
  • POST an Cloudflare API
DateienClasses/Command/UtilityCommandController.php
RMS-Nummern-Vergabe bei Benutzerregistrierung Benutzerregistrierung abschließen (FlowUserCreationService::createUserFromRegistrationFlow()) FlowUserCreationService (intern) + UtilityCommandController::setCounterToUsersCommand() Registrierungs-Flow: anonym (Teil des Sign-up-Prozesses); CLI-Commands: nur Admins mit Flow-CLI-Zugang RMS ist die interne SOLCOM-Identifier-Nummer für Projektpartner (Salesforce-Brücke). In Drupal muss ein äquivalentes Feld (field_rms_number) am User und ein atomarer Inkrement-Mechanismus (DB-Transaktion oder Sequence) implementiert werden. Die Migrations-Plugins in solcom_projektpartner (NeosProjektpartnerProfile.php) migrieren dieses Feld bereits. Atomizität der Vergabe muss in Drupal sichergestellt werden (Race-Condition-Risiko bei hohem Anmeldeaufkommen).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • RmsRepository::findActive() holt den aktiven RMS-Zähler-Datensatz (singleton-like)
  • Aktueller Counter-Wert wird als RMS-Nummer am User gesetzt (setRmsNumber())
  • RmsObj::setCounterUp() inkrementiert den Zähler um 1
  • RmsRepository::update() + persistenceManager::persistAll() speichern Zähler
  • CLI-Command setCounterToUsersCommand() als Batch-Backfill für bestehende User ohne RMS-Nummer
  • CLI-Command setCounterCommand($counter) für manuelle Korrekturen des Zählers
Domain-Objekte
Side-Effects
  • DB-Write auf rms-Entität (Zähler-Tabelle)
  • DB-Write auf user-Entität (rmsNumber-Feld)
DateienClasses/Domain/Model/Rms.php, Classes/Domain/Repository/RmsRepository.php, Classes/Domain/Service/Flow/FlowUserCreationService.php, Classes/Command/UtilityCommandController.php
SalesStaff-Zuordnung für Dashboard und Donation-Prozesse DashboardController::indexAction() (Anzeige), DonationCommandController (CLI-Import) DashboardController::indexAction() / DonationCommandController::importCommand() Dashboard: nur eingeloggte Admins (ACP); CLI-Import: nur Flow-CLI-Admins SalesStaff-Entität ist ein internes SOLCOM-Vertriebs-Datenmodell (VM = Vertriebsmitarbeiter). In Drupal als Custom-Entity oder Node-Typ abbilden. Die Spenden-Kampagnen (Donation-Prozess) sind ein separates Feature-Cluster; Drupal-Rebuild-Bedarf unklar (kein P2503-Ticket gesichtet).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • DashboardController: SalesStaffRepository::findAll() lädt alle Vertriebs-Mitarbeiter und übergibt sie an das Dashboard-Template
  • DonationCommandController: CSV-Import von Vertriebs-Mitarbeiterdaten (Name, E-Mail, Position, Adresse, Telefon, Fax, Mobil)
  • findOneByName() / findOneByEmail() prüfen auf Duplikate vor dem Anlegen
  • SalesStaff wird per Donation-Prozessen (DonationController) mit Spendenorganisationen verknüpft
Domain-Objekte
Side-Effects
  • DB-Read (Dashboard)
  • DB-Write bei CSV-Import (DonationCommandController)
  • Kein externer API-Aufruf
DateienClasses/Domain/Model/SalesStaff.php, Classes/Domain/Repository/SalesStaffRepository.php, Classes/Controller/DashboardController.php, Classes/Command/DonationCommandController.php
Agent-Modell (Vertriebsmitarbeiter-Profil für Verfügbarkeits-Mails) AvailabilityCommandController::importCommand() sendet Mail-Template Mail/Availability/Agent.html an Agents (Mailtyp=2) AvailabilityCommandController::importCommand() CLI-Only Agent-Entität ist in Drupal noch nicht vorhanden. Die Verfügbarkeits-E-Mail-Kampagne (Aktualisierungsanfrage-Workflow) ist ein komplexes eigenständiges Feature. Klären ob im Drupal-MVP enthalten.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • XML-Import-File wird gelesen (Aktualisierungsanfrage.xml)
  • Für Mailtyp=2: Agent-Template wird verwendet ('Ihr Mitarbeiterprofil für unsere Projekte')
  • Agent-Modell (shortName, firstName, lastName, eMail, phoneNumber, department, xingURL) ist Entität in AgentRepository
  • Mail wird via SwiftMailer direkt (ohne MailerService-Wrapper) gesendet
Domain-Objekte
Side-Effects
  • E-Mail-Versand an Agent-E-Mail-Adresse
  • DB-Write auf availability-Entität
DateienClasses/Domain/Model/Agent.php, Classes/Domain/Repository/AgentRepository.php, Classes/Command/AvailabilityCommandController.php
Location-Entität (SOLCOM-Niederlassungs-Stammdaten) Datenmodell; direkt genutzt über LocationRepository in ViewHelper und Eel-Helper (Schema.org) LocationRepository::findAll() / ViewHelper/SchemaOrgHelper Öffentlich (Schema.org-Daten im HTML; Niederlassungsseiten öffentlich) Drupal-Äquivalent: SOLCOM-Niederlassungen als Taxonomy-Terme oder Custom-Entity. Das coordinates-Feld ist relevant für Google Maps (geoField / Geocoder-Module). Schema.org-Ausgabe via Metatag-Modul oder custom field_formatter.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Location-Entität speichert Niederlassungs-Daten: companyName, street, postalCode, city, country, phoneNumber, faxNumber, eMail, coordinates
  • coordinates-Feld enthält Geo-Koordinaten (für Google Maps-Einbindung im Frontend)
  • SchemaOrgHelper verwendet Location für strukturierte Daten (JSON-LD schema.org/JobPosting mit jobLocation)
Domain-Objekte
Side-Effects
  • DB-Read
  • Kein externer API-Aufruf
DateienClasses/Domain/Model/Location.php, Classes/Domain/Repository/LocationRepository.php, Classes/Eel/Helper/SchemaOrgHelper.php
Level-Entität (Karriere-Level-Mapping für Personio-Integration) Datenmodell; genutzt über LevelRepository bei Karriere-Projekten LevelRepository::findAll() Intern/öffentlich (Teil der Karriere-Seite) Drupal-Äquivalent: Taxonomy-Vokabular 'Karrierelevel' mit internem Maschinenname und deutschem Label. Oder direkt als Mapping-Array in Settings/Config.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Level-Entität mappt interne Karriere-Level-Bezeichnungen (careerLevelNameInternal) auf deutschsprachige Anzeigenamen (careerLevelNameDE)
  • Wird bei der Anzeige von Personio-Karriereprojekten für die Übersetzung der Level-Bezeichnung verwendet
Domain-Objekte
Side-Effects
  • DB-Read
  • Kein externer API-Aufruf
DateienClasses/Domain/Model/Level.php, Classes/Domain/Repository/LevelRepository.php
Pabbly-Webhook bei Formular-Submit (B2B- und Normal-Formulare) NormalEmailFinisher::executeInternal() und LandingpageFinisher::executeInternal() nach Form-Submit NormalEmailFinisher::executeInternal() / LandingpageFinisher::executeInternal() Intern; durch Formular-Finisher getriggert; Gäste können über öffentliche Formulare indirekt triggern Pabbly ist eine Automatisierungsplattform (Zapier-ähnlich). In Drupal muss dieser Webhook-Call als Webform-Handler oder als Hook nach dem Form-Submit implementiert werden. Achtung: Der Cookie-Consent-Check ist auskommentiert – vor dem Go-Live überprüfen ob DSGVO-konform. Der Pabbly-Webhook-URL sollte in Config ausgelagert werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Nach E-Mail-Versand werden Formulardaten (firstname, lastname, company, email, phone) sowie Tracking-IDs aus Cookies/URL-Params (gclid via _gcl_aw-Cookie oder URL, msclkid via _uetmsclkid-Cookie oder URL) und Cookie-Consent-Status ermittelt
  • formUrl (HTTP_REFERER), dataCookie (akzeptiert/abgelehnt je nach cookieconsentCategory-Cookie) werden ebenfalls erfasst
  • JSON-Payload wird via cURL-POST an hardcodierte Pabbly-Connect-Webhook-URL gesendet
  • Pabbly-Workflow verarbeitet die Daten weiter (Ziel-System: vermutlich CRM oder Salesforce)
  • Hinweis: Cookie-Consent-Check ist im Code auskommentiert – Pabbly-Call läuft immer
Domain-Objekte
Side-Effects
  • POST an Pabbly Connect Webhook (https://connect.pabbly.com/...)
  • Pabbly-Downstream-Systeme (CRM/Salesforce) werden getriggert
DateienClasses/Finishers/NormalEmailFinisher.php, Classes/Finishers/LandingpageFinisher.php, Classes/Domain/Service/CookieService.php
Verfügbarkeits-E-Mail-Kampagne (Aktualisierungsanfrage-Import) CLI-Command: php flow availability:import (liest XML-Datei Resources/Private/Import/Aktualisierungsanfrage.xml) AvailabilityCommandController::importCommand() CLI-Only; kein HTTP-Endpoint Dieser Workflow ist ein komplexer CRM-getriggerter Outreach-Prozess ('Bald verfügbar?'-Mails an Freelancer). In Drupal müsste er als Drush-Command oder Queue-Worker implementiert werden. Klären ob im MVP-Scope (kein P2503-Ticket bekannt).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • XML-Datei wird geladen und geparst (simplexml_load_file)
  • Für jeden Eintrag: E-Mail-Adresse via RFC822-Regex validiert
  • Existenz-Check via AvailabilityRepository::findByStaffId(); bei Existenz: Update, sonst: Neu-Anlage
  • Je nach Mailtyp (1=Partner, 2=Agent, 3=WithoutContactPerson, 4=WithContactPerson) wird Template gewählt
  • Fluid-Template gerendert mit availability-Objekt, Link (Neos-Node-URL) und Absender-Signatur
  • E-Mail direkt via SwiftMailer an Empfänger gesendet
  • Logging: Versand-Protokoll im CommandLogger
Domain-Objekte
Side-Effects
  • E-Mail an jeden validen Availability-Empfänger gesendet (From: projektpartner@solcom.de)
  • DB-Write: Availability-Einträge angelegt/aktualisiert
  • Logging in CommandLoggerInterface
DateienClasses/Command/AvailabilityCommandController.php, Classes/Domain/Service/MailerService.php

Routing & Access-Control — vollständige Route-Map Infra 76 Flows

FlowAuslöserEntrypointZugriffDrupal-Nachbau
HTTP-Middleware: Referer-Cookie setzen (SolcomComponent) Jede HTTP-Anfrage an karriere.solcom.de — vor dem Routing (position: before routing) SolcomComponent::handle Gast (kein Auth-Gate, läuft vor Routing und Security) In Drupal über hook_init() oder EventSubscriber auf KernelEvents::REQUEST abbilden; Cookie-Logik muss portiert werden (solcom_projekt_partner oder separates Tracking-Modul). Lücke: derzeit kein Äquivalent in Drupal.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Flow-HTTP-Chain ruft SolcomComponent auf, noch vor dem Routing
  • HTTP_HOST wird geprüft: nur karriere.solcom.de / karriere.solcom.local
  • HTTP_REFERER bzw. utm_source-Query-Parameter wird ausgelesen
  • Falls Referer nicht von solcom.de/karriere.solcom.de kommt: Cookie 'info' mit base64-kodierter Referer-URL + Timestamp (24h TTL) setzen oder aktualisieren
Domain-Objekte
Side-Effects
  • Setzt/entfernt Cookie 'info' (Referer-Tracking, 24h TTL)
DateienClasses/Http/SolcomComponent.php, Configuration/Settings.Flow.yaml
HTTP-Middleware: UTM-Jobposting-Cookie setzen (SolcomComponent) Jede HTTP-Anfrage (Production-Context) mit utm_source + utm_medium + utm_campaign=Jobposting SolcomComponent::handle Gast (kein Auth-Gate) UTM-Jobposting-Tracking fehlt komplett in Drupal. Muss als EventSubscriber oder Custom-Module nachgebaut werden. Quellen: freelancercheck.de, gulp.de, jooble, xing, projektwerk, trovit.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • SolcomComponent prüft FLOW_CONTEXT != Development
  • utm_campaign === 'Jobposting' und utm_source ist einer der bekannten Jobportale (freelancercheck.de, gulp.de, jooble, xing, projektwerk, trovit)
  • Cookie 'utm_time' mit utm_source, utm_sourcename, utm_medium, Timestamp (24h TTL) setzen
  • Ohne UTM-Parameter: Cookie nach 24h entfernen (TTL-Cleanup)
Domain-Objekte
Side-Effects
  • Setzt/entfernt Cookie 'utm_time' (24h TTL)
DateienClasses/Http/SolcomComponent.php
HTTP-Middleware: Enhanced Conversion Tracking (gclid/msclkid-Cookie) Jede HTTP-Anfrage (Production) mit gclid (Google Ads) oder msclkid (Microsoft Ads) Query-Parameter SolcomComponent::handle Gast (kein Auth-Gate) Lücke in Drupal. Conversion-Tracking-Cookie (sol_act) muss mit gleichem Verschlüsselungsschema nachgebaut werden, sofern das Backend diesen Cookie auswertet. Hardcoded Key '«redigiert»' ist ein Sicherheitsrisiko — in Drupal als Config-Variable hinterlegen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • SolcomComponent prüft auf gclid / msclkid in Query-Params
  • Daten werden AES-128-CBC-verschlüsselt (Key: '«redigiert»', zufälliger IV)
  • Verschlüsselter Wert wird als Cookie 'sol_act' gesetzt (base64, 24h TTL)
  • Ohne gclid/msclkid: Cookie nach 24h entfernen
Domain-Objekte
Side-Effects
  • Setzt/entfernt Cookie 'sol_act' (AES-verschlüsselt, 24h TTL)
DateienClasses/Http/SolcomComponent.php
HTTP-Middleware: UTM Measurement Protocol + Ads-Tracking-Cookie Jede HTTP-Anfrage (Production) mit utm_source + utm_medium, oder utm_campaign 444/555/666, oder gid=googleadsfreelancer SolcomComponent::handle Gast (kein Auth-Gate) Measurement-Protocol-Cookie ist als @deprecated markiert. Ads-Tracking-Cookie (utm_ads) hat noch keine Entsprechung in Drupal. Portierung bei Bedarf als EventSubscriber.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • SolcomComponent setzt Cookie 'utm_measurement' mit utm_source, utm_medium, ggf. utm_campaign (24h TTL)
  • Parallel: utm_campaign 444 (Xing), 555 (LinkedIn), 666 (Bing) oder gid=googleadsfreelancer (Google, Campaign 333) setzen Cookie 'utm_ads'
  • Cookies werden nach 24h ohne erneutem UTM-Hit gelöscht
Domain-Objekte
Side-Effects
  • Setzt/entfernt Cookie 'utm_measurement' und 'utm_ads' (24h TTL)
DateienClasses/Http/SolcomComponent.php
Security-Muster: Frontend vs. Backend Auth-Provider-Trennung (NeosRequestPattern) Jede eingehende HTTP-Anfrage — Flow Security Framework prüft Request-Pattern vor Authentifizierung NeosRequestPattern::matchRequest Systemseitig — trennt Neos-Backend-Auth von Portal-Frontend-Auth; verhindert Cross-Provider-Authentifizierung In Drupal ist die Backend-/Frontend-Trennung durch getrennte Authentifizierungs-Plugins (cookie-based vs. basic_auth) von Haus aus gegeben. Kein direktes Äquivalent nötig, aber die zwei separaten Provider (Login + ACP) müssen als separate Drupal-Rollen und -Guards abgebildet werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Flow Security ruft matchRequest() auf für jeden konfigurierten Auth-Provider
  • Pattern prüft Request-Pfad: beginnt mit /neos oder /setup, oder enthält '@' → gilt als Backend
  • Provider 'Neos.Neos:Backend' wird nur bei Backend-Requests aktiviert
  • Provider 'Contenance.Solcom:Login' (PersistedUsernamePasswordProvider) wird nur bei Frontend-Requests aktiviert
  • Provider 'Contenance.Solcom:Acp' (PersistedUsernamePasswordProvider) wird nur bei Frontend-Requests aktiviert
Domain-Objekte
Side-Effects
DateienClasses/Security/NeosRequestPattern.php, Configuration/Settings.Flow.yaml
Policy: Zugriffsmatrix — alle Controller öffentlich (Everybody) Flow Security Policy-Evaluation bei jeder Controller-Action-Aufruf Policy.yaml (MethodPrivilege) Alle Controller-Actions sind für Everybody (anonym) per Policy freigeschaltet; Authenticated-Only-Logik ist Controller-intern implementiert In Drupal: die meisten Routen sind als 'access: TRUE' bzw. per Permission 'access content' öffentlich zugänglich. Authenticated-Only-Prüfungen (Profil, Dokumente) müssen als Drupal-Permissions/Access-Callbacks nachgebaut werden — das ist eine kritische Lücke, da die Neos-Policy dies komplett an die Controller delegiert.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Flow Security wertet Policy.yaml aus
  • Alle definierten Privilege-Targets sind der Rolle 'Neos.Flow:Everybody' (= anonym) mit GRANT zugewiesen
  • Betrifft: Login/ResetPasswordController, ApiController, AcpController, AvvController, RssController, DonationController, SommerfestController, DashboardController, FormsController, ProjectController (mainSearch/ajaxSearch/index/projectAlert/landingpage/twitterRedirect/jobApplication/detail/businesses/technologies), SearchController (ajaxSearch), CareerPersonioController, BookmarkController (count/add/remove/removeAll/index), AvailabilityController (index/formResult/send), ProjectAlertController (projectAlert/projectAlertSend/signIn/signOut/signOutGuest), AccountController (alle Account-Actions), IndexedSearchController, AbstractAuthenticationController::logoutAction
  • Hinweis: Feingranulare Zugriffskontrolle für eingeloggte Projektpartner (z.B. Profil bearbeiten) ist NICHT in Policy.yaml geregelt, sondern in den Controllern selbst (redirectToLogin-Checks)
Domain-Objekte
Side-Effects
DateienConfiguration/Policy.yaml
Route: Sommerfest-Anmeldeseite anzeigen URL GET /de/signin/{identifier} SommerfestController::indexAction Everybody (Policy GRANT für Contenance.Solcom:Sommerfest) Einmalige Event-Route — kein Drupal-Äquivalent geplant (historisch).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /de/signin/{identifier}
  • Identifier wird als route part 'identification' übergeben
  • SommerfestController::indexAction rendert die Anmeldeseite für das Sommerfest
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Classes/Controller/SommerfestController.php
Route: Sommerfest-Formular-Submit URL POST /api/sommerfest/submit SommerfestController::submitAction Everybody (Policy GRANT für Contenance.Solcom:Sommerfest) Einmalige Event-Route — kein Drupal-Äquivalent geplant.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht POST /api/sommerfest/submit
  • SommerfestController::submitAction verarbeitet Formular-Daten
Domain-Objekte
Side-Effects
  • DB-Write (SommerfestPerson)
DateienConfiguration/Routes.yaml, Classes/Controller/SommerfestController.php
Route: ACP Accounts-Dashboard URL GET /acp/accounts AcpController::indexAccountAction Everybody (Policy GRANT) — Auth-Gate ist Controller-intern (ACP-Login-Session wird im Controller geprüft) Muss als Drupal-Admin-Route unter /admin/... mit Permission 'administer users' nachgebaut werden. Bereits teilweise in solcom_projektpartner implementiert.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /acp/accounts
  • AcpController::indexAccountAction rendert das Admin-Dashboard für Accounts
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: ACP Accounts-Verwaltung URL GET /acp/accounts/management AcpController::managementAccountAction Everybody (Policy GRANT) — Auth-Gate Controller-intern Drupal-Admin-Äquivalent unter /admin/solcom/projektpartner geplant.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /acp/accounts/management
  • AcpController::managementAccountAction rendert die Benutzerverwaltung
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: ACP Alle User laden (AJAX) URL GET /acp/accounts/management/ajax/getusers AcpController::getAllUsersAction Everybody (Policy GRANT) — Auth-Gate Controller-intern AJAX-Endpoint im Drupal-Admin-Modul nachbauen (Views + AJAX Callback oder REST Resource).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /acp/accounts/management/ajax/getusers
  • AcpController::getAllUsersAction liefert alle User als HTML/JSON für AJAX
Domain-Objekte
Side-Effects
  • DB-Read (UserRepository)
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: ACP E-Mail-Blacklist-Verwaltung URL GET /acp/emailblacklist AcpController::emailBlacklistAction Everybody (Policy GRANT) — Auth-Gate Controller-intern Entsprechende Admin-Seite in Drupal fehlt noch. Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /acp/emailblacklist
  • AcpController::emailBlacklistAction rendert und verwaltet die E-Mail-Blacklist
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: ACP User-Login bearbeiten URL GET /acp/accounts/management/edituser AcpController::editUserLoginAction Everybody (Policy GRANT) — Auth-Gate Controller-intern Drupal user/NID/edit mit entsprechendem Formular-Widget.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /acp/accounts/management/edituser
  • AcpController::editUserLoginAction rendert das Edit-Formular für User-Login-Daten
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: ACP User-Account bearbeiten URL GET /acp/accounts/management/edituseraccount AcpController::editUserAccountAction Everybody (Policy GRANT) — Auth-Gate Controller-intern In Drupal über Custom-Formular im solcom_projektpartner-Modul.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /acp/accounts/management/edituseraccount
  • AcpController::editUserAccountAction rendert das Edit-Formular für User-Account-Profildaten
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: ACP User-Account-Formular speichern URL POST /acp/accounts/management/edituseraccountsubmit AcpController::editUserAccountSubmitAction Everybody (Policy GRANT) — Auth-Gate Controller-intern Form-Submit im Drupal-Admin-Modul.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /acp/accounts/management/edituseraccountsubmit
  • AcpController::editUserAccountSubmitAction speichert geänderte Account-Daten
Domain-Objekte
Side-Effects
  • DB-Write (UserRepository / AccountRepository)
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: ACP User löschen (AJAX POST) URL POST /acp/accounts/management/ajax/removeuser AcpController::removeUserAction Everybody (Policy GRANT) — Auth-Gate Controller-intern; httpMethods: POST In Drupal über user_delete() mit AJAX-Confirmation im Admin-Modul.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht POST /acp/accounts/management/ajax/removeuser
  • AcpController::removeUserAction löscht den User aus dem System
Domain-Objekte
Side-Effects
  • DB-Delete (UserRepository, AccountRepository)
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: ACP Eingeloggte User vergleichen (AJAX POST) URL POST /acp/accounts/management/ajax/compareloggedinusers AcpController::compareLoggedInUsersAction Everybody (Policy GRANT) — Auth-Gate Controller-intern; httpMethods: POST Kein direktes Äquivalent in Drupal. Session-Management über Drupal-Core.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht POST /acp/accounts/management/ajax/compareloggedinusers
  • AcpController::compareLoggedInUsersAction vergleicht aktive Sessions mit DB-Einträgen
Domain-Objekte
Side-Effects
  • DB-Read
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: ACP User freischalten (AJAX POST) URL POST /acp/accounts/management/ajax/activateuser AcpController::activateUserAction Everybody (Policy GRANT) — Auth-Gate Controller-intern; httpMethods: POST Bereits in Drupal als Admin-Action im solcom_projektpartner-Modul vorhanden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht POST /acp/accounts/management/ajax/activateuser
  • AcpController::activateUserAction schaltet einen registrierten User frei
Domain-Objekte
Side-Effects
  • DB-Write (Account-Status-Update)
  • ggf. E-Mail an User
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: ACP Donation-Dashboard URL GET /acp/donations AcpController::indexDonationAction Everybody (Policy GRANT) — Auth-Gate Controller-intern Donation-Feature nicht in Drupal geplant (historisches Feature).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /acp/donations
  • AcpController::indexDonationAction rendert die Spenden-Übersicht
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: ACP Login URL GET/POST /acp/login AcpController::loginAction Everybody (kein Auth-Gate — ist die Login-Seite selbst) Drupal-Admin-Login unter /user/login. ACP-Zugangsdaten müssen als Drupal-Rolle 'acp_admin' abgebildet werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /acp/login
  • AcpController::loginAction zeigt das ACP-Login-Formular
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: ACP Logout URL GET /acp/logout AcpController::logoutAction Everybody (Policy GRANT) Drupal /user/logout.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /acp/logout
  • AcpController::logoutAction beendet die ACP-Session
Domain-Objekte
Side-Effects
  • Session-Destroy
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: ACP Authenticate (Login-Submit) URL POST /acp/authenticate AcpController::authenticateAction Everybody (Policy GRANT — ist der Auth-Endpunkt selbst) Drupal-Core-Login-Handling über /user/login Form. Separater ACP-Auth-Provider entfällt in Drupal.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /acp/authenticate
  • AcpController::authenticateAction verarbeitet Login-Credentials
  • Bei Erfolg: Session setzen, Redirect auf ACP-Dashboard
  • Bei Fehler: Redirect zurück auf /acp/login
Domain-Objekte
Side-Effects
  • Session-Write (ACP-Auth-Token)
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: ACP Development-Index URL GET /acp/development AcpController::indexDevelopementAction Everybody (Policy GRANT) — Auth-Gate Controller-intern Nur Entwicklungsseite — kein Drupal-Äquivalent nötig.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /acp/development
  • AcpController::indexDevelopementAction rendert Development-Tools/Debug-Seite
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Classes/Controller/AcpController.php
Route: Geschützte User-Datei abrufen (protecteduserfile) URL GET /protecteduserfile (mit node-Routing-Parameter) ApiController::getpersistentfileAction Everybody (Policy GRANT für Contenance.Solcom:Api) — Auth-Gate Controller-intern (nur eingeloggter Projektpartner darf eigene Dateien sehen) In Drupal als Private-File-Download-Handler mit hook_file_download() oder Managed-File + Access-Callback. Bereits teilweise in solcom_projektpartner vorhanden (PrivateDocuments-Migration).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /protecteduserfile
  • FrontendNodeRoutePartHandler aufgelöst
  • ApiController::getpersistentfileAction prüft Authentifizierung intern
  • Liefert persistente Benutzerdatei (Dokument) aus
Domain-Objekte
Side-Effects
  • File-Read aus persistentem Storage
DateienConfiguration/Routes.yaml, Classes/Controller/ApiController.php
Route: Personio-Karriere-Bewerbungsformular (Subpage-Plugin-Route) URL GET/POST {node-path}_{personio-project-id} (z.B. /karriere/job-title_12345) CareerPersonioController::careerJobApplicationAction Everybody (Policy GRANT für Contenance.Solcom:PersonioProject) In Drupal als Career-Node-Typ mit Webform-Attachment. Personio-Integration bereits als Drupal-Feature geplant (F-340).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht {node}_{careerproject} mit ObjectType PersonioProject
  • Neos Frontend-Node wird gerendert
  • Als Plugin-Sub-Request: CareerPersonioController::careerJobApplicationAction lädt Bewerbungsformular für das Personio-Projekt
Domain-Objekte
Side-Effects
  • Formular-Submit: E-Mail an Personio/HR
  • Ggf. Personio-API-Aufruf
DateienConfiguration/Routes.yaml, Classes/Controller/CareerPersonioController.php
Route: Projektportal-Bewerbungsformular (Subpage-Plugin-Route) URL GET/POST {node-path}_{project-id} (z.B. /projektangebote/projekt-titel_42) ProjectController::jobApplicationAction Everybody (Policy GRANT für Contenance.Solcom:Project) In Drupal: project_offer-Node mit Webform. Route-Pattern ist in Drupal als canonical-URL ohne Suffix gelöst.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht {node}_{project} mit ObjectType Project
  • Neos Frontend-Node wird gerendert
  • Als Plugin-Sub-Request: ProjectController::jobApplicationAction zeigt Bewerbungsformular für das Salesforce-Projekt
Domain-Objekte
Side-Effects
  • Formular-Submit: E-Mail an SOLCOM / Salesforce-Integration
DateienConfiguration/Routes.yaml, Classes/Controller/ProjectController.php
Route: Projektangebote-Liste mit Pagination URL GET {node}/page-{currentPage} (z.B. /fuer-freiberufler/projektangebote/page-2) ProjectController::indexAction Everybody (Policy GRANT für Contenance.Solcom:Project) Drupal-Pager über Views-Pagination gelöst. URL-Pattern /page-N muss als Custom-Pager-URL oder Views-Override abgebildet werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht {node}/page-{currentPage}
  • Neos Frontend-Node gerendert
  • Als Plugin-Sub-Request: ProjectController::indexAction mit Pagination-Parameter
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Classes/Controller/ProjectController.php
Route: Projektportal-Landingpage mit Pagination URL GET {node}/page{currentPage} (z.B. /projektpartnerportal/page2) ProjectController::landingpageAction Everybody (Policy GRANT für Contenance.Solcom:Project) Drupal-Views-Pager. URL-Pattern ohne Trennzeichen (page2 statt page-2) beachten.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht {node}/page{currentPage}
  • Neos Frontend-Node gerendert
  • Als Plugin-Sub-Request: ProjectController::landingpageAction mit Pagination
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Classes/Controller/ProjectController.php
Route: Portal-Login Authenticate-Endpunkt URL POST {node}/auth (z.B. /mein-portal/auth) LoginController::authenticateAction Everybody (Policy GRANT für Contenance.Solcom:Login) Drupal: /user/login Form-Action. Bereits in Drupal implementiert (solcom_projektpartner). URL-Schema {node}/auth entspricht node-relativ — in Drupal als eigene Route /projektpartnerportal/login.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht {node}/auth ohne Status-Parameter
  • Neos Frontend-Node gerendert
  • Als Plugin-Sub-Request: LoginController::authenticateAction verarbeitet Login-Credentials
  • Bei Erfolg: Redirect auf Dashboard / ursprüngliche Seite
  • Bei Fehler: Redirect zurück auf Login mit Fehler-Status
Domain-Objekte
Side-Effects
  • Session-Write (Projektpartner-Auth-Token)
DateienConfiguration/Routes.yaml, Classes/Controller/LoginController.php
Route: Portal-Login-Seite anzeigen (mit Status) URL GET {node}/auth/{status} (z.B. /mein-portal/auth/failed) LoginController::loginAction Everybody (Policy GRANT für Contenance.Solcom:Login) Drupal: /projektpartnerportal/login. Status-Parameter wird als Query-Parameter oder Flash-Message abgebildet. Bereits vorhanden in solcom_projektpartner.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht {node}/auth/{status}
  • Neos Frontend-Node gerendert
  • Als Plugin-Sub-Request: LoginController::loginAction rendert Login-Formular mit optionalem Status (z.B. 'failed', 'logout')
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Classes/Controller/LoginController.php
Route: Portal-Logout URL GET {node}/logout (z.B. /mein-portal/logout) LoginController::logoutAction Everybody (Policy GRANT für AbstractAuthenticationController::logoutAction) Drupal: /user/logout. Bereits in Drupal implementiert.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht {node}/logout
  • Neos Frontend-Node gerendert
  • Als Plugin-Sub-Request: LoginController::logoutAction beendet Projektpartner-Session
Domain-Objekte
Side-Effects
  • Session-Destroy (Projektpartner-Auth-Token)
DateienConfiguration/Routes.yaml, Classes/Controller/LoginController.php
Route: Projektpartner-Registrierung absenden URL POST {node}/submit (z.B. /registrierung/submit) AccountController::registerAction Everybody (Policy GRANT für Contenance.Solcom:Account) Bereits in Drupal implementiert (ProjektpartnerRegistrationSubmitter). URL {node}/submit entspricht Custom-Route /projektpartnerportal/register/submit.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht {node}/submit
  • Neos Frontend-Node gerendert
  • Als Plugin-Sub-Request: AccountController::registerAction verarbeitet Registrierungsformular
  • Erstellt RegistrationFlow-Objekt, sendet Bestätigungs-E-Mail
Domain-Objekte
Side-Effects
  • DB-Write (RegistrationFlow)
  • E-Mail (Bestätigungslink)
DateienConfiguration/Routes.yaml, Classes/Controller/AccountController.php
Route: Passwort vergessen — E-Mail absenden URL POST {node}/send (z.B. /passwort-vergessen/send) AccountController::passwordForgottenSendAction Everybody (Policy GRANT für Contenance.Solcom:Account) Drupal: /user/password. Bereits in Drupal implementiert (ProjektpartnerMailManager).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht {node}/send
  • Neos Frontend-Node gerendert
  • Als Plugin-Sub-Request: AccountController::passwordForgottenSendAction sendet Reset-Link per E-Mail
Domain-Objekte
Side-Effects
  • DB-Write (ResetPasswordFlow)
  • E-Mail (Reset-Link)
DateienConfiguration/Routes.yaml, Classes/Controller/AccountController.php
Route: Passwort zurücksetzen — neues Passwort speichern URL POST {node}/aktion (z.B. /passwort-zuruecksetzen/aktion) AccountController::passwordResetSendAction Everybody (Policy GRANT für Contenance.Solcom:Account) Drupal: /user/reset/{uid}/{timestamp}/{hash}. Bereits in Drupal implementiert.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht {node}/aktion
  • Neos Frontend-Node gerendert
  • Als Plugin-Sub-Request: AccountController::passwordResetSendAction validiert Token, setzt neues Passwort
Domain-Objekte
Side-Effects
  • DB-Write (Account-Passwort-Update)
  • DB-Delete (ResetPasswordFlow)
DateienConfiguration/Routes.yaml, Classes/Controller/AccountController.php
Route: User-Profil-Datei hochladen (API) URL POST /api/profile/file/upload AccountController::userProfileUploadAction Everybody (Policy GRANT für Contenance.Solcom:Account) — Auth-Gate Controller-intern (nur eingeloggter Projektpartner) In Drupal als Managed-File-Upload-Endpoint oder Custom REST Resource. Bereits teilweise vorhanden (PrivateDocuments-Migration zielt auf dieses Feature).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht POST /api/profile/file/upload
  • AccountController::userProfileUploadAction empfängt Datei-Upload
  • Speichert Datei als UserFile, verknüpft mit Account
Domain-Objekte
Side-Effects
  • File-Upload (persistentes Storage)
  • DB-Write (UserFile)
DateienConfiguration/Routes.yaml, Classes/Controller/AccountController.php
Route: User-Profil-Datei entfernen (API) URL POST /api/profile/file/remove AccountController::userProfileUploadRemoveAction Everybody (Policy GRANT) — Auth-Gate Controller-intern Entsprechender DELETE-Endpoint in Drupal fehlt noch. Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht POST /api/profile/file/remove
  • AccountController::userProfileUploadRemoveAction löscht verknüpfte Datei
Domain-Objekte
Side-Effects
  • File-Delete (persistentes Storage)
  • DB-Delete (UserFile)
DateienConfiguration/Routes.yaml, Classes/Controller/AccountController.php
Route: Projekt-Alert aus Profil entfernen (API) URL POST /api/profile/projectalert/remove AccountController::removeProfileProjectAlertAction Everybody (Policy GRANT) — Auth-Gate Controller-intern (nur eingeloggter Projektpartner) Projekt-Alerts in Drupal geplant, Endpoint fehlt noch.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht POST /api/profile/projectalert/remove
  • AccountController::removeProfileProjectAlertAction löscht den Projekt-Alert des Nutzers
Domain-Objekte
Side-Effects
  • DB-Delete (ProjectAlert)
DateienConfiguration/Routes.yaml, Classes/Controller/AccountController.php
Route: Projektsuche AJAX (Portal) URL GET/POST /api/search ProjectController::ajaxSearchAction Everybody (Policy GRANT für Contenance.Solcom:Project) In Drupal als Views-AJAX-Endpoint oder Custom REST Resource. Bereits in solcom_project_offer vorhanden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /api/search
  • ProjectController::ajaxSearchAction führt Volltextsuche über Projekte aus
  • Gibt gefilterte Ergebnisse als HTML-Fragment zurück
Domain-Objekte
Side-Effects
  • DB-Read (ProjectRepository)
DateienConfiguration/Routes.yaml, Classes/Controller/ProjectController.php
Route: Hauptsuche AJAX (Site-weit) URL GET/POST /api/mainsearch SearchController::ajaxSearchAction Everybody (Policy GRANT für Contenance.Solcom:Search) In Drupal als Search-API-AJAX-Endpoint. Drupal-Modul search_api geplant.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /api/mainsearch
  • SearchController::ajaxSearchAction führt site-weite Suche aus (Volltextsuche über Neos-Content und Projekte)
Domain-Objekte
Side-Effects
  • DB-Read (SearchIndex)
DateienConfiguration/Routes.yaml, Classes/Controller/SearchController.php
Route: Temporäre Datei hochladen (API) URL POST /api/uploadtempfile ApiController::uploadTempFileAction Everybody (Policy GRANT für Contenance.Solcom:Api) Temporäre Datei-Uploads in Drupal über Managed-File oder Custom-Upload-Handler. Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht POST /api/uploadtempfile
  • ApiController::uploadTempFileAction speichert Datei temporär
  • Gibt JSON-Response mit Temp-File-ID zurück
Domain-Objekte
Side-Effects
  • File-Upload (temporäres Storage)
  • DB-Write (TemporaryFile)
DateienConfiguration/Routes.yaml, Classes/Controller/ApiController.php
Route: Temporäre Datei entfernen (API) URL POST /api/removetempfile ApiController::removeTempFileAction Everybody (Policy GRANT für Contenance.Solcom:Api) Kein Äquivalent in Drupal. Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht POST /api/removetempfile
  • ApiController::removeTempFileAction löscht temporäre Datei
Domain-Objekte
Side-Effects
  • File-Delete (temporäres Storage)
  • DB-Delete (TemporaryFile)
DateienConfiguration/Routes.yaml, Classes/Controller/ApiController.php
Route: E-Mail-Verfügbarkeit prüfen (AJAX) URL GET /api/account/emailcheck AccountController::emailcheckAction Everybody (Policy GRANT für Contenance.Solcom:Account) In Drupal als Custom REST Resource oder AJAX-Callback im Registrierungsformular. Bereits in solcom_projektpartner implementiert.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht GET /api/account/emailcheck
  • AccountController::emailcheckAction prüft ob E-Mail bereits registriert ist
  • Gibt Boolean-Response zurück (für Formular-Validierung)
Domain-Objekte
Side-Effects
  • DB-Read (UserRepository)
DateienConfiguration/Routes.yaml, Classes/Controller/AccountController.php
Route: Rückruf an SOLCOM senden URL POST /api/senderecalltosolcom ApiController::sendRecallToSolcomAction Everybody (Policy GRANT für Contenance.Solcom:Api) Rückruf-Feature fehlt in Drupal. Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht POST /api/senderecalltosolcom
  • ApiController::sendRecallToSolcomAction versendet Rückrufanfrage an SOLCOM-Interna
Domain-Objekte
Side-Effects
  • E-Mail an SOLCOM-Vertriebs-Team
DateienConfiguration/Routes.yaml, Classes/Controller/ApiController.php
Route: Anzahl Suchergebnisse für Projekte zählen (API) URL GET /api/projects/countsearchresults ApiController::countsearchresultsAction Everybody (Policy GRANT für Contenance.Solcom:Api) In Drupal als Views-Count-Endpoint oder Custom REST Resource.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht GET /api/projects/countsearchresults
  • ApiController::countsearchresultsAction liefert Gesamtzahl der Suchergebnisse für aktuelle Filter
Domain-Objekte
Side-Effects
  • DB-Read (ProjectRepository)
DateienConfiguration/Routes.yaml, Classes/Controller/ApiController.php
Route: Bookmark-Anzahl abrufen URL GET /api/bookmark/count BookmarkController::countAction Everybody (Policy GRANT für Contenance.Solcom:Bookmark) — Auth-Gate Controller-intern In Drupal als Watchlist-Count-Endpoint bereits in solcom_project_offer WatchlistController vorhanden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht GET /api/bookmark/count
  • BookmarkController::countAction liefert Anzahl der Bookmarks des aktuellen Nutzers
Domain-Objekte
Side-Effects
  • DB-Read (BookmarkRepository)
DateienConfiguration/Routes.yaml, Classes/Controller/BookmarkController.php
Route: Alle Bookmarks entfernen URL POST /api/bookmark/removeall BookmarkController::removeAllAction Everybody (Policy GRANT) — Auth-Gate Controller-intern WatchlistController in Drupal vorhanden, removeAll fehlt noch. Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht POST /api/bookmark/removeall
  • BookmarkController::removeAllAction löscht alle Bookmarks des aktuellen Nutzers
Domain-Objekte
Side-Effects
  • DB-Delete (alle Bookmarks des Nutzers)
DateienConfiguration/Routes.yaml, Classes/Controller/BookmarkController.php
Route: Bookmark hinzufügen URL GET/POST /api/bookmark/add/{project} BookmarkController::addAction Everybody (Policy GRANT) — Auth-Gate Controller-intern WatchlistController::add in Drupal bereits in solcom_project_offer vorhanden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /api/bookmark/add/{project} mit ObjectType Project
  • BookmarkController::addAction fügt das Projekt zur Bookmark-Liste des Nutzers hinzu
Domain-Objekte
Side-Effects
  • DB-Write (Bookmark)
DateienConfiguration/Routes.yaml, Classes/Controller/BookmarkController.php
Route: Bookmark entfernen URL GET/POST /api/bookmark/remove/{project} BookmarkController::removeAction Everybody (Policy GRANT) — Auth-Gate Controller-intern WatchlistController::remove in Drupal bereits vorhanden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /api/bookmark/remove/{project} mit ObjectType Project
  • BookmarkController::removeAction entfernt das Projekt aus der Bookmark-Liste
Domain-Objekte
Side-Effects
  • DB-Delete (Bookmark)
DateienConfiguration/Routes.yaml, Classes/Controller/BookmarkController.php
Route: Twitter-Kurzlink-Redirect URL GET /t/{id} ProjectController::twitterRedirectAction Everybody (Policy GRANT für Contenance.Solcom:Project) Twitter-Integration ist deprecated. Kein Äquivalent in Drupal nötig.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /t/{id}
  • ProjectController::twitterRedirectAction löst Kurzlink auf und leitet zu Projekt-Detail-Seite weiter
Domain-Objekte
Side-Effects
  • HTTP 301/302 Redirect
DateienConfiguration/Routes.yaml, Classes/Controller/ProjectController.php
Route: XML-Feed Expertfinder24 URL GET /asp/robots/expertfinder24.xml Neos Frontend\Node::showAction (format: xml.expertfinder24) Everybody (öffentlicher Feed) In Drupal als Views-XML-Feed oder Custom-Route mit XML-Response-Format. Lücke: alle XML-Feeds müssen einzeln als Drupal-Feeds nachgebaut werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/expertfinder24.xml am Site-Root
  • Neos rendert Site-Node im Format xml.expertfinder24
  • Fusion-Template für expertfinder24 liefert XML-Feed
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Expertprofiles URL GET /asp/robots/expert-profiles.xml Neos Frontend\Node::showAction (format: xml.expertprofiles) Everybody (öffentlicher Feed) Lücke — muss als Drupal-Feed nachgebaut werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/expert-profiles.xml
  • Fusion rendert im Format xml.expertprofiles
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Freelance.de URL GET /asp/robots/freelance.xml Neos Frontend\Node::showAction (format: xml.freelance) Everybody (öffentlicher Feed) Lücke. Freelance.de-Feed gehört zu F-360-Scope.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/freelance.xml
  • Fusion rendert im Format xml.freelance
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Freelancercheck URL GET /asp/robots/freelancercheck.xml Neos Frontend\Node::showAction (format: xml.freelancercheck) Everybody (öffentlicher Feed) Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/freelancercheck.xml
  • Fusion rendert im Format xml.freelancercheck
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Freelancermap URL GET /asp/robots/freelancermap.xml Neos Frontend\Node::showAction (format: xml.freelancermap) Everybody (öffentlicher Feed) Lücke. Freelancermap-Feed gehört zu F-340-Scope.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/freelancermap.xml
  • Fusion rendert im Format xml.freelancermap
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Gulp URL GET /asp/robots/gulplist.asp (und /asp/robots/gulplist.aspx) Neos Frontend\Node::showAction (format: xml.gulplist) Everybody (öffentlicher Feed) Lücke. Zwei URL-Varianten (.asp und .aspx) müssen als Routen-Aliase nachgebaut werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/gulplist.asp (und .aspx-Variante)
  • Fusion rendert im Format xml.gulplist
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Jobrapido URL GET /asp/robots/jobrapido.asp Neos Frontend\Node::showAction (format: xml.jobrapido) Everybody (öffentlicher Feed) Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/jobrapido.asp
  • Fusion rendert im Format xml.jobrapido
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Jooble URL GET /asp/robots/jooble-de.xml Neos Frontend\Node::showAction (format: xml.jooble) Everybody (öffentlicher Feed) Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/jooble-de.xml
  • Fusion rendert im Format xml.jooble
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Indeed URL GET /asp/robots/indeed.xml Neos Frontend\Node::showAction (format: xml.indeed) Everybody (öffentlicher Feed) Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/indeed.xml
  • Fusion rendert im Format xml.indeed
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Kununu URL GET /asp/robots/kununu.xml Neos Frontend\Node::showAction (format: xml.kununu) Everybody (öffentlicher Feed) Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/kununu.xml
  • Fusion rendert im Format xml.kununu
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Njobs URL GET /asp/robots/njobs.xml Neos Frontend\Node::showAction (format: xml.njobs) Everybody (öffentlicher Feed) Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/njobs.xml
  • Fusion rendert im Format xml.njobs
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Portalderwirtschaft URL GET /asp/robots/portalderwirtschaft.xml Neos Frontend\Node::showAction (format: xml.portalderwirtschaft) Everybody (öffentlicher Feed) Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/portalderwirtschaft.xml
  • Fusion rendert im Format xml.portalderwirtschaft
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Projectfeed (RSS) URL GET /rss/projectfeed.xml Neos Frontend\Node::showAction (format: xml.projectfeed) Everybody (öffentlicher Feed) Drupal-Views-RSS-Feed. Bereits geplant in solcom_project_offer (RssController-Äquivalent).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /rss/projectfeed.xml
  • Fusion rendert im Format xml.projectfeed — RSS-Feed mit aktuellen Projekten
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Newsfeed (RSS) URL GET /rss/newsfeed.xml Neos Frontend\Node::showAction (format: xml.newsfeed) Everybody (öffentlicher Feed) In Drupal als Views-RSS-Feed über News-Content-Type.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /rss/newsfeed.xml
  • Fusion rendert im Format xml.newsfeed — RSS-Feed mit Neuigkeiten
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Projektwerk URL GET /asp/robots/projektwerk.xml (und /asp/robots/projektwerk.xml.aspx) Neos Frontend\Node::showAction (format: xml.projektwerk) Everybody (öffentlicher Feed) Lücke. Zwei URL-Varianten (.xml und .xml.aspx) beachten.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/projektwerk.xml (und .aspx-Variante)
  • Fusion rendert im Format xml.projektwerk
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Trovit URL GET /asp/robots/trovit.xml Neos Frontend\Node::showAction (format: xml.trovit) Everybody (öffentlicher Feed) Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/trovit.xml
  • Fusion rendert im Format xml.trovit
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Xing Jobs URL GET /asp/robots/xing-jobs.xml Neos Frontend\Node::showAction (format: xml.xing-jobs) Everybody (öffentlicher Feed) Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/xing-jobs.xml
  • Fusion rendert im Format xml.xing-jobs
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Xing Personio Jobs V2 URL GET /asp/robots/xing-jobs-v2.xml Neos Frontend\Node::showAction (format: xml.xing-jobs-v2) Everybody (öffentlicher Feed) Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/xing-jobs-v2.xml
  • Fusion rendert im Format xml.xing-jobs-v2
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Xing Projects URL GET /asp/robots/xing-projects.xml Neos Frontend\Node::showAction (format: xml.xing-projects) Everybody (öffentlicher Feed) Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/xing-projects.xml
  • Fusion rendert im Format xml.xing-projects
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Xisa URL GET /asp/robots/xisa.xml Neos Frontend\Node::showAction (format: xml.xisa) Everybody (öffentlicher Feed) Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/xisa.xml
  • Fusion rendert im Format xml.xisa
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: XML-Feed Yovadis URL GET /asp/robots/yovadis.xml Neos Frontend\Node::showAction (format: xml.yovadis) Everybody (öffentlicher Feed) Lücke.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/yovadis.xml
  • Fusion rendert im Format xml.yovadis
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Configuration/Settings.Flow.yaml
Route: Projekt-Detail-Kompakt (Xing/externe Jobbörse) URL GET /jetzt-bewerben/stellen-details-kompakt.html ApiController::projectDetailCompactAction Everybody (Policy GRANT für Contenance.Solcom:Api) Kein Äquivalent in Drupal vorhanden. Lücke — externe Jobbörsen-Einbettung muss als Custom-Route nachgebaut werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /jetzt-bewerben/stellen-details-kompakt.html
  • ApiController::projectDetailCompactAction liefert kompakte Projektdetail-Ansicht (eingebettet in Jobbörsen-Bewerbungsflow)
Domain-Objekte
Side-Effects
  • DB-Read (ProjectRepository)
DateienConfiguration/Routes.yaml, Classes/Controller/ApiController.php
Route: Projekt-Detail Legacy (ASP-URL) URL GET /asp/robots/detail(-xing).asp(x) ApiController::projectDetailAction Everybody (Policy GRANT für Contenance.Solcom:Api) Legacy-URL muss als Redirect auf Drupal-Canonical-URL abgebildet werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /asp/robots/detail.asp und /asp/robots/detail-xing.asp(x) (Regex-Pattern)
  • ApiController::projectDetailAction liefert Legacy-Projektdetail-View
Domain-Objekte
Side-Effects
  • DB-Read (ProjectRepository)
DateienConfiguration/Routes.yaml, Classes/Controller/ApiController.php
Route: Spendenaktion-Seite URL GET /de/spendenaktion/in/{donation-identifier} DonationController::indexAction Everybody (Policy GRANT für Contenance.Solcom:Donation) Donation-Feature nicht in Drupal MVP geplant (historisches Feature).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /de/spendenaktion/in/{identifier} mit ObjectType Donation
  • DonationController::indexAction rendert die individuelle Spendenkampagnen-Seite
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Classes/Controller/DonationController.php
Route: Dashboard Hauptseite URL GET /solcomdashboard DashboardController::indexAction Everybody (Policy GRANT für Contenance.Solcom:Dashboard) — Auth-Gate Controller-intern Internes Dashboard — kein öffentliches Drupal-Äquivalent. Könnte als Admin-View realisiert werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht /solcomdashboard
  • DashboardController::indexAction rendert das interne SOLCOM-Dashboard
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml, Classes/Controller/DashboardController.php
Route: Dashboard Login (Authenticate) URL POST /solcomdashboard/authenticate DashboardController::authenticateAction Everybody (Policy GRANT) — ist der Auth-Endpunkt selbst Separates Dashboard-Auth-System — in Drupal über Standard-Login abdeckbar.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht POST /solcomdashboard/authenticate
  • DashboardController::authenticateAction verarbeitet Dashboard-Login
Domain-Objekte
Side-Effects
  • Session-Write
DateienConfiguration/Routes.yaml, Classes/Controller/DashboardController.php
Route: Dashboard Logout URL GET /solcomdashboard/logout DashboardController::logoutAction Everybody (Policy GRANT) Drupal /user/logout.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Route matcht GET /solcomdashboard/logout
  • DashboardController::logoutAction beendet die Dashboard-Session
Domain-Objekte
Side-Effects
  • Session-Destroy
DateienConfiguration/Routes.yaml, Classes/Controller/DashboardController.php

Rendering (Fusion) Rendering 21 Flows

FlowAuslöserEntrypointZugriffDrupal-Nachbau
Portal-Seiten-Grundstruktur (Document.Portal) HTTP-Request auf eine Neos-Seite vom NodeType Contenance.Solcom:Document.Portal Contenance.Solcom:Document.Portal (erbt Contenance.Solcom:Document.Base) Öffentlich (die Portal-Hülle selbst); einzelne Plugins prüfen Auth intern In Drupal: Seiten-Template für /projektportal-Unterseiten; body-Klasse 'portal' muss gesetzt werden. Layout entspricht Standard-Page mit Header/Teaser/Main/PreFooter/Footer; kein eigenständiger Drupal-Node-Typ nötig, da der Layout-Wrapper bereits im Theme abgebildet ist.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Neos-Routing löst Seiten-Node auf, Typ Contenance.Solcom:Document.Portal
  • Fusion-Prototype Document.Portal erbt Document.Base: Header, Teaser-ContentCollection (nodePath='teaser'), Main-ContentCollection (nodePath='main'), PreFooter, Footer, CookieBanner werden zusammengestellt
  • Document.Base delegiert an Contenance.Solcom:Document mit Layout-Wrapper Contenance.Solcom:Presentation.Layout.LandingPage
  • Contenance.Solcom:Document setzt bodyTag.class='portal' (via solcomBodyClass-Case-Auflösung)
  • Neos.Neos:Page rendert vollständiges HTML-Dokument mit head (Stylesheet, Javascript, Metadata), body und strukturiertem Layout
  • Im main-Bereich liegen Portal-Plugin-Instanzen (z.B. Contenance.Solcom:Portal.Project.Index)
Domain-Objekte
  • Contenance.Solcom:Document.Portal
  • Contenance.Solcom:Document.Base
Side-Effects
DateienResources/Private/Fusion/Components/Document/Portal/Portal.fusion, Resources/Private/Fusion/Components/Document/Base/Base.fusion, Resources/Private/Fusion/Components/Document/Document.fusion, Resources/Private/Fusion/Presentation/Layout/LandingPage.fusion, Resources/Private/Fusion/Presentation/Layout/Page.fusion
Projektliste-Rendering (Portal.Project.Index) HTTP-GET auf /projektportal (oder konfigurierte Unterseite), Neos-Plugin Contenance.Solcom:Portal.Project.Index im ContentCollection 'main' Contenance.Solcom:Portal.Project.Index → ProjectController::indexAction Gast und eingeloggter Projektpartner (keine Auth-Pflicht) Drupal: solcom_project_offer-Modul stellt /fuer-freiberufler/projektliste bereit. Paginierung und Bookmark-Toggle sind bereits in WatchlistController + watchlist.js implementiert. Sidebar-Suchfilter (SidebarSearch-Partial) muss als Block/Region in Drupal abgebildet werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Fusion-Plugin Contenance.Solcom:Portal.Project.Index (package=Contenance.Solcom, controller=Project, action=index) delegiert an Flow-MVC
  • ProjectController::indexAction liest searchArguments (searchParameter, business, itemsPerPage) aus Request
  • Controller ruft Salesforce-/Search-Backend ab und befüllt $searchResults, $projectCounter
  • Template Resources/Private/Templates/Project/Index.html rendert Suchergebnis-Liste mit Paginierung (f:widget.paginate)
  • Jede Projekt-Karte zeigt: Titel, Projekt-Nr., Beschreibung (stripTags), Dauer/Start/Ort/Typ-Icons, Bookmark-Toggle (solcom:Bookmark-ViewHelper), Detail-Link (solcom:projectLink-ViewHelper)
  • Linke Spalte: Partial Project/SidebarSearch mit Suchfiltern; rechte Spalte: Ergebnisliste + SidebarBottom
  • Angebote-pro-Seite-Formular sendet GET an action=index
Domain-Objekte
  • Project
  • ProjectRepository
  • BookmarkViewHelper
Side-Effects
  • DB-Read: Projektdaten aus Salesforce/Search-Index; Session-Read für Bookmark-Status
DateienResources/Private/Fusion/Content/Portal/Project/Index.fusion, Resources/Private/Templates/Project/Index.html, Resources/Private/Partials/Project/SidebarSearch.html, Resources/Private/Partials/Project/SidebarBottom.html
Projektdetail-Rendering (Portal.Project.Detail) HTTP-GET auf Projektdetail-URL mit project-Argument, Neos-Plugin Contenance.Solcom:Portal.Project.Detail Contenance.Solcom:Portal.Project.Detail → ProjectController::detailAction Gast und eingeloggter Projektpartner Drupal: Projektdetail-Seite bereits als Node-View im solcom_project_offer-Modul vorhanden. Social-Sharing-Links und Bookmark-Toggle müssen als Block ergänzt werden. freelancerlink/agencylink-Fallunterscheidung muss in Drupal-Template nachgebaut werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Fusion-Plugin delegiert an ProjectController::detailAction
  • Controller lädt Projektdaten (title, id, duration, location, start, type, business, html-details, html-withlist, html-requirements, additional, freelancerlink, agencylink)
  • Template Resources/Private/Templates/Project/Detail.html rendert Detailansicht mit Zurück-Link zur Projektliste (solcom:PluginLink UUID 8b48d6d5...) oder Landingpage-Node
  • Beschreibung wird über solcom:project()-ViewHelper mit Eigenschaften html-details, html-withlist, html-requirements geladen
  • Footer-Bereich: Bookmark-Toggle, Bewerbungs-Link (freiberuflich via freelancerlink oder intern via jobApplication-Action UUID 58d0789b...)
  • Technologie-Banner links (falls technology-Node vorhanden): Link zur Technologie-Landingpage
  • Social-Sharing-Links (XING, LinkedIn, Twitter, Facebook, E-Mail, Link kopieren)
  • Linke Spalte: SidebarSearch-Partial
Domain-Objekte
  • Project
  • ProjectRepository
  • BookmarkViewHelper
  • ProjectViewHelper
Side-Effects
  • DB-Read: Projektdaten; Session-Read für Bookmark-Status
DateienResources/Private/Fusion/Content/Portal/Project/Detail.fusion, Resources/Private/Templates/Project/Detail.html, Resources/Private/Partials/Project/SidebarSearch.html
Technologie-/Branchen-Landingpage Projektliste (Portal.Project.Landingpage) HTTP-GET auf Technologie-/Branchen-Landingpage, Neos-Plugin Contenance.Solcom:Portal.Project.Landingpage im ContentCollection Contenance.Solcom:Portal.Project.Landingpage → ProjectController::landingpageAction Gast und eingeloggter Projektpartner Drupal: Entspricht Technology-/Business-gefilterten Projektlisten-Views (Portal.Project.Technologies / Portal.Project.Businesses). Lücke: Redaktionelle headerContent/footerContent-Slots (ContentCollections) fehlen in Drupal noch; müssen als Block-Regions oder Paragraph-Felder am Node nachgebaut werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Fusion-Plugin mit headerContent- und footerContent-ContentCollections (Neos-Editor befüllt diese Slots redaktionell)
  • Controller::landingpageAction ermittelt Technologie-/Branchen-Tag aus aktuellem Node (tagTitle, tagIdentifier) und filtert Projekte
  • Template Resources/Private/Templates/Project/Landingpage.html zeigt gefilterte Projektliste mit Überschrift 'Projektangebote für {tagTitle} ({count})'
  • SidebarLandingpage-Partial statt SidebarSearch
  • Detail-Links beinhalten refererNode-Parameter für Rück-Navigation
  • headerContent und footerContent werden als f:format.htmlentitiesDecode eingebettet (redaktioneller Inhalt)
Domain-Objekte
  • Project
  • ProjectRepository
  • TechnologyNode
  • BusinessNode
Side-Effects
  • DB-Read: gefilterte Projektdaten nach Technologie/Branche
DateienResources/Private/Fusion/Content/Portal/Project/Landingpage.fusion, Resources/Private/Templates/Project/Landingpage.html, Resources/Private/Partials/Project/SidebarLandingpage.html
Haupt-Suchwidget Rendering (Portal.Project.MainSearch) Neos-ContentElement Contenance.Solcom:Portal.Project.MainSearch auf beliebiger Seite (z.B. Homepage) Contenance.Solcom:Portal.Project.MainSearch → ProjectController::mainSearchAction Öffentlich Drupal: Suchwidget ist als Block im byte_theme oder als custom Block-Plugin umsetzbar. AJAX-Suche muss separat implementiert werden. Aktuell in Drupal noch nicht vorhanden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Fusion-Plugin delegiert an ProjectController::mainSearchAction
  • Controller befüllt $projectCounter (Gesamtanzahl aktiver Projekte)
  • Template Resources/Private/Templates/Project/MainSearch.html rendert kompaktes Suchformular mit Freitext-Eingabe und 'Projekte finden'-Button
  • Form-Action zeigt auf searchResultNode (Projektlisten-Seite), Method=POST mit searchArguments[searchParameter]
  • Enthält AJAX-Ergebnisbereich (div.ajaxSearchResults) für Live-Suche
Domain-Objekte
  • ProjectRepository
Side-Effects
  • DB-Read: Projektzähler
DateienResources/Private/Fusion/Content/Portal/Project/MainSearch.fusion, Resources/Private/Templates/Project/MainSearch.html
Merkliste-Rendering (Portal.Bookmark.Bookmarks) HTTP-GET auf Merklisten-Seite, Neos-Plugin Contenance.Solcom:Portal.Bookmark.Bookmarks Contenance.Solcom:Portal.Bookmark.Bookmarks → BookmarkController::indexAction Eingeloggter Projektpartner (Session-basiert; Bookmarks funktionieren laut Template auch ohne Login via Session) Drupal: WatchlistController bereits vorhanden (solcom_project_offer-Modul). Template und 'Für alle Projekte bewerben'-CTA fehlen noch. Max-7-Grenze muss in Drupal übernommen werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Fusion-Plugin delegiert an BookmarkController::indexAction (package=Contenance.Solcom, controller=Bookmark, action=index)
  • Controller lädt gemerkten Projekte aus Session/DB für aktuellen Nutzer, max. 7 Einträge
  • Template Resources/Private/Templates/Bookmark/Index.html rendert Liste gemerkter Projekte mit counter 'X von max. 7'
  • Jede Karte: Titel, Projekt-Nr., Beschreibung (stripTags), Icons, Bookmark-Toggle (Entfernen), Detail-Link
  • Footer der Liste: 'Alle Projekte entfernen' und 'Für alle Projekte bewerben' (Link zu JobApplication UUID 58d0789b...)
  • Leerer Zustand: Hinweis + CTA 'Projekt Alert Abonnieren' (Link zu ProjectAlert UUID 380446c0...)
  • Linke Spalte: SidebarSearch-Partial
Domain-Objekte
  • Bookmark
  • BookmarkRepository
  • Project
Side-Effects
  • Session/DB-Read: Bookmark-Daten des Nutzers
DateienResources/Private/Fusion/Content/Portal/Project/Bookmarks.fusion, Resources/Private/Templates/Bookmark/Index.html, Resources/Private/Partials/Project/SidebarSearch.html
Projekt-Alert Formular-Rendering (Portal.Project.ProjectAlert) HTTP-GET auf Projekt-Alert-Seite, Neos-Plugin Contenance.Solcom:Portal.Project.ProjectAlert Contenance.Solcom:Portal.Project.ProjectAlert → ProjectAlertController::projectAlertAction Gast (mit Pflicht-Kontaktfeldern) und eingeloggter Projektpartner Drupal: Projekt-Alert-Funktion noch nicht vollständig umgesetzt. Formular mit OR/AND-Keyword-Logik (bis zu 4 Felder) und PLZ-Bereich-Ausschluss ist komplex und muss als Custom-Form oder Webform nachgebaut werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Fusion-Plugin mit containerContent-ContentCollection (redaktioneller Slot) und Delegation an ProjectAlertController
  • Controller prüft Auth-Status (solcom:ifAuthenticated ViewHelper), befüllt technologyOptions, tagsForTemplate, noTagsForTemplate, projectAlertStates/-Locations, userView-Flag
  • Template Resources/Private/Templates/ProjectAlert/ProjectAlert.html rendert Alert-Formular
  • Nicht-eingeloggte Nutzer sehen zusätzlich Kontaktdaten-Felder (Anrede, Vorname, Nachname, E-Mail) und Datenschutz-Checkbox
  • Schlagworte-Selektion: bis zu 4 OR-verknüpfte Keyword-Felder (tags1..4) + bis zu 2 Ausschluss-Felder (tagsNot1..2)
  • Regionen-Ausschluss: Checkboxen für Remote, AT, CH, DE (mit PLZ-Bereich 0-9), International
  • Eingeloggte Nutzer: Button 'Aktualisieren', Gäste: Button 'Jetzt Anmelden' + Datenschutzerklärung + optionaler Newsletter
  • Form-Action: ProjectAlertController::projectAlertSendAction
Domain-Objekte
  • ProjectAlert
  • ProjectAlertRepository
  • TechnologyRepository
Side-Effects
  • DB-Read: bestehende Alert-Konfiguration des Nutzers; DB-Write bei Send: ProjectAlert-Datensatz
DateienResources/Private/Fusion/Content/Portal/Project/ProjectAlert.fusion, Resources/Private/Templates/ProjectAlert/ProjectAlert.html, Resources/Private/Templates/ProjectAlert/SignIn.html, Resources/Private/Templates/ProjectAlert/SignOut.html, Resources/Private/Templates/ProjectAlert/ProjectAlertSend.html
Login-Seite Rendering (Portal.Account.Login) HTTP-GET auf Login-Seite /projektportal/login, Neos-Plugin Contenance.Solcom:Portal.Account.Login Contenance.Solcom:Portal.Account.Login → LoginController::loginAction Öffentlich (Gast) Drupal: Login-Seite bereits vorhanden (Drupal-Core-Auth + solcom_projektpartner-Modul). redirectAfterLogin/-Logout als konfigurierbare Node-Properties fehlt; in Drupal als Redirect-Konfiguration im Login-Block umsetzbar.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Fusion-Plugin liest redirectAfterLogin und redirectAfterLogout aus Node-Properties (konfigurierbar im Neos-Backend)
  • node-Kontext wird übergeben
  • Template Resources/Private/Templates/Login/Login.html rendert Login-Formular mit E-Mail + Passwort
  • Statusmeldungen: 'logout' (Erfolgreich ausgeloggt), 'authfailure' (Nicht registriert), 'authfailurebutexistingaccount' (Account noch nicht aktiviert)
  • Bereits-eingeloggte Nutzer sehen Link 'Zu meinem Profil' (UUID 336130ab...)
  • Links zu 'Passwort vergessen' (UUID 2c4b138f...) und 'Jetzt registrieren' (UUID 4931491d...)
  • Form-Action: LoginController::authenticateAction (Flow Security Authentication)
Domain-Objekte
  • Login
  • Flow Security Authentication Token
Side-Effects
  • Session: Neos/Flow-Security-Token bei Login
DateienResources/Private/Fusion/Content/Portal/Account/Login.fusion, Resources/Private/Templates/Login/Login.html, Resources/Private/Partials/LogoutForm.html
Registrierungs-Formular Rendering (Portal.Account.Register) HTTP-GET auf Registrierungsseite, Neos-Plugin Contenance.Solcom:Portal.Account.Register Contenance.Solcom:Portal.Account.Register → AccountController::registerFormAction Gast (nicht-eingeloggt) Drupal: Registrierungsformular bereits in ProjektpartnerRegistrationSubmitter vorhanden. 3-stufiges Multi-Step-Formular mit optionalen Schritten muss geprüft werden - in Drupal ggf. Webform oder Custom-Form-API. Netzwerk-Felder (GULP, Freelancermap etc.) und Magazine-Opt-in müssen abgedeckt sein.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Fusion-Plugin delegiert an AccountController::registerFormAction
  • Template Resources/Private/Templates/Account/RegisterForm.html rendert 3-stufiges Registrierungsformular
  • Stufe 1 (Pflicht): Persönliche Daten (Anrede, Titel, Vorname, Nachname, Geburtsdatum, Firma), Kontaktdaten (Telefon, Mobil, E-Mail, Website, Adresse), Dokument-Upload (bis zu 5 Dateien, PDF/DOCX/DOC, max. 5MB), Passwort + Bestätigung, Datenschutz-Checkbox (policy), Nutzungsbedingungen-Checkbox (policy2), optionaler Newsletter (magazine)
  • Stufe 2 (optional): Einsatzorte (DE/AT/CH/International/Alle), Stundensatz, Verfügbarkeitsdatum, Netzwerke (GULP-ID, Freelance.de, XING, Freelancermap)
  • Stufe 3 (optional): Sprachen (Mehrfachauswahl), Skills (Freitext, max. 500Z), Bewerbungstext (max. 2000Z)
  • Bereits eingeloggte Nutzer sehen Fehlermeldung 'Sie sind bereits angemeldet'
  • Nach Submit: AccountController::registerAction, Ergebnis in Template Account/Register.html: Status 'ok' (Aktivierungsmail gesendet) oder 'emailInUse'
Domain-Objekte
  • RegistrationFlow
  • ProjektpartnerUser
  • PersistentResource
Side-Effects
  • DB-Write: RegistrationFlow-Entity; Datei-Upload: Bewerbungsunterlagen; Mail: Aktivierungsmail an Nutzer
DateienResources/Private/Fusion/Content/Portal/Account/Register.fusion, Resources/Private/Templates/Account/RegisterForm.html, Resources/Private/Templates/Account/Register.html
Account-Aktivierung Rendering (Portal.Account.Activation) HTTP-GET auf Aktivierungs-URL mit Token, Neos-Plugin Contenance.Solcom:Portal.Account.Activation Contenance.Solcom:Portal.Account.Activation → AccountController::activationAction Öffentlich (tokenbasiert) Drupal: Aktivierungsflow bereits im solcom_projektpartner-Modul vorhanden. Die drei redaktionellen Slots (tokenOk/tokenExpire/tokenWrong als ContentCollections) entsprechen in Drupal ggf. konfigurierbaren Texten im Modul-Settings oder Block-Konfiguration.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Fusion-Plugin mit drei ContentCollection-Slots: tokenOk, tokenExpire, tokenWrong (im Neos-Backend redaktionell befüllbar)
  • Controller prüft Token-Status: 1=gültig, 2=falsch, 3=abgelaufen, 4=Account existiert bereits
  • Template Resources/Private/Templates/Account/Activation.html rendert entsprechende ContentCollection per f:format.htmlentitiesDecode
  • Bei abgelaufenem Token (3): Link 'Neuen Aktivierungslink generieren' (AccountController::reActivationAction)
  • Bei bereits existierendem Account (4): Hinweismeldung mit Link zu Passwort-Vergessen
Domain-Objekte
  • RegistrationFlow
  • Account
Side-Effects
  • DB-Write: Account-Aktivierung; Mail: ggf. neue Aktivierungsmail bei reActivation
DateienResources/Private/Fusion/Content/Portal/Account/Activation.fusion, Resources/Private/Templates/Account/Activation.html
E-Mail-Aktivierung nach Account-E-Mail-Änderung (Portal.Account.ActivationChangeAccountEmail) HTTP-GET auf Bestätigungs-URL mit Token nach E-Mail-Änderung Contenance.Solcom:Portal.Account.ActivationChangeAccountEmail → AccountController::activationChangeAccountEmailAction Öffentlich (tokenbasiert) Drupal: E-Mail-Änderungs-Bestätigung muss implementiert werden; noch nicht im solcom_projektpartner-Modul vorhanden laut Analyse.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Analog zur Account-Aktivierung: drei ContentCollection-Slots tokenOk, tokenExpire, tokenWrong
  • Controller validiert Token und ändert Account-E-Mail wenn gültig
  • Template Resources/Private/Templates/Account/ActivationChangeAccountEmail.html
Domain-Objekte
  • AccountChange
  • Account
Side-Effects
  • DB-Write: Account-E-Mail-Aktualisierung
DateienResources/Private/Fusion/Content/Portal/Account/ActivationChangeAccountEmail.fusion, Resources/Private/Templates/Account/ActivationChangeAccountEmail.html
Passwort-Vergessen Rendering (Portal.Account.PwForgotten) HTTP-GET auf /projektportal/passwort-vergessen, Neos-Plugin Contenance.Solcom:Portal.Account.PwForgotten Contenance.Solcom:Portal.Account.PwForgotten → AccountController::passwordForgottenAction Öffentlich (Gast) Drupal: Passwort-Vergessen-Flow bereits im solcom_projektpartner-Modul vorhanden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Controller stellt E-Mail-Eingabeformular bereit
  • Template Resources/Private/Templates/Account/PasswordForgotten.html
  • Nach Submit: AccountController sendet Passwort-Reset-E-Mail; Template PasswordForgottenSend.html zeigt Bestätigung
Domain-Objekte
  • Account
  • PasswordResetToken
Side-Effects
  • DB-Read: Account-Lookup; Mail: Passwort-Reset-Mail an Nutzer (Template Mail/Account/PasswordForgotten.html)
DateienResources/Private/Fusion/Content/Portal/Account/PwForgotten.fusion, Resources/Private/Templates/Account/PasswordForgotten.html, Resources/Private/Templates/Account/PasswordForgottenSend.html, Resources/Private/Templates/Mail/Account/PasswordForgotten.html
Passwort-Reset Rendering (Portal.Account.PwReset) HTTP-GET auf Passwort-Reset-URL mit Token Contenance.Solcom:Portal.Account.PwReset → AccountController::passwordResetAction Öffentlich (tokenbasiert) Drupal: Passwort-Reset bereits im solcom_projektpartner-Modul vorhanden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Controller validiert Reset-Token
  • Template Resources/Private/Templates/Account/PasswordReset.html zeigt neues-Passwort-Formular
  • Nach Submit: Passwort wird geändert; Template PasswordResetSend.html zeigt Bestätigung
Domain-Objekte
  • Account
  • PasswordResetToken
Side-Effects
  • DB-Write: Passwort-Hash-Aktualisierung
DateienResources/Private/Fusion/Content/Portal/Account/PwReset.fusion, Resources/Private/Templates/Account/PasswordReset.html, Resources/Private/Templates/Account/PasswordResetSend.html
Profil-Dashboard Rendering (Portal.Account.Profile) HTTP-GET auf /projektportal/mein-profil, Neos-Plugin Contenance.Solcom:Portal.Account.Profile Contenance.Solcom:Portal.Account.Profile → AccountController::profileAction Eingeloggter Projektpartner (Auth-Pflicht, implizit durch Flow Security) Drupal: Profil-Seite muss als Dashboard zusammengestellt werden. Sektionen 'Meine Daten', 'Mein Account', 'Meine Dokumente' und 'Mein Projekt Alert' fehlen noch. Download über /protecteduserfile entspricht dem privaten Dokument-Download-Flow. Modal-basierte Löschbestätigung muss im Drupal-Theme nachgebaut werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Controller lädt vollständige Nutzerdaten: Stammdaten, Kontakt, Verfügbarkeit & Qualifikation, Dokumente, ProjectAlerts, mailInMailingworks-Status
  • Template Resources/Private/Templates/Account/Profile.html rendert Dashboard mit mehreren Sektionen:
  • Sektion 'Meine Daten': Anzeige aller Profil-Felder + Button 'Bearbeiten' (UUID 9874a65a...)
  • Sektion 'Mein Account': Login-E-Mail, Passwort (maskiert), Erstelldatum, letztes Änderungsdatum + Button 'Bearbeiten' (UUID 0bf2f019...)
  • Sektion 'Meine Dokumente': Upload-Feld (AJAX) + Liste vorhandener Dokumente mit Lösch-Button und Download-Link (/protecteduserfile?file=SHA1)
  • Sektion 'Mein Projekt Alert': Anzeige aktiver Alert-Konfiguration (Tags, Ausschluss-Tags, Regionen) + Bearbeiten/Erstellen-Link + Löschen-Button (Featherlight-Modal)
  • Sektion 'SOLCOM Freiberufler Magazin': Abo-Status mit An-/Abmelden-Link (addMagazine/removeMagazine)
  • Sektion 'Nutzerkonto Löschen': Lösch-Bestätigung per Featherlight-Modal → AccountController::deleteAction
  • Statusmeldungen: AuthSuccess, profileChanged, accountChangedEmail, accountChangedPassword etc.
  • Linke Spalte (ausgeblendet auf kleinen Breakpoints): SidebarSearch
Domain-Objekte
  • ProjektpartnerUser
  • ProjectAlert
  • PersistentResource
  • Account
Side-Effects
  • DB-Read: Nutzerdaten, ProjectAlerts, UserFiles; Mailingwork-API-Read für Newsletter-Status
DateienResources/Private/Fusion/Content/Portal/Account/Profile.fusion, Resources/Private/Templates/Account/Profile.html
Profil bearbeiten Rendering (Portal.Account.EditProfile) HTTP-GET auf Profil-bearbeiten-Seite, Neos-Plugin Contenance.Solcom:Portal.Account.EditProfile Contenance.Solcom:Portal.Account.EditProfile → AccountController::editProfileAction Eingeloggter Projektpartner Drupal: Profil-Bearbeitung muss als Edit-Form des Projektpartner-Profil-Entities implementiert werden. Lücke vorhanden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Controller lädt bestehende Profildaten und Optionslisten (Länder, Sprachen)
  • Template Resources/Private/Templates/Account/EditProfile.html rendert vorausgefülltes Formular (identische Struktur wie Registrierung, aber mit user.*-Werten)
  • 3-stufig: Stammdaten/Kontakt/Dokumente, Verfügbarkeit/Netzwerke, Qualifikationen
  • Nach Submit: AccountController speichert Änderungen, leitet auf Profil zurück mit Status 'profileChanged'
Domain-Objekte
  • ProjektpartnerUser
  • PersistentResource
Side-Effects
  • DB-Write: Nutzer-Stammdaten; DB-Write: Dateien
DateienResources/Private/Fusion/Content/Portal/Account/EditProfile.fusion, Resources/Private/Templates/Account/EditProfile.html
Account-Zugangsdaten bearbeiten Rendering (Portal.Account.EditAccount) HTTP-GET auf Account-bearbeiten-Seite, Neos-Plugin Contenance.Solcom:Portal.Account.EditAccount Contenance.Solcom:Portal.Account.EditAccount → AccountController::editAccountAction Eingeloggter Projektpartner Drupal: Passwort-Änderung über Drupal-Core. E-Mail-Änderung mit Double-Opt-In-Bestätigung fehlt noch.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Controller stellt Formular für Login-E-Mail und Passwort bereit
  • Template Resources/Private/Templates/Account/EditAccount.html
  • Bei E-Mail-Änderung: Bestätigungsmail an neue Adresse (ActivationChangeAccountEmail-Flow)
  • Bei Passwort-Änderung: Sofortige Wirksamkeit
  • Redirect auf Profil mit Status 'accountChangedEmail', 'accountChangedPassword' oder 'accountChangedPasswordAndEmail'
Domain-Objekte
  • Account
  • AccountChange
Side-Effects
  • DB-Write: Account-Identifier und/oder Passwort; Mail bei E-Mail-Änderung
DateienResources/Private/Fusion/Content/Portal/Account/EditAccount.fusion, Resources/Private/Templates/Account/EditAccount.html
Projekt-Bewerbungsformular Rendering (Portal.Project.JobApplication) HTTP-GET auf Bewerbungsseite mit project-Argument, Neos-Plugin Contenance.Solcom:Portal.Project.JobApplication Contenance.Solcom:Portal.Project.JobApplication → ProjectController::jobapplicationAction Gast (ohne Profil-Prefill) und eingeloggter Projektpartner (mit Profil-Prefill) Drupal: Bewerbungsformular muss noch als zentrales Portal-Feature implementiert werden. Komplexes 3-Stufen-Formular mit AJAX-Datei-Upload, Profil-Prefill, Gast/User-Branching, Mehrfach-Projekt-Bewerbung aus Merkliste. Lücke: containerUser/containerGuest als redaktionelle Slots (ContentCollections) haben kein Drupal-Äquivalent.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Fusion-Plugin mit zwei ContentCollection-Slots: containerUser (für eingeloggte Nutzer) und containerGuest (für Gäste)
  • Controller prüft Auth-Status; befüllt projects-Liste, user-Daten, userUploads, countryOptions, languageOptions
  • Template Resources/Private/Templates/Project/JobApplication.html zeigt unterschiedliche UI je nach Login-Status:
  • Gast-Ansicht: Auswahlbox 'Als Gast bewerben oder registrieren' vs. 'Mit bestehendem Account anmelden' (mit Inline-Login-Form)
  • Nach Gast-Auswahl: Vollständiges Formular ohne vorausgefüllte Daten
  • Eingeloggte Nutzer: Vollständiges Formular mit vorausgefüllten Nutzerdaten (user.*)
  • 3-stufig: Kontakt/Unterlagen, Verfügbarkeit (optional), Qualifikationen (optional)
  • Dokument-Upload per Drag-and-Drop oder Browse (CSRF-Token), max. 4 Dokumente
  • Projektdaten als Hidden-Fields (projects[N][title/identifier/projectuuid])
  • Profile-Change-Abfrage: Modal wenn Formular-Daten von Profildaten abweichen
  • Form-Action: ProjectController::jobApplicationSendAction
  • Bei Bewerbung aus Merkliste: mehrere Projekte gleichzeitig
Domain-Objekte
  • Project
  • ProjektpartnerUser
  • PersistentResource
  • JobApplication
Side-Effects
  • DB-Write: Bewerbungsdaten; Datei-Upload: Bewerbungsunterlagen; Mail: Bewerbungsbestätigung an Nutzer + interne Mail
DateienResources/Private/Fusion/Content/Portal/Project/JobApplication.fusion, Resources/Private/Templates/Project/JobApplication.html, Resources/Private/Templates/Project/JobApplicationSend.html, Resources/Private/Templates/Project/JobApplicationSuccess.html
Verfügbarkeits-Update Rendering (Portal.Availability) HTTP-GET auf Verfügbarkeits-URL mit availability-Token (aus E-Mail-Link), Neos-Plugin Contenance.Solcom:Portal.Availability Contenance.Solcom:Portal.Availability → AvailabilityController::indexAction Öffentlich (tokenbasiert, Link aus E-Mail) Drupal: Verfügbarkeits-Update-Flow fehlt noch. In Drupal als tokenbasierter Controller-Flow mit Formular umsetzbar; kein direktes Äquivalent vorhanden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Fusion-Plugin mit container-ContentCollection (redaktioneller Slot)
  • Controller validiert Token (availability.status=1 = gültig), lädt formValues
  • Template Resources/Private/Templates/Availability/Index.html zeigt Formular mit: Verfügbarkeitsdatum (Pflicht), Dokument-Upload (bis zu 5 Dateien), Freitext-Nachricht, Datenschutz-Checkbox + Nutzungsbedingungen-Checkbox
  • Bei ungültigem Token: Fehlermeldung 'Link nicht mehr gültig'
  • Form-Action: AvailabilityController::sendAction
Domain-Objekte
  • Availability
  • ProjektpartnerUser
  • PersistentResource
Side-Effects
  • DB-Write: Verfügbarkeitsdaten des Nutzers; Datei-Upload; Mail: Bestätigung an Nutzer, Benachrichtigung an Agent und SOLCOM (Mail/Availability/Partner.html, Agent.html, Solcom.html)
DateienResources/Private/Fusion/Content/Portal/Availability.fusion, Resources/Private/Templates/Availability/Index.html, Resources/Private/Templates/Availability/FormResult.html, Resources/Private/Templates/Mail/Availability/Agent.html, Resources/Private/Templates/Mail/Availability/Confirmation.html, Resources/Private/Templates/Mail/Availability/Partner.html, Resources/Private/Templates/Mail/Availability/Solcom.html
Passwortschutz-Overlay für beliebige Seiten (Helper.ProtectContent) Neos-ContentElement Contenance.Solcom:Helper.ProtectContent als Wrapper um geschützten Inhalt prototype(Contenance.Solcom:Helper.ProtectContent) Passwort-geschützt (konfiguriertes Seitenpasswort, kein User-Login) Drupal: Kein direktes Äquivalent. In Drupal kann ein Password-Protect-Modul oder Custom-Access-Check verwendet werden. Für das Projektpartner-Portal eher nicht relevant (Portal-Seiten sind nicht passwortgeschützt).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Fusion-Prototype prüft ob configuredPassword am Node gesetzt ist (String.length > 0)
  • Im Neos-Backend: Inhalt immer sichtbar
  • Kein oder falsches Passwort (request.arguments.passwd): Passwort-Eingabeformular wird gerendert (GET-Parameter 'passwd')
  • Korrektes Passwort: Inhalt wird direkt gerendert
  • Cache-Discriminator: request.arguments.passwd (entryDiscriminator)
Domain-Objekte
Side-Effects
DateienResources/Private/Fusion/Components/Helper/ProtectContent.fusion
Technologie-Liste Rendering (Portal.Project.Technologies) Neos-Plugin Contenance.Solcom:Portal.Project.Technologies auf Technologie-Übersichtsseite Contenance.Solcom:Portal.Project.Technologies → ProjectController::technologiesAction Öffentlich Drupal: Technologie-Übersicht kann als View über Taxonomy-Terme oder dedizierte Node-Typen umgesetzt werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Controller lädt alle Technologie-Nodes (Typ Contenance.Solcom:Document.Portal.Technology)
  • Template Resources/Private/Templates/Project/Technologies.html rendert Technologie-Linkliste
Domain-Objekte
  • Contenance.Solcom:Document.Portal.Technology
Side-Effects
  • DB-Read: Neos-NodeTree für Technologie-Nodes
DateienResources/Private/Fusion/Content/Portal/Project/Technologies.fusion, Resources/Private/Templates/Project/Technologies.html
Branchen-Liste Rendering (Portal.Project.Businesses) Neos-Plugin Contenance.Solcom:Portal.Project.Businesses auf Branchen-Übersichtsseite Contenance.Solcom:Portal.Project.Businesses → ProjectController::businessesAction Öffentlich Drupal: Branchen-Übersicht analog zur Technologie-Liste umsetzbar.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Controller lädt alle Business-Nodes (Typ Contenance.Solcom:Portal.Project.Business)
  • Template Resources/Private/Templates/Project/Businesses.html rendert Branchen-Linkliste
Domain-Objekte
  • Contenance.Solcom:Portal.Project.Business
Side-Effects
  • DB-Read: Neos-NodeTree für Business-Nodes
DateienResources/Private/Fusion/Content/Portal/Project/Businesses.fusion, Resources/Private/Templates/Project/Businesses.html

Karriere / Personio Peripher 11 Flows

FlowAuslöserEntrypointZugriffDrupal-Nachbau
Personio-Sync (Cron-Import) CLI-Command: `./flow personio:import` (Cron) PersonioCommandController::importCommand Kein Webzugriff; nur über CLI (Flow-Command-Controller); kein Security-Context Drupal-Entsprechung: Migrate-Sync-Modul (analog solcom_project_offer) mit Quelle Personio XML-Feed. Lücke: Neos erstellt pro Stelle auch einen CMS-Knoten (eigene URL, SEO-Properties); in Drupal muss dafür ein 'job_offer'-Knoten-Typ mit automatisch generiertem URL-Alias (PathaAuto) angelegt werden. Aktuell im Feature-Branch (P2503-226) noch nicht implementiert.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • PersonioService::getPersonioProjects() ruft die Personio XML-Feed-URL (Settings: personio.xml) per cURL ab
  • XML-Response wird via SimpleXML geparst; jede <position> wird in ein assoziatives Array (id, office, department, recruitingCategory, name, subcompany, jobDescriptions, employmentType, seniority, schedule, yearsOfExperience, occupation, occupationCategory, createdAt) umgewandelt
  • Für jede importierte Stelle: PersonioService::findPersonioProject() prüft, ob ein PersonioProject-DB-Eintrag mit der Personio-ID bereits existiert
  • Existiert der Eintrag nicht: createPersonioProject() legt ein neues PersonioProject-Doctrine-Entity an, ruft PersonioService::createNode() auf (erstellt einen Neos-CMS-Dokumentknoten vom Typ Contenance.Solcom:Document.Career.Personio.Project im konfigurierten personioCollectionNode, setzt title, titleOverride, metaDescription, uriPathSegment, projectid, fügt Teaser- und Detail-Content-Knoten ein), speichert Knoten-UUID im Entity, persistiert über PersonioProjectRepository::add()
  • Existiert der Eintrag bereits: modifyPersonioProject() vergleicht alle Felder via JSON-Stringvergleich; bei Abweichung wird das Entity aktualisiert und PersonioService::updateNode() synchronisiert title, titleOverride, metaDescription, uriPathSegment des Neos-Knotens
  • Nach Verarbeitung aller API-Stellen: Alle in der DB vorhandenen PersonioProject-Entities werden mit den importierten IDs abgeglichen; fehlende Einträge (Stelle wurde in Personio gelöscht) werden via removePersonioProjectAndNode() entfernt (Neos-Knoten + DB-Entity)
  • Ausgabe von Zählern (Added / Modified / Removed / Not touched) auf der Konsole
Domain-Objekte
Side-Effects
  • Personio XML-API (GET, öffentlich)
  • DB-Writes: PersonioProject-Tabelle (INSERT / UPDATE / DELETE)
  • Neos Content Repository: Dokumentknoten erstellen / aktualisieren / löschen (workspace: live)
  • ObjectPathMappingRepository: URI-Pfad-Mappings werden beim pruneCommand entfernt
  • Log-Datei: SOLCOM_Personio.log
DateienClasses/Command/PersonioCommandController.php, Classes/Domain/Service/Career/PersonioService.php, Classes/Domain/Model/PersonioProject.php, Classes/Domain/Repository/PersonioProjectRepository.php, Configuration/NodeTypes.Document.Career.Personio.Project.yaml, Configuration/Settings.Flow.yaml
Personio-Prune (Alle Stellen löschen) CLI-Command: `./flow personio:prune` PersonioCommandController::pruneCommand Nur CLI Drupal-Entsprechung: `drush migrate:rollback personio_jobs` oder eigener Drush-Command. Im aktuellen Stand nicht explizit als Drush-Command vorhanden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • PersonioService::removeAllPersonioProjects() wird aufgerufen
  • Alle PersonioProject-Entities werden aus der DB gelöscht (PersonioProjectRepository::findAll() + remove())
  • Alle Neos-Dokumentknoten vom Typ Contenance.Solcom:Document.Career.Personio.Project unterhalb des personioCollectionNode werden entfernt (mit persistAll() nach jedem Knoten)
  • Alle ObjectPathMapping-Einträge für den Typ PersonioProject werden gelöscht (URI-Routing-Cleanup)
  • Konsolenausgabe: Erfolgsbestätigung
Domain-Objekte
Side-Effects
  • DB-Writes: Vollständige Löschung der PersonioProject-Tabelle
  • Neos Content Repository: Alle Personio-Dokumentknoten gelöscht (workspace: live)
  • ObjectPathMappingRepository: Alle URI-Mappings für PersonioProject entfernt
DateienClasses/Command/PersonioCommandController.php, Classes/Domain/Service/Career/PersonioService.php
Karriere-Stellen-Listing (Index / Kategorie-Seiten) HTTP GET auf Neos-Seite mit eingebettetem Plugin Contenance.Solcom:Career.Personio.Index CareerPersonioController::indexAction Gast (öffentlich, Karriere-Seite) Drupal-Entsprechung: Views-basiertes Listing mit Exposed Filters (department, office/location, seniority) auf dem job_offer-Inhaltstyp. Besonderheit: standortspezifischer Pre-Filter über Knoten-Property muss als URL-Parameter oder Block-Kontext übergeben werden. Im Drupal-Nachbau noch keine dedizierte Listing-View für Personio-Jobs vorhanden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Request-Argumente aus den Plugin-Namespaces '--contenance_solcom-career_personio_mainsearch' und '--contenance_solcom-career_personio_index' werden ausgelesen (department, location, level, findAll)
  • Optionaler 'predefined'-Parameter aus URI-Query überschreibt den location-Filter
  • Optionaler 'location'-Property am Neos-Knoten (NodeType-Property) überschreibt den request-basierten location-Filter (für standortspezifische Kategorie-Seiten)
  • PersonioProjectRepository::findByDepartmentAndLocationAndLevel() wird mit den ermittelten Filtern aufgerufen (LIKE-Queries auf department, office, seniority)
  • Ohne Filter und ohne gesetztes Argument: Alle Stellen werden geladen, Departments-Liste für Kategorie-Navigation wird aufgebaut
  • searchResultNode wird über FlowQuery (UUID aus Settings: personio.searchResultPage) aus dem Live-Kontext ermittelt und der View übergeben
  • View rendert Stellenliste mit Zähler
Domain-Objekte
Side-Effects
  • DB-Read: PersonioProject-Tabelle
  • Neos Content Repository Read (für searchResultNode)
DateienClasses/Controller/CareerPersonioController.php, Classes/Domain/Repository/PersonioProjectRepository.php, Resources/Private/Fusion/Content/Career/Personio/Index.fusion, Resources/Private/Templates/CareerPersonio/Index.html
Karriere-Stellen-Hauptsuche (MainSearch-Widget) HTTP GET/POST auf Seite mit eingebettetem Plugin Contenance.Solcom:Career.Personio.MainSearch CareerPersonioController::mainSearchAction Gast (öffentlich, Karriere-Seite) Drupal-Entsprechung: Exposed Filter Block einer Job-Offer-View oder ein Custom Block mit aggregierten Filterlisten. Besonderheit: Seniority-Level-Mapping (en → de) muss in Drupal als field_formatter oder hook repliziert werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Alle PersonioProject-Entities werden geladen (findAll())
  • Aus den Entities werden distinct-Listen für Departments, Standorte (office) und Seniority-Level aggregiert
  • Seniority-Werte werden via PersonioUtilityService::careerLevelMapping() auf deutsche Labels gemappt
  • Listen werden alphabetisch sortiert und der View übergeben
  • Wenn searchArguments übermittelt werden (location, level, department), werden die aktuell selektierten Filterwerte als 'selected'-Variablen in die View gegeben
  • searchResultNode (für Form-Submit-Ziel) wird über UUID aus Settings ermittelt
  • Gesamtzahl aller Stellen (countAll()) wird als projectCounter übergeben
Domain-Objekte
Side-Effects
  • DB-Read: PersonioProject-Tabelle (findAll + countAll)
DateienClasses/Controller/CareerPersonioController.php, Resources/Private/Fusion/Content/Career/Personio/MainSearch.fusion, Resources/Private/Templates/CareerPersonio/MainSearch.html
Karriere-Stellen-Detailseite HTTP GET auf Neos-Dokumentknoten vom Typ Contenance.Solcom:Document.Career.Personio.Project (URL-Segment enthält Personio-ID, z.B. /karriere/stellentitel-12345) CareerPersonioController::detailAction Gast (öffentlich, Karriere-Seite) Drupal-Entsprechung: Node-Template für job_offer-Typ. Besonderheit: standortspezifische Content-Blöcke (7 Standorte hardcoded) müssen als Block-Visibility-Conditions (node field_office) oder als Paragraphs/Layout-Builder-Blocks abgebildet werden. Zufällige verwandte Stellen könnten als View-Block mit DISTINCT/RAND-Order realisiert werden. Lücke: cityContent-Mechanismus ist in Drupal noch nicht geplant.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Dokumentknoten wird aus __documentNode-Argument ermittelt; die Node-Property 'projectid' (UUID des PersonioProject-Entities) wird ausgelesen
  • PersonioProject-Entity wird via PersonioProjectRepository::findByIdentifier() geladen
  • 3 zufällige Verwandte Stellen aus demselben Department werden via PersonioProjectRepository::findByDepartment() ermittelt (rand mit array_rand)
  • Standort (office) wird normalisiert (strtolower/trim); je nach Standort wird ein Neos-ContentCollection-Knoten mit standortspezifischem Inhalt (Reutlingen, Essen, Hamburg, Karlsruhe, München, Stuttgart, Berlin) als 'cityContent' in die View gegeben (UUID-Mapping aus Settings: personio.locations.*)
  • Seiten-UUIDs für searchResultPage, growWithTheBestPage und careerJobApplicationPage werden aus Settings geladen
  • PersonioUtilityService::salutationByDescriptionIsFormal() analysiert Beschreibungstext (Häufigkeit formeller vs. informeller Anredeformen) und setzt 'salutationNotFormal'-Flag
  • View rendert Detailseite mit Stelle, Anzeigen verwandter Stellen und standortspezifischem Content-Block
Domain-Objekte
Side-Effects
  • DB-Read: PersonioProject-Tabelle
  • Neos Content Repository Read (standortspezifische ContentCollections)
DateienClasses/Controller/CareerPersonioController.php, Classes/Domain/Service/Career/PersonioUtilityService.php, Resources/Private/Fusion/Content/Career/Personio/Detail.fusion, Resources/Private/Templates/CareerPersonio/Detail.html, Configuration/NodeTypes.Document.Career.Personio.Project.yaml
Karriere-Bewerbungsformular anzeigen HTTP GET auf Route '{node}_{--contenance_solcom-careerjobapplication.careerproject}' (URL-Pattern: /stelle/{uriPathSegment}_{personioId}); Route in Configuration/Routes.yaml CareerPersonioController::careerJobApplicationAction Gast (öffentlich, Karriere-Seite) Drupal-Entsprechung: Webform-Node oder custom Form-Plugin auf einer Job-Application-Seite. Besonderheit: Das Formular ist an eine konkrete Stelle (PersonioProject) gebunden und wird über URL-Parameter aufgerufen. Lücke: Kein entsprechendes Webform mit dynamischer Stellen-Bindung in Drupal vorhanden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Im Neos-Backend (inNeosBackend()): Formular wird ohne Stellen-Kontext gerendert (leeres project)
  • Plugin-Argumente werden aus mehreren möglichen Namespaces ausgelesen (contenance_solcom-careerjobapplication, contenance_solcom-career_personio_careerjobapplication, älterer 'project'-Key) um die PersonioProject-UUID zu ermitteln
  • Falls keine UUID via Argument vorhanden: URL wird per RegEx auf bewerbung_{id} geparst und PersonioProjectRepository::findById() aufgerufen
  • PersonioProject-Entity wird geladen; falls nicht gefunden: Redirect auf '/de/stelle-wird-nicht-mehr-besetzt'
  • Live-Prüfung gegen Personio-API (personioProjectIsExisting) ist auskommentiert (TODO-Kommentar im Code)
  • Dokument-Typen-Liste (Lebenslauf, Anschreiben, Arbeitszeugnis, Gesamte Bewerbungsunterlagen) wird für Upload-Felder zusammengestellt
  • Policy-UUID (Datenschutz-Node) wird aus Settings geladen
  • PersonioUtilityService::salutationByDescriptionIsFormal() setzt Anrede-Flag
  • View rendert Bewerbungsformular mit Upload-Feldern (max. 3), Pflichtfeldern (policy, gender, firstname, lastname, email, phone), optionalem Startdatum und Gehaltsvorstellung
Domain-Objekte
Side-Effects
  • DB-Read: PersonioProject-Tabelle
DateienClasses/Controller/CareerPersonioController.php, Configuration/Routes.yaml, Resources/Private/Templates/CareerPersonio/CareerJobApplication.html, Resources/Private/Fusion/Content/Career/Personio/CareerJobApplication.fusion
Karriere-Bewerbung absenden (Personio API Submit) HTTP POST auf Bewerbungsformular (Action jobApplicationSend); Form-Submit aus careerJobApplicationAction-Template CareerPersonioController::jobApplicationSendAction Gast (öffentlich, Karriere-Seite); kein Login erforderlich Drupal-Nachbau (Lücke): Diese Kernfunktion ist noch nicht implementiert. Benötigt: Webform mit File-Upload-Feldern (max. 3), Custom Webform Handler für Personio-API-Submit (POST /v1/recruiting/applications + vorangestellter File-Upload), Cookie-basierte Herkunfts-/Kanal-Erkennung, Google Measurement Protocol Integration. Achtung: Der Personio Recruiting API Key (recruitingApiKey) und X-Company-ID (78231) müssen als Drupal-Settings konfiguriert werden. Hinweis aus Memory: Personio Public API ist für Recruiting read-only; POST erfordert den Multiposting-Scope (recruitingApiKey).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • POST-Daten aus $_POST['--contenance_solcom-career_personio_careerjobapplication'] werden ausgelesen
  • Pflichtfelder-Validierung (policy, gender, firstname, lastname, email, phone) via isFieldInvalid(); bei Fehler: Redirect zurück mit errorId 1696925263
  • Startdatum (inputAdmissionDate) wird validiert (Format dd.mm.YYYY); bei Fehler: Redirect mit errorId 1696940976
  • Formularwerte werden in Personio-API-Datenstruktur überführt: job_position_id, first_name, last_name, email, application_date, attributes (gender, phone, available_from, salary_expectations)
  • Mindestens ein Upload-Dokument (Lebenslauf, Anschreiben, Arbeitszeugnis oder Gesamte Bewerbungsunterlagen) wird geprüft; fehlt es: Redirect mit errorId 1696925319
  • Bis zu 3 Datei-Uploads werden über handleUploads() verarbeitet: TemporaryFile-Entities werden aus temporaryFileRepository geladen, jede Datei via PersonioService::personioApiUploadFile() (POST https://api.personio.de/v1/recruiting/applications/documents, Bearer-Token, X-Personio-App-ID: SOLCOM_CAREER_PORTAL, X-Company-ID: 78231) zu Personio hochgeladen; bei Fehler: Redirect mit errorId 1696925382
  • Upload-UUIDs werden mit Kategorie (cv / cover-letter / certificate / other) in den Application-Payload übernommen
  • Talentpool-Zustimmung (policy2) wird als custom_attribute_2722696 (Ja/Nein) ergänzt
  • Herkunfts-Cookie ('info', base64-kodierte URL) wird ausgelesen und als custom_attribute_2722693 (Ursprung) übertragen; fehlt der Cookie: Wert 'DIREKTER AUFRUF'
  • Kanal-Erkennung: Domain aus Herkunfts-URL wird gegen eine Liste von >35 bekannten Job-Portalen (StepStone, Indeed, LinkedIn, Xing, Google, Bing etc.) gemappt und als custom_attribute_2722695 (Kanal) übertragen
  • Vollständiger Application-Payload wird via PersonioService::personioApplication() (POST https://api.personio.de/v1/recruiting/applications, Bearer-Token) an Personio übermittelt; bei Fehler: Redirect mit errorId 1696925414
  • Google Measurement Protocol: GoogleService::upCareerProjectTransactionId() inkrementiert TransactionId; wenn kein _ga-Cookie vorhanden: GoogleService::sendCareerApplicationHit() feuert Conversion-Hit
  • Redirect auf careerJobApplicationSuccess mit personioProject und transactionId
Domain-Objekte
Side-Effects
  • Personio Recruiting API (POST /v1/recruiting/applications/documents — Datei-Upload)
  • Personio Recruiting API (POST /v1/recruiting/applications — Bewerbung einreichen)
  • Google Measurement Protocol (Conversion-Hit, nur wenn kein _ga-Cookie)
  • DB-Read: TemporaryFile-Repository (für Upload-Dateien)
  • Log-Datei: SOLCOM_Personio.log (Erfolg/Fehler der Übermittlung)
DateienClasses/Controller/CareerPersonioController.php, Classes/Domain/Service/Career/PersonioService.php, Resources/Private/Templates/CareerPersonio/CareerJobApplication.html, Resources/Private/Templates/CareerPersonio/JobApplicationSend.html, Configuration/Routes.yaml
Karriere-Bewerbung Erfolgsseite Redirect nach erfolgreichem jobApplicationSendAction (intern: CareerPersonio::careerJobApplicationSuccess) CareerPersonioController::careerJobApplicationSuccessAction Gast (öffentlich, Karriere-Seite) Drupal-Entsprechung: Webform-Confirmation-Page oder Redirect-URL mit Bestätigungstext. Die transactionId für Google Analytics muss als Query-Parameter weitergegeben werden. Lücke: Noch nicht implementiert.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • PersonioProject-Objekt und transactionId werden aus Redirect-Parametern übernommen
  • PersonioUtilityService::salutationByDescriptionIsFormal() setzt Anrede-Flag für personalisierten Erfolgstext
  • View rendert Bestätigungsseite mit Stellen-Kontext (Stellentitel) und transactionId (für Google Analytics E-Commerce-Tracking)
Domain-Objekte
Side-Effects
DateienClasses/Controller/CareerPersonioController.php, Resources/Private/Templates/CareerPersonio/CareerJobApplicationSuccess.html
Standort-DataSource für Neos-Backend (Location-Dropdown) Neos-Backend Inspector lädt DataSource 'contenance-solcom-career-personio-searchresult' beim Bearbeiten eines Personio-Content-Knotens CareerPersonioLocationDataSource::getData Neos-Backend-Benutzer (Editor / Admin); DataSource wird nur im CMS-Backend aufgerufen Drupal-Entsprechung: Ein Allowed Values Callback oder ein EntityReferenceField mit dynamischen Optionen für ein 'location'-Feld auf dem Job-Offer-Typ. In Drupal wird der Standort-Filter üblicherweise als Taxonomy-Term (Standort-Vokabular) abgebildet. Diese DataSource entspricht keiner Drupal-Frontend-Funktion, sondern nur der CMS-Backend-Konfiguration.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • 7 hardcodierte Standorte (Reutlingen, Karlsruhe, Hamburg, München, Essen, Stuttgart, Berlin) werden als feste Optionen vorangestellt
  • Alle PersonioProject-Entities werden geladen (findAll())
  • Aus den Entities werden weitere einzigartige office-Werte ermittelt (die nicht bereits in der Hardcode-Liste sind) und als zusätzliche Optionen angehängt
  • Array mit label/value-Paaren wird als JSON-serialisierbare Daten zurückgegeben
Domain-Objekte
Side-Effects
  • DB-Read: PersonioProject-Tabelle
DateienClasses/DataSource/CareerPersonioLocationDataSource.php, Classes/Domain/Repository/PersonioProjectRepository.php
Personio-Stellen Eel-Helper für Fusion-Templates (getProjectNodes) Fusion-Template-Rendering: Aufruf von CareerPersonioProjects.getProjectNodes() in einem Fusion-Prototype (z.B. für XML-Feeds oder Stellen-Übersichten) CareerPersonioProjectsHelper::getProjectNodes Wird im Fusion-Rendering-Kontext aufgerufen (Frontend, öffentlich); kann auch für Backend-Previews genutzt werden Drupal-Entsprechung: EntityQuery auf node-Typ job_offer oder ein Views-DataProvider. Wird in Drupal als Service oder Views-Resultset bereitgestellt. Im Neos-Kontext dient dieser Helper primär für XML-Job-Feed-Generierung (z.B. Xing Jobs v2 Feed).
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Neos Content Repository: Live-Kontext wird erzeugt (workspaceName: live)
  • Site-Knoten '/sites/solcom-career' wird geladen
  • FlowQuery sucht alle Knoten vom Typ Contenance.Solcom:Document.Career.Personio.Project unterhalb des Site-Knotens
  • Ergebnis wird nach _index absteigend sortiert und als Array zurückgegeben
Domain-Objekte
Side-Effects
  • Neos Content Repository Read (alle Personio-Projektknoten des Career-Sites)
DateienClasses/Eel/Helper/CareerPersonioProjectsHelper.php, Resources/Private/Templates/XML/XingjobsPersonio.xml
Xing/Stellenbörsen XML-Feed (Personio Jobs) HTTP GET auf Route '{node}<xing-jobs-v2>' (URL-Suffix: <xing-jobs-v2>, Format: xml.xing-jobs-v2); konfiguriert in Configuration/Settings.Flow.yaml und Routes.yaml Neos.Neos:Frontend\Node::showAction (Neos CMS Frontend-Rendering mit Format xml.xing-jobs-v2) — Template: Resources/Private/Templates/XML/XingjobsPersonio.xml Gast (öffentlich, kein Login); XML-Feed wird von externen Job-Portalen (Xing, StepStone, Indeed etc.) gecrawlt Drupal-Entsprechung: Custom XML-Feed-View oder eigenes Feed-Modul (z.B. via Views XML output oder REST-Export). Lücke: Im aktuellen Drupal-Nachbau noch kein Job-XML-Feed für Stellenbörsen vorhanden. Achtung: Es gibt viele XML-Feeds (indeed, kununu, jobrapido, trovit, projektwerk etc.) für den alten Projektpartner-Teil — diese sind von den Personio-Karriere-Feeds zu trennen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Neos rendert die Site-Node mit Format 'xml.xing-jobs-v2' (Views.yaml-Mapping auf XingjobsPersonio.xml-Template)
  • Template iteriert über alle Stellen-Knoten (via {items}-Variable, die über CareerPersonioProjectsHelper oder Node-Collection bereitgestellt wird)
  • Für jeden Knoten mit gesetzter projectid-Property werden Personio-Felder via Fluid-ViewHelper (solcom:careerPersonioProperty, solcom:careerPersonioIdDescription) ausgelesen
  • XML-Feed wird mit Feldern id, job_title, description (mit HTML-Beschreibungsblöcken), location, company, job_type, url und weiteren Stellenbörsen-Pflichtfeldern aufgebaut
  • Video-Content-Erkennung: solcom:careerPersonioIdVideoContent bestimmt, ob die Stelle primär aus einem Video-Embed besteht (andere Beschreibungsstruktur)
Domain-Objekte
Side-Effects
  • DB-Read: PersonioProject-Tabelle (via ViewHelper)
  • Neos Content Repository Read
DateienResources/Private/Templates/XML/XingjobsPersonio.xml, Classes/Eel/Helper/CareerPersonioProjectsHelper.php, Configuration/Routes.yaml, Configuration/Settings.Flow.yaml

Peripher — Donation, Sommerfest, RSS, API, ACP Peripher 29 Flows

FlowAuslöserEntrypointZugriffDrupal-Nachbau
Spendenaktion: Abstimmung per E-Mail-Link (einfach) URL GET /de/spendenaktion/in/{identifier} — personalisierter Link aus der Start-E-Mail (mailStatus=0→1) DonationController::indexAction Gast (kein Login erforderlich, UUID-Link als impliziter Token) OUT OF SCOPE für Drupal-Nachbau. Einmalige Weihnachtsspendenaktion 2021, hardcodiertes Enddatum 2021-12-24. Kein Äquivalent im Projektpartnerportal vorgesehen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Donation-Entity per UUID-Identifier aus DonationRepository laden
  • Wenn Donation existiert und donationStatus = false: donationStatus auf true setzen, spendDate auf now setzen, persistieren
  • Redirect zu /de/spendenaktion (UTM-parametrisiert)
  • Wenn Donation bereits abgestimmt oder nicht gefunden: ebenfalls Redirect zu /de/spendenaktion (ohne UTM)
Domain-Objekte
Side-Effects
  • DB-Write: Donation.donationStatus = true, Donation.spendDate = now
DateienClasses/Controller/DonationController.php, Classes/Domain/Model/Donation.php, Resources/Private/Templates/Donation/Index.html, Configuration/Routes.yaml
Spendenaktion: Organisations-Auswahl und Abstimmung (AJAX) URL POST /spendenaktion/{donationid}/organisation/{organisationid} — JS-Aufruf aus der Organisation-Auswahlseite DonationController::checkDonationAction Gast (UUID-basierter Zugang) OUT OF SCOPE. Spendenaktion-spezifisch, kein Pendant im Projektpartnerportal.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Beide UUIDs (donationid, organisationid) per Regex validieren
  • Donation und DonationOrganisation aus Repositories laden
  • Wenn Donation noch nicht abgestimmt: donationStatus = true, donationOrganisation setzen, spendDate = now
  • DonationOrganisation-Counter um 1 erhöhen, persistieren
  • Redirect-URI bauen (/spendenaktion/{donationid}/thanks) und als JSON zurückgeben
  • Wenn bereits abgestimmt oder UUIDs ungültig: JSON-Fehler zurückgeben
Domain-Objekte
Side-Effects
  • DB-Write: Donation.donationStatus = true, Donation.donationOrganisation, Donation.spendDate
  • DB-Write: DonationOrganisation.counter += 1
DateienClasses/Controller/DonationController.php, Classes/Domain/Model/Donation.php, Classes/Domain/Model/DonationOrganisation.php
Spendenaktion: Danke-Seite URL GET /spendenaktion/{identifier}/thanks DonationController::thanksAction Gast (UUID-basierter Zugang) OUT OF SCOPE. Spendenaktion-spezifisch.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Donation per UUID laden
  • Wenn Donation nicht gefunden: Redirect zu noaccess
  • Wenn Donation noch nicht abgestimmt: Redirect zu indexAction
  • Wenn Donation abgestimmt: Danke-View mit Donation-Objekt rendern
Domain-Objekte
Side-Effects
DateienClasses/Controller/DonationController.php, Resources/Private/Templates/Donation/Thanks.html
Spendenaktion: Countdown-Dashboard (donationClock) URL GET /spendenaktion/{identifier}/clock — verlinkt in Info-Mails (mailStatus 2 oder 3) DonationController::donationClockAction Gast (UUID-basierter Zugang) OUT OF SCOPE. Hardcodiertes Datum 2021-12-24.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • UUID validieren, Donation laden
  • mailStatus prüfen: nur 2 oder 3 erlaubt, sonst Redirect zu indexAction
  • Verbleibende Zeit bis 2021-12-24 23:59:59 berechnen (Tage/Stunden als Textstring)
  • Alle DonationOrganisationen laden, nach orderNum sortieren
  • Gesamtspendenzahl = Summe aller counter * 4 (Multiplikator)
  • View mit companies, timeleft, donationCount, donation rendern
Domain-Objekte
Side-Effects
DateienClasses/Controller/DonationController.php, Resources/Private/Templates/Donation/DonationClock.html
Spendenaktion: Abschluss-Dashboard (end) URL GET /spendenaktion/{identifier}/end — verlinkt in der Abschluss-Mail (mailStatus 4) DonationController::endAction Gast (UUID-basierter Zugang) OUT OF SCOPE.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • UUID validieren, Donation laden
  • mailStatus = 4 prüfen, sonst Redirect zu indexAction
  • Alle DonationOrganisationen laden, sortieren, Gesamtsumme berechnen
  • View mit Ergebnissen rendern
Domain-Objekte
Side-Effects
DateienClasses/Controller/DonationController.php, Resources/Private/Templates/Donation/End.html
Spendenaktion: Organisations-Auswahlseite URL GET /spendenaktion/{donationid}/organisation/{organisationid} DonationController::organisationAction Gast (UUID-basierter Zugang) OUT OF SCOPE.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Beide UUIDs validieren
  • Donation und DonationOrganisation laden
  • View mit donation, donationid und donationOrganisation rendern
Domain-Objekte
Side-Effects
DateienClasses/Controller/DonationController.php, Resources/Private/Templates/Donation/Organisation.html
Spendenaktion: Cron — CSV-Import Donations/Organisations/Staff CLI-Command: ./flow donation:importDonations / donation:importOrganisations / donation:importStaff DonationCommandController::importDonationsCommand / importOrganisationsCommand / importStaffCommand Cron/CLI (kein HTTP-Zugang) OUT OF SCOPE. Einmalige Kampagnen-Datenpflege.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • CSV-Datei aus Resources/Private/Import/ laden (Spendenaktion.csv / Spendenaktion_Organisations.csv / Spendenaktion_Staff.csv)
  • Jede Zeile validieren (Spaltenanzahl, E-Mail RFC822)
  • Auf Duplikate prüfen (findOneByEmail / findOneByTitle)
  • Neue Entities (Donation / DonationOrganisation / SalesStaff) anlegen und persistieren
  • Fehlerzählung und Ergebnis ausgeben
Domain-Objekte
Side-Effects
  • DB-Write: neue Donation-, DonationOrganisation- und SalesStaff-Einträge
DateienClasses/Command/DonationCommandController.php, Classes/Domain/Model/Donation.php, Classes/Domain/Model/DonationOrganisation.php
Spendenaktion: Cron — Mailing-Kampagne (Start-/Info-/End-/After-Mail) CLI-Commands: donation:sendStartmailMA / donation:sendStartmailPP / donation:sendInfomailFirst / donation:sendInfomailSecond / donation:sendEndmailSolcom / donation:sendEndmailPP / donation:sendAfterMail DonationCommandController (jeweiliges Command) Cron/CLI OUT OF SCOPE. Einmalige Weihnachtsaktion 2021.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Donations per mailStatus-Limit-Filter aus Repository laden (Status 0→1→2→3→4→6/7→99)
  • Für jede Donation den zugehörigen SalesStaff per salesName ermitteln
  • Fluid-Template rendern (personalisiert mit Donation-Daten und SalesStaff-Signatur)
  • E-Mail via SwiftMailer senden (From: spendenaktion@solcom.de, To: Empfänger-E-Mail)
  • mailStatus auf Folgestatus setzen und persistieren
  • Mitarbeiter-Variante (sendStartmailMA) ohne SalesStaff-Lookup
Domain-Objekte
Side-Effects
  • E-Mail-Versand (SwiftMailer direkt, kein MailingworkService)
  • DB-Write: Donation.mailStatus erhöhen (Statusmaschine 0→1→2→3→4/6/7→99)
DateienClasses/Command/DonationCommandController.php, Resources/Private/Templates/Mail/Donation/
Spendenaktion: Cron — Status-Verwaltung und Diagnose CLI-Commands: donation:setEmailStatus / donation:status / donation:checkVmMatching / donation:findByEmail / donation:pruneDonations / donation:pruneOrganisations / donation:pruneStaff DonationCommandController (jeweilige Commands) CLI/Admin OUT OF SCOPE.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • setEmailStatus: Alle Donations mit mailStatus=from auf mailStatus=to umschreiben
  • status: Anzahl abgestimmter vs. ausstehender Donations ausgeben (optional mit Details)
  • checkVmMatching: Alle Donations durchgehen, salesName gegen SalesStaffRepository prüfen, nicht gefundene Namen listen
  • findByEmail: Einzelnen Donation-Eintrag per E-Mail suchen und dumpen
  • pruneDonations/pruneOrganisations/pruneStaff: Alle Einträge löschen
Domain-Objekte
Side-Effects
  • DB-Write: mailStatus-Updates (setEmailStatus)
  • DB-Write: Löschoperationen (prune-Commands)
DateienClasses/Command/DonationCommandController.php
Sommerfest: Anmelde-Formular anzeigen URL GET /de/signin/{identifier} — personalisierter QR-Code-Link SommerfestController::indexAction Gast (Identifikations-Token im URL als impliziter Zugang) OUT OF SCOPE. Einmalige interne Event-Anmeldung (SOLCOM Sommerfest), nicht Teil des Projektpartnerportals.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • SommerfestPerson per identification-String aus Repository laden
  • Person dem View zuweisen und Formular rendern
Domain-Objekte
Side-Effects
DateienClasses/Controller/SommerfestController.php, Classes/Domain/Model/SommerfestPerson.php, Configuration/Routes.yaml
Sommerfest: Anmelde-Formular absenden URL POST /api/sommerfest/submit — AJAX-Submit des Anmeldeformulars SommerfestController::submitAction Gast (Token-basiert) OUT OF SCOPE. Interne Event-Anmeldung.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • POST-Daten einlesen (identification, field01-04 für Shuttle-Option, Abfahrtszeiten)
  • SommerfestPerson per identification suchen
  • Wenn Person noch nicht angemeldet (status=0): Antworten in Klartextstrings übersetzen
  • CSV-Zeile mit Timestamp, Personendaten und Antworten in Datei a298579827894324output.csv (FLOW_PATH_WEB) appenden
  • Person.status auf 1 setzen, submitDate auf now, persistieren
  • Rückgabe: '1' (Erfolg), '2' (bereits angemeldet), '3' (Datei-Fehler), '0' (keine POST-Daten)
Domain-Objekte
Side-Effects
  • DB-Write: SommerfestPerson.status = 1, submitDate = now
  • Datei-Append: FLOW_PATH_WEB/a298579827894324output.csv
DateienClasses/Controller/SommerfestController.php, Classes/Domain/Model/SommerfestPerson.php, Configuration/Routes.yaml
Sommerfest: Cron — CSV-Import und Prune CLI-Commands: sommerfest:import / sommerfest:prune SommerfestCommandController::importCommand / pruneCommand CLI/Admin OUT OF SCOPE.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • import: sommerfest.csv aus Resources/Private/Import/ laden
  • Jede Zeile validieren (E-Mail RFC822), auf Duplikate prüfen (findOneByIdentification)
  • Neue SommerfestPerson-Entities anlegen (gender, firstname, lastname, email, identification)
  • prune: Alle SommerfestPerson-Einträge aus DB löschen
Domain-Objekte
Side-Effects
  • DB-Write: neue SommerfestPerson-Einträge oder Löschung aller Einträge
DateienClasses/Command/SommerfestCommandController.php, Classes/Domain/Model/SommerfestPerson.php
RSS-Feed: Freiberufler-Blog-Artikel abrufen und rendern URL GET — Neos-Frontend-Node mit RssController-Einbindung (kein eigener Route-Eintrag in Routes.yaml) RssController::indexAction Gast (öffentlicher Content-Block) RELEVANT für Drupal-Nachbau. Einbindung eines externen Blogs (freiberufler-blog.de) als Content-Widget ist ein redaktioneller Feature-Wunsch. In Drupal über Core-Aggregator-Modul oder Custom-Block mit cURL/Symfony-HTTP realisierbar. Lücke: kein Äquivalent bisher im Drupal-Nachbau erkennbar.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Externen RSS-Feed https://www.freiberufler-blog.de/feed/ per cURL abholen (SSL-Verify deaktiviert)
  • Encoding erkennen und in UTF-8 konvertieren
  • XML per SimpleXMLElement parsen
  • Ersten 4 Items (title, link, description, pubDate) extrahieren
  • Array dem View zuweisen und Fusion-Template rendern
Domain-Objekte
Side-Effects
  • Externe HTTP-Anfrage: https://www.freiberufler-blog.de/feed/
DateienClasses/Controller/RssController.php
API: Rückrufanfrage an SOLCOM senden URL POST /api/senderecalltosolcom — AJAX-Submit des Rückruf-Widgets ApiController::sendRecallToSolcomAction Gast (öffentliches Widget) RELEVANT für Drupal-Nachbau. Rückruf-Widget ist vermutlich im Drupal-Projekt über ein Webform abzubilden. Lücke prüfen: Gibt es ein vergleichbares Kontakt/Rückruf-Formular im Drupal-Nachbau?
Schritte, Domain-Objekte & Side-Effects
Schritte
  • JSON-Body aus Request-Arguments dekodieren (name, company, phone, date, time)
  • Fluid-Template (Mail/Callback/Callback.html) rendern
  • E-Mail via MailerService senden (From: no-reply@solcom.de, To: crm@solcom.de, Cc: F.Letsche@solcom.de, Betreff: Anfrage Rückrufformular Webseite)
  • JSON-Erfolgsantwort zurückgeben
Domain-Objekte
Side-Effects
  • E-Mail-Versand: crm@solcom.de (MailerService)
DateienClasses/Controller/ApiController.php, Resources/Private/Templates/Mail/Callback/Callback.html
API: Temporäre Datei hochladen (Bewerbungsformular) URL POST /api/uploadtempfile — AJAX-Dateiupload im Bewerbungs- oder Profil-Formular ApiController::uploadTempFileAction Gast (CSRF-gesichert) RELEVANT für Drupal-Nachbau. Temporärer Dateiupload ist Teil des Bewerbungsflows (ProjectController/AccountController). In Drupal über managed_file / Webform-File-Element abgebildet. Lücke: API-Endpunkt /api/uploadtempfile muss im Drupal-Nachbau als eigenständiger AJAX-Endpoint oder über Webform-Mechanismus bereitgestellt werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • CSRF-Token validieren
  • Genau eine Datei im Request prüfen
  • UploadService.checkUploadError / checkMimeType / checkFileSize aufrufen
  • Bei Validierungsfehler: JSON-Fehler zurückgeben
  • UploadService.uploadFile aufrufen und Ergebnis als JSON zurückgeben
Domain-Objekte
Side-Effects
  • Datei-Upload: temporäre Datei wird auf dem Server gespeichert (UploadService)
DateienClasses/Controller/ApiController.php
API: Projekt-Suchergebnis-Zähler (AJAX) URL GET /api/projects/countsearchresults?searchval=… — Live-Suchtreffer-Zählung im Frontend ApiController::countSearchResultsAction Gast RELEVANT für Drupal-Nachbau. Live-Trefferzählung ist Feature des Projekt-Suchformulars. In Drupal als Views-AJAX oder Custom-Endpoint realisierbar. Prüfen ob im Drupal-Projekt bereits ein Äquivalent existiert.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • searchval aus Request lesen, Kommas durch Leerzeichen ersetzen, HTML-Entities encodieren
  • In einzelne Suchbegriffe aufteilen, Leerzeichen/Duplikate filtern
  • ProjectRepository.findBySearchString aufrufen und Trefferzahl zählen
  • JSON mit error-Flag und count zurückgeben
Domain-Objekte
Side-Effects
DateienClasses/Controller/ApiController.php
API: Geschützte Datei ausliefern (protecteduserfile) URL GET /protecteduserfile?file={sha1hash/filename} — Abruf gespeicherter Nutzerdokumente ApiController::getpersistentfileAction Eingeloggter Projektpartner (AuthenticationProviderManager.isAuthenticated()) RELEVANT für Drupal-Nachbau. Private Dateiauslieferung für eingeloggte Nutzer. In Drupal über private:// Dateisystem-Stream-Wrapper und hook_file_download() abbildbar. Lücke: dieser spezifische Endpunkt muss im Drupal-Modul abgebildet werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Authentifizierung über AuthenticationProviderManager prüfen
  • file-Parameter aus Request lesen, SHA1-Hash extrahieren
  • ResourceManager.getResourceBySha1 aufrufen
  • Datei aus FLOW_PATH_DATA/Persistent/Resources/{4-char-prefix}/{sha1} lesen
  • Passende HTTP-Header setzen (Content-Type, Content-Length, Content-Disposition, Cache-Control)
  • Datei direkt ausgeben (echo $file)
Domain-Objekte
Side-Effects
DateienClasses/Controller/ApiController.php, Configuration/Routes.yaml
API: Legacy-Projekt-Detailseite (kompakt/xing-redirect) URL GET /jetzt-bewerben/stellen-details-kompakt.html?op={personio_id} — Redirect von Legacy-/Xing-URLs ApiController::projectDetailCompactAction Gast RELEVANT für Drupal-Nachbau (Redirect-Pflege). Legacy-Xing-Links müssen weiter funktionieren. In Drupal über Redirect-Modul oder Custom-Controller abzubilden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • PersonioProject per op (Personio-ID) aus Repository laden
  • Wenn nicht gefunden: Redirect zu /de/stelle-wird-nicht-mehr-besetzt
  • Dazugehörigen Neos-Node per FlowQuery suchen (Document.Career.Personio.Project mit passender projectid)
  • Node-URL ermitteln und Redirect dorthin ausführen
Domain-Objekte
Side-Effects
DateienClasses/Controller/ApiController.php, Configuration/Routes.yaml
ACP: Login und Authentifizierung URL GET /acp/login — ACP-Login-Seite; URL POST /acp/authenticate — Formular-Submit AcpController::loginAction / onAuthenticationSuccess / onAuthenticationFailure ACP-User (separater Auth-Provider Contenance.Solcom:Acp) RELEVANT für Drupal-Nachbau. Das ACP ist ein internes Admin-Tool für SOLCOM-Mitarbeiter. In Drupal wird dies über standard Drupal-Admin-UI + Rollen/Berechtigungen abgebildet (kein Custom-ACP nötig). Rollen-Mapping: ACP-Rolle 1/2 = Admin, Rolle 3 = Spendenaktions-Viewer.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • loginAction: Wenn bereits eingeloggt → checkRole und Redirect zu indexAccount (Rolle 1/2) oder indexDonation (Rolle 3)
  • authenticate: AbstractAuthenticationController verarbeitet Credentials (Provider: Contenance.Solcom:Acp)
  • onAuthenticationSuccess: Eingeloggten User anhand Rolle weiterleiten
  • onAuthenticationFailure: Redirect zu /acp/login?status=AuthFailure
  • logoutAction: Alle Tokens invalidieren, Redirect zu /acp/login
Domain-Objekte
Side-Effects
  • Session-Write: Authentication-Token anlegen/löschen
DateienClasses/Controller/AcpController.php, Configuration/Routes.yaml
ACP: Account-Dashboard mit Statistiken URL GET /acp/accounts — ACP-Dashboard für Nutzerkonten AcpController::indexAccountAction ACP-Admin (Rolle 1 oder 2) RELEVANT für Drupal-Nachbau (Admin-Seite). In Drupal als Custom-Admin-Dashboard-Page oder über Views/Charts realisierbar. Die Statistik-Endpunkte (Logins, neue Accounts) entsprechen Views-Aggregationen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Rolle 1 oder 2 prüfen (checkLoginAndRole)
  • Gesamtzahl aktivierter User (UserRepository.findAll) und ausstehender Registrierungen (RegistrationFlowRepository.findAll) laden
  • Neuregistrierungen des aktuellen vs. letzten Monats vergleichen
  • Letzte 5 Registrierungen und letzte 5 Online-User laden
  • Zeitreihe: Aktive PP der letzten 12 Tage und 12 Monate berechnen (findLatestOnlineUsers mit Datumsgrenzen)
  • Zeitreihe: Neue Accounts der letzten 6 Monate berechnen
  • Alle Daten dem View zuweisen
Domain-Objekte
Side-Effects
DateienClasses/Controller/AcpController.php, Classes/Domain/Model/Statistics.php
ACP: Nutzerverwaltung — Suche und Anzeige URL GET /acp/accounts/management — Nutzerliste; URL POST /acp/accounts/management/ajax/getusers — AJAX-Suche AcpController::managementAccountAction / getAllUsersAction ACP-Admin (Rolle 1 oder 2) RELEVANT für Drupal-Nachbau. Nutzerverwaltung ist in Drupal über /admin/people mit Views abbildbar. RMS-Nummer als Custom-Suchfeld müsste als zusätzliches Filter-Feld in Views ergänzt werden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • managementAccountAction: Alle User laden und als Tabelle rendern
  • getAllUsersAction: searchTerm aus POST lesen
  • UserRepository.findByRmsOrEmail aufrufen (aktivierte User)
  • RegistrationFlowRepository.findByEmail aufrufen (ausstehende Registrierungen, nur noch gültige Token)
  • DataTables-kompatibles JSON-Array zurückgeben (mit HTML-Badges für Status)
Domain-Objekte
Side-Effects
DateienClasses/Controller/AcpController.php, Configuration/Routes.yaml
ACP: User-Account bearbeiten (E-Mail und Passwort) URL GET /acp/accounts/management/edituseraccount?identifier={uuid} — Edit-Formular; URL POST /acp/accounts/management/edituseraccountsubmit — Submit AcpController::editUserAccountAction / editUserAccountSubmitAction ACP-Admin (Rolle 1 oder 2) RELEVANT für Drupal-Nachbau. In Drupal über /admin/people/{uid}/edit abbildbar. Passwort-Reset durch Admin ist Drupal-Standard.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Rolle 1 oder 2 prüfen
  • User per Identifier aus Repository laden
  • editUserAccountSubmitAction: newAccountEmail prüfen — Duplikat-Check gegen AccountRepository, ggf. AccountIdentifier updaten
  • newAccountPassword prüfen — wenn gesetzt: HashService.hashPassword, credentialsSource updaten
  • AccountRepository.update aufrufen
Domain-Objekte
Side-Effects
  • DB-Write: Account.accountIdentifier (neue E-Mail)
  • DB-Write: Account.credentialsSource (neues Passwort-Hash)
DateienClasses/Controller/AcpController.php, Configuration/Routes.yaml
ACP: User-Account löschen (Anonymisierung) URL POST /acp/accounts/management/ajax/removeuser?identifier={uuid} — AJAX-Button in Nutzerliste AcpController::removeUserAction ACP-Admin (Rolle 1 oder 2) RELEVANT für Drupal-Nachbau. Account-Löschung ist bereits als P2503-233 implementiert. Drupal-Pendant prüfen: Anonymisierung vs. Löschung, Rate-Limiting, Admin-Benachrichtigungs-Mail.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • ACP-User aus Session laden, lastActionDelete prüfen (Rate-Limit: 30 Sekunden zwischen Löschungen)
  • User per Identifier laden
  • Eventuell existierenden AccountChangeFlow entfernen
  • Account-Identifier auf 'REMOVED__{rand}_{email}' setzen, AuthenticationProviderName auf 'Contenance.Solcom:Removed'
  • AccountRepository updaten und persistieren
  • Benachrichtigungs-E-Mail an bewerbung@solcom.de senden (Template: Mail/Acp/AccountDelete.html)
  • AcpUser.lastActionDelete auf now setzen
Domain-Objekte
Side-Effects
  • DB-Write: Account-Anonymisierung (kein echtes Löschen der User-Entity)
  • DB-Write: AccountChangeFlow-Löschung (falls vorhanden)
  • DB-Write: AcpUser.lastActionDelete
  • E-Mail-Versand: bewerbung@solcom.de (MailerService)
DateienClasses/Controller/AcpController.php, Resources/Private/Templates/Mail/Acp/AccountDelete.html
ACP: User-Registrierung manuell freischalten URL POST /acp/accounts/management/ajax/activateuser — AJAX-Button in Nutzerliste AcpController::activateUserAction ACP-Admin (Rolle 1 oder 2) RELEVANT für Drupal-Nachbau. Manuelle Freischaltung durch Admin ist in Drupal über /admin/people Standard. Lücke: muss geprüft werden ob im Drupal-Projekt der manuelle Freischalt-Pfad explizit vorgesehen ist.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • RegistrationFlow per E-Mail (identifier) laden
  • UserCreationService.createUserAndAccount aufrufen
  • RegistrationFlowRepository.remove aufrufen
  • JSON true/false zurückgeben
Domain-Objekte
Side-Effects
  • DB-Write: neuer User+Account wird angelegt
  • DB-Write: RegistrationFlow wird gelöscht
DateienClasses/Controller/AcpController.php
ACP: Login-Statistiken im Zeitraum vergleichen (AJAX) URL POST /acp/accounts/management/ajax/compareloggedinusers — Datumsbereich-Picker im Dashboard AcpController::compareLoggedInUsersAction ACP-Admin RELEVANT für Drupal-Nachbau (Admin-Statistik). In Drupal über Views-Aggregation oder Custom-AJAX-Endpoint abzubilden.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • compareLoggedInFrom und compareLoggedInTo aus POST-Daten lesen
  • UserRepository.findLatestOnlineUsers mit Datumsgrenzen aufrufen
  • JSON mit fromDateString, toDateString, countLogins zurückgeben
Domain-Objekte
Side-Effects
DateienClasses/Controller/AcpController.php
ACP: Spendenaktions-Dashboard URL GET /acp/donations — Übersichtsseite für Spendenaktions-Viewer AcpController::indexDonationAction ACP-User (Rolle 1 oder 3) OUT OF SCOPE. Spendenaktion-spezifisch.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Rolle 1 oder 3 prüfen (Spendenaktions-Viewer hat Rolle 3)
  • AcpUser dem View zuweisen und Dashboard rendern
Domain-Objekte
Side-Effects
DateienClasses/Controller/AcpController.php, Resources/Private/Templates/Acp/IndexDonation.html
ACP: Development-Dashboard (GitHub-Releases und Commits) URL GET /acp/development — Entwickler-Übersicht mit Repository-Infos AcpController::indexDevelopementAction ACP-Super-Admin (Rolle 1 ausschließlich) OUT OF SCOPE für Drupal-Nachbau. Internes Entwickler-Tool.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Nur Rolle 1 (Super-Admin) erlaubt
  • GitHub-Client per hardcodiertem Access-Token authentifizieren
  • Releases und Commits aus iSeries/contenance.solcom-Repository abrufen
  • Dem View zuweisen und rendern
Domain-Objekte
Side-Effects
  • Externe HTTP-Anfrage: GitHub API (api.github.com)
DateienClasses/Controller/AcpController.php
Statistik-Entität: Bewerbungs-Counter inkrementieren Interner Aufruf durch ProjectController oder CareerPersonioController bei Bewerbungs-Submit (kein eigener HTTP-Endpunkt) Statistics::setProjectAppilcationCounterUp / setCareerprojectAppilcationCounterUp Intern (kein direkter HTTP-Zugang) RELEVANT für Drupal-Nachbau. Bewerbungszähler sind Reporting-Feature. In Drupal als Custom-Statistik-Entity oder über Drupal-Statistics-Modul/Custom-Field abzubilden. Lücke: kein Äquivalent bisher erkennbar.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Statistics-Entity aus Repository laden
  • projectApplicationCounter oder careerprojectApplicationCounter um 1 erhöhen
  • Repository.update und persistieren
Domain-Objekte
Side-Effects
  • DB-Write: Statistics.projectApplicationCounter oder careerprojectApplicationCounter
DateienClasses/Domain/Model/Statistics.php
XML-Feeds für externe Job-/Projektbörsen URL GET /{node}<feedname> — z.B. /de<xing-jobs>, /de<freelancermap>, /de<projectfeed>, /de<newsfeed> etc. (18+ Feed-Varianten in Routes.yaml) Neos Frontend\Node::showAction mit Feed-Format (xml.xing-jobs, xml.freelancermap, xml.projectfeed, xml.newsfeed, xml.freelance, etc.) Gast (öffentliche Feed-URLs) RELEVANT für Drupal-Nachbau. Externe Plattform-Feeds (Xing, Freelancermap, Freelance.de, Projektwerk, Gulp, Indeed, Jooble, Jobrapido, Trovit etc.) müssen im Drupal-Projekt als XML-Views oder Feed-Endpunkte weiter bedient werden. Im solcom_project_offer-Modul sind einige Feeds bereits implementiert (F-340, F-360). Vollständige Liste der 18+ Feeds gegen Drupal-Implementierung abgleichen.
Schritte, Domain-Objekte & Side-Effects
Schritte
  • Neos-Frontend-Node-Route mit feed-spezifischem Format matchen
  • Fusion-Template für das jeweilige Format rendern (xml.{feedname})
  • Projekt- oder Stellendaten aus ContentRepository/ProjectRepository aggregieren
  • XML-Response mit passenden Content-Type-Headern ausliefern
Domain-Objekte
Side-Effects
DateienConfiguration/Routes.yaml

Provenienz

Generiert aus zwei Multi-Agent-Workflows über web/neos/DistributionPackages/Contenance.Solcom (extrahiert aus temp/web54_2026-06-10_06-15.tar.gz, Stand 2026-06-10). Flow-Inventar: 11 Reader + 1 Completeness-Critic. Datenfluss: 8 Grenz-Reader. Neos-Version 4.0.x. Dieses Dokument ist ein Snapshot einer fremden Quelle (Legacy-System), keine Architekturentscheidung — Entscheidungen gehören in docs/adr/.