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.
Fixed
Short URLs of posts/pages now follow the current permalink (#888): a post's short URL stored the permalink captured at creation, so changing the slug (or the site's permalink structure) stranded it; the redirect, the admin Destination column and the CSV export now resolve a post-linked short URL from the post's current permalink (falling back to the stored target only if the post is gone). Manual links keep their stored destination. Self-healing, no migration.
Certificate layout toolbar polish (#865): the Import HTML / Background Image / Load buttons now share one size, border and dashicon alignment, and the duplicated inline-dashicon + leftover emoji rules were removed.
Default certificate templates no longer copy shipped images into the Media Library (#865): a default with a stale html/ image ref made the Rewrite html/ Image References migration side-load it into every site's uploads; the migration now skips shipped defaults and a seeder bump refreshes their bodies to the assets/ source.
Internal (#849) — the GitHub-updater's SHA-256 mismatch error is now an English source string (was hard-coded pt_BR), so the translation flows through the catalog.
User-edit FFC role assignments survive the native "Update user" save (#826): FFC roles are snapshotted and re-applied on profile_update (core's single-role <select> was collapsing the user back and dropping the FFC role); the role chips now also surface the real failure reason and disable the non-Administrator chips for admin targets (per the #739 policy).
Internal (#822) — the datetime→unix safety-net migration now targets the real ffc_self_scheduling_appointments table (it pointed at a non-existent name and silently no-op'd); behaviour-preserving.
Added
URL Shortener enhancements (#886, #888, #889): a per-post-type Expose column publishes a page's short URL as the WordPress canonical shortlink — the rel="shortlink"<head> tag + HTTP Link header, via pre_get_shortlink (opt-in, a server-enforced subset of the shortened types); an Edit action changes a manual link's destination/title (post-linked ones follow the page); a Generate missing short URLs backfill creates links for already-published posts; and GET /ffc/v1/short-urls/{code}/qr returns a link's QR as base64 (cap-gated by ffc_view_url_shortener via an Application Password, for external automations like N8N). All reuse the existing qr_cache/generation and add no module coupling.
Certificate template pool (#865): a database-backed ffc_cert_template pool (gated by ffc_manage_forms / ffc_view_forms) with a Templates management screen — list table, Show/Hide visibility toggle, HTML-body editor, Duplicate / Export (.html) / Add New / Restore defaults, and a sample-data Preview iframe — plus a non-destructive versioned seeder for the shipped defaults. The form editor's layout box wires to it: Load reads the pool server-side (replacing the unauthenticated html/ fetch), Save as model stores the current HTML, and Insert Image / Background Image use wp.media. Shipped defaults are read-only (Duplicate to customize). The first step toward retiring the update-fragile html/ folder.
Certificate branding tokens {{logo_gov}} / {{logo_org}} (#865): two Settings → General → Branding logo URLs resolve into the certificate, ficha and appointment-receipt templates (Media Library picker, shipped fallback), de-hardcoding the instance-specific logos.
Retire the html/ drop-folder — migration cards (#865): two Settings → Migrations cards (gated by ffc_manage_settings_dangerzone) import any user certificate layouts left in html/ into the template pool and rewritehtml/ image references (form layouts, backgrounds and pool bodies) into the Media Library — each distinct file side-loaded once, idempotent, missing files reported; plus an admin notice flagging any remaining html/ reference.
Authenticated operator REST API (#858, #840): POST /ffc/v1/operator/certificates issues a certificate (reusing the public form's SubmissionHandler + validation) and GET …/{id}/pdf returns its pdf_data, both via WordPress Application Passwords. Issuance is gated by ffc_manage_certificates; the PDF route by that cap or certificate ownership. Omits the public path's anti-anonymous defenses (a per-user_id rate-limit guards instead) and audits each issuance; additive — the existing handlers are untouched.
Encryption Key Health — one-click key/salt suggestions (#851, #846): while FFC's encryption key still derives from the WordPress salts, the Advanced-tab card offers ready-to-paste FFC_ENCRYPTION_KEY / FFC_HASH_SALT snippets (64 chars, generated client-side over a PHP-literal-safe charset, never saved or sent) with Generate another / Copy, each carrying a self-documenting phpdoc comment and the mandatory rotation caveat. Hidden once decoupled.
Auto-update via GitHub Releases (#820): WordPress's update check now sees the plugin's GitHub Releases — the update appears in Dashboard → Updates with the release changelog and the per-plugin auto-update toggle (opt-in). Points at the built ffcertificate-X.Y.Z.zip, caches 12h with an ETag request, verifies the package SHA-256 before install, and adds an Update URI: header so wp.org can't hijack the slug.
Internal (#809) — added a tests/Integration/ layer: a PluginBootstrapSmokeTest boots the real plugin object graph over a stubbed WordPress boundary and asserts it composes and lands its hooks / crons / roles (the wiring the unit LoaderTest deliberately excludes). Tests only; the coverage floor is untouched.
Changed
Internal (#890) — plugin-wide inline-style consolidation: lifted static inline style="…" across the admin/settings, audience, recruitment and admin-template views into reusable classes — spacing/layout utilities (ffc-mt-8/12/16, ffc-mb-2/4, ffc-ml-18, ffc-inline, ffc-list-disc) plus component rules — and moved the colour swatches/dots onto the --ffc-color custom property. No behaviour change; email templates, the PDF-rendered certificate/ficha/receipt HTML, JS-toggled display states and documentation examples were left inline by necessity.
⚠ Internal (#883) — CSS class-name consistency pass: aligned shorthand admin classes to the spelled-out ffc- kebab convention (ffc-em-* → ffc-email-model-*, ffc-sched-exc-* → ffc-schedule-exception-*, the CSV info-table ffc-j* → ffc-info-table-*, and the margin utilities ffc-mt15/ffc-mt20/ffc-mb5 → ffc-mt-15/ffc-mt-20/ffc-mb-5). No behaviour change; breaking only for custom CSS that overrode the old class names.
Certificate editor UX (#865): the template edit screen gained the form editor's CodeMirror (read-only for defaults); shipped defaults are read-only except an AJAX-autosaving Show in Load list sidebar toggle (now a .ffc-toggle switch); and the default templates' full-page background moved from a baked <img> into the dedicated Background Image field (seed v3, so Load carries it into the form like any user template).
Shipped default assets moved out of html/ (#865): the default background/signature images now live in assets/img/certificate-defaults/ and the ficha/receipt templates in templates/documents/, both versioned/update-safe; only the legacy html/default_certificate_* glob remains there as a deprecated fallback.
Internal (#882) — the develop → testes deploy fails fast instead of hanging on a stalled SSH connection (BatchMode=yes, a timeout 300 around rsync, a 25-min job backstop). Deploy/CI-only.
Internal (#850, #849) — swept the ~50 hand-built <div class="notice …"> admin notices onto WordPress's canonical wp_admin_notice() (WP 6.4+) and extracted a shared AbstractDismissibleNotice base for the persistent-dismiss notices; bumped Requires at least 6.2 → 6.4 to use it. Behaviour-preserving.
Encryption Key Rotation now requires both decoupling constants (#857): the rotation card and its gate stay hidden until bothFFC_ENCRYPTION_KEY and FFC_HASH_SALT are defined (rotating with only the key set would rebuild the search hashes under the shared salt and force a second rotation), and the Advanced panel surfaces a pending-record count linking to it.
Internal (#809, #822) — updated the vendored thumbmarkjs 1.9.1 → 1.10.1 (drop-in; the privacy logging:false beacon-disable is preserved), flag-guarded the user_id → wp_users foreign-key migration so it stops re-writing an all-skipped audit line on every activation, and pushed audience test coverage (tests only, no product change).
Docs (#822, #800, #802, #820, #858, #711) — recorded the reregistration accepted-orphan user-deletion decision and rewrote CLAUDE.md §4 with the "retain records, drop relationships" principle; and closed documentation-tab gaps for Modules, the Activity Log + its CSV export, Privacy & LGPD, Updates, the operator REST API and the Short URLs export cap. Docs only.
Deprecated
Legacy html/ certificate-layout glob (#865): the pre-pool fallback that discovered certificate layouts by globbing the plugin's html/ folder is deprecated in favour of the database template pool. On the project's 3-release cycle it is scheduled for removal at N+3, after which the (emptied) html/ folder is retired; sites relying on files dropped there should import them (Settings → Migrations) first.
Removed
Internal (#849) — removed the now-dead ffc-device-threshold-notice.js (the device-threshold notice uses the shared ffc-dismissible-notice.js after the AbstractDismissibleNotice extraction).
Internal (#809) — retired the pre-4.6.15 orphan-cron cleanup calls (inert on any install newer than 4.6.15); behaviour-preserving.
⚠ (#809) — removed the pre-6.2.0 certificate-cap rename migration. Dormant on every install that passed through 6.2.0–6.17.0. Edge case: a site upgrading directly from pre-6.2.0 to 6.18.0 skips the rename — first update to any 6.2.0–6.17.0 release, then to 6.18.0.
⚠ Breaking (#739) — CptEditorCompat shim removed. WordPress Editors (holders of edit_others_posts) no longer receive ffc_manage_forms / ffc_manage_calendars — completing the 6.16.0 CPT-capability decoupling (its two-release window has closed). Action: grant the FFC Administrator role — or the management capability — to the users who need it.
Security
⚠ REST PII masking (#838): the GET /submissions[/{id}] and public POST /verify endpoints no longer leak unmasked PII — /verify now masks cpf/rf/rg/email like the /valid page, and /submissions returns plaintext only to the ffc_view_certificates_pii cap (audited). Per-key masking now lives in the shared DocumentFormatter::mask_field_value(). Breaking for external REST consumers on a view/operator role: grant ffc_view_certificates_pii to keep plaintext.
Public certificate CSV minimized (#836): dropped the magic-link Token column (a live access credential leaking in bulk) plus the User IP / Consent / Status columns; the admin export is unchanged and /valid now emits noindex. Public CSV column layout changed.
Encryption key rotation + FFC→FFC re-keying (#857, #863, #846, #840): a new Encryption Key Rotation migration (Settings → Migrations, gated by ffc_manage_settings_dangerzone) re-encrypts submissions + appointments PII under a strong FFC_ENCRYPTION_KEY and rebuilds the CPF/RF/email search hashes under FFC_HASH_SALT. Encryption::decrypt() gains a fallback chain (active → FFC-previous → WP-derived) so rows stay readable during rotation — define FFC_ENCRYPTION_KEY_PREVIOUS for a zero-downtime FFC→FFC re-key (finish one rotation before starting the next). A key/salt health surface (Encryption::key_health() → ok/weak/placeholder/empty) flags predictable/placeholder secrets via a dismissible notice + an Advanced-tab panel (source, fingerprint, decoupled?), never disabling encryption.
Rate-limit + input-hardening batch (#839): the ffc_rate_limits counter table now hashes non-IP identifiers (email/CPF) at rest; the recruitment classifications CSV import validates extension + a 10 MB cap before reading; the no-JS [ffc_verification] fallback applies the same throttle as the AJAX path (closing an un-throttled auth-code probing hole); and the nginx deny rule for the CSV-export temp dir is documented.
User-deletion integrity — attribution + promoted-candidate coverage (#834): the deleted_user hook now also SET-NULLs the nullable actor-attribution columns (edited_by, approved_by/cancelled_by, created_by/updated_by, reviewed_by, …) and the promoted-candidate link, so a deleted admin/operator's id no longer dangles as stale attribution. The NOT NULLcreated_by columns stay accepted orphans on retained records; row-body encrypted PII is scrubbed only by the manual PrivacyErasers (LGPD erasure) path, by design.
Fixed two JS attribute-context escaping gaps (#837, #564): the admin submission-edit user search and the dashboard profile edit now encode "/' in data-* / value / style attributes, closing a stored-XSS via a crafted display_name (a privilege-boundary crossing) and a self-XSS. Bundles rebuilt.
Dev/CI dependency security bumps (#884): squizlabs/php_codesniffer 3.13.5 → 3.13.6 (CVE-2026-67434, OS command injection) plus the npm build-chain advisories (brace-expansion, js-yaml, postcss) via lockfile. Dev/build tooling only — not shipped in the plugin runtime.