includes/class-plugin.php boots REST, user data, Firebase-backed stores, page/form provisioners, permalink helpers, account firewall, patterns, and blocks under src/.
Authentication model
- Firebase Authentication handles sign-in (magic link and federated providers).
- REST mutations authenticate with
X-PRC-User-IdandX-PRC-User-Tokenheaders (seeextract_user_auth_from_request()). POST /user-accounts/get-useradditionally requires a WordPress nonce (prc-user-accounts) in the JSON body before Firebase work runs.
Provisioning
| Class | Role |
|---|---|
Page_Provisioner | Ensures /my-account/* pages exist |
Form_Provisioner | Seeds login, verify-token, and complete-profile forms |
Patterns | Block patterns for buttons and forms |
Older rewrite maps under class-permalinks-rewrites.php still describe /profile/* paths; current page provisioning and client redirects use /my-account/*. Prefer /my-account/ for new work.
Protected files
Attachment meta _prc_protected is the source of truth. Protected_Files::grant_for_user() returns a file URL only after the caller has constructed User_Data (token verified). The Private Files block stores display IDs only; it never prints file_url. Dataset downloads call the same grant, then keep dataset telemetry and users/{uid}/datasets.v2. Generic downloads write users/{uid}/files.v1 through log_file_downloads() (one unpathed multi-location update). When the generic grant omits dataset_id, datasets still run ATP if the attachment belongs to an ATP dataset.
Private Files download analytics live on the host post, keyed by each block's persisted instanceId. Private_Files::record_download() writes _private_files_blocks and yearly nested maps so two Private Files blocks on one post (including a dataset post) keep separate totals. Recording runs after a successful grant only when post_id and instance_id are present and the attachment is listed on that instance. A failed record does not fail the grant. Gutenberg clientId is not used. Two copies of the same synced pattern on one post share one instanceId and therefore one bucket. Until a block is saved with an instanceId, downloads are not recorded. A post that already has legacy _private_files_total_downloads and exactly one Private Files block copies those maps into that instance once.
Protected_Files::zip_for_user() is the zip operation: grant every ID, fail closed on any denial, stage bytes with wp_remote_get into get_temp_dir(), then stream a Protected_Files_Archive. Stars live on a sibling RTDB map users/{uid}/starredFiles/{id} (star / unstar / get_starred); a bookmark is not a grant.
VIP cache and firewall
Account_Firewall integrates with VIP vary-cache so authenticated and anonymous readers can receive different HTML without breaking edge cache. Cookies register with the WP Consent API as functional cookies.
Security touchpoints
| Control | Where |
|---|---|
| Firebase token validation | User_Data::check_token() on protected-file download and get-user |
| Protected file grant | Protected_Files::grant_for_user() |
| WP nonce on get-user | permission_check_get_user |
| Cloudflare Turnstile | Auth forms when PRC_PLATFORM_TURNSTILE_SECRET_KEY is set and a captcha token is submitted |
| Account deletion | DELETE /user-accounts/delete-user |
Required configuration
| Constant | Purpose |
|---|---|
PRC_PLATFORM_FIREBASE_CONFIG | Client Firebase config array |
PRC_PLATFORM_TURNSTILE_SECRET_KEY | Optional Turnstile verification |
DEFAULT_TECHNICAL_CONTACT | Technical contact for notifications |
Do not commit service-account JSON or real secrets into docs/.