17 July 2026¶
Account data export, admin fixes & scaffolding polish. Self-service GDPR data export with an app-extensible hook, consistent account navigation across all three themes, the admin Tokens/Token Actions fixes, the init admin-password prompt, project:switch-ui, and assorted DevPanel/email/cache fixes.
6 changes:
- Self-service data export (GDPR) with an app-extensible hook
- Consistent account navigation across all three themes
- Admin Tokens & Token Actions pages fixed (all themes)
initprompts for the admin password (with a safe fallback)- New
project:switch-uicommand - Assorted fixes: DevPanel cache flush, email audit log, cache adapter
Self-service data export (GDPR) with an app-extensible hook¶
Users can now download a complete copy of their data from the account UI, and applications built on the framework can contribute their own sections to that export without touching the framework.
Added¶
Account::exportdata()— GET renders a confirmation page listing exactly what the download will contain; POST (CSRF-checked) streams a pretty-printed JSON attachment and records adata_export_requestedactivity entry.Account::buildExportData()aggregates every personal-data source: profile, authorized applications, OAuth consents, passkeys, two-factor status, active sessions, tokens, token activity, account details, privacy settings and the activity log.- Extensibility hook —
buildExportData()fires theaccount.data_exportevent with the user id; listeners return[section => data]maps that are merged into the payload. Core sections are protected from being overwritten, so an application (e.g. one adding licensing/billing data) extends the export without forking.
Security¶
- The export is metadata-only where it matters: passwords/salts, token values,
TOTP secrets and backup codes, passkey public keys and credential ids, raw
request bodies (
tokenactions.params) and password-reset hashes are never included. Each collector is individually guarded — a missing table degrades to an empty section rather than failing the whole export.
Consistent account navigation across all three themes¶
The built-in account area now has one coherent navigation model in the plain-CSS, Bootstrap and Tailwind scaffolding.
Changed¶
- Shared sidebar + breadcrumb partials (
partials/account_sidebar,partials/account_breadcrumb), included via the framework's$this->insert()view-in-view mechanism, so every account page shares the same navigation. - A clear hierarchy: Security groups change-password, two-factor and passkeys; Privacy groups data export and account deletion. Options that live inside a section are no longer duplicated in the sidebar.
- Stable back-links via a single
accountBase, and the 2FA/passkey controllers render inside the same account chrome. All auth-view JavaScript is consolidated into the shared, data-attribute-drivenpf-auth.js. - Footers no longer carry a "Powered by" line in any theme (including the scaffolder-generated footer for new/switched projects).
Admin Tokens & Token Actions pages fixed (all themes)¶
The built-in admin Tokens and Token Actions pages showed nothing (or wrong data), and their view/revoke links were dead. Both are now correct across the plain-CSS, Bootstrap and Tailwind themes.
Fixed¶
- Admin actions read the route id from the request option, not a method
argument. The dispatcher always calls controller actions with an empty
$args, so…/view/5,…/revoke/5,…/show/5etc. must read the id fromRequest::staticGetOption(). Tokens, Token Actions, Applications, Permissions, Emails and Queue admin controllers were corrected — fixing the dead view/revoke/edit/delete links. - Token Actions used the wrong columns. It now selects the real
actionidprimary key, joinsurlsto show the human-readable endpoint (the row stores an integerurlid), renders the Unixservertimeas a date, and the CSV export carries the endpoint instead of the opaque id. - Tokens no longer hides session/API tokens. The applications join is a
LEFT join, so
web_session/API tokens (which have noapplicationid) appear; the list shows the username and a formatted last-used time, and theuser_id/app_idfilters work. - Bootstrap & Tailwind parity. Both themes were brought in line with the corrected plain-CSS views, and the Tailwind tables got proper cell styling and pill status badges (they previously rendered unstyled).
init prompts for the admin password (with a safe fallback)¶
Project initialisation now lets you set the first admin account's password interactively, and hardens that input against terminal editing quirks.
Added¶
- Admin password prompt in
project:init— press enter to accept a strong random default, or type your own.
Fixed¶
- UTF-8-aware line editing. Before prompting,
initenablesstty iutf8on the terminal so backspace erases a whole multibyte character. On terminals left in byte-oriented mode (notably WSL) a backspace would otherwise delete a single byte of a multibyte character, silently corrupting the stored password. - Defensive
sanitizePassword(). As a second layer, the entered value is cleaned of stray backspace/DEL bytes, invalid UTF-8 and control characters (a warning is shown if anything was removed), so the saved password always matches what the user can reproduce at login. writeFile()creates the parent directory before writing, soinstallUiFramework()works standalone (e.g.project:switch-ui) against a project missing a sub-directory.
New project:switch-ui command¶
Switch an existing project between the bundled UI frameworks in place — no re-scaffolding — to preview the built-in account/auth UI under each.
Added¶
project:switch-ui <plain-css|bootstrap|tailwind>—- updates
scaffold_themeinapp/app.php(and relaxes the CSPstyle-srcto'unsafe-inline'for Tailwind's runtime build, strict otherwise); - re-installs the theme chrome and
www/assetsand pulls the framework's CSS/JS vendor assets (delegating toInit::installUiFramework()).
The scaffolded account/auth views are theme-agnostic and resolve per-framework from the bundled scaffolding, so switching needs no view copying.
Assorted fixes: DevPanel cache flush, email audit log, cache adapter¶
A handful of smaller fixes across the DevPanel, the mailer and the file cache.
Added¶
- Email audit log —
Email::send()records every outbound attempt (success or failure) in themailstable, so the admin Emails viewer has a complete delivery log. On by default; suppress per send with$recordToMails = false.
Fixed¶
- DevPanel "flush all cache" now returns its JSON for an in-page AJAX request instead of navigating the browser to a raw JSON response.
- File cache adapter — flushing or cleaning a cache group that was never
written (or already removed) no longer throws:
listDirectoryFiles()returns no files for a missing directory instead of lettingRecursiveDirectoryIteratorraiseUnexpectedValueException.