Waiver Exemption/Attestation System
Overview
The Waiver Exemption system allows users to attest that a waiver is not required for a specific gathering activity, providing a configurable reason for the exemption. This creates an audit trail of why waivers were not collected for specific activities.
Database Schema
waivers_waiver_types Table
- New Column:
exemption_reasons(TEXT, nullable)- Stores a JSON array of valid exemption reasons
- Example:
["Pre-approved by legal", "Not applicable for this activity", "Covered by existing waiver"] - Empty or null means exemptions are not allowed for this waiver type
waivers_gathering_waivers Table
Modified to support both uploaded waivers and exemptions (attestations):
- New Column:
is_exemption(BOOLEAN, default false)- When true, this record represents an exemption rather than an uploaded waiver
- New Column:
exemption_reason(VARCHAR 500, nullable)- The selected exemption reason (only set when is_exemption is true)
- Must match one of the waiver type’s configured exemption_reasons
- Existing Column:
document_id(nullable)- Null for exemptions (since no document is uploaded)
- Required for regular waiver uploads
- Existing Column:
notes(TEXT, nullable)- Can contain additional context for both waivers and exemptions
Note: Exemptions are linked to activities through the existing waivers_gathering_waiver_activities join table, allowing a single exemption to apply to multiple activities (just like regular waivers).
Components
Backend
Entities
WaiverType.php- Added
exemption_reasonsaccessible field - Added
exemption_reasons_parsedvirtual field _getExemptionReasonsParsed()method returns parsed JSON array
- Added
GatheringWaiver.php- Added
is_exemptionaccessible field - Added
exemption_reasonaccessible field - Supports both uploaded waivers (document_id set) and exemptions (is_exemption=true)
- Added
Table Classes
WaiverTypesTable.php- Added
hasManyrelationship toGatheringWaivers - Validation for
exemption_reasonsfield (must be array of non-empty strings)
- Added
GatheringWaiversTable.php- Modified to handle both uploaded waivers and exemptions
- Validation ensures exemptions have exemption_reason set and document_id null
- Validation ensures regular waivers have document_id set
Controllers
GatheringWaiversController.php
- New
attest()action:- Accepts POST with gathering_activity_ids[], waiver_type_id, gathering_id, reason, notes
- Creates a single GatheringWaiver with is_exemption=true
- Associates it with multiple activities through the join table
- Validates authorization (user must be able to edit the gathering)
- Verifies all activities belong to the gathering
- Validates the reason is in the waiver type’s configured exemption_reasons
- Returns JSON response (success/error)
View Cells
GatheringWaiversCell.php
- Modified
display()method to:- Query GatheringWaivers with is_exemption=true for the gathering
- Include exemption data in waiverRows structure
- Add ‘exempted’ count to overallStats
- Exemptions appear in the same table as regular waivers with appropriate badges
Frontend
Templates
display.php(Waivers template)- Shows exemption badge with tooltip for exempted waivers
- “Attest Not Needed” button appears only for:
- Waivers with status “Pending”
- Waiver types that have exemption_reasons configured
- Includes
waiver_attestation_modalelement
waiver_attestation_modal.php- Bootstrap modal with:
- Dynamic reason radio button list
- Optional notes textarea
- Error/success alert containers
- Submit button with Stimulus action bindings
- Bootstrap modal with:
add.phpandedit.php(WaiverTypes forms)- Dynamic exemption reasons management section
- Add/remove reason inputs
- JSON storage in hidden field
- Helpful instructions for administrators
JavaScript Controllers
waiver-attestation-controller.js- Manages attestation modal display and form submission
showModal(): Opens modal, populates with waiver datapopulateReasons(): Builds radio button list from waiver type’s reasonssubmitAttestation(): AJAX POST to /waivers/gathering-waivers/attest- Handles CSRF tokens, error/success feedback, page reload
exemption-reasons-controller.js- Manages dynamic list of exemption reasons in WaiverTypes forms
addReason(): Adds new reason input fieldremoveReason(): Removes reason field (keeps at least one)reasonChanged(): Updates hidden JSON field, auto-adds new field when last one is filled- Automatic focus management for better UX
User Flow
Attesting No Waiver Needed
- User navigates to Gathering Waivers tab on a gathering detail page
- For activities with pending waivers that have exemption reasons configured, an “Attest Not Needed” button appears
- Clicking the button opens a modal showing:
- Activity name
- Waiver type name
- List of configurable reasons (radio buttons)
- Optional notes field
- User selects a reason and optionally adds notes
- User clicks “Attest Not Needed”
- System validates:
- User has permission to edit the gathering
- Activity belongs to the gathering
- Selected reason is valid for this waiver type
- No duplicate exemption exists
- On success:
- Exemption record is created
- Page reloads to show updated status
- Waiver row now shows “Exempted” badge with tooltip showing reason
Configuring Exemption Reasons
- Administrator navigates to Waiver Types add/edit form
- Scrolls to “Exemption Reasons” section
- Adds reasons using dynamic input fields:
- Type reason text and press Enter or Tab
- System automatically adds a new empty field
- Remove reasons using the X button
- Save the waiver type
- Reasons are stored as JSON array in database
- When empty, attestation is not available for this waiver type in the upload wizard
Display Logic
Waiver Status Badge
- Uploaded: Blue badge - waiver has been uploaded
- Exempted: Warning badge with info icon - exemption created, shows reason
- Pending: No badge - waiver needs to be uploaded or exempted
Upload Wizard
The waiver upload wizard (Step 3: Add Pages) provides two modes:
- Upload Mode: User uploads waiver document images
- Attest Mode: User attests that waiver is not needed (only available when waiver type has exemption_reasons configured)
Users can toggle between modes when exemption reasons are available for the selected waiver type.
Technical Details
Validation Rules
WaiverType.exemption_reasons:
- Optional field (can be null or empty)
- Must be valid JSON array when present
- Each reason must be a non-empty string
GatheringWaiver (when is_exemption=true):
gathering_id: Required, must existwaiver_type_id: Required, must existis_exemption: Set to trueexemption_reason: Required, non-empty string, must be in waiver type’s exemption_reasonsdocument_id: Must be null (no document for exemptions)member_id: Required, must exist (who created the exemption)notes: Optional text fieldcreated: Auto-populated timestamp- Links to activities via
waivers_gathering_waiver_activitiesjoin table
Security
- Authorization checked via
Authorization->canEditGathering() - CSRF token required for all POST requests
- User identity captured in waiver record (member_id)
- Activity ownership validated (must belong to specified gathering)
- Reason validation prevents arbitrary text entry
- Exemptions are treated like waivers and can be reviewed/approved by gathering staff
Data Integrity
- Foreign keys ensure referential integrity
- Exemptions stored in same table as waivers for simplified reporting
is_exemptionflag differentiates between uploaded waivers and exemptions- CASCADE delete when gathering or waiver type is deleted
- JSON validation ensures data quality for exemption_reasons
- Single exemption record can apply to multiple activities (via join table)
Future Enhancements
Potential improvements for future iterations:
- Exemption Management
- View list of all exemptions for a gathering (already supported - exemptions appear in waiver list)
- Ability to revoke/delete exemptions (treat like regular waivers)
- Exemption history with change log
- Reporting
- Report of exempted waivers by gathering/activity
- Export exemption data for compliance audits
- Notifications
- Email notification when exemption is created
- Alert gathering staff of exemptions
- Bulk Operations
- Apply same exemption reason to multiple activities at once
- Copy exemptions from previous gathering
- Enhanced Validation
- Require manager approval for certain exemption reasons
- Limit exemptions based on activity type or risk level
Files Modified
Database
/app/plugins/Waivers/config/Migrations/20251106163803_AddExemptionReasonsToWaiverTypes.php/app/plugins/Waivers/config/Migrations/20251106172020_AddExemptionFieldsToGatheringWaivers.php
Backend
/app/plugins/Waivers/src/Model/Entity/WaiverType.php/app/plugins/Waivers/src/Model/Entity/GatheringWaiver.php/app/plugins/Waivers/src/Model/Table/WaiverTypesTable.php/app/plugins/Waivers/src/Model/Table/GatheringWaiversTable.php/app/plugins/Waivers/src/Controller/GatheringWaiversController.php/app/plugins/Waivers/src/View/Cell/GatheringWaiversCell.php
Frontend Templates
/app/plugins/Waivers/templates/cell/GatheringWaivers/display.php/app/plugins/Waivers/templates/GatheringWaivers/upload.php/app/plugins/Waivers/templates/GatheringWaivers/mobile_upload.php/app/plugins/Waivers/templates/element/GatheringWaivers/upload_wizard_steps.php/app/plugins/Waivers/templates/WaiverTypes/add.php/app/plugins/Waivers/templates/WaiverTypes/edit.php
JavaScript
/app/plugins/Waivers/assets/js/controllers/waiver-upload-wizard-controller.js/app/plugins/Waivers/assets/js/controllers/exemption-reasons-controller.js