You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
Email texts hub — every plugin email editable in one place (#964, #965): a new Email texts hub lets an admin edit the global default subject + body of every token-based plugin email — the certificate email, recruitment convocation, the three reregistration emails, audience booking/cancellation, and (tokenized from former echo-partials) the self-scheduling booking confirmation, the five appointment lifecycle emails (approval, cancellation, reminder, waitlist-promotion, waitlisted) and the access-granted + calendar-deletion notices. Each is a wp_editor with {{token}} help, pre-rendered button/block tokens ({{receipt_button}}, {{cancel_button}}, {{waitlist_button}}, {{dashboard_button}}, {{cancellation_reason_block}}, …) and a "Restore Default Text" button. Built on a global-override store (ffc_email_bodies + EmailTemplates::effective_body() cascade, dormant when empty) and gated by a dedicated ffc_manage_email_templates capability (one-shot migration seeds it onto current Settings holders). Forms and per-calendar/audience bodies can still override; a per-form Global/Custom toggle decides whether a form follows the hub. Unconfigured installs render identically.
Two documentation hub pages + a Documentation-tab refresh (#976): adds Email texts hub and Document Templates hub pages, and brings the Documentation tab in line with the current plugin — Emails & Delivery (three tabs, all editable emails), the per-form Global/Custom toggle, pool-backed certificate/ficha/receipt templates, and Personal Calendars (custom calendars, waitlist/promotion, per-user block cap, ffc_bypass_appointment_capacity); the Developer page gains the ClientIpResolver filters, ffcertificate_ficha_template_html and the shared batched-CSV-export engine. Ten legacy "Section N / #141" docblocks migrated. Documentation-only.
Document Templates hub — pool-backed certificate, ficha & receipt templates (#945, #951): a read-only "Current assignments" overview plus editable/duplicable pool templates for the reregistration ficha (new ficha kind, seeded default, selected on a new Reregistration settings tab, resolves via ffcertificate_ficha_template_html) and the appointment receipt (a _ffc_template_kind discriminator — absent = certificate, fully retrocompatible; Regular + Custom defaults, per-mode selection on a new Scheduling → Receipt tab; adds {{appointment_end}} / {{appointment_time_range}} tokens). No data migration; unconfigured installs render exactly as before.
"All plugin emails" directory (#951): a read-only index listing every email the plugin can send, grouped by feature, each with a purpose, a personalisation state (Editable text / On-off only / System default) and an Open → deep-link. Discoverability only — no control or capability moves; each group gated by its view cap.
Self-scheduling: custom calendars, waitlist & per-user block cap (#941): calendars can use a custom schedule type — explicit date/time blocks with per-block vacancies — instead of the regular weekday pattern (mode locks once a calendar has bookings). A per-calendar waitlist (waitlist_enabled, optional capacity) queues bookings when a slot/block is full and auto-promotes the oldest queued entry (transactional FOR UPDATE, promotion email) when a spot frees; a max_blocks_per_user cap limits distinct blocks per user (counts active + waitlisted; admin overbook via ffc_bypass_appointment_capacity skips it) with a read-only Occupancy Report meta box. All off/empty by default; existing calendars unchanged.
IP Diagnostics detects Cloudflare behind a host proxy (#920): a new cloudflare_via_proxy verdict for the CF → host-LB → PHP topology — the secure strategy auto-trusts CF-Connecting-IP when the TCP peer is a private/reserved load balancer with a Cloudflare edge as the first public X-Forwarded-For hop. The tab is redesigned (Legacy×Secure comparison, a headers table with a Situation column incl. CF-Ray, and a warning against pasting Cloudflare ranges into the custom-proxy field).
Cloudflare page-cache safety check (#921): behind Cloudflare, the Cache tab runs a best-effort loopback probe of the edge cf-cache-status and warns if HTML is being cached (a "Cache Everything"/APO misconfiguration that can serve forms stale or leak the per-user dashboard), with guidance to exclude FFC URLs. Result cached up to 6h.
Changed
One consistent standard across all default emails + normalized subjects (#976): every plugin email now follows a single skeleton — an <h2> event title (no emoji, semantic colour: green = confirmed/approved, red = cancelled, amber = reminder, purple = waitlist, blue = informational), a greeting, one context line and a details box — and every subject the shared Event: {{reference}} shape (colon, no [{{site_name}}] prefix, no trailing dashes). The audience booking/cancellation emails gained their previously-missing subjects; admin notifications keep the visual pattern; audience emails drop their body-level sign-off for the shared Email Model footer; CTAs harmonised to brand blue. ⚠️ Because the shipped defaults changed, a per-form/schedule/calendar body still holding the old default now reads as Custom (keeps its text) until "Restore Default Text"; blank (Global-tracking) entities render the new standard automatically.
Email settings restructured into three tabs (#976): the overloaded SMTP tab is split into SMTP (transport + the global "Disable all emails" switch), Email Model (the shared chrome editor + preview + test-send) and Email texts (the per-email hub + directory), ordered SMTP → Email Model → Email texts. The Email texts hub edits one email at a time via a feature-grouped selector with TinyMCE initialized on demand instead of stacking ~15 editors; a single "Save email texts" persists all, degrading gracefully to plain textareas. Save flows unchanged — a relocation; every directory deep-link points at the new tab.
Recruitment convocation email absorbed into the hub (#964): its subject + body are edited in the Email texts hub with every other email instead of the bespoke Recruitment → Settings editor (retired, replaced by a link); the dispatcher resolves through EmailTemplates::effective_body('recruitment-convocation', …), and email_subject/email_body_html are removed from ffc_recruitment_settings. A one-shot migration (ffc_recruitment_email_hub_v1) moves any customised text into the hub. ⚠️ The convocation body now passes through wp_kses_post (previously arbitrary admin HTML) — an accepted, admin-cap-gated tightening.
Settings side-nav grouped into domain sections + soft-blue hover (#951): the 17-item Settings nav (page=ffc-settings) — previously flat alphabetical — now renders under seven subheadings (General · Content · Communication · Security & Access · Tools · Go to · System) via a new SettingsTab::get_group(), staying alphabetical within each and a single ARIA tablist (axe-clean). The Document Templates hub is reached from a Settings tab (the ffc_cert_template CPT relabelled kind-neutral, its standalone menu item dropped), and the Scheduling → Receipt tab is now a pure selector that links into that hub. Nav item hover/focus uses the theme-aware --ffc-primary-light tint. No tab content changes.
IP reads unified on one global strategy toggle (#927, #931): rate-limiting, geofencing, the public listing throttle and logging now resolve the client IP through the shared ClientIpResolver following the singlelegacy/secure strategy on the IP Diagnostics tab, with secure (unspoofable behind Cloudflare / a configured proxy) recommended; the rate-limit log's ip_address records the IP actually enforced, and public CSV-download audit logs use the same resolver (unresolvable → 0.0.0.0). ⚠️Breaking: the client-spoofable ffc_trust_forwarded_headers filter and the interim ffc_rate_limit_ip_source filter/control are removed — set the strategy to secure instead.
Internal (#945, #949, #950) — code-reuse sweeps: consolidated wall-clock date/time validation onto DateFormatter::is_valid_time() / is_valid_date() (replacing ~6 hand-rolled regex copies); extracted the shared CodeMirror initializer into window.FFCCodeEditor (ffc-code-editor-core.js) used by the form-editor, cert-template and receipt editors (restoring the placeholder overlay / dark theme / disabled notice the receipt tab had lost); folded the Email Model logo picker into ffc-branding-media.js; migrated three $.post callers to FFC.request; and added RequestInput::is_truthy(), SettingsTab::should_enqueue_on() and Core\Utils::read_file_contents(). Behaviour unchanged; tests added.
Internal (#935) — comment-only cleanups: dropped the namespace-migration (Phase N) codename from ~50 class docblocks + the bootstrap (issue-anchored phase refs untouched), pointed DocumentFormatter::validate_phone() at the PHONE_REGEX constant, and removed 18 files' worth of auto-generated duplicate docblock summary lines (492 lines). No runtime change.
Removed
Dead-code sweep — orphaned AJAX handlers, helpers & constants (#935): removes ten admin-ajax actions with zero JS/template caller (each superseded by a live path) — the two audience placeholders ffc_audience_create_booking / ffc_audience_get_schedule_slots, the five URL-shortener row actions ffc_{delete,toggle,trash,restore,empty_trash}_short_url(s), ffc_audience_check_conflicts, ffc_generate_tickets and ffc_preview_date_format — plus two unused helpers (CsvExportTrait::extract_dynamic_values, SettingsTab::get_tab_url), several never-read constants (FFC_MIN_WP_VERSION, FFC_MIN_PHP_VERSION, FFC_JQUERY_UI_VERSION, RateLimitChecker::WEAK_SIGNALS, two ActivityLog::ACTION_*) and a vestigial no-op (SelfSchedulingEditor::display_save_errors()). No user-facing behaviour change.
Fixed
Settings-nav icons render uniformly monochrome (#951): the tab-nav glyphs are emoji (.ffc-icon-*), and some codepoints (🆔, 👤) render in colour in the admin's emoji font while most render flat — an inconsistency impossible to fix per-glyph without whack-a-mole. A single filter: grayscale(1) on .ffc-settings-tabs__icon desaturates every nav icon uniformly, so any glyph (present or future) reads monochrome regardless of platform. Supersedes the per-glyph icon swaps below. CSS-only.
Frontend accessibility: labelled honeypot + named environment filter (#951): the anti-spam honeypot input (ffc_honeypot_trap) on the certificate, self-scheduling and reregistration forms is now wrapped by its existing <label> (it was rendered off-screen — not display:none — so it stayed in the accessibility tree, meaning a screen-reader user could fill the "invisible" trap and be rejected as a bot); and the audience calendar's environment filter <select> gains an aria-label. Behaviour-preserving — the spam check keys on the field name, unchanged — and no new violations introduced (implicit label association, so no duplicate-id). Found via a one-off axe/WCAG 2.2 AA pass.
Reregistration settings-tab icon back to monochrome (#951): the new Recadastramento tab used the ffc-icon-id badge (🆔), which the admin emoji font renders as a solid colour and broke the flat/mono icon row in the Settings nav; switched to the ffc-icon-clipboard glyph, which that font renders monochrome (as the Activity-Log tab already does). CSS-class-only change; the ffc-icon-id badge stays where an ID connotation fits (CPF rate-limit heading, CPF-split migration card).
pt_BR translations for recent features (#951): the appointment-receipt / Document Templates hub UI, the self-scheduling waitlist and custom-calendar/occupancy strings (#941), and the new Settings "Document Templates" tab were showing in English — the pt_BR catalog is brought up to date (73 strings) and the runtime .mo / .l10n.php regenerated so they load. Translation assets only; no code change.
Submission auto-delete now works — and is explicitly opt-in (#936): the daily cleanup read a standalone ffc_cleanup_days option that was never written (the value lives in ffc_settings['cleanup_days']), so age-based deletion of old submissions never ran regardless of the configured retention — a silent data-retention gap. It now reads the real setting, gated behind a new "Auto-delete old submissions" toggle (default off) plus a day window (≥ 1, default 365). ⚠️ Because the feature was dormant, it ships off so upgrades change no behaviour; enabling it will permanently delete published submissions older than the window on the next daily run.
In-admin Documentation tab accuracy (#934): the Template Variables reference now describes {{date}} / {{fill_date}} as the submission date (alias of {{submission_date}}), not the PDF generation date, and drops {{nome}} (only satisfies save-time validation, never renders); Quiz {{score}} / {{max_score}} are documented as point sums, not answer/question counts; and a stale "Settings → Geofence" cross-reference now points to "Settings → Geolocation". pt_BR translations updated to match.
Security
Self-scheduling confirmation escapes its scalar tokens (#965): the booking-confirmation email now esc_html-escapes the user- and admin-supplied scalar values it substitutes ({{user_name}}, {{user_email}}, {{calendar_title}}, notes) and sanitises the per-calendar custom body with wp_kses_post (was sanitize_textarea_field), so an editable/tokenized body can never inject raw HTML from booking data.
Per-user dashboard kept off the Cloudflare edge cache (#921): the [user_dashboard_personal] page now also emits Cloudflare-CDN-Cache-Control: no-store / CDN-Cache-Control: no-store — Cloudflare ignores DONOTCACHEPAGE / X-LiteSpeed-Cache-Control, so these edge-specific directives keep per-user HTML from being shared across visitors under CDN cache rules that honour them.
Recruitment public listing throttle is now fail-closed (#927, #931): the per-IP rate limit on [ffc_recruitment_queue] resolves the client IP via the shared resolver (RequestInput::get_user_ip()) instead of a bespoke header walk, and no longer waves through a caller whose IP can't be identified — they share the 0.0.0.0 bucket. Closes the fail-open gap; header-rotation evasion is ineffective once the secure strategy is enabled.