Current release: v0.6.2
Rally Quest Engine is a WordPress plugin that helps organizers produce and manage a high-quality scavenger-hunt style rally for Motorcyclists, Bicyclists, Geocachers, or Horseback participants. Efficient setup guides each event through its required details, focused administration keeps participants, conveyances, rosters, and event roles in one place, and near-real-time results update as email or website bonus submissions are reviewed. Requires WordPress 6.2+, PHP 7.4+, MySQL 8.0+ or MariaDB 10.6+ with InnoDB, and the installed/active Action Scheduler plugin. Licensed under the GNU General Public License v3 or later (see LICENSE).
Version status: v0.6.2 provides readiness-aware setup, event administration, encrypted global/event inboxes (Gmail API/IMAP/Microsoft 365), approved participant self-registration with a locked-down participant role, pending-action header alerts for registration requests, bonus reviews, failed setup checks and inbox errors, private website bonus submission and history, administrator/scorer review, overall rankings, retained certificate history, public results embeds, and manual exports/backups.
Future Enhancements: Scheduled encrypted filesystem backups (deferred), bonus acceptance/rejection notifications via email, phone applications, and beautification of all plugin tabs and public-facing pages.
- Download
rally-quest-engine-vX.Y.Z.zipfrom the release marked Latest on GitHub Releases. - In WordPress, open Plugins > Add New > Upload Plugin, select the ZIP, and activate Rally Quest Engine.
- Open Rally Quest in the WordPress administrator menu.
- In Initial Setup, read the getting-started steps, explicitly save the primary scavenger hunt style (Motorcycle, Bicycle, Horseback, or GeoCache), and configure a shared inbox if needed. The Events tab becomes the default only after all four setup sections are green.
- Populate the Bikes or Horses tab, add participants, create events, and enroll participants. Assign the global inbox explicitly to one unlocked event; existing event inboxes remain overrides.
The plugin supports individual WordPress sites. On multisite, activate and manage it separately on each site; network activation is not supported.
- Locked-down participant accounts: RQE-created accounts use the capability-free Rally Quest Participant role, with no wp-admin, toolbar or application-password access. Rejected never-activated accounts are locked, and Users offers an audited bulk action to remove unused locked accounts.
- Approvals where you work: pending requests open automatically at the top of Participants and in Events > Configure (now above the embed section), with recent decisions and links to the Administrative Audit.
- Required-action alerts: the RQE header lists pending requests, failed required setup checks and inbox errors with direct remediation links; the Rally Quest logo and admin menu show a count badge.
- Setup references: Diagnostics explains persistent private storage (
RQE_IMPORT_TEMP_DIR), and Bonus Point Library help includes an optional Docker Compose ClamAV setup.
Upgrade preparation: back up the database and private participant proofs. Schema 18 creates the participant role, moves RQE-created Contributor accounts to it and locks never-activated accounts whose requests were all rejected; scores are unchanged.
- Bonus review alerts: when submissions are awaiting review, assigned event administrators and scorers see the pending count in the Rally Quest header and menu/logo badge, with a direct link to the event's pending-review tab.
- Setup readiness: Initial Setup turns green when complete or appropriately acknowledged, orange when unresolved, and red for critical database, encryption or private-storage failures that cannot be overridden.
- Certificate history: Results retains print/PDF generation and email outcomes, with confirmed individual deletion and a verified bulk-deletion option in Diagnostics.
- Diagnostics: Manual export and database backup controls share Backup & Export; deletion controls use Destructive Operations.
- Participant access: Event-scoped registration and private portal shortcodes support email verification, administrator approval, immutable bonus submissions, and participants' own scoring history.
Upgrade preparation: back up the database, public event galleries and private participant proofs separately. Schema 17 adds account/request and certificate-history tables without changing existing scores, and resets setup readiness for a fresh critical-prerequisite check. Before enabling participant website access, configure persistent private storage, working ClamAV, HTTPS and cache exclusions as described below. Scheduled encrypted backups remain deferred.
The plugin is administrator-operated, with optional public results and opt-in participant registration and private submission portals. New participant accounts use the locked-down Rally Quest Participant (rqe_participant) role, which has no WordPress capabilities; existing users retain their roles. The Rally Quest screen opens at Initial Setup until Welcome, Default Email Inbox Setup, Primary Scavenger Hunt Style, and Required Plugins and Configuration readiness are complete; afterward, Events becomes the default. Detailed diagnostics are private to the checking administrator and expire after 15 minutes. Manual reviews and noncritical failures can be acknowledged for readiness only; database integrity/schema, credential encryption/decryption and private storage failures cannot be overridden. Acknowledgments never bypass runtime protections. Both setup and diagnostics show applicable override counts, counting overlaps once and excluding passed, removed and critical checks. Expired counts are labeled last check. Upgrading to schema 17 resets the legacy readiness summary so the critical prerequisites must be checked again. Override requests are audited; only the minimal summary is shared site-wide.
Site-administrator tabs appear in this order: Initial Setup, Events, Rosters, Scoring, Results, Participants, Bikes, Diagnostics, About. Horseback uses Horses in place of Bikes; GeoCache omits that tab. Initial Setup is green when complete, orange when requirements remain unresolved, and red after a critical non-overridable diagnostic failure. A textual status accompanies the color. About remains green, Events/Rosters/Scoring/Results blue, and Diagnostics orange. The active tab has bold underlined text and a current-page accessibility marker. Event roles retain their restricted navigation.
| Tab | Current function | Access |
|---|---|---|
| Initial Setup | Welcome guidance; one encrypted shared inbox assigned to one active event; an explicitly saved primary scavenger-hunt style; and Required Plugins and Configuration checks. Assignment is independent of the header event. | WordPress administrators |
| Participants | List participants with Add Participant on the left and Search all records right-aligned on the same toolbar; open separate Edit, participant-filtered Events, and Results destinations; and manage contact, emergency-contact, insurance, notes, and conveyance attributes for the primary style (motorcycles or bicycles, horses, or none for GeoCache). | WordPress administrators can edit shared participant records; Event Administrators have read-only access to participants enrolled in assigned events |
| Events | Add events and open separate Configure, Roster, and Scoring destinations. The Events list shows style-coded Event IDs such as MC-7. |
WordPress administrators manage events, roles, and inboxes; Event Administrators manage assigned event details and status catalogs, with a read-only global inbox summary |
| Rosters | Use the header event and view Participant ID, Bib ID, editable Registration Status, name, email, score, and overall placing. Participant IDs open participant details/editing; bibs open the event scoring workspace. | WordPress administrators and assigned Event Administrators can change Registration Status; manual enrollment remains site-admin-only, while assigned Event Administrators can approve verified self-registration requests |
| Scoring | Use the header event, manage its Bonus Point Library, compare pending participant photos with matching library records, accept or reject submissions, and review retained submission history. Reviewed bibs open read-only participant score sheets. Accepted points are added transactionally exactly once; Contested remains unchanged. | WordPress administrators and assigned Event Administrators; assigned Event Scorers receive Scoring and read-only score-sheet access |
| Results | Rank event participants, show accepted/rejected submission counts, edit Event Status, open participant score sheets, and view/delete retained certificate generation and email history. | WordPress administrators and assigned Event Administrators |
| Bikes | Shown for the Motorcycle and Bicycle styles. Add or edit make/model catalog records, import motorcycle models by make from NHTSA (Motorcycle only), and bulk-delete selected unassigned records. Bicycle style renames manual entry to Manual Bicycle Entry, hides the NHTSA import, and greys out fuel-capacity fields. | WordPress administrators |
| Horses | Shown for the Horseback style instead of Bikes. Manage horses by Name, Breed, Sex, Age, and Color, and assign up to 20 horses to a participant. | WordPress administrators |
| Diagnostics | Check configuration readiness, use Backup & Export for JSON/SQL downloads, and perform verified scoped data deletion or delete all certificate history under Destructive Operations. | WordPress administrators |
| About | Show what Rally Quest Engine does, version/environment details, repository and license links, an optional Gallery for SpotWalla recommendation, source attribution, privacy guidance, and reporting instructions. | WordPress administrators and assigned Event Administrators or Event Scorers |
The plugin has no site-wide administrator-assignment feature. WordPress users with the core manage_options capability manage the primary style, shared participants, conveyances, event creation, diagnostics, enrollment, and event role assignments. Event Administrators are assigned only on an event's Configure page and can access assigned event configuration, rosters, scoring, results, and enrolled participant details. Event Scorers are separate event-only assignments and can access Scoring, About, and read-only score sheets for assigned events, but cannot edit either participant status or access the Results tab.
| Style | Code | Conveyance |
|---|---|---|
| Motorcycle | MC |
Bikes tab with NHTSA import and fuel capacity |
| Bicycle | BC |
Bikes tab with Manual Bicycle Entry; fuel capacity greyed out |
| Horseback | HB |
Horses tab (Name, Breed, Sex, Age, Color) |
| GeoCache | GC |
No conveyance |
The Primary Style section's saved style sets the default style for new events and which conveyance tab and fields appear. Each event stores its own style, and its Event ID is the style code followed by the numeric ID, for example MC-7. Events that existed before styles were introduced presume Motorcycle.
Events > Configure (rqe-event-edit) uses two columns that stack on narrow screens. The left column holds Event Details and Event Administrator/Scorer assignments. The right begins with one Email Setup section identifying Global Email or Event Specific Email, the effective mailbox, verification, polling status and interval. It provides Configure Event Specific Email, Test Event Email, Pull Email Now, and Remove Event Inbox Override actions when applicable. Testing and pulling resolve the effective source without creating an override; removal deletes only the event's credentials. Email Setup becomes green and starts minimized when the effective inbox is verified and enabled. Global readiness also requires assignment to this unlocked, unarchived event. Disabled or unverified event overrides never fall back to a ready global inbox. Email actions return to Event Configuration with a success/error notice. Event Status Customization and Registration Status Customization follow in that order. The public embed controls remain with Event Details. Directly below Embed this Event on a Post or Page, an expanded Event Protection and Certificates panel shares the Results tab's state controls and certificate actions. Its status is Active (green), Locked (yellow), or Locked & Archived (blue); changes in either screen are reflected in both and return to the invoking screen. Existing lock/archive permissions remain unchanged. Both status catalogs and the embed section start minimized, with their collapse marker beside the title. Move Up / Move Down orders status choices without changing participant assignments. Event Name, Event Description, Maximum Bib, Registration Opens/Closes, Event Starts/Ends, Participant Price, and Minimum Finisher Points are required to save. Event Details and assignments turn green and start minimized once complete.
In Events > Configure > Participant Website Access & Registration, enable website access and copy [rqe_register id="MC-7"] into a WordPress Shortcode block for Add a Participant, and [rqe_participant id="MC-7"] for private bonus submission/history. Use the actual style-qualified Event ID (MC, BC, HB or GC). Access is off by default. Registration and approvals require the configured registration window and an unlocked/unarchived event; website bonus submission also requires the event start/end window. Disable website access to stop both shortcodes and participant-owned proof access.
Anonymous new participants receive a Rally Quest Participant account with a random password, blocked from password and cookie/application-password/REST authentication until both email verification and scoped administrator approval. Verification links expire after 24 hours and are single-use; resubmitting after the rate-limit interval resends verification for an existing pending request. Only site administrators or that event's assigned Event Administrators can approve/reject requests. Approval confirms account identity, links a participant with the same verified email or creates a new participant, rechecks capacity and assigns a free Bib. Scorers cannot approve. Existing users must sign in before requesting access; their roles and login are unchanged, but event approval remains required. Anonymous email matching never attaches an existing account. Approved new accounts receive WordPress password-setup notification. Rejected new accounts are blocked from logging in; administrators can delete them through WordPress Users using the unused-account bulk action.
The private portal requires an approved account-to-participant link and current noncancelled event enrollment, not merely a WordPress role. Participants can append one JPEG, PNG, GIF or WebP proof up to 10 MB for a library bonus, and view their own submission decisions, awarded points and total score. They cannot edit/delete submissions or access RQE administration. Pending/accepted duplicates and replayed forms are blocked. Existing scorer review awards points transactionally; rejection permits a new submission using a fresh form. Every website upload uses the mandatory ClamAV and raster/executable-content policies, with no antivirus bypass.
Participant WordPress role. RQE-created participant accounts use the Rally Quest Participant (rqe_participant) role, which grants no WordPress capabilities at all (not even read). Registration, login, the private portal and proof uploads work through front-end pages and admin-post.php, so no capability is needed. For accounts whose only role is rqe_participant, wp-admin redirects to the home page (admin-post.php and admin-ajax.php stay available), the toolbar is hidden, application passwords are disabled and logins that would land in wp-admin go to the home page. Participants change passwords with the standard WordPress lost-password link. Schema 18 creates the role and moves existing accounts that RQE created as Contributors (random rqe- login, participant request, no authored content) to it. Accounts with other or additional roles, existing users and any account that has authored content are unchanged. Administrators can still assign another role manually, which removes these restrictions. Opt-in uninstall removes the role only if no user still holds it. Do not use Contributor or Customer for participants.
Rejected new accounts are locked: they cannot sign in, keep a session, use REST/application passwords or reset their password, and login shows a "declined" message. Existing WordPress users and accounts already activated by an earlier approval are never locked by a rejection. An authorized approval of another event request for the same account unlocks it. Schema 18 also locks earlier never-activated RQE accounts whose requests were all rejected.
Cleaning up unused accounts. In Users, a Rally Quest Account column shows each account's state, and the Locked RQE participants view lists locked accounts. An account is flagged Locked — rejected, unused only when it was created by RQE, holds only the participant role, is locked, has no participant link, no pending/verified/approved request and no authored content. Select accounts and choose the bulk action Delete unused RQE participant accounts (on multisite, Remove…, which removes them from the current site only). Every selected account is rechecked; anything not unused, and your own account, is kept and counted in the result notice. Removals require the WordPress delete-users (multisite: remove-users) capability and are recorded in the Administrative Audit as participant_accounts_deleted.
Approving requests and pending-action alerts. Pending (unverified) and verified requests appear in two places, each expanded with a pending count while any request waits:
- Participants tab: Approve or Reject Self-Registered Participants is at the top of the page and lists requests for every event you administer. Decisions return to the Participants tab.
- Events > Configure: Participant Website Access & Registration is placed above Embed this Event on a Post or Page. Decisions return to the event.
Only verified requests can be approved; unverified requests can be rejected. Every approval and rejection is recorded in the Administrative Audit as participant_approved or participant_rejected (with username and UTC time). Both places show the latest 20 decisions for their events, and site administrators get a link to the full audit in Diagnostics > Administrative Audit.
Whenever an RQE page loads, a header alert list with direct remediation links appears for:
- participant requests awaiting a decision (Event Administrators see only their events);
- failed required setup checks from the last shared Diagnostics run (site administrators; noncritical failures are hidden once their setup override is recorded);
- global or event inbox polling or test errors (site administrators).
The Rally Quest logo shows a count badge and the WordPress menu shows a count bubble while alerts are open. Alerts clear automatically when requests are decided, Diagnostics pass or the inbox's next successful poll/test clears its error; there is no separate acknowledgement. Links ending in a section anchor open and scroll to that collapsed section.
Host setup: configure RQE_IMPORT_TEMP_DIR to a persistent, private directory outside the web root, WordPress, wp-content and uploads. The operating-system temporary directory is not durable website-proof storage. Restrict the rqe-participants-{site-id} subdirectory to the PHP service account (0700 on Unix; equivalent host-managed Windows ACLs). Website proofs are served only through an authenticated ownership/scorer endpoint and remain private even when accepted public bonus details are enabled. Back up this private subdirectory separately; JSON/SQL backups include database records, not image bytes. Preserve the configured path during host migration.
Use HTTPS and exclude both embedding pages from WordPress/CDN/full-page caching; the plugin also sets no-cache headers and DONOTCACHEPAGE before rendering recognized shortcode pages. Public registration uses nonces, per-browser CSRF tokens, a honeypot and atomic short-lived rate limits. Registration/verification service events and administrator decisions are audited. Privacy export/erasure includes requests, account links, private proofs and certificate history. RQE deletion never deletes WordPress users or changes existing roles. Before deactivating/uninstalling RQE, review or delete pending WordPress accounts: inactive plugins cannot enforce their login restriction. Opt-in uninstall removes plugin data/files, not WordPress accounts; retained pending metadata is intentionally not treated as approval.
Initial Setup's Default Email Inbox Setup supports one encrypted IMAP, Gmail API, or Microsoft 365 mailbox explicitly assigned to one unlocked, unarchived event at a time. Assignment is independent of the selected event in the page header. Successful global inbox saves return to Initial Setup, and global connection tests return there with their success or error notice; event inbox saves and tests return to Event Configuration. Failed configuration saves remain on the appropriate global/event configuration form with an error for correction. The global setup section becomes green and collapses after a successful connection test; polling additionally requires enabling it and assigning an active event. Connection testing authenticates without reading or marking messages. Saving credentials or changing assignment invalidates verification. Before switching or clearing an existing assignment, an administrator must acknowledge handling unread messages for the previous event: messages do not identify their event and would otherwise be routed to the newly assigned event.
Existing per-event mailboxes remain independent explicit overrides and always take precedence for their event, even when their polling is disabled. This preserves legacy configuration without automatically moving credentials. A site administrator may configure/test or remove an override; removing it deletes that event's saved credentials and restores use of the global mailbox only if that mailbox is assigned to the event. Global configuration, assignment, connection testing and manual polling remain site-admin-only. Polling and settings changes use shared per-source locks; messages are marked processed only after their database record commits. Locked/archived events pause the global inbox, and the poller checks for event overrides before reading global messages. Independent enabled sources must use different mailbox addresses: conflicting configurations are rejected and polling of legacy conflicts stops without reading messages.
Each inbox has an independent Polling interval (minutes) accepting whole numbers from 5 to 1,440 (1 day). Events using global email inherit the global interval; event-specific inboxes use their own. Existing configurations without an interval default to five minutes. The shared Action Scheduler dispatcher wakes every five minutes and checks each source's elapsed interval under its source lock, based on its last successful or failed polling attempt. This is a minimum interval, not an exact wall-clock guarantee; actual execution depends on site cron. A manual Pull Email Now bypasses only the timing gate, records the attempt, and starts a new interval while retaining disabled-inbox, event protection, mailbox-conflict and ingestion safeguards. Changing credentials or polling configuration requires a fresh verification.
The global credentials use row 0 in the existing encrypted event-mailbox table, so the current database JSON/SQL backups and all-data deletion include it without a schema migration. Backups contain ciphertext and remain sensitive. Deactivation preserves the settings; opt-in uninstall deletes them with the existing inbox table. Delete All Data also clears the minimal shared setup-readiness option.
Finisher tiers (inclusive starting in v0.2.2): Event Details includes optional Bronze, Silver, and Gold Finisher Points immediately after Minimum Finisher Points. Blank means Not Used (stored as NULL). Each configured tier must strictly exceed the highest configured lower threshold; intermediate tiers may be omitted. Scores equal to or greater than a threshold qualify, and the highest qualifying tier becomes Preliminary Finisher, Preliminary Bronze Finisher, Preliminary Silver Finisher, or Preliminary Gold Finisher. This changes equality cases from v0.2.1, including a zero score when the minimum is zero. Automatic statuses recalculate as points or thresholds change; manual statuses, including Cancelled → DNS, remain authoritative until returned to Automatic. Final Finisher/DNF/DNS still require administrator assignment.
Registration Status and Event Status catalogs default to minimized on Event Configuration. Diagnostics' Export Data and Database Backup panels also default to minimized. Event Status labels now allow 64 bytes; Registration Status remains limited to 20 bytes. Schema 13 adds optional thresholds without changing existing scores or manual statuses and seeds the new system labels. Releases older than v0.2.0 are marked deprecated; their artifacts remain available for migration/history.
In the application header, choose a scavenger hunt style (defaulting to the primary style), then choose one of that style's events and press Set Event. The selection is remembered per WordPress user and site until changed; Rosters, Scoring, and Results use it automatically across navigation and reloads. Explicit event links also update the header context. Only authorized events are offered, and access is checked on every request. Changing events through the header clears participant filters and record-specific context, returning from sub-pages to the relevant main tab. Open forms retain their own event ID, so another tab's event change cannot redirect a save into a different event. Missing/revoked selections display a warning and require reselection. Participant-filtered links show a selection message if that participant is not enrolled in the header event.
Event Configuration has independent Registration Status and Event Status catalogs immediately after Email Setup. WordPress administrators and assigned Event Administrators can add values (up to 20 bytes), disable/re-enable them, and delete unused values. Disabled values remain visible on existing riders but cannot be newly assigned; in-use values cannot be deleted. Existing event/rider data is retained during the schema upgrade, including historical custom labels and manual status assignments. Back up before upgrading.
Rosters' Registration Status defaults are Initial Registration, Paid, Fully Registered, and Cancelled. Keep at least one value enabled. New enrollments and blank import statuses use Initial Registration when enabled, otherwise the first enabled value in the configured order. Results' Event Status is separately managed: Automatic derives Registered below Minimum Finisher Points and a preliminary finisher status at or above the applicable threshold. At the end of the event, an administrator must manually assign Finisher, DNF, or DNS. A manual status survives subsequent score or threshold changes until the administrator explicitly returns the participant to Automatic. Registered, preliminary statuses, and DNS cannot be deleted because automatic calculation and cancellation need them; disabling these values removes them from new manual choices, not system assignments.
On Results, an Event Administrator can Lock Event to prevent changes to scores, participant statuses, enrollments, event details, status catalogs, bonus imports/edits and submission review. Inbox polling skips locked events and does not consume new messages. WordPress site administrators alone can unlock, archive or unarchive. Archive Event also locks it; Unarchive Event keeps it locked until explicitly unlocked. Locking does not finalize preliminary statuses. Public results and certificates remain available. Diagnostics rejects deletions that would affect locked events; privacy erasure still anonymizes participant data.
The header's Show archived events checkbox and Apply Visibility remember the preference per user and site. Archived events are hidden by default; a currently selected archived event remains visible with a read-only notice, rather than silently switching context.
Certificates show event name, start year, participant name, event Bib (Participant Number), total score, accepted/rejected bonus counts and final Event Status. Only manually finalized certificate-eligible statuses qualify: Finisher, Bronze Finisher, Silver Finisher and Gold Finisher are always eligible; custom final statuses can opt in through the Event Status catalog. Preliminary statuses, Registered, DNF and DNS never qualify. Print individually or Print All / Save as PDF; individual PDF downloads use bundled Dompdf with a Unicode font. Server-rendered PDF downloads are limited to 100 certificates; larger print-all sets use the browser.
Certificate template update (v0.2.3): The certificate template uses an original cream, charcoal and antique-gold ornamental frame on A4 landscape. It also includes the Event Image, Event Description and competition-ranked Overall Position. Final statuses containing the whole word Bronze, Silver or Gold display a matching medal (Gold takes precedence if more than one tier is named); ordinary Finisher has no medal. A four-page sample PDF demonstrates each variation with fictional event/participant data. Long descriptions are preserved on following description pages rather than clipped or overprinted.
Event status and design update (v0.2.3, schema 15): Event Status defaults are now ordered Registered, DNS, Active, Preliminary Finisher, Preliminary Bronze Finisher, Preliminary Silver Finisher, Preliminary Gold Finisher, Finisher, Bronze Finisher, Silver Finisher, Gold Finisher, DNF. The one-time upgrade adds missing choices and places custom labels afterward, preserving disabled choices, scores and participant assignments. Move Up/Down remains available after migration; later visits do not reset the order.
Finisher, Bronze Finisher, Silver Finisher and Gold Finisher always qualify for individual/all printing and emailing after manual finalization, even if the status is disabled for new assignments. Their certificate eligibility cannot be switched off. Existing custom final-status opt-ins remain supported; preliminary statuses, DNS and DNF do not qualify.
In Events > Configure > Event Details, select Finisher Certificate Design and save: Vintage Ornamental (cream/charcoal/gold) or Classic Formal (white/navy/gold). This event-level selection is reused for print, PDF download and email preview/attachments. It selects visual styling, not the participant's final status: medal color always follows the actual Bronze/Silver/Gold status. Changing the design requires fresh email approval. Compare the Vintage samples and Classic samples.
Event images come from the configured Event Image media ID. Certificates embed a normalized local copy, never fetch a remote image, and do not store image bytes in email review records. Supported local-upload rasters are JPEG, PNG, GIF or WebP up to 10 MB / 16 million pixels. Missing or unsupported configured images show an actionable error; events without an image use a Rally Quest Engine wordmark. PDF images require PHP GD in addition to DOM/mbstring. Changes to the event description, image content, overall position or template revision invalidate an earlier email approval.
Review & Email opens individual or all certificate previews and recipients, with editable subject/message. An administrator must explicitly approve before any email is sent. Draft reviews expire after one hour; changes to certificate data, status eligibility or email addresses require re-review. Approved batches send separate PDF attachments to at most ten participants per request, with explicit continuation links. Recent batches appear on Results. WordPress wp_mail() uses the site's existing outbound-mail configuration; sent means accepted by that system, not confirmed delivery. Unchanged pending/sending/sent certificates are skipped unless explicitly approved for resend. Failed, stale or interrupted/uncertain deliveries are not automatically retried.
PDFs require PHP's DOM and mbstring extensions; missing extensions show an actionable error and browser printing remains available. PDFs are generated locally, without remote resources, and staged outside the web root using the existing private-directory policy (RQE_IMPORT_TEMP_DIR if needed). Attachments are removed after each send. Pending approvals expire after one day; delivery details are retained for 30 days and cleaned when a site administrator accesses wp-admin. Duplicate protection covers that retained delivery history. Sent/failed snapshots and message text are cleared immediately. Private reviews may persist for their one-hour transient lifetime; erasure or recipient changes invalidate them before sending. Certificate delivery records are included in diagnostics backup/export, personal-data export and participant erasure. No Action Scheduler dependency is added for certificate emailing.
Certificate History (v0.5.0) on Results retains paginated print-view/PDF generation and email outcomes independently of the expiring mail queue. Print/PDF history proves generation, not physical printing; Sent means mail-system acceptance, not delivery. Existing retained sent/failed/stale/uncertain mail outcomes are backfilled once on upgrade; past prints cannot be reconstructed. Delete an individual entry after its confirmation prompt, or use Diagnostics > Destructive Operations > Delete all event certificate history with randomized verification. Both are authorized and audited, and never alter scores, mail queues or their retained duplicate-send protection. History persists until explicit deletion, scoped participant/roster deletion, privacy erasure or opted-in uninstall.
Diagnostics groups the existing manual Export Data and Database Backup controls under Backup & Export, and uses the heading Destructive Operations. Recurring encrypted password-protected backups are deferred, not implemented by this release.
Changing Registration Status to Cancelled sets manual Event Status to DNS in the same transaction, including when DNS is disabled for manual selection. This also applies to Cancelled imports and enrollments. Administrators may change Event Status afterward; saving an unchanged Cancelled registration does not reset that override, and changing registration away from Cancelled does not restore the previous Event Status. Cancelling again assigns DNS again. The schema 11 upgrade performs a one-time backfill of existing Cancelled registrations to manual DNS, replacing any previous Event Status for those riders without changing scores or placing. Back up before upgrading and review any affected final statuses afterward.
Results' Accepted Bonuses and Rejected Bonuses are counts of accepted/rejected submissions, not point totals or distinct Bonus IDs. Pending and invalid messages are excluded. Riders without reviewed decisions show zero; counts do not change scores or placing.
Starting in v0.3.0, Embed this Event on a Post or Page also offers Publish accepted bonus details and photos, disabled by default. Enable public results and this additional option to make public Bibs link to the participant's accepted-bonus listing. Each accepted bonus shows its ID, description, awarded points and submitted proof photo; thumbnails open the larger image. Missing images are explicitly identified. Administrative Results and Reviewed Bib links open the accepted-bonus section of the existing participant score sheet, which retains full submission history.
Public detail requests recheck event publication, the additional photo/detail opt-in and participant enrollment. Rejected/pending submissions, email addresses, rejection reasons and inbox metadata are not exposed. Obtain participant permission before publishing proof photos. Photos use existing direct uploads URLs: disabling publication prevents new listings, but cannot revoke shared image URLs, downloaded copies or cached pages. Purge WordPress/CDN caches after publication or score changes.
The v0.6.0 review applied the same WordPress and SOLID checks to the new code. Responsibilities stay separated: RQE_Participant_Role owns the capability-free role and its wp-admin/toolbar/application-password restrictions; RQE_Participant_Accounts owns account locking and Users-screen cleanup; RQE_Alerts is a read-only alert query service that views only render; and RQE_Participants keeps enrollment decisions. WordPress practices applied: the Users bulk action verifies the core bulk-users nonce plus delete/remove capabilities and rechecks every account server-side; the locked-account view filters only the Users list table (users_list_table_query_args) rather than every user query; output is escaped and event IDs are integer-cast before SQL; alerts are capability-scoped (site-only alerts for site administrators); and deletions and decisions are audited. Accounts with authored content, other roles or active links are never removed, and on multisite the action only removes users from the current site.
The v0.5.0 scope separates account/enrollment/intake rules (RQE_Participants), shortcode/admin presentation (RQE_Participant_Views), certificate-history persistence (RQE_Certificate_History), and shared setup policy (RQE_Setup). Existing event locks, scorer review, scanner/raster policy and gallery staging are reused rather than bypassed. The focused SOLID check covers these responsibility boundaries; the larger pre-existing admin/view classes are not claimed to satisfy every SOLID principle. New regressions cover pending-account authentication, event ownership and approval, capacity/window/protection failures, replay prevention, private multipart storage/recovery, readiness colors and critical override exclusions, and independent history retention/deletion. Multipart tests use a deterministic loopback protocol fixture, not a real antivirus certification. Real WordPress/provider/host staging acceptance remains required.
The v0.4.1 review uses WordPress security guidance and Plugin Directory guidelines as references: validate inputs, prepare database values, escape at output, enforce capabilities and nonces on mutations, use WordPress APIs and keep setup acknowledgements separate from runtime validation. RQE_Setup owns the minimal shared readiness summary while detailed diagnostic reports remain in per-administrator transients; RQE_Email_Submissions retains provider access, encryption, ingestion and review boundaries, with narrowly scoped lock helpers coordinating mutations and polls. The focused SOLID review found these responsibilities aligned with single-purpose helper/service boundaries and dependency direction; broader existing admin/view classes are not claimed to satisfy every SOLID principle, and this release does not undertake an unrelated architecture rewrite.
The v0.4.1 refinements keep override counting in RQE_Setup and share one protection-panel renderer between Event Configuration and Results; both continue to use RQE_Event_State for authorized, transactional, audited changes. tests/admin-navigation.php covers parent-screen email redirects, verification failures, permissions/nonces, inbox precedence, shared protection states and tab visibility without contacting real providers.
Public release gates include CodeQL JavaScript security-extended analysis, Semgrep PHP/JavaScript and WordPress exploit rules, Gitleaks history/tree scans, Trivy dependencies/secrets, Composer audit and Dependabot alerts. CodeQL does not analyze PHP; PHP coverage comes from the other scanners and regression tests. These checks are not WordPress.org approval, a complete WordPress Coding Standards certification, a formal SOLID certification or a guarantee of no vulnerabilities.
Mailbox hardening: Only WordPress site administrators can configure inboxes, test connections, assign the global inbox or manually poll. Event Administrators and Scorers retain authorized submission review. A blank IMAP password is retained only if host, port, encryption and username are unchanged; changing that credential destination/account requires a newly entered password. Site administrators must use trusted mailbox servers and encrypted transport.
The global mailbox is encrypted in row 0 of the existing event-mailbox table and assigned explicitly to one active event; assignment is unrelated to the header selection. A configured event mailbox is an independent override, including legacy event mailboxes, and takes precedence even if polling is disabled until a site administrator removes it. Global connection tests authenticate without reading messages; settings/assignment saves invalidate verification. Polls, tests, saves and override changes coordinate through atomic source locks, and messages are marked processed only after their database transaction commits. The global mailbox pauses for locked/archived events, and event overrides are checked before any global messages are read.
The WordPress Coding Standards checker was also run on the administrative and mailbox code. It reported an existing backlog of formatting, internationalization/documentation and context-insensitive security-sniff findings. Authorization/nonces and late escaping are reviewed at their actual request/render boundaries rather than silencing those diagnostics globally. Full WPCS conformance is not claimed.
Public results are off by default for every event. A WordPress administrator or assigned Event Administrator can open Events > Configure (rqe-event-edit), find Public Results Embed, enable public results, and save. The section displays the read-only style-coded RQE Event ID and a copyable shortcode such as [rqe_results id="MC-7"]. In a WordPress post or page, add the existing Shortcode block, paste that event's shortcode, then publish or update the post. The explicit ID is independent of the remembered administrator header selection, and multiple events can be embedded on one page. The style code must match the event's style; a bare numeric ID such as 7 is also accepted.
Anyone, including logged-out visitors, can view an enabled event's name, description, event image (when one is set), Bib, Participant Name, Total Score, Accepted Bonuses, Rejected Bonuses, Event Status, and Overall Placing. Rankings and provisional/manual statuses match administrative Results. Contact information, Registration Status, private review details, administrative controls, and administrative score-sheet links are excluded. Accepted proof photos require the additional opt-in above. Missing or disabled events return an unavailable message rather than results. Publication does not grant access to administrative screens.
Disabling public results stops new renders, but previously delivered content cannot be recalled. Purge WordPress page caches and CDN caches after enabling/disabling publication and after score/status changes so cached posts reflect the current results and visibility.
- Choose the primary style and build conveyances: explicitly save the primary scavenger hunt style in Initial Setup > Primary Style. For Motorcycle or Bicycle, add make/model records manually (Motorcycle can also import a make from NHTSA); for Horseback, add horses on the Horses tab; GeoCache needs no conveyance. Select individual records—or all records loaded on the current server page—to delete them together. Bulk deletion is all-or-nothing and stops if any selected record is assigned to a participant.
- Create participants: open Participants > Add Participant, enter one attribute per row, and attach up to 20 catalog motorcycles or bicycles, or up to 20 horses. Motorcycle filtering uses make and model only. The optional four-digit year belongs to the participant's assignment and is not checked against the catalog.
- Create and configure events: open Events > Add Event, choose its scavenger hunt style, and complete the required details. Then use Configure for event roles, inbox overrides, public results, and independent Event Status and Registration Status catalogs. The two columns stack on narrow screens.
- Build event rosters: set the event in the header, open Rosters, then use Add Rider to Event to select a rider who is not already enrolled and assign an available bib. If no unenrolled riders remain, Add New Rider creates the master rider, automatically assigns the event's lowest available bib, and returns to that roster.
- Track registration: choose a roster entry's Registration Status and press Save. Cancelled assigns DNS once on the transition; other registration changes do not overwrite Event Status.
- Ingest and review rider bonuses: configure a shared global mailbox in Initial Setup > Global Inbox, assign it explicitly to an active event, or configure an independent event override using IMAP, Gmail API, or Microsoft 365. Credentials are encrypted with AES-256-GCM from WordPress authentication salts. Scheduled or manual checks consume at most 25 unread messages per poll. Sender email must identify one registered rider, the subject must identify exactly one existing Bonus ID, and the message must contain exactly one supported image. Images pass the same strict format, executable-content, and mandatory ClamAV checks used by scoring imports. Event Administrators and Event Scorers compare the rider image with the matching library image, then accept or reject it. Rejections require a reason; accepted points update the rider total in the same database transaction and cannot be awarded twice.
- Review results: open Results for the header event. Scores are ordered highest to lowest using competition ranking; equal scores share a place and the next place is skipped. Review accepted/rejected submission counts, then use Change Event Status to manually finalize Finisher, DNF, or DNS at the event's end. Rider score sheets retain pending and reviewed submission history and are also linked from Reviewed bibs.
- Export or maintain data: use Diagnostics to download JSON or SQL before any destructive operation. Destructive actions are arranged in three columns (two or one on smaller screens). Select an action to open its verification pop-up, review the selected event or rider, and type the randomized verification string before confirming. Without dialog support or JavaScript, verification remains inline. Normal authorization and nonce checks still apply. Successful database deletions and their scope are recorded with the administrator's username and UTC timestamp; audit-storage failure rolls back the deletion.
Successful create, edit, assignment, enrollment, import, and deletion forms return to the form or tab from which they were submitted.
Global inbox setup and event-override configuration, connection testing, assignment changes and manual polling require a WordPress site administrator. Event Administrators and Scorers continue to review submissions within their assigned events. Blank IMAP passwords are retained only when host, port, encryption and username are unchanged; a changed server/account requires a newly entered password.
Configure the shared mailbox under Initial Setup > Global Inbox, then explicitly assign it to one active event; this assignment does not follow the header event. Existing event mailboxes remain independent overrides and take precedence, including when disabled. On Events > Configure, the single Email Setup section identifies the effective source and provides configuration, testing, manual pulling and override removal. Removing an override deletes its saved credentials and permits the assigned global inbox to be used for that event. Provider forms display only required settings with instructions: IMAP requires the PHP IMAP extension plus host, port, encryption, folder, username, and password. Selecting Gmail API displays a Help? hover/focus panel with Google Cloud project, Gmail API, OAuth consent, client-credential, scope, and refresh-token setup steps. Gmail API requires an OAuth client ID, client secret, refresh token, and authorization for the configured mailbox with https://www.googleapis.com/auth/gmail.modify; API requests address the account authenticated by that refresh token. Microsoft 365 requires a tenant ID, OAuth client ID/client secret, refresh token, mailbox, folder, and delegated Mail.ReadWrite offline_access consent. The plugin accepts administrator-provided refresh tokens; it does not implement the providers' interactive OAuth consent callback. Saving blank password, client-secret, or refresh-token fields preserves the existing secret only when the provider is unchanged. Successful connection tests authenticate without reading messages, and configuration or assignment saves invalidate verification. Polling, tests, saves and override changes use shared source locks to prevent races; messages are marked processed only after the database transaction commits.
Secrets are encrypted before database storage using AES-256-GCM with a key derived from WordPress authentication salts. Changing those salts intentionally makes existing inbox credentials unreadable and requires reconfiguration. JSON exports and SQL backups contain encrypted credential ciphertext, not plaintext, but remain sensitive administrative backups. Inbox polling uses provider HTTPS APIs with WordPress safe HTTP requests or the configured IMAP endpoint, processes at most 25 unread messages per event per run, records invalid consumed messages for audit/idempotency, and marks messages processed only after their result is stored.
Under Rosters, WordPress administrators can choose Import Participants beside Add Participant to open the selected event's dedicated import screen and download a sample CSV. Back to Rosters returns to that event; successful imports also return to its roster. Columns are Rider ID, Email, Bib ID, Registration Status, and Pillion; the legacy Status heading is also accepted, but cannot be combined with Registration Status. Each row must identify an existing rider by ID or email; when both are supplied they must match. No master rider records are created or changed. Blank bibs receive the lowest available bib after all supplied bibs are reserved. Supplied statuses must be enabled in this event's Registration Status catalog. Blank status defaults to Initial Registration when enabled, otherwise the first enabled value. Score, Event Status, and overall placing are not imported; Cancelled registrations receive manual DNS automatically. Files are limited to 10 MB and 5,000 rows, and XLSX uses its first worksheet. ClamAV must be available; the bonus-import bypass does not apply to roster imports. Formulas, executable content, unknown/duplicate headers, erased/unknown riders, existing enrollments, duplicate riders/bibs, and capacity violations reject the entire batch. Database errors roll back the import.
Configure this on the WordPress hosting server, not in plugin settings. Despite its name, RQE_IMPORT_TEMP_DIR holds permanent participant proof images as well as temporary staging files. Do not use an operating-system temporary directory or any location that is periodically emptied.
-
Create an existing private directory. Choose an absolute path outside every website document root, the WordPress installation,
wp-contentand uploads. Do not expose it through a web-server alias or symbolic link. An.htaccessdenial inside public uploads is not a substitute. -
Restrict access to the PHP service account. On Linux, have the host create a directory such as
/srv/rqe-private, owned by the actual PHP worker account, with mode 0700 (not 0777). On Windows/IIS, an example isD:\RQEPrivate: in Properties > Security > Advanced, disable inherited permissions, remove unrelated entries, and grant the actual PHP/application-pool identity Modify for this folder, subfolders and files. Do not grantEveryone,Usersor unrelated application pools access. Retain necessary operating-system administration and authorized backup access according to hosting policy. -
Define the path in
wp-config.php. Before the "stop editing" line, add one definition using the actual path:Linux example:
define( 'RQE_IMPORT_TEMP_DIR', '/srv/rqe-private' );
Windows example:
define( 'RQE_IMPORT_TEMP_DIR', 'D:\\RQEPrivate' );
PHP must be able to create, read, write and delete files there. If PHP
open_basediris enabled, the host must allow this location. Check disk space and applicable filesystem restrictions too. -
Back up permanent proofs privately. Preserve every
rqe-participants-{site-id}subdirectory recursively, including its event subdirectories and proof files, alongside the WordPress database. JSON/SQL exports do not contain the image bytes. Restrict backup access, preserve permissions on restore, and exclude these directories from temporary-file cleanup. When moving the configured path, securely copy the existing participant subdirectories before switching the constant and retain the originals until verification is complete. -
Rerun Diagnostics. Open RQE > Diagnostics > Required Plugins and Configuration and run the check. Both Participant private storage and Private import staging should pass. If not, follow the specific error to check ownership/ACLs, PHP restrictions and whether the path is inside a public root. The critical private-storage check cannot be overridden.
Above Diagnostics exports/backups, Required Plugins and Configuration provides an on-demand, nonce-protected check for WordPress administrators. It reports Action Scheduler installation/activation and polling API availability, OpenSSL and optional IMAP support, WordPress/PHP versions and import functions, database version, all twelve InnoDB tables and schema version, effective upload/PHP limits, writable gallery storage, private staging, and a live harmless-file ClamAV scan. Reports are private to the administrator and expire after 15 minutes. Missing Action Scheduler offers Install Action Scheduler; an installed inactive copy offers Activate Action Scheduler, only with the appropriate WordPress capabilities. These use WordPress's standard installer/activation flow, including compatibility and filesystem checks. Rerun diagnostics afterwards. The plugin dependency header is enforced by WordPress 6.5+; on older supported WordPress versions, install and activate Action Scheduler manually. Action Scheduler remains required; Gallery for SpotWalla remains optional. The checker does not install PHP extensions or ClamAV or edit server settings.
Each Diagnostics section can be minimized using its heading's expand/collapse control, including configuration checks, exports, backups, the destructive-operations group, and each deletion action. Destructive operations and Administrative Audit default to collapsed. Configuration results with failures use a red outline, stay expanded and highlight failed rows red; without failures, manual-review results use an orange outline and all-passed results a green outline, both collapsed initially. The summary names the result even when collapsed. After results expire, the last outcome's border color and expanded/minimized default remain, with Last check and Results expired labels. Only the last outcome is retained per administrator/site; expired report details are not retained or presented as current. Before any check, configuration stays neutral and expanded. Other sections initially remain expanded. Collapsing a section does not change its form values or verification requirements.
Failed requirements include expanded How to fix this requirement instructions with specific WordPress menus, PHP settings, database repair precautions, private storage configuration, and ClamAV troubleshooting. Manual review rows include expandable verification guidance for scanner policies, hosting request limits, resources, and scheduled cleanup. A failed ClamAV live check becomes Manual review when the current administrator has an audited scoring-page override; a working scanner remains Passed. The override does not certify scanning or relax import enforcement, and roster imports still require ClamAV. Rerun Diagnostics after changing an override to refresh its snapshot. Server changes must be performed by the hosting administrator; back up and test changes on staging, then rerun the check.
The check retains RQE's safety limits: 10 MB per spreadsheet/image, 50 MB remote download/ZIP, 100 MB accepted images and 200 images per queue, and 5,000 spreadsheet rows. upload_max_filesize and the WordPress upload cap must support at least 10 MB; post_max_size should support at least 11 MB (one 10 MB file plus multipart overhead), or 0 for unlimited. Sequential image uploads require only one file per request. No 256 MB import allowance is introduced. Web-server/proxy request limits, memory/time/disk resources, cron delivery, signature freshness, antivirus policies and scanner size limits remain explicitly Manual review rather than falsely passing. Verify ClamAV StreamMaxLength/MaxFileSize of at least 60 MB, MaxScanSize of at least 200 MB, freshclam updates and AlertExceedsMax yes. A clean small scan is not certification that a maximum-sized batch will succeed.
Rider email addresses are unique. A rider can have up to 20 catalog make/model motorcycles with optional rider-specific four-digit years and fuel-capacity notes. Event enrollment assigns a bib that must be unique within that event and may record optional pillion details.
Event registration open, close, start, and end values use the WordPress site timezone and must be chronological. The event date is derived from the start time. Rider and pillion prices are configuration values only; the plugin does not process payments.
The Bonus Point Library stores listings separately per event. Step 1: validate a CSV or XLSX workbook with Bonus ID, Bonus Description, Bonus Category, Bonus Points, and Bonus Locations (GPS coordinates); normally review the security scan report and approve the listing import. Locations use one latitude,longitude pair. Matching IDs are updated on re-import, while other IDs remain. Step 2: local images upload and scan sequentially, then every successful image is listed in one combined review for a single bulk authorization; public HTTPS image/ZIP/directory sources retain their combined scan review. Each image filename must be its Bonus ID (for example, B12.jpg). For URL imports, an image error blocks the batch by default. For local queues, an error blocks the staged set by default. Select Import all successful images and skip failed images to exclude invalid files and bulk import the successful set; scanner errors stop either workflow. An unavailable daemon can be explicitly bypassed as described below. Reports are private to the importing administrator and available for 15 minutes. Errors in database rollback or image recovery stop the import rather than risking additional changes.
Step 1 includes Download sample bonus listing XLSX with the five required column headings and one example row. Replace or remove that row before importing your event bonuses; keep the headings on the first worksheet and store GPS coordinates as latitude,longitude.
Steps 0, 1, and 2 appear in a three-column import header, stacking on narrow screens. A successful live ClamAV connection check collapses Step 0 to a green ClamAV Enabled label; expand it to access setup guidance and connection controls. Import notices, errors, scan-review controls, and operation-specific warnings share one section immediately below the header, grouped by originating step; the persistent ClamAV bypass warning lives inside Step 0. Successful notifications are highlighted in green; unscanned-import warnings remain warnings. Import actions return to this feedback area in the selected event. Feedback reports are private to the administrator and expire after 15 minutes.
A remembered bypass instead collapses Step 0 to an orange ClamAV Manually Disabled label for that administrator. Without bypass or a successful connection check, Step 0 stays expanded and highlighted red. These labels do not change scan enforcement: an available daemon is still used even with remembered bypass consent.
Step 0 includes Disable ClamAV Scanning, requiring explicit risk consent to enable the existing per-account unavailable-scanner override and audit the choice. With the override enabled, expand Step 0 to see the ClamAV bypass warning and use Remove ClamAV Scanning Override. These controls do not stop or uninstall the server daemon.
Step 2 offers a single Load by URL button beside the local file chooser. Like Choose Files, it prompts for the source: a dialog asks for the public HTTPS URL, validates it, and imports only that source using the same scanning and approval safeguards (without JavaScript, the URL field and button appear inline). All image-import guidance (filenames, local uploads, HTTPS/source requirements, limits, and skip-failed behavior) is in Help? beside the Step 2 heading, using the Bikes page's hover, keyboard-focus, or tap/click pattern. The partial-image checkbox sits below both the local file chooser and URL load controls and applies to either source.
Local scoring image uploads use a sequential scan queue with JavaScript: choose a folder, clear the URL, then Validate and Import Bonus Images. Each request uploads and scans exactly one image, but no passing staged image is imported until all selected files have been processed. The page lists every successful image and its scan result, then one Approve and Bulk Import Images authorization imports the passing set. The existing audited remembered unavailable-scanner override still avoids repeated prompts; working scanners always require the combined review. A 200-image/100 MB queue is not sent in one HTTP request. Individual images still must fit PHP/host per-file limits and RQE's 10 MB limit; this is not chunked uploading of oversized individual files. Without JavaScript, import one image at a time using the same server-side review.
Before bulk authorization, Import all successful images and skip failed images allows invalid images to be excluded from the combined review; without it, a failed image cancels the staged passing set. Scanner errors and uncertain network/server outcomes stop the queue without automatic retries. Stop after current file finishes an in-flight request, then cancels the staged set. During the authorized bulk update, each fingerprint-verified staged image is still committed through the guarded server endpoint; if a later request fails, earlier confirmed imports remain and the page reports the interruption. Refresh the gallery to inspect saved results; use the saved-review link if an outcome is uncertain. Navigating away loses the browser queue, while abandoned private staging expires through the existing cleanup. URL imports keep the existing batch review and approval behavior.
The import screen includes Step 0: Set up or verify ClamAV, with expandable Ubuntu/Debian installation commands, configuration guidance, and links for other platforms. Run the commands on the server with server-administrator permissions, or have your hosting provider complete them; WordPress does not execute installation commands or elevate privileges. Check ClamAV Connection performs a small local file-stream scan and displays the result without importing any event data. It does not certify signature freshness or scan-limit configuration. Existing README-based pre-installation remains supported: skip installation and use the connection check.
Bonus imports normally require a local ClamAV daemon (clamd) with current signatures maintained by freshclam. The default endpoint is unix:///var/run/clamav/clamd.ctl. If needed, set RQE_CLAMAV_SOCKET in wp-config.php to your local Unix socket or tcp://127.0.0.1:3310 (loopback only). Give the WordPress PHP process permission to access the socket. Never expose clamd's unauthenticated TCP listener to the network.
Enable archive, PE/ELF, and Office scanning in clamd, and set AlertExceedsMax yes so scan-limit violations are blocked rather than reported clean. Configure StreamMaxLength and MaxFileSize to at least 60 MB, and MaxScanSize to at least 200 MB for the supported archives. Host administrators must keep the engine and signatures updated; this plugin does not install or configure ClamAV. Timeouts, permission/configuration errors, and scanner errors block approval.
When WordPress runs in a container, run the official clamav/clamav image as a separate service and share only its Unix socket. ClamAV does not need the WordPress files because uploads are streamed to it. Put the socket in a tmpfs volume created with fixed ownership so permissions are identical after every restart, with no manual chmod. This uses stock images with no build: steps, which some stack managers (such as Portainer) cannot run.
Create clamd.conf on the Docker host (for example /path/to/clamav/clamd.conf):
DatabaseDirectory /var/lib/clamav
User clamav
LogTime yes
LocalSocket /run/clamav/clamd.sock
LocalSocketMode 666
FixStaleSocket yes
TCPSocket 3310
TCPAddr 127.0.0.1
ScanArchive yes
ScanPE yes
ScanELF yes
ScanOLE2 yes
AlertExceedsMax yes
StreamMaxLength 60M
MaxFileSize 60M
MaxScanSize 200MAdd or adapt these Compose entries, keeping your existing WordPress settings:
services:
wordpress:
# ...existing WordPress settings...
depends_on:
- clamav
volumes:
# ...existing volumes...
- clamav_socket:/run/clamav
clamav:
image: clamav/clamav:stable
restart: unless-stopped
volumes:
- clamav_socket:/run/clamav
- /path/to/clamav/db:/var/lib/clamav
- /path/to/clamav/clamd.conf:/etc/clamav/clamd.conf:ro
healthcheck:
test: ["CMD-SHELL", "echo PING | nc 127.0.0.1 3310 | grep -q PONG"]
interval: 60s
timeout: 10s
retries: 3
start_period: 6m
volumes:
clamav_socket:
driver: local
driver_opts:
type: tmpfs
device: tmpfs
o: "uid=100,gid=33,mode=0750,size=1m"- The socket directory belongs to the image's
clamavuser (UID 100) and the PHP account's group (GID 33 iswww-datain the official Debian-based WordPress images) with mode0750, so only those accounts can reach the socket; the directory enforces access, not the socket mode. Confirm IDs withdocker exec <container> id clamav/id www-dataand adjust if different. - Docker applies
driver_optsonly when it creates a volume: remove an existing socket volume or use a new name. - The
127.0.0.1:3310listener exists only inside the ClamAV container for its health check; never publish port 3310. - Add
define( 'RQE_CLAMAV_SOCKET', 'unix:///run/clamav/clamd.sock' );towp-config.php, redeploy, wait for the first signature download, then use Check ClamAV Connection and rerun Diagnostics. To troubleshoot, rundocker exec <wordpress-container> ls -la /run/clamav.
If the local socket is missing or the daemon refuses the connection, bulk imports show Not scanned results and require explicit Confirm Bypass and Import consent before saving. This cannot distinguish an uninstalled daemon from a stopped daemon. Consent is remembered only for that administrator on that site. Subsequent validated bulk listing/image imports while ClamAV is unavailable proceed without another approval prompt; each post-import notice warns that no antivirus security scan was performed. Available ClamAV is still used and its scan report still requires approval. Malware detections are excluded/blocked, scanner errors block the batch, and format, executable-content, size, and fingerprint checks remain mandatory. Manual editor replacement images do not use this bypass.
Consent, revocation, unscanned import start/completion/failure records, and Diagnostics deletions are stored in {prefix}RQE_import_audit with user ID, username snapshot, UTC date/time, event ID (0 for site-wide actions), action, and details. Audit insertion must succeed before unscanned files can be imported. Diagnostics > Administrative Audit displays the latest 100 records; JSON exports and SQL backups include all records. Event deletion and Delete All Data preserve this history and remembered choices. Delete Virus Scan Audit Data explicitly clears the audit table and resets every administrator's remembered ClamAV override, then leaves a new audit record identifying who deleted the history and when. Remove ClamAV Scanning Override in Step 0 revokes your remembered consent and records the change. Opt-in destructive uninstall removes the audit table with the other plugin tables; otherwise it is retained.
Files are streamed to the local scanner, never to a third-party service. CSV formulas, XLSX formulas/macros/embedded files, XML entities, script markers, and appended image content are rejected in addition to format checks. Use values-only XLSX workbooks without embedded images. Local checks and antivirus reduce risk but do not guarantee the absence of malicious content.
JPEG, PNG, GIF and WebP content checks distinguish compressed pixels and binary color tables from metadata: image data is not searched as script text, avoiding false positives from short byte sequences such as <%. Metadata remains checked, including bounded decompression of PNG text/profile metadata. Malformed/truncated containers and trailing content are rejected, and ClamAV still scans the entire file. JPEG, PNG, GIF and WebP imports keep only the validated primary image: data appended after its end marker (JPEG EOI, PNG IEND, GIF trailer, WebP RIFF length)—gain maps, MPF images, editor/vendor trailers, motion-photo video or padding—is removed before scanning and never stored. This does not relax malware detection, scan review or unavailable-scanner consent requirements.
Pending files stay in a random, private temporary directory outside the web root and uploads. If the server's default temporary directory is web-accessible, define RQE_IMPORT_TEMP_DIR as an existing writable directory outside the document root, with filesystem permissions restricted to the PHP service account. Approval is user/event-bound, expires after 15 minutes, is single-use, and verifies staged file SHA-256 fingerprints before importing. Cancel removes staged files; abandoned files are removed by WordPress cron after expiry (configure a reliable cron runner on low-traffic sites). Recovery backups are retained with an error log entry when automatic cleanup would risk losing data. Manual replacement images in the bonus editor are also scanned before saving.
Images are stored in the plugin's event-specific upload folder and are not added to the WordPress media library. Click a gallery thumbnail for a larger overlay preview; close it with the Close image button, Escape, or a click outside the preview to return to the listing. Without dialog support, the image link opens in a new tab. Bonus scoring calculations and the other submission-review tabs are not implemented.
New event registrations use registration status Initial Registration, automatic result status, score 0, and no stored placing. A score equal to or greater than the event's minimum finish score produces at least Preliminary Finisher; otherwise it is Registered. Results provides authorized manual finalization until the event is locked. Participant numbers link to an authorized score sheet containing the participant's pending, accepted, and rejected email-submission history.
The optional factual import uses the NHTSA Product Information Catalog and Vehicle Listing (vPIC) API. The administrator supplies only a make.
An administrator must explicitly start and confirm the import. It never runs during activation or page load. The importer:
- Calls Get Models for Make, then intersects those model IDs with a second NHTSA response restricted to the
motorcyclevehicle type. - Limits each response to 256 KB and follows no redirects.
- Imports only intersected records explicitly identified as
VehicleTypeName: Motorcycle. - Stores only the confirmed make and model. A year is entered separately and optionally when assigning a motorcycle to a rider.
- Rejects unavailable, malformed, unconfirmed, and non-motorcycle results.
The requests disclose the site server's IP address, the entered make, and ordinary HTTP metadata to NHTSA; rider, administrator, and event data are never sent. Manual motorcycle entry remains available.
- Search existing issues to see whether the problem is already reported.
- If not, open a new issue with a short, descriptive title.
- Include the expected and actual behavior, steps to reproduce the problem, the affected tab or workflow, and any error messages or screenshots.
- Include the plugin, WordPress, PHP, and database versions. The plugin, WordPress, and PHP versions are shown on the About tab.
- Do not include passwords, private rider information, emergency contacts, insurance details, or other sensitive data. Report security vulnerabilities privately to the repository owner rather than in a public issue.
Activation creates plugin-owned tables using the current site's WordPress database prefix followed by RQE_. WordPress users and media remain in core tables and are referenced rather than copied or owned by the plugin.
| Stored area | Contents |
|---|---|
| Event Administrators | References to WordPress users assigned to specific events |
| Mailbox settings | Encrypted event inbox credentials and one optional global inbox in row 0; global event assignment is stored inside its encrypted settings |
| Setup readiness | Minimal site-wide section readiness, diagnostic counts/timestamp, ignored manual-review names and setup-override state; detailed reports remain private to the checking administrator |
| Motorcycle catalog | Motorcycle make/model labels and source attribution |
| Riders (participants) | Contact, emergency-contact, insurance, notes, and unique email data |
| Rider motorcycles | Catalog association for motorcycles or bicycles, optional manually entered model year, and fuel-capacity text |
| Horses | Name, breed, sex, age, color, and optional participant owner |
| Events | Name, description, scavenger hunt style, schedule, registration window, prices, location, coordinates, logo reference, bib capacity, and minimum-finisher-points configuration |
| Registrations | Event/rider association, unique event bib, pillion text, registration lifecycle status, automatic/manual result status, score, and placing |
| Status options | Independent event-specific Registration Status and Event Status labels and enabled/disabled flags |
| Participant account links | Unique WordPress-account-to-participant association; website access additionally requires approved event enrollment |
| Participant requests | Event/account association, verification and approval state, pending registration details, hashed expiring verification token, approving administrator and timestamps |
| Bonus submissions | Email or website proof reference, participant/event identity, description, decision, awarded points and review details; website proof files remain in private storage |
| Certificate mail queue | Temporary approval/delivery state and duplicate-send protection; sensitive snapshots and message text are cleared after outcomes |
| Certificate history | Retained participant-name/design snapshots, print/PDF generation or email outcome, initiating user, detail and UTC timestamp; separate from the temporary mail queue |
| Bonus listings | Event-specific bonus IDs, descriptions, categories, points, GPS coordinates, and image filenames |
| Import audit | Administrator ID and username snapshot, UTC date/time, event ID, bypass consent/revocation, unscanned import activity and Diagnostics deletions; retained except when explicitly deleting audit data |
Rider records can contain names, aliases, email addresses, phone numbers, motorcycle make/model assignments, manually entered model years, fuel capacity, emergency contacts, insurance details, and free-form notes. Registrations link riders to events and store bib, pillion, registration status, result status, score, and placing values. Diagnostics exports contain all plugin database data and SQL backups contain restorable schema and records, including encrypted global/event mailbox credential ciphertext, so both must be protected as sensitive data. Detailed diagnostic reports are private; only a minimal readiness summary is shared site-wide. Bonus gallery files are stored under a dedicated rqe-event-gallery uploads directory, outside the WordPress media attachment library; gallery files are not included in database exports or backups and must be backed up separately. Site operators are responsible for participant notices, consent where required, access control, retention, backups, and secure handling of this data.
WordPress personal-data export and erasure tools include relevant plugin data. Erasure anonymizes a rider and removes motorcycle and pillion details while retaining anonymous registration identifiers and results fields for event integrity. Event Administrator assignments remain for access control and can be removed from the event's Configure page. WordPress administrator access is controlled only by WordPress roles and capabilities. Deactivation preserves data. Fresh installs retain data on uninstall by default; existing sites continue to honor any previously saved uninstall-retention preference.
The plugin does not include analytics, telemetry, payment processing, remotely hosted JavaScript, or automatic external requests. Use TLS, least-privilege accounts, restricted database access, secure backups, and supported versions of WordPress, PHP, and the database server.
The plugin uses composition rather than an inheritance framework. WordPress authorization, nonces and redirects belong at the administrative request boundary; import operations receive explicit inputs rather than reading request globals. Database transactions and gallery rollback remain coordinated by the operation that owns the mutation.
RQE_Bonus_Application_Service provides explicit-input import, review, override and editor operations. RQE_Roster_Import_Service owns roster validation and atomic enrollment; RQE_Diagnostics_Deletion_Service owns audited destructive operations and gallery recovery. Bonus and roster imports share RQE_Spreadsheet_Reader, retaining the data-only XLSX protections and size/row limits. RQE_Import_Audit owns audit persistence and per-administrator override lookup.
Each import creates its own RQE_Scan_Session, separating report state and unavailable-scanner policy from RQE_ClamAV_Transport and RQE_Executable_Content_Policy. Pending review storage, cleanup scheduling and gallery filesystem operations use small injectable adapters; audit persistence also accepts a database adapter. Existing static bonus/scanner entry points remain for compatibility, while administrative requests use the explicit application service. Custom adapters must preserve failure reporting, single-use review expiry/locking, private staging and coordinated recovery semantics; they are not a way to bypass scanning or audit requirements.
Administrative screens use RQE_Queries read models for event-scoped lists, searches, pagination, rider visibility and score summaries. Its database adapter is constructor-injected; query failures raise explicit errors rather than appearing as empty lists. Callers still enforce screen authorization, and assigned-administrator queries retain their user/event restrictions. RQE_Results contains the shared pure competition-ranking and automatic/manual status rules, so roster, results and score-sheet screens use the same calculations.
RQE_Diagnostics collects cohesive plugin, runtime, database, upload, storage, scanner and hosting check groups. An explicitly supplied callable list can compose a report without modifying the collector. RQE_Diagnostic_Report validates result rows and applies the shared Failed > Manual review > Passed precedence used by report persistence and rendering. Authorization runs before any check, including supplied checks.
Public-contract regressions cover ranking edge cases, parameterized read models, database failures and diagnostic composition. RQE_Statuses owns event-local status choices and assignments, sharing the event row lock with enrollment/imports so disabling/deleting choices cannot race a new assignment. php tests/event-administration.php uses development-only PDO SQLite to execute status migration and result aggregation SQL, check permissions and header persistence, and assert rendered page contracts; production continues to use WordPress's MySQL/MariaDB adapter. Run standalone suites with php tests/<suite>.php; the release workflow runs these alongside the import, roster, queue and Diagnostics regressions. No additional production dependencies are required.
Open tests/image-queue-browser.html in a browser to exercise the single-image request with a mocked response and real form controls. This covers the hidden action field shadowing the form's JavaScript action property; requests must read the form's destination attribute instead.
The Security checks and ZIP release GitHub Actions workflow runs on pull requests, branch pushes, version tags, and manual dispatch. It:
- Checks PHP and JavaScript syntax.
- For public repositories, runs CodeQL's extended JavaScript security queries on the exact checked-out commit, uploads code-scanning results, and rejects any SARIF findings or missing/failed analysis. For private repositories, skips CodeQL with an Actions notice, job summary and explicit release-note disclosure; publication continues when the other gates pass. Unknown visibility blocks the job. CodeQL does not support PHP; Semgrep remains the PHP static-analysis gate.
- Requires zero open repository Dependabot alerts of any severity on release tags, using paginated API reads, then rechecks immediately before publishing. Missing credentials, disabled alerts and API errors block publication.
- Scans PHP and JavaScript with Semgrep security rules covering common exploit patterns, including SQL injection, cross-site scripting, command injection, file inclusion, unsafe deserialization, and server-side request forgery. Supplemental WordPress rules check request data reaching
$wpdbqueries (including unsafepreparetemplates), HTML output, and unsafe WordPress HTTP calls; vulnerable and safe fixtures verify those rules before each scan. - Uses Trivy's vulnerability and secret scanners on the working tree.
- Runs a separate, version/checksum-pinned Gitleaks CLI check with redacted output against all available Git history and the working tree. Checkout fetches full history so deleted historical secrets are also checked.
- Blocks packaging when findings or scanner failures occur.
These checks support secure development but do not certify NIST compliance, guarantee the absence of CVEs, or replace penetration testing and manual review. Bundled Dompdf dependencies are declared in rally-quest-engine/composer.json and its lockfile and audited by Composer during release validation. These checks do not assess the deployed WordPress installation, PHP runtime, database server, or other plugins.
All required scanners are release gates: findings, invalid rules, failed fixture tests, download/checksum failures, and scanner errors stop publication. CodeQL is required only for public repositories; private repositories receive an explicit skip notice instead. Packaging may finish independently, but the release job cannot publish until every applicable gate succeeds. Do not bypass findings to publish; fix exploit findings and revoke/rotate any detected secret, then address exposed history as appropriate. Registry Semgrep rules are updated upstream; the supplemental rules are versioned in .github/semgrep/wordpress.yml. Static analysis is not exhaustive, especially across helper/function boundaries or for stored-data flows.
Before publishing, enable Dependency graph and Dependabot alerts under the repository's Settings > Advanced Security (called Code security and analysis in some GitHub interfaces). Add an Actions repository secret named RELEASE_SECURITY_TOKEN: use a fine-grained token restricted to this repository with Dependabot alerts: Read-only permission. The normal workflow token cannot read Dependabot alerts. Never put the token in source or release notes. Enable GitHub Code Security/code scanning where required by the repository's visibility/license, and use this workflow's advanced CodeQL setup rather than a conflicting default setup. Manual workflow dispatch also exercises the Dependabot gate; dispatch on a branch to verify access without publishing a release.
Dependabot version updates are configured weekly for GitHub Actions in .github/dependabot.yml. Dependabot alert checks reflect GitHub's repository dependency graph, not a new scan of the tagged package; a clear alert list is not proof of deployed dependencies being secure. The bundled PDF dependencies have a Composer manifest and lockfile; there is no npm runtime dependency manifest. Keep release dependencies reflected in the default branch's dependency graph and resolve open alerts before tagging. Run node tests/release-security.cjs to exercise the fail-closed alert and SARIF gate contracts.
.github/gitleaks.toml retains all default Gitleaks rules and allows only the exact non-secret transient-key prefix rqe_bonus_pending_1_ (and its pre-rename history form mrsa_bonus_pending_1_) in tests/bonus-import.php for the generic API-key rule. It does not exclude test files or historical commits from secret scanning.
For a release, update the plugin Version: header, RQE_VERSION, and the Stable tag in rally-quest-engine/readme.txt to the same semantic version. Merge the intended release commit, then push a matching tag such as v0.2.0.
Only a matching vMAJOR.MINOR.PATCH tag can publish. After all security checks pass, the workflow builds a fresh installable ZIP from rally-quest-engine/, verifies it, creates SHA256SUMS, and publishes both files to a GitHub release. Branch and pull-request runs validate only and never publish.
The release job alone receives repository write permission. Protect release tags and restrict who can create them in repository rulesets.