Skip to content

v6.13.0

Choose a tag to compare

@github-actions github-actions released this 15 Jul 22:32
· 16 commits to main since this release
b0d8d9a

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_settings and 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 require ffc_manage_settings for every mutation, matching all sibling settings paths. (#637)
  • Public certificate verification leaked unmasked PII: the /valid page renderer printed the bare rf (Registro Funcional) in full next to the already-masked cpf_rf, and the public /verify REST endpoint returned raw email and rf. 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/bookings and the conflict probe) applied no schedule-visibility check, so unauthenticated callers could read bookings — dates, times, descriptions, environment and audience-group names — from schedules marked private. 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 CsvWriter were 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 IGNORE claim against a UNIQUE wp_options row (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-action button 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) and html/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 via wp_add_inline_style( 'admin-menu', … ) on admin_head, which fires after admin_print_styles, so the inline style was attached too late to ever output. Registered on admin_enqueue_scripts instead 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 (always ffc-recruitment), so "Notices" stayed highlighted on every tab and internal pages didn't track the sidebar. Added a submenu_file filter 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 found when rendered: notice-edit/general-section.php (RecruitmentNoticeRepositoryRecruitmentNoticeReader), notice-edit/classification-filters-form.php (RecruitmentAdjutancyRepositoryRecruitmentAdjutancyReader), and admin-page/tabs.php (added the missing use import for RecruitmentAdminPage). 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 the ffc_form CPT — 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 from edit.php?post_type=ffc_form&page=ffc-short-urls to admin.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) and migrations (CPF/RF-split strategy). Every includes/ module is now ≥80% (lowest: audience 80.7%); overall PHP statement coverage 82.95%→86.37%, and the floor COVERAGE_FLOOR_LINES is ratcheted 78→82. Tests only.

  • Internal (#563 — coverage) — raised admin 71.1%→91.3% and frontend 70.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 floor COVERAGE_FLOOR_LINES is ratcheted 73→78. Tests only.

  • Internal (#563 — coverage) — lifted the last two sub-70% modules over the line: admin 65.7%→71.1% (CsvExporter AJAX export, SettingsActionHandler routes, SettingsSaveHandler, PreflightStatsService) and recruitment 56.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). Every includes/ module is now ≥70%; overall PHP statement coverage 69.16%→77.96%, and the floor COVERAGE_FLOOR_LINES is ratcheted 67→73. Tests only.

  • Internal (#563 — coverage) — pushed the frontend, url-shortener and settings modules over the 70% line (step #3 cluster): PublicCsvExporter sync-limit + AJAX batch/download paths (4%→34%); url-shortener handle_actions removal branches, meta-box enqueue_assets, qr-handler generate_svg (module ~63%→73%); TabGeolocation enqueue_scripts + location-delete logic (44%→78%, settings module ~60%→70%). Tests only.

  • Internal (#563 — coverage) — extended SelfSchedulingShortcodeTest to drive the SelfSchedulingShortcode render 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 full enqueue_assets() asset/localize path. The shortcode goes 6%→94%; the self-scheduling module clears 70% (70%→82%). Tests only.

  • Internal (#563 — coverage) — added ActivatorMigrationsTest covering the #249 instant-column migrations (maybe_migrate_submission_date_to_unix incl. the destructive rename path, maybe_migrate_submitted_at_to_unix, maybe_migrate_sibling_instants_to_unix), maybe_add_perf_indexes, and the upgrade_auth_code_unique_constraints helper — option-flag short-circuits + table/column-guarded run paths. Activator 59%→84%; the (root) module clears 70% (~75%). Tests only.

  • Internal (#563 — coverage) — extended QRCodeGeneratorTest to cover the per-submission QR cache read/write (get_from_cache/save_to_cache) and the parse_and_generate() cache-hit / cache-after-generate paths. QRCodeGenerator 67%→76%; the generators module clears 70% (~72%). Tests only.

  • Internal (#563 — coverage) — extended IpGeolocationTest to cover get_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-dispatched wp_remote_get stubs. IpGeolocation 38%→97%; the integrations module 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); the recruitment module 50%→55%, overall PHP coverage 70.2%→71%.

  • Internal (#563 — coverage hygiene) — @covers-gap audit: FormEditorSaveHandler and CsvValidator were 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 — FormEditorSaveHandler 0%→70%, CsvValidator 0%→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_LINES 66 → 67 after the markup-extraction sweep (#605/#606/#607) moved ~788 uncovered statements into templates/ (out of scope); re-measured 69.16%.

  • Internal (#563 — coverage hygiene) — SubmissionHandlerTest already exercises SubmissionHandler end-to-end (process/update/trash/restore/delete/bulk/decrypt/magic-token, 47 tests), but its @covers listed only the extracted SubmissionLifecycleService, so PHPUnit filtered the handler's executed lines out (reported 0%). Added the missing @covers \\FreeFormCertificate\\Submissions\\SubmissionHandler (+ a class_exists() preload), attributing the existing coverage — the handler goes 0%→90% and the submissions module 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) into templates/admin/reregistration/*.php partials. 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 the templates/ convention. The AdminUI::render_toggle() calls move into the form partial too, eliminating the Reregistration→Admin module-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) into templates/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 the templates/ 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) into templates/admin/recruitment/admin-page/*.php partials. 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 established templates/ 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.php and classification-filters-form.php referenced the retired RecruitmentNoticeRepository / RecruitmentAdjutancyRepository classes (now RecruitmentNoticeReader / RecruitmentAdjutancyReader), and templates/admin/recruitment/admin-page/tabs.php referenced RecruitmentAdminPage unqualified in a global-namespace file (added the missing use import). Each would have thrown a "class not found" fatal when its branch rendered. (#563)