5.7 Waivers Plugin
Last Updated: February 2026
Status: Production
Plugin: Waivers
Source: plugins/Waivers/
Migration Order: 4
The Waivers plugin provides comprehensive waiver management for gatherings, including waiver type configuration, document upload tracking, exemption attestation, compliance monitoring, decline workflows, and closure tracking.
Purpose
This plugin enables:
- Waiver Type Configuration: Define waiver types with retention policies, templates, and exemption reasons
- Activity-Level Requirements: Assign required waiver types to gathering activities
- Document Upload: Upload waiver documents with image-to-PDF conversion and mobile camera capture
- Exemption Attestation: Attest that a waiver is not needed with tracked reasons
- Compliance Monitoring: Dashboard showing waiver collection status across gatherings
- Decline Workflow: Decline uploaded waivers within 30 days with tracked reasons
- Closure Tracking: Two-stage closure (ready-to-close → closed) for waiver collection
- Steward Access: Gathering stewards can upload waivers and view pending waivers
Architecture Overview
Plugin Class
WaiversPlugin extends BasePlugin and implements KMPPluginInterface.
Bootstrap registrations:
NavigationRegistry→WaiversNavigationProviderViewCellRegistry→WaiversViewCellProvider- Configuration via
initializeSettings()(versioned, currently1.0.1)
Configuration settings:
Plugin.Waivers.Active— plugin enabled/disabledPlugin.Waivers.ShowInNavigation— navigation visibilityWaivers.ComplianceDays— compliance window (default: 2 days)
Controllers
| Controller | Actions | Description |
|---|---|---|
GatheringWaiversController |
index, view, upload, download, preview, delete, decline, needingWaivers, dashboard, calendarData, indexGathering, mobileSelectGathering, mobileUpload, changeWaiverType, changeActivities, gridData | Waiver upload, viewing, compliance, and management |
WaiverTypesController |
index, view, add, edit, delete, toggleActive, downloadTemplate, gridData | Waiver type CRUD and template management |
GatheringActivityWaiversController |
add, delete, availableWaiverTypes | Manage waiver requirements on gathering activities |
GatheringWaiversController uses DataverseGridTrait for server-side grid filtering and injects DocumentService, ImageToPdfConversionService, and RetentionPolicyService.
WaiverTypesController uses DataverseGridTrait and injects DocumentService.
Services
| Service | Purpose |
|---|---|
GatheringActivityService |
Business logic for adding/removing waiver requirements on activities. Returns ServiceResult. |
WaiversNavigationProvider |
Provides navigation items: Waivers Uploaded, Waiver Dashboard, Waiver Types (Config), Gatherings Needing Waivers (Action Items with badge count) |
WaiversViewCellProvider |
Registers view cells for gathering activity views and gathering views, plus mobile menu items |
Data Model
WaiverType Entity
Defines types of waivers with retention policies and templates.
| Field | Type | Description |
|---|---|---|
id |
int | Primary key |
name |
string | Waiver type name (unique) |
description |
string|null | Description |
document_id |
int|null | FK to documents table for uploaded template |
template_path |
string|null | External URL to template |
retention_policy |
string | JSON-encoded retention policy |
exemption_reasons |
string|null | JSON array of exemption reason strings |
convert_to_pdf |
bool | Whether to convert uploaded images to PDF |
is_active |
bool | Whether this type is available for use |
created |
datetime | |
modified |
datetime | |
created_by |
int|null | Footprint |
modified_by |
int|null | Footprint |
deleted |
datetime|null | Soft delete (Trash) |
Virtual fields: retention_policy_parsed (array), retention_description (human-readable string), exemption_reasons_parsed (array)
Retention policy JSON structure:
{
"anchor": "gathering_end_date|upload_date|permanent",
"duration": { "years": 1, "months": 0, "days": 0 }
}
GatheringWaiver Entity
Represents an uploaded waiver document or an exemption attestation for a gathering.
| Field | Type | Description |
|---|---|---|
id |
int | Primary key |
gathering_id |
int | FK to gatherings |
waiver_type_id |
int | FK to waiver types |
document_id |
int|null | FK to documents (null for exemptions) |
is_exemption |
bool | True if this is an exemption, not an upload |
exemption_reason |
string|null | Reason waiver wasn’t needed |
status |
string | pending, active, or deleted |
retention_date |
date | When this waiver can be purged |
created_by |
int | Uploader member ID |
created |
datetime | |
modified |
datetime | |
declined_at |
datetime|null | When waiver was declined |
declined_by |
int|null | Who declined |
decline_reason |
string|null | Why declined |
Virtual fields: is_expired, is_active, is_declined, can_be_declined (within 30 days, not already declined, not expired/deleted), status_badge_class, status_display
getBranchId(): Returns the branch ID from the gathering’s hosting branch for authorization scoping.
GatheringActivityWaiver Entity
Join table linking gathering activities to required waiver types.
| Field | Type | Description |
|---|---|---|
gathering_activity_id |
int | Composite PK, FK to gathering_activities |
waiver_type_id |
int | Composite PK, FK to waiver_types |
created |
datetime | |
created_by |
int|null | Footprint |
modified_by |
int|null | Footprint |
deleted |
datetime|null | Soft delete |
GatheringWaiverClosure Entity
Tracks waiver collection closure status for a gathering. Supports two stages.
| Field | Type | Description |
|---|---|---|
id |
int | Primary key |
gathering_id |
int | FK to gatherings (unique) |
ready_to_close_at |
datetime|null | When marked ready to close |
ready_to_close_by |
int|null | Who marked ready |
closed_at |
datetime|null | When fully closed |
closed_by |
int|null | Who closed |
created |
datetime | |
modified |
datetime |
Methods: isReadyToClose(), isClosed()
Tables
WaiverTypesTable
- DB Table:
waivers_waiver_types - Display Field:
name - Behaviors: Timestamp, Footprint, Trash
- Associations: HasMany → GatheringActivityWaivers, GatheringWaivers
- Validation: name (required, unique, max 100), retention_policy (required, valid JSON with anchor/duration), exemption_reasons (optional, valid JSON array), convert_to_pdf (boolean), is_active (boolean)
- Business Rules: isUnique on name
- Custom Finders:
findActive()— filters tois_active = true
GatheringWaiversTable
- DB Table:
waivers_gathering_waivers - Extends:
Table(not BaseTable) - Behaviors: Timestamp, Footprint, Trash
- Associations: BelongsTo → Gatherings (INNER), WaiverTypes (INNER), Documents (LEFT), CreatedByMembers, DeclinedByMembers; HasMany → AuditNotes
- Validation: gathering_id (required int), waiver_type_id (required int), document_id (optional int), status (required, in: pending/active/deleted), retention_date (required date)
- Business Rules: existsIn on gathering_id, waiver_type_id, document_id, created_by
- Custom Finders:
findExpired(),findByStatus($status),findByGathering($gatheringId),findValidByGathering($gatheringId)(excludes declined and deleted) - Static:
countGatheringsNeedingWaivers($memberId)— counts gatherings with missing required waivers, respects branch permissions and steward access, excludes closed gatherings
GatheringActivityWaiversTable
- DB Table:
waivers_gathering_activity_waivers - Primary Key: Composite (
gathering_activity_id,waiver_type_id) - Behaviors: Timestamp, Footprint, Trash
- Associations: BelongsTo → GatheringActivities (INNER), WaiverTypes (INNER)
- Validation: gathering_activity_id (required int), waiver_type_id (required int)
- Business Rules: existsIn on gathering_activity_id, waiver_type_id
GatheringWaiverClosuresTable
- DB Table:
waivers_gathering_waiver_closures - Behaviors: Timestamp
- Associations: BelongsTo → Gatherings (INNER), ClosedByMembers, ReadyToCloseByMembers
- Validation: gathering_id (required int), closed_at/closed_by/ready_to_close_at/ready_to_close_by (optional)
- Business Rules: existsIn on gathering_id, closed_by, ready_to_close_by; isUnique on gathering_id
- Key Methods:
isGatheringClosed($gatheringId)— checks if closed_at is setisGatheringReadyToClose($gatheringId)— checks if ready_to_close_at is setgetClosureForGathering($gatheringId)— returns closure record with contained membersgetClosedGatheringIds($gatheringIds?)— batch query for closed gathering IDsgetReadyToCloseGatheringIds($gatheringIds?)— batch query for ready-to-close IDsmarkReadyToClose($gatheringId, $memberId)— creates or updates closure recordunmarkReadyToClose($gatheringId)— removes ready-to-close status (only if not already closed)
Policies
All policies extend BasePolicy which provides super-user bypass via before().
Entity Policies
| Policy | Custom Methods | Notes |
|---|---|---|
GatheringWaiverPolicy |
canDownload, canPreview, canChangeWaiverType, canViewGatheringWaivers, canNeedingWaivers, canUploadWaivers, canCloseWaivers, canDecline |
Steward support: canViewGatheringWaivers, canNeedingWaivers, and canUploadWaivers fall back to steward check if standard permission fails. Steward check respects closure status. |
WaiverTypePolicy |
canToggleActive, canDownloadTemplate |
Standard permission delegation |
GatheringActivityWaiverPolicy |
availableWaiverTypes |
Standard permission delegation |
WaiverPolicy |
(none — inherits BasePolicy) | Empty policy for GatheringWaiver entity aliasing |
Table Policies
| Policy | Custom Methods | Notes |
|---|---|---|
GatheringWaiversTablePolicy |
canNeedingWaivers |
Falls back to steward check for non-closed gatherings |
WaiverTypesTablePolicy |
(none — inherits BasePolicy) | Standard table authorization |
GatheringActivityWaiversTablePolicy |
(none — inherits BasePolicy) | Standard table authorization |
Controller Policy
| Policy | Methods |
|---|---|
GatheringWaiversControllerPolicy |
canNeedingWaivers, canUpload, canChangeWaiverType, canChangeActivities, canDashboard, canCalendarData, canMobileSelectGathering, canMobileUpload |
Steward-aware methods (canNeedingWaivers, canUpload, canMobileSelectGathering, canMobileUpload) check standard permission first, then fall back to _isAnySteward() which verifies the user is a steward for at least one non-closed gathering.
JavaScript Controllers
| Controller | File | Purpose |
|---|---|---|
waiver-upload-wizard |
waiver-upload-wizard-controller.js |
Multi-step upload wizard: select waiver type → upload pages or attest exemption → review & submit. Uses PDF.js for preview. |
waiver-upload |
waiver-upload-controller.js |
File selection, validation, preview, and upload progress for waiver images. Supports multiple files with mobile camera integration. |
camera-capture |
camera-capture-controller.js |
Mobile camera capture UI enhancements using HTML5 capture="environment". |
waiver-attestation |
waiver-attestation-controller.js |
Modal for attesting waiver not needed. Handles reason selection, AJAX submission, and feedback. |
waiver-calendar |
waiver-calendar-controller.js |
Monthly calendar showing gatherings color-coded by waiver status (red=none, yellow=partial, green=complete, blue=closed). |
waiver-template |
waiver-template-controller.js |
Toggle between file upload and external URL for waiver type template source. |
add-requirement |
add-requirement-controller.js |
Dynamic waiver type discovery for gathering activities. Fetches available types excluding already-assigned ones. |
retention-policy-input |
retention-policy-input-controller.js |
Structured retention policy editor with anchor, years/months/days inputs, and real-time JSON preview. |
exemption-reasons |
exemption-reasons-controller.js |
Dynamic list editor for exemption reasons on waiver types. Add/remove reasons stored as JSON array. |
View Cells
GatheringActivityWaiversCell
- Context: GatheringActivities → view
- Purpose: Shows waiver requirements for a specific activity
- Data: Queries
GatheringActivityWaiverswith containedWaiverTypes
GatheringWaiversCell
- Context: Gatherings → view
- Purpose: Comprehensive waiver view combining requirements, upload status, and management
- Data: Aggregates required waiver types across all activities, counts uploads per type, tracks exemptions, calculates completion stats, checks closure status
- Template variables:
gathering,waiverRows(array of waiver_type + uploaded_count + is_complete + exemption),overallStats,waiverCollectionClosed,isReadyToClose,canCloseWaivers
Mobile Menu Cell
- Type:
PLUGIN_TYPE_MOBILE_MENU - Label: “Submit Waiver”
- Auth: Checks
canUploadWaiverspermission
Routes
All routes under /waivers/ prefix using CakePHP fallback routing:
| URL Pattern | Controller | Action |
|---|---|---|
/waivers/gathering-waivers |
GatheringWaivers | index |
/waivers/gathering-waivers/view/:id |
GatheringWaivers | view |
/waivers/gathering-waivers/upload/:id |
GatheringWaivers | upload |
/waivers/gathering-waivers/dashboard |
GatheringWaivers | dashboard |
/waivers/gathering-waivers/needing-waivers |
GatheringWaivers | needingWaivers |
/waivers/gathering-waivers/mobile-select-gathering |
GatheringWaivers | mobileSelectGathering |
/waivers/gathering-waivers/mobile-upload/:id |
GatheringWaivers | mobileUpload |
/waivers/waiver-types |
WaiverTypes | index |
/waivers/waiver-types/view/:id |
WaiverTypes | view |
/waivers/waiver-types/add |
WaiverTypes | add |
/waivers/waiver-types/edit/:id |
WaiverTypes | edit |
/waivers/gathering-activity-waivers/add |
GatheringActivityWaivers | add |
/waivers/gathering-activity-waivers/delete/:activityId/:waiverTypeId |
GatheringActivityWaivers | delete |
/waivers/gathering-activity-waivers/available-waiver-types/:activityId |
GatheringActivityWaivers | availableWaiverTypes |
Navigation
Registered under the “Waivers” parent section (order: 600):
| Item | Section | Order | Badge |
|---|---|---|---|
| Waiver Dashboard | Waivers | 5 | — |
| Waivers Uploaded | Waivers | 6 | — |
| Waiver Types | Config | 100 | — |
| Gatherings Needing Waivers | Action Items | 25 | Dynamic count via countGatheringsNeedingWaivers() |
Migration History
| Migration | Description |
|---|---|
20251021180737 |
Create waivers_waiver_types |
20251021180804 |
Create waivers_gathering_activity_waivers |
20251021180827 |
Create waivers_gathering_waivers |
20251021180858 |
Create waivers_gathering_waiver_activities |
20251022150936 |
Add document_id to waiver types |
20251023162456 |
Add deleted to gathering_activity_waivers unique index |
20251024012044 |
Remove member_id from gathering_waivers |
20251026000000 |
Add decline fields (declined_at, declined_by, decline_reason) to gathering_waivers |
20251106163803 |
Add exemption_reasons to waiver types |
20251106172020 |
Add exemption fields (is_exemption, exemption_reason) to gathering_waivers |
20251221083000 |
Drop gathering_waiver_activities table |
20251223090000 |
Create waivers_gathering_waiver_closures |
20260131001511 |
Add ready_to_close fields to gathering_waiver_closures |