v6.13.0
Security
- Settings → Geolocation and Rate Limit tabs gated their inline form save on a nonce only, not a capability. Because the Settings page opens on
ffc_view_settingsand the read-only affordance is a client-side<fieldset disabled>, a view-only user could POST the page nonce directly to change anti-fraud geolocation/rate-limit settings, whitelist their own IP/email/CPF, and add/edit/delete geofence locations. Both tabs now requireffc_manage_settingsfor every mutation, matching all sibling settings paths. (#637) - Public certificate verification leaked unmasked PII: the
/validpage renderer printed the barerf(Registro Funcional) in full next to the already-maskedcpf_rf, and the public/verifyREST endpoint returned rawemailandrf. Both fields are now masked (mask_rf()/mask_email()) on the public paths, consistent with the existing CPF masking. (#637) - Audience booking REST reads (
GET /ffc/v1/audience/bookingsand the conflict probe) applied no schedule-visibility check, so unauthenticated callers could read bookings — dates, times, descriptions, environment and audience-group names — from schedules markedprivate. Reads are now constrained to the caller's readable schedule set (admins/bypass unrestricted, logged-in users their accessible schedules, anonymous users active public schedules only), mirroring the shortcode's visibility gate. (#637) - CSV exports were vulnerable to spreadsheet formula injection (CSV/DDE): unauthenticated form-submission values reaching the shared
CsvWriterwere written verbatim, so a cell starting with=,+,-,@, TAB or CR would execute as a formula when a privileged operator opened the file. Such cells are now neutralized with a leading single quote at the canonical write point, covering every exporter. (#637) - One-use form "ticket" restrictions could be bypassed by a race condition: the ticket was consumed with a non-atomic post-meta read-modify-write, so two concurrent submissions could both pass the membership check and each issue a certificate from a single ticket. Ticket consumption now makes an atomic
INSERT IGNOREclaim against a UNIQUEwp_optionsrow (the same single-use pattern as the scheduling exception tokens), so exactly one concurrent caller wins and the rest are rejected as already-used. (#638)
Added
- Short URLs admin page now shows a "Settings" shortcut (a standard
.page-title-actionbutton next to the page title) linking straight to the URL Shortener settings tab (ffc-settings&tab=url_shortener). Gated on the settings view cap so it only appears for users who can open that page. (#627)
Fixed
- The develop→testes deploy no longer excludes the bundled
html/templates directory.html/holds the plugin's built-in certificate/ficha/receipt layouts, which the form editor loads at runtime (glob( FFC_PLUGIN_DIR . 'html/*.html' )), but the rsync excluded it — likely mistaken for a coverage-report dir — so the testes site had an empty layout-template picker and 404s when loading a default template. (#629)
Removed
- Deleted two files that did not belong in the plugin source:
html/atestado_estagios.html(an install-specific template) andhtml/ludmila_santos.png(a real individual's scanned signature). Neither is referenced by any bundled template or code. (#629)
Fixed
- Scheduling menu section separators ("Self"/"Audience") lost their dashicons and became clickable on admin screens that don't load
ffc-audience-admin.css(e.g. the self-scheduling CPT list/new screens). The global fallback registered the separator styles viawp_add_inline_style( 'admin-menu', … )onadmin_head, which fires afteradmin_print_styles, so the inline style was attached too late to ever output. Registered onadmin_enqueue_scriptsinstead so the separators keep their icons and non-clickable styling on every admin page. (#625) - Recruitment admin tabs now highlight the open tab in the wp-admin sidebar. The tab submenus register slugs like
ffc-recruitment&tab=candidates, but WordPress resolves the "current" row from the?page=value alone (alwaysffc-recruitment), so "Notices" stayed highlighted on every tab and internal pages didn't track the sidebar. Added asubmenu_filefilter mapping the current?tab=onto its submenu slug. (#625) - Recruitment admin screens — fixed three latent fatal errors in extracted templates that referenced classes retired in the #594 façade cleanup, so the affected branches would
Fatal error: class not foundwhen rendered:notice-edit/general-section.php(RecruitmentNoticeRepository→RecruitmentNoticeReader),notice-edit/classification-filters-form.php(RecruitmentAdjutancyRepository→RecruitmentAdjutancyReader), andadmin-page/tabs.php(added the missinguseimport forRecruitmentAdminPage). Surfaced by new render smoke-tests. (#618)
Changed
- Internal (CI) — the develop→testes deploy keeps its 3 rsync attempts but spaces them further apart (a fixed 120s between attempts instead of 20s/40s, a ~5.5-minute total window): the old backoff was shorter than a typical managed-hosting restart, so all attempts landed inside the same outage and the testes site silently stayed on a stale version. (#628)
- URL Shortener is now a top-level admin menu with its own sidebar icon (
dashicons-admin-links, contiguous with the other FFC menus) instead of a submenu under theffc_formCPT — it's a standalone module. The menu still only appears when the module is enabled in settings (UrlShortenerLoader::init()bails before registering it otherwise). The page URL moves fromedit.php?post_type=ffc_form&page=ffc-short-urlstoadmin.php?page=ffc-short-urls; all in-page links and redirects updated accordingly. (#625)
Changed
-
Internal (#563 — coverage) — took every remaining sub-80% module to ≥80%:
api(UserAudience/Form REST controllers),reregistration(ficha generator, data processor, activator),repositories(submission reader/writer),settings(all tab classes),url-shortener(admin-page/meta-box/qr-handler),generators(PdfGenerator),(root)(Loader),shortcodes(DashboardShortcode),submissions(lifecycle service) andmigrations(CPF/RF-split strategy). Everyincludes/module is now ≥80% (lowest: audience 80.7%); overall PHP statement coverage 82.95%→86.37%, and the floorCOVERAGE_FLOOR_LINESis ratcheted 78→82. Tests only. -
Internal (#563 — coverage) — raised
admin71.1%→91.3% andfrontend70.5%→92.4% (both also clearing 90%) with AJAX-export, list-table, edit-page, render and submission-pipeline tests across SubmissionsList, AdminSubmissionEditPage, the Admin orchestrator, ActivityLogPage, UserCustomFields, ConditionalAssets, FormListColumns, Settings, PublicCsvDownload (+ AJAX handlers), SubmissionPersister, VerificationHandler (+ AJAX), PublicCsvExporter, and the rate-limit/pdf/success stages. Overall PHP statement coverage 77.96%→82.95%; the floorCOVERAGE_FLOOR_LINESis ratcheted 73→78. Tests only. -
Internal (#563 — coverage) — lifted the last two sub-70% modules over the line:
admin65.7%→71.1% (CsvExporter AJAX export, SettingsActionHandler routes, SettingsSaveHandler, PreflightStatsService) andrecruitment56.6%→82.0% (the three REST controllers, the four list-tables, the reason/adjutancy edit-pages, CandidateReader, CandidatePersister, and the notice-edit/admin-page renderers). Everyincludes/module is now ≥70%; overall PHP statement coverage 69.16%→77.96%, and the floorCOVERAGE_FLOOR_LINESis ratcheted 67→73. Tests only. -
Internal (#563 — coverage) — pushed the
frontend,url-shortenerandsettingsmodules over the 70% line (step #3 cluster):PublicCsvExportersync-limit + AJAX batch/download paths (4%→34%); url-shortenerhandle_actionsremoval branches, meta-boxenqueue_assets, qr-handlergenerate_svg(module ~63%→73%);TabGeolocationenqueue_scripts+ location-delete logic (44%→78%, settings module ~60%→70%). Tests only. -
Internal (#563 — coverage) — extended
SelfSchedulingShortcodeTestto drive theSelfSchedulingShortcoderender paths end-to-end: the full booking-interface render, the private-visibility (show/hide modes) and private-scheduling messages, the business-hours viewing restriction, the approval notice, and the fullenqueue_assets()asset/localize path. The shortcode goes 6%→94%; theself-schedulingmodule clears 70% (70%→82%). Tests only. -
Internal (#563 — coverage) — added
ActivatorMigrationsTestcovering the #249 instant-column migrations (maybe_migrate_submission_date_to_unixincl. the destructive rename path,maybe_migrate_submitted_at_to_unix,maybe_migrate_sibling_instants_to_unix),maybe_add_perf_indexes, and theupgrade_auth_code_unique_constraintshelper — option-flag short-circuits + table/column-guarded run paths.Activator59%→84%; the(root)module clears 70% (~75%). Tests only. -
Internal (#563 — coverage) — extended
QRCodeGeneratorTestto cover the per-submission QR cache read/write (get_from_cache/save_to_cache) and theparse_and_generate()cache-hit / cache-after-generate paths.QRCodeGenerator67%→76%; thegeneratorsmodule clears 70% (~72%). Tests only. -
Internal (#563 — coverage) — extended
IpGeolocationTestto coverget_location()and its fetch/cache/cascade paths (ip-api + ipinfo success/error responses, transient cache hit, primary→alternative cascade, unknown-service guard, request-IP fallback) with URL-dispatchedwp_remote_getstubs.IpGeolocation38%→97%; theintegrationsmodule clears 70% (~97%). Tests only. -
Internal (#563 — coverage) — added a dedicated unit test for
CsvStagingService(the four-phase batched CSV import: ingest → validate → promote → commit), covering each phase's happy path and guard/error branches with alias-mocked collaborators + a partial$wpdb. The class goes 0%→94% (353/374); therecruitmentmodule50%→55%, overall PHP coverage70.2%→71%. -
Internal (#563 — coverage hygiene) —
@covers-gap audit:FormEditorSaveHandlerandCsvValidatorwere exercised by their dedicated tests (FormEditorSaveHandlerTest,RecruitmentCsvImporterTest) but filtered out of coverage because those tests@covers'd only a sibling/parent class. Added the missing@covers(+class_exists()preloads), attributing the existing execution —FormEditorSaveHandler0%→70%,CsvValidator0%→97%, overall PHP coverage 69.16%→70.17%. No new test code; no behavior change. -
Internal (#563 — coverage hygiene) — ratcheted the PHP coverage floor
COVERAGE_FLOOR_LINES66 → 67 after the markup-extraction sweep (#605/#606/#607) moved ~788 uncovered statements intotemplates/(out of scope); re-measured 69.16%. -
Internal (#563 — coverage hygiene) —
SubmissionHandlerTestalready exercisesSubmissionHandlerend-to-end (process/update/trash/restore/delete/bulk/decrypt/magic-token, 47 tests), but its@coverslisted only the extractedSubmissionLifecycleService, so PHPUnit filtered the handler's executed lines out (reported 0%). Added the missing@covers \\FreeFormCertificate\\Submissions\\SubmissionHandler(+ aclass_exists()preload), attributing the existing coverage — the handler goes 0%→90% and thesubmissionsmodule 43%→76%. No new test code; no behavior change. -
Internal refactor (#563 — coverage hygiene) — extracted the inline admin markup from
ReregistrationAdminRenderer(campaign list + row, create/edit form, submissions list + row, audience transfer list) intotemplates/admin/reregistration/*.phppartials. Markup is byte-identical; the renderer keeps the data-prep logic and includes each partial (self::sibling renderers resolve in the including method scope). The view class shrinks 691→293 lines (403→122 in-scope statements), moving pure presentation out of the coverage scope per thetemplates/convention. TheAdminUI::render_toggle()calls move into the form partial too, eliminating theReregistration→Adminmodule-boundary edge (baseline tightened, 130→129). -
Internal refactor (#563 — coverage hygiene) — extracted the inline admin markup from
UrlShortenerAdminPage::render_page()(stats cards, create form, search/filter, the links table and the QR-code modal) intotemplates/admin/url-shortener/short-urls-page.php. Markup is byte-identical; the controller keeps the data-prep/pagination logic and includes the partial. The class shrinks 626→365 lines (371→174 in-scope statements), moving pure presentation out of the coverage scope per thetemplates/convention. -
Internal refactor (#563 — coverage hygiene) — extracted the inline admin markup from
RecruitmentAdminPageRenderer(settings tab, candidates CSV-import section, the create-notice/adjutancy/reason forms, the tab nav, the first-run empty state and the REST pointer) intotemplates/admin/recruitment/admin-page/*.phppartials. Markup is byte-identical; the renderer keeps only the data-prep/capability logic and includes each partial. The view class shrinks 729→354 lines (437→127 in-scope statements), moving pure presentation out of the coverage scope per the establishedtemplates/convention.
Fixed
- Recruitment admin templates — fixed three latent fatal references to symbols removed in the #594 Reader/Writer façade retirement, surfaced while adding render-test coverage (#563).
templates/admin/recruitment/notice-edit/general-section.phpandclassification-filters-form.phpreferenced the retiredRecruitmentNoticeRepository/RecruitmentAdjutancyRepositoryclasses (nowRecruitmentNoticeReader/RecruitmentAdjutancyReader), andtemplates/admin/recruitment/admin-page/tabs.phpreferencedRecruitmentAdminPageunqualified in a global-namespace file (added the missinguseimport). Each would have thrown a "class not found" fatal when its branch rendered. (#563)