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
Internal (#809) — CI coverage-job hardening: the shard partitioner now weights test files by test-method count and LPT-packs the four shards (isolated-method spread across shards ~31% → ~0%), and the coveralls.io upload step is continue-on-error so a 503 outage can't block merge — the line-coverage floor stays enforced by its own separate step.
Internal (#809) — WPCS suppression audit (comment-only): removed a superfluous EscapeOutput ignore, swapped eight silent FormCache cache-purge catches for a gated Debug::log_form breadcrumb (off by default), and narrowed the file-wide phpcs:disable blocks across 46 files (28 no-op/redundant dropped, 18 trimmed to their load-bearing sniff).
Internal (#809 Fase 1) — recruitment coverage push (tests only, no product change): reader tests for RecruitmentCallReader (~35% → ~96%) and RecruitmentNoticeReader (~25% → ~94%) (#812), plus the two heaviest renderers RecruitmentPublicShortcodeRenderer (~46% → ~99%) and RecruitmentAdminPage (~39% → ~99%).
Internal (#807 follow-up) — raised unit coverage on three under-tested logic classes (tests only, no product change): recruitment CsvParserparse()/normalise_id() (~14% → ~96%), ScheduleExceptionGuard's live-token contract (~13% → ~87%), and the per-email rate limiter's week/month/allowed branches (~40% → ~98%).
Internal (#807) — extracted inline HTML from four markup-heavy renderers into templates/ partials (outside the coverage scope), keeping all logic in the class: RecruitmentPublicShortcodeRenderer, ReregistrationFormRenderer, AudienceAdminSettings, RecruitmentCandidateEditPage. Behaviour-preserving; adds a first render test for the candidate-edit page.
⚠ Activity Log moved into Settings (#804, #802 Phase B): it is now a Settings tab (page=ffc-settings&tab=activity_log) gated by its own ffc_view_activity_log / ffc_export_activity_log caps, replacing the standalone Activity Log submenu. Audit-only operators (e.g. the read-only role) reach the Settings page via the computed page-entry meta-cap and see only this tab. Breaking: old page=ffc-activity-log bookmarks/deep links no longer resolve.
Internal (#803) — Settings now uses a per-tab capability model (#802 Phase A): each tab declares its own view/manage cap (defaulting to ffc_view_settings / ffc_manage_settings), the page renders only the tabs a user can view, and the read-only lock keys on the active tab's manage cap. The menu registers under a computed ffc_view_settings_page meta-cap so it appears iff ≥1 tab is visible.
Internal (#800) — decoupled Settings from AdminLoader (the orchestrator wires it directly as an always-on cross-cutting service) and relocated the Recruitment schema/role lifecycle out of RecruitmentLoader into the orchestrator, so the new module toggles can skip a feature bootstrap without disabling the Settings screen or dropping recruitment tables/roles.
Settings moved to a dedicated top-level admin menu (admin.php?page=ffc-settings) instead of hanging under the Certificate CPT menu, so the new Modules tab has a module-agnostic home. ⚠ The page URL changed from edit.php?post_type=ffc_form&page=ffc-settings; old bookmarks won't resolve. All in-app links and post-save redirects updated (#799).
The dashboard "Join Groups" list now follows the per-node self-join model (#791 follow-up): a group shows a Join/Leave button whenever its own self-join is on — child, top-level, or a parent that also has joinable children — and a non-self-join parent still appears as a header when it has joinable descendants; unrelated groups are hidden.
Admin label ordering is now alphabetical by the translated label, locale-aware (accents collate correctly) with stable anchors, via a new shared Core\LabelSorter: the Settings tabs, the Recruitment and Scheduling Settings sub-tabs, and the role→capability editor / per-user permission-panel groups (within each Self-service / Administration section) — the latter replacing module-map order (#787, #797).
The audience/reregistration and certificate form-editor field-type <select>s now show translated labels instead of a raw ucfirst() of the slug (e.g. "Dependent_select" → "Dependent Select"), alphabetized with display-only types pinned last and sharing one canonical label map across the server-rendered select and the JS field builder (which now also offers Checkbox + Hidden Field). Slugs unchanged — display-only, no migration (#797, #798).
The six admin CSV exports (submissions, activity-log, url-shortener, appointments, audience-bookings, reregistration) now show the same progress-overlay modal as the public download instead of an inline text line — one shared FFCProgressOverlay component drives both surfaces (#786).
⚠ Breaking (#772) — CSV export hook rename.ffcertificate_csv_export_{headers,filename,data,completed} → ffc_export_{headers,filename,data,completed} (signatures unchanged). External integrations on the old names must migrate — there is no back-compat shim.
Internal (#772) — consolidated every CSV export behind one source contract with two Core adapters (timeout-safe BatchedCsvExport, synchronous CsvStreamer), a type-based SourceRegistry + one AJAX dispatcher, and a shared ffc-batched-export.js: the seven data exports moved to the batched engine (now id-DESC keyset order) and the bounded outputs to the sync adapter. Built on the injectable CsvStreamer pipeline + paged-appointment-export precursor (#764 / #757).
Internal (#739 follow-up) — two behaviour-preserving refactors: AbstractRecruitmentListTable, a shared fetch→search→sort→paginate→bulk-delete base for the Notices/Adjutancies/Reasons tables (~500 duplicated lines removed), and RecruitmentPiiAccessPolicy reduced to a thin adapter over the shared Core\PiiAccessPolicy 3-tier engine.
The FFC role admin surfaces were unified (#739 follow-up): "Blocked Roles" is grouped (FFC end users / WordPress core / third-party) with the admin ladder behind an auto-opening caveat disclosure; role display labels normalized to FFC <Module> - <Tier> (pt_BR re-keyed, slugs/caps unchanged); and both surfaces list roles in one canonical order — cross-cutting first, then each module ladder alphabetically, weakest→strongest.
Added
Per-module enable/disable toggles in the Settings → Modules tab (#800): a switch per feature module (Certificates, Audiences/Scheduling, Self-Scheduling, Reregistration, URL Shortener, Recruitment) that gates the module's bootstrap in Loader::init_plugin()and its scheduled crons (reregistration expiry/reminders, appointment reminders no longer run for a disabled module). All default ON; disabling hides a module's screens, stops its runtime and crons, but never deletes data. Saves instantly via the settings-autosave endpoint, with a confirmation prompt before a toggle goes off (generic data-ffc-confirm-off support added to ffc-admin-autosave.js).
New Modules tab in Settings (scaffold) — the future home for per-module enable/disable toggles (#799).
Recruitment convocation email now has a send mode (#794): Settings → Recruitment offers Always / Never / Ask. "Ask" surfaces a default-on "Notify by email" checkbox in the call confirmation modal — single and bulk — so the operator decides per call whether to notify the candidate; the global "disable all emails" switch still wins. Gated in RecruitmentEmailDispatcher::should_send().
Outbound emails are now tagged with a per-function mail-queue source (#793): when the sibling Total Mail Queue is active, each email carries an X-TMQ-Source-* header so it is attributed per feature (plugin:ffcertificate_certificate, _scheduling, _audience, _reregistration, _recruitment, _account, _admin) instead of one blanket plugin:ffcertificate row. The queue strips the header; gated on the queue being present (filter ffcertificate_emit_source_headers).
Internal (#764) — an injectable, unit-testable CSV output boundary: CsvDownloadInterface / HttpCsvDownload / CsvStreamer (pure orchestration over an iterable of rows).
CSV export for the Audience Bookings page (#760): "Export CSV" gated by ffc_export_audiences, carrying the page's schedule/environment/status/date filters (no direct PII — FK ids only).
CSV export for the URL Shortener page (#761): "Export CSV" carrying search/status/sort; adds a dedicated ffc_export_url_shortener cap, seeded onto current ffc_manage_url_shortener holders via a one-shot migration.
Fixed
URL Shortener QR modal — action-button icons sat ~3px low (#811): the Copy URL / PNG / SVG buttons are flex-centred, but the shared .ffc-dashicon-valign helper added a baseline margin on top; neutralised it for the modal action dashicons so the flex centring aligns them with their labels. CSS-only.
Cron lifecycle gaps on module toggle / deactivation (#800 follow-up): the Certificates daily expired-ticket cleanup (ffc_daily_expired_tickets_cleanup) ran unconditionally from AdminLoader even with Certificates off — now wired only when the module is on; and Deactivator::deactivate() never cleared ffcertificate_reregistration_expire_hook (only uninstall.php did), leaving a stale daily event — now cleared on deactivate and in uninstall_cleanup() (which also unschedules the expired-tickets cron for parity).
The Activity Log Settings tab showed a coloured emoji icon in the tab nav while the others are monochrome (#805, #804 follow-up): ffc-icon-clipboard was missing from the nav's dashicon override map and fell back to its emoji glyph. Mapped it to dashicons-list-view so it renders mono.
URL Shortener menu vanished even with its module toggle ON (#801, #800 follow-up): UrlShortenerService::is_enabled() strict-compared the enable slot against the integer 1, but the Modules-tab autosave persists it as a boolean true, so the module (and its admin menu) went offline. The stored value is now cast to int. Also removed the duplicate "Enable URL Shortener" toggle from the URL Shortener settings tab — the on/off switch lives solely on the Modules tab, and its form no longer zeroes the flag on save.
Self-join to a top-level audience failed with "Group not found or does not allow self-join" (#791): the join/leave/membership-count paths required parent_id IS NOT NULL, rejecting the standalone top-level self-join groups that the dashboard's joinable-groups list already offers. Any active self-join audience is now joinable, whether it's a child or a top-level group.
Dashboard appointment times were TZ-shifted (#790): the appointments REST controller rendered the wall-clock start/end by re-interpreting them as UTC instants (format_time(strtotime(...))), shifting them by the site offset (e.g. 13:00→10:00 in UTC-3) and making the "Export Calendar" end time wrong. Now uses format_wallclock_time; the export consumes a new raw end_time_raw field.
Audience Bookings "Export CSV" did nothing — no request, no console error (#772 regression): the click handler sat inside initCalendarPermissions(), which early-returns on the bookings page, so it never bound. Moved to its own initBookingsExport(); regression test hardened.
Public operator CSV download failed the page-nonce check at download (#772 regression): the shared driver $.extend-ed the routing type into a serialized-string startData, spreading it to char-index keys and dropping _ffc_pcd_nonce. It now appends &type= to a string payload.
Appointments Export CSV button was a dead <a href="#"> never wired to the exporter (#758); it is now a real nonce'd POST gated by ffc_export_appointments, carrying the on-screen filters.
Self-scheduling booking returned 403 on submit (#755): the serialized payload's trailing fallback nonce= overrode the form's; submit now passes the live ffcCalendar.nonce and strips the baked nonce/action.
FFC role labels showed untranslated in the admin (#739 follow-up): the relabel only ran on wp_roles_init (too early); it now also runs at init:1, and "Blocked Roles" renders names via translate_user_role().
Security
Closed four #739 RBAC escapes — CSRF-protected surfaces on raw WP caps not FFC caps: certificates-calendar REST → ffc_view_certificates; ffc_generate_codes/ffc_load_template → ffc_manage_forms; ffc_search_user → ffc_edit_certificates; Notices bulk-delete → ffc_delete_recruitment.
Removed
⚠ Breaking (#730) — legacy audit-log summary keys + UserDataRestController facade.PublicCsvDownload::get_audit_log_summary() / CsvDownloadAuditLog::get_summary() no longer return the deprecated success / fail keys — use access_success / failed_access (count stays). The 12 @deprecatedget_user_* delegate methods on UserDataRestController are removed — call the sub-controllers (UserProfileRestController, UserCertificatesRestController, …) directly. Both were deprecated in 6.15.0; the removal window (2nd feature release after) has closed. Zero internal callers; external integrators on the old names must migrate.