Implementation plan · SOLCOM Drupal · 2026-07-06

Bauleitung-Formular: contact_form wird Salesforce-Web-to-Lead-fähig, Tracking inklusive

Auftrag (Grilling-Ergebnis) Auf /Bauleitung-freelancer-finden beide Salesforce-Embeds (Web-to-Lead-HTML, Delta 31 + Marketing-Form-iFrame, Delta 97) entfernen und durch die Drupal-Webform contact_form ersetzen — dieselbe Webform wie auf /experten-finden-lassen. Die Webform wird auf die Salesforce-Web-to-Lead-Feldnamen umgebaut (SF-HTML „Leadformulare" ist führend), bekommt den bestehenden Salesforce-Handler zusätzlich zum E-Mail-Handler, 13 versteckte Tracking-Felder mit Browser-Speicherlogik und feuert nach erfolgreichem Submit das DataLayer-Event lead_submit (Kunden-Lead-Szenario, P2503-109). Vollständige Begriffsklärung: CONTEXT.md → Web-to-Lead-Pfad, Tracking-Felder, Kunden Lead.
Aufwand
~1 Tag
Oberflächen
2 Module · 1 Webform · 2 Canvas-Pages
Neue Felder
13 hidden + zip/city
Extern offen
00N…-IDs (Fr. Baur)
01

Arbeitspakete

Fünf Slices, jede einzeln testbar. Reihenfolge ist Abhängigkeitsreihenfolge: erst Webform-Kontrakt, dann Modul, dann JS, dann Seiten-Umbau, dann End-to-End-Verify. TDD gegen die bestehenden Handler-/Mapper-Tests.

Slice 1

Webform contact_form auf SF-Feldkontrakt umbauen

Element-Keys 1:1 auf SF-Namen: anrede→salutation (radios, Werte Mr./Ms./Mx./Not specified, deutsche Labels), vorname→first_name, nachname→last_name, geschaeftliche_email→email, telefon→phone, unternehmen→company, message→description. Neu sichtbar: zip, city (optional). Raus: kontaktweg, erreichbarkeit. Pflicht: first_name, last_name, email, company, description (+ Datenschutz). Neu hidden: 13 Tracking-Felder (utm_source/medium/campaign/term/content, gclid, msclkid, landing_page, first_landing_page, referrer, first_referrer, page_url, consent) + die 3 Handler-Status-Felder (salesforce_web_to_lead_status/_sent_at/_error, #access: false). SF-Handler aktivieren, E-Mail-Handler bleibt. Webform-Config salesforce_web_to_lead löschen.

config/sync/webform.webform.contact_form.ymlwebform.webform.salesforce_web_to_lead.yml (löschen)
Slice 2

Mapper/Handler: Lead-Quelle raus, Tracking-Passthrough rein

Mapper: Konstanten LEAD_SOURCE/DETAIL_LEAD_SOURCE + Felder 00NSZ00000Ju6Hm/J4 entfernen. Tracking-Felder mit Übersetzungstabelle Webform-Key → SF-Parametername mappen — initial identisch (sprechende Namen, Servlet verwirft unbekannte folgenlos), nach ID-Lieferung von Fr. Baur nur Tabellenwerte auf 00N… tauschen. Unit-/Kernel-Tests entsprechend anpassen (Lead-Quelle-Assertions raus, Tracking-Passthrough-Assertions rein).

solcom_salesforce_web_to_lead/src/Service/SalesforceWebToLeadMapper.phptests/
Slice 3

Tracking-JS + lead_submit-DataLayer (Danke-Seiten-Redirect)

Neue Library solcom_seo/salesforce-web-to-lead-tracking: Speicherlogik nach Referenz-Script v2 (localStorage sf_tracking_* für First-Touch/Kampagnen-Werte, sessionStorage sf_session_* für Session-Werte), befüllt die Hidden-Felder der Webform; utm/gclid-Befüllung Usercentrics-gated, consent immer als true/false aus UC-Status. Nach Submit leitet die Webform auf die Danke-Seite /Formular-Danke weiter (confirmation_type: url; Seite existiert auf Staging, lokal wird eine minimale canvas_page angelegt). Für lead_submit: der Handler/postSave legt die Payload (email_type per Private-Domain-Liste aus P2503-109-Doku, SHA-256-Hash der normalisierten E-Mail, form_location = Ursprungsseite der Submission) in den PrivateTempStore; die Danke-Seite gibt sie einmalig via drupalSettings aus und löscht sie (serverseitiger Once-Guard). JS pusht Event mit lead_type: customer, lead_subtype: contact_request, lead_category: "Kunden Lead", lead_action: "Kontaktanfrage", form_name: "Kunden Kontaktformular", form_id: "contact_form". E-Mail nie im Klartext in den DataLayer.

solcom_seo/js/solcom_seo.libraries.ymlsolcom_seo.module
Slice 4

Canvas-Seite 100 umbauen (Content-Edit, kein Config-Deploy!)

Per drush php:script auf canvas_page 100: Delta 31 (sdc.byte_theme.salesforce-web-to-lead-form) durch block.webform_block (webform_id: contact_form) ersetzen, Delta 97 (sdc.byte_theme.salesforce-marketing-form) ersatzlos entfernen. Anker-Ziel des Hero-CTAs (#freiberufler-finden) prüfen/erhalten. Die SDC selbst bleibt im Codebase. canvas_page 9 (/experten-finden-lassen) braucht keinen Umbau — dort hängt der contact_form-Block bereits. Achtung: Canvas-Pages sind lokal kuratierter Content — der Umbau muss auf Staging/Prod wiederholt werden (Script mitliefern).

canvas_page 100drush php:scriptkein config/sync-Artefakt
Slice 5

End-to-End-Verify + Qualität

PHPUnit (Modul-Tests + WebformPrefill-Suite), PHPCS/PHPStan lokal (CI läuft nur auf PR), Browser-Verify auf beiden Seiten: Formular rendert, Pflichtfelder greifen, Submit → Bestätigung → lead_submit im DataLayer sichtbar, Hidden-Felder tragen Werte nach Aufruf mit ?gclid=test1234&utm_source=google…. Kein Live-POST an Salesforce aus DDEV heraus verifizieren (nur Request-Payload loggen/asserten). PR gegen develop, kein Merge durch KI.

agent-browserphpunitPR → develop
02

Datenfluss

Durchgezogen = Lead-Datenpfad (first-party, serverseitig ab Submit). Gestrichelt = Analytics-Pfad nach erfolgreicher Übermittlung — blockiert den Lead-Pfad nie.

Besucher-URL ?gclid=…&utm_source=… Tracking-JS solcom_seo · UC-gated · Storage contact_form sichtbar + 13 hidden Felder Webform-Submission Drupal · E-Mail-Handler parallel SF Web-to-Lead POST · Mapper · Org 00DWx… Danke-Seite /Formular-Danke TempStore → drupalSettings (einmalig) dataLayer / GTM lead_submit → GA4 / Google Ads erfassen + speichern hidden befüllen Submit Handler-POST Confirmation-Render lead_submit

Durchgezogen = Lead-Pfad (Browser → Drupal → Salesforce, serverseitig). Gestrichelt = Analytics-Pfad; feuert nur nach erfolgreicher Übermittlung, E-Mail nur als SHA-256-Hash.

03

Feld-Kontrakt

Das Salesforce-Web-to-Lead-HTML „Leadformulare" ist führend: Webform-Keys und Select-Werte heißen exakt wie die SF-Input-Namen. Tracking-Felder senden bis zur ID-Lieferung ihre sprechenden Namen (Übersetzungstabelle im Mapper, Werte später auf 00N…-IDs tauschbar).

A · Sichtbare Felder (Webform-Key = SF-Name)
Key / SF-Feld Typ Pflicht Hinweis
salutationradiosneinWerte Mr./Ms./Mx./Not specified, Labels Herr/Frau/Divers/Keine Angabe
first_nametextfieldjamaxlength 40
last_nametextfieldjamaxlength 80
emailemailjamaxlength 80
phonetelneinmaxlength 40
companytextfieldjamaxlength 40
zip / citytextfieldneinneu; maxlength 20/40
descriptiontextareajabisher message
datenschutz_bestaetigungcheckboxjaDrupal-only, geht nicht an SF
B · Tracking-Felder (hidden) → SF-Zielfelder
Hidden-Key SF-Feld (existiert seit 03.07.) Quelle im Browser
utm_source/_medium/_campaign/_term/_contentSC_UTM_*__cURL-Param → localStorage
gclid / msclkidSC_GCLID__c / SC_MSCLKID__cURL-Param → localStorage
landing_pageSC_LandingPage__csessionStorage, once pro Session
first_landing_pageSC_FirstLandingPage__clocalStorage, once ever
referrer / first_referrerSC_Referrer__c / SC_FirstReferrer__cdocument.referrer (session/local)
page_urlkein SF-Feld — nachgefragtwindow.location.href
consentSC_Consentzugestimmt__cUsercentrics-Status true/false

Web-to-Lead-Parametername pro Feld = 00N…-ID, liegt noch nicht vor (Anfrage an Fr. Baur). Bis dahin sendet der Mapper die sprechenden Namen — Servlet verwirft sie folgenlos, Werte bleiben in der Drupal-Submission erhalten.

04

Schlüssel-Code

Die zwei fehleranfälligsten Stellen: die Mapper-Übersetzungstabelle (ID-Tausch als Einzeiler vorbereiten) und der lead_submit-Push (erst auf Bestätigung, Hash serverseitig).

solcom_salesforce_web_to_lead/src/Service/SalesforceWebToLeadMapper.php (Zielzustand)
// Webform-Key => SF-Web-to-Lead-Parametername.
// Sprechende Namen bis die 00N…-IDs der SC_*-Felder
// vorliegen (Anfrage Fr. Baur) — dann NUR Werte tauschen.
private const TRACKING_FIELDS = [
  'utm_source'         => 'utm_source',   // → 00N…
  'utm_medium'         => 'utm_medium',
  'utm_campaign'       => 'utm_campaign',
  'utm_term'           => 'utm_term',
  'utm_content'        => 'utm_content',
  'gclid'              => 'gclid',
  'msclkid'            => 'msclkid',
  'landing_page'       => 'landing_page',
  'first_landing_page' => 'first_landing_page',
  'referrer'           => 'referrer',
  'first_referrer'     => 'first_referrer',
  'page_url'           => 'page_url',
  'consent'            => 'consent',
];

// map(): Lead-Quelle-Konstanten + 00NSZ00000Ju6Hm/J4
// ersatzlos entfernen; TRACKING_FIELDS anhängen,
// leere Werte auslassen.
solcom_seo/js/salesforce-web-to-lead-tracking.js (Skizze)
// 1) Storage nach Referenz-Script v2:
//    setLocal(utm_*, gclid, msclkid) bei URL-Treffer,
//    setLocalOnce(first_landing_page, first_referrer),
//    setSessionOnce(landing_page), setSession(referrer, page_url)

// 2) Hidden-Felder befüllen — nur mit UC-Consent
//    („Salesforce Marketing Cloud", Muster salesforce-consent.js):
function fillHiddenFields(form, data) {
  Object.entries(data).forEach(([key, value]) => {
    const input = form.querySelector(
      'input[name="' + key + '"]');
    if (input && value) input.value = value;
  });
}

// 3) lead_submit auf der Bestätigungsansicht:
//    PHP liefert drupalSettings.solcomSeo.leadSubmit
//    (email_type, sha256_email_address, form_location) —
//    E-Mail-Normalisierung + Hash serverseitig, nie Klartext.
window.dataLayer.push({
  event: 'lead_submit',
  lead_type: 'customer',
  lead_subtype: 'contact_request',
  lead_category: 'Kunden Lead',
  lead_action: 'Kontaktanfrage',
  form_name: 'Kunden Kontaktformular',
  form_id: 'contact_form',
  form_location: settings.formLocation,
  email_type: settings.emailType,
  user_data: { sha256_email_address: settings.emailHash }
});
05

Risiken & Gegenmaßnahmen

Risiko
Sev
Gegenmaßnahme
Tracking-Werte erreichen Salesforce erst nach ID-Lieferung — bis dahin verwirft das Servlet die sprechenden Parameternamen still.
MED
Bewusst akzeptiert (Beschluss 06.07.): Werte stehen ab Tag 1 in jeder Drupal-Submission, nichts geht verloren. ID-Tausch = ein Wert pro Zeile in TRACKING_FIELDS. Anfrage an Fr. Baur läuft parallel.
contact_form-Umbau ändert Element-Keys — alte Submissions (anrede/vorname/…) und neue (salutation/first_name/…) mischen sich in derselben Webform; E-Mail-Handler-Template mit _default-Body zieht neue Keys automatisch, custom Tokens würden brechen.
MED
E-Mail-Handler nutzt _default (verifiziert) — kein Token-Bruch. Alte Submissions bleiben lesbar (Keys bleiben in DB erhalten). WebformPrefill-Kernel-Tests im solcom_project_offer-Modul auf contact_form-Referenzen prüfen.
Canvas-Umbau (Seite 100) ist Content, kein Config — Deploy vergisst ihn; Staging/Prod zeigen weiter die alten SF-Embeds.
HIGH
Umbau-Script (drush php:script) versioniert mitliefern und im PR als Deploy-Schritt dokumentieren; nach Deploy canvas_page-Cache-Tag invalidieren (bekannte Falle: stale Page-Cache nach Content-Änderung).
lead_submit-Doppelfeuer bei Reload der Danke-Seite (/Formular-Danke) oder Direktaufruf ohne Submission — Abnahmekriterium „feuert genau einmal" verletzt.
MED
Payload liegt im PrivateTempStore und wird beim ersten Danke-Seiten-Render ausgegeben + gelöscht (serverseitiger Once-Guard); Direktaufruf ohne Payload rendert ohne Event. Zusätzlich sessionStorage-Guard im JS.
Usercentrics-Timing: Hidden-Felder werden befüllt bevor/ohne Consent-Antwort — utm/gclid landen ohne Einwilligung im Lead.
MED
Befüllung ausschließlich im UC-Event-Callback (UC_UI_INITIALIZED/UC_CONSENT_CHANGED, Muster salesforce-consent.js); consent-Feld trägt den tatsächlichen Status, SF-seitig auswertbar.
06

Offene Punkte (extern, blockieren den Bau nicht)

00N…-Feld-IDs der Tracking-Felder
Web-to-Lead adressiert Custom-Felder nur per Feld-ID. Die SC_*-Felder existieren seit 03.07. (A. Wiechert) — es fehlt nur das neu generierte Web-to-Lead-HTML mit den Tracking-Feldern bzw. die ID-Liste. Anforderungstext liegt vor (Session 06.07.). Nach Lieferung: Werte in TRACKING_FIELDS tauschen, ein Commit.
Warten auf · Fr. Baur (Salesforce), unabhängig von Slices 1–5
SF-Feld für page_url
In Wiecherts Feldliste vom 03.07. fehlt ein Gegenstück für die URL der Formularseite. Nachgefragt zusammen mit den IDs; bis dahin wird page_url erfasst und in der Drupal-Submission gespeichert.
Warten auf · Salesforce-Team
GTM-Verdrahtung des lead_submit-Events
DataLayer-Variablen, Trigger und GA4-/Ads-Tags (inkl. Enhanced Conversions mit dem vorgehashten Wert) legt das Marketing-Team im GTM an — Doku dazu ist P2503-109-Anhang Kap. 6–8. Website-seitig ist mit dem Event alles geliefert.
Übergabe an · A. Kern (GTM), nach Slice 5