WebProCMS can generate a privacy policy that matches how the site actually works. Instead of a static legal page that drifts out of date, the CMS inspects its own configuration — which features are enabled, whether analytics runs cookieless or with a persistent cookie, whether Google Analytics or tracking snippets are configured, whether any page embeds a YouTube/Vimeo video, which OAuth and email providers are wired up — and composes an accurate policy from curated section templates. When the detected services later change (a feature is toggled, a GA ID is added, an embed appears), the dashboard flags the policy as out of date and offers one-click regeneration.
How it works
Three pieces under app/Support/PrivacyPolicy/:
PrivacySignalRegistry— the inventory of every data-practice signal the CMS can detect at runtime. Each signal reports whether it's active, a human-readable status line (shown on the settings page), and the data its policy section needs (retention days, provider names, OAuth provider labels, …).PrivacyPolicyGenerator— composes the full policy HTML from the active signals plus the manual facts, then writes it through the same pipeline the page editor uses:content_overridesupsert → page sidecar recompile (PageDataCompiler) → response-cache clear. It locates the policy's rich-text row by parsing the live page blade (the row slug is install-specific — ThemeInstaller rewrites theme slugs), never by hardcoding a slug.- Section templates in
resources/views/partials/privacy-policy/— one curated Blade partial per signal plus the fixed skeleton (intro, cookies table, data-sale/CCPA, retention, your-rights, children, changes, contact). Every sentence is__()-wrapped with:placeholdersubstitution, so the wording is translator-ready.
The generated policy is written into the page's editable rich-text row — the page stays fully hand-editable in the page editor afterward. Generation is always explicit (a button), never automatic.
Detected signals
| Signal | Active when | Section discloses |
|---|---|---|
| Built-in analytics | Analytics feature enabled | Server-side, cookieless tracking; daily-rotating SHA-256 visitor hashes; retention period; the optional consent-gated persistent visitor cookie when enabled |
| Google Analytics | A GA4 measurement ID is configured | Google receives visitor data; link to Google's policy |
| Cookie consent banner | Cookie Consent enabled | The visitor's cookie choices, preference cookie lifetime |
| Tracking snippets | Any active snippet categorized Analytics or Marketing | Third-party scripts may collect visit data and set cookies |
| Embedded videos | Any page has an embed-mode x-dl.video (detected from content_overrides) |
YouTube/Vimeo may set cookies once a video loads |
| Forms | Any form exists | Stored submissions + IP (when "save submissions" is on), CRM feeding, reCAPTCHA/Turnstile processing |
| CRM | CRM feature enabled | Contact records kept (name, email, phone, company, interactions) |
| Newsletters | Newsletters feature enabled | Subscriber storage, unsubscribe, open/click tracking, the delivery provider (Postmark/Resend/Mailgun/SendGrid) when not sending from the server |
| Online shop | Ecommerce feature enabled | Order data stored; Stripe processes payments |
| Member accounts | Memberships feature enabled | Account data, enabled OAuth sign-in providers, Stripe subscriptions |
| Blog comments | Comments enabled | Comment text + name/email/IP/user-agent, OAuth sign-in providers |
| AI chat assistant | AI Chat feature enabled | Conversation storage, third-party AI processing, retention period |
| Google Reviews | Places API key + Place ID configured | Reviews fetched server-side, no visitor data sent |
| Search query logging | Analytics feature enabled | Search terms logged, not identity-linked |
The cookies table at the bottom of the policy is derived from the same signals: session/CSRF and wpcms_visitor always; wpcms_consent when the consent banner is on; wpcms_vid when the analytics persistent cookie is enabled; a third-party row when GA/snippets/embeds are present.
Manual facts (Settings → Privacy & Compliance)
The dashboard page at Settings → Privacy & Compliance records the things the code can't know, stored under privacy.* Settings:
- Legal entity name (defaults from Business settings) and privacy contact email
- Governing law / jurisdiction (optional)
- "We sell or share personal data" toggle — drives the CCPA wording
- Data retention statement (optional override of the generic paragraph)
- Other third-party services free text (CDN, fulfillment partner, …) — gets its own section
- Last reviewed date (for the operator's records)
- Policy page slug (advanced — only if the policy lives at a non-default URL)
The same page shows the live detected data practices readout (every signal with an active/inactive dot and status line) and the Generate / Regenerate controls.
Drift detection
At generation time the registry's fingerprint (every signal's active state + data, plus the manual facts) is snapshotted into the privacy.snapshot Setting along with a content hash of the generated HTML. On the settings page:
- Drift callout — shown when the current fingerprint differs from the snapshot ("your detected services have changed — regenerate").
- Hand-edit warning — when the current override's hash differs from the snapshot's content hash, the page was edited by hand since generation; regenerating asks for confirmation (a Flux modal) before overwriting.
AI rewrite (optional)
Generate + rewrite with AI composes the deterministic policy, then passes it through the configured AI text provider (AiTextService) with a system prompt that forbids adding, removing, or changing any factual claim — tone and readability only. If the provider fails or returns unusable output, the deterministic text is written instead and the error is surfaced. The button only appears when an AI provider is configured.
Adding a signal for a new feature
- Add one private builder method to PrivacySignalRegistry returning a
PrivacySignal(key, translated label,active, statussummary, templatedata,view), and one line inall(). - Add the section partial at
resources/views/partials/privacy-policy/{key}.blade.php— semantic class-free HTML (<h3>,<p>,<ul>), every sentence__()-wrapped. - That's it — the readout, generation, and drift detection all flow from the registry.
Limits / notes
- Multilingual: v1 generates in the site's default language only. Translations can be layered later via the
{key}__{lang}override convention. - Not legal advice: the generated text is an accurate, honest description of what the CMS does, but operators in regulated industries should still have counsel review it.
- The generator targets the standard theme-installed privacy page; a renamed page is supported via the page-slug setting, and cloned (page-scoped) overrides are written back to their own scope.