5.6.4 Activity Entity Reference
Last Updated: December 3, 2025
Status: Complete
Scope: Activities Plugin - Activity Entity
Comprehensive technical reference for the Activity entity, covering data model, relationships, field definitions, and integration patterns with the authorization system.
Table of Contents
- Activity Entity Overview
- Database Schema
- Entity Properties
- Relationships
- Virtual Properties
- Mass Assignment Security
- Methods
- Authorization Architecture
- Usage Examples
Activity Entity Overview
The Activity entity represents authorization types within KMP that require member authorization for participation. Activities define configuration requirements for member participation, including approval workflows, age restrictions, and permission requirements.
Core Responsibilities:
- Define activity types requiring authorization (e.g., “Marshal”, “Rapier Authorizer”)
- Configure authorization approval workflows
- Manage age-based eligibility restrictions
- Link to RBAC permissions for approver discovery
- Associate automatic role grants upon authorization
Location: plugins/Activities/src/Model/Entity/Activity.php
Extends: App\Model\Entity\BaseEntity
Table Class: Activities\Model\Table\ActivitiesTable
Database Schema
Table: activities_activities
The Activity entity maps to the activities_activities database table with the following structure:
| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
id |
INT(11) | NO | Auto | Primary key, auto-incrementing |
name |
VARCHAR(255) | NO | Unique activity identifier | |
term_length |
INT(11) | NO | Authorization validity in days | |
activity_group_id |
INT(11) | NO | Foreign key to ActivityGroup | |
minimum_age |
TINYINT(2) | YES | NULL | Minimum age requirement |
maximum_age |
TINYINT(2) | YES | NULL | Maximum age requirement |
num_required_authorizors |
TINYINT(2) | NO | 1 | Required approvers for new requests |
num_required_renewers |
TINYINT(2) | NO | 1 | Required approvers for renewals |
permission_id |
INT(11) | YES | NULL | Foreign key to Permission (RBAC) |
grants_role_id |
INT(11) | YES | NULL | Foreign key to Role (auto-granted) |
created |
DATETIME | NO | Creation timestamp | |
created_by |
INT(11) | YES | NULL | Creator member ID |
modified |
DATETIME | YES | NULL | Last modification timestamp |
modified_by |
INT(11) | YES | NULL | Last modifier member ID |
deleted |
DATETIME | YES | NULL | Soft deletion timestamp |
Indexes:
- PRIMARY:
id - UNIQUE:
name - INDEX:
activity_group_id - INDEX:
deleted(for soft deletion queries)
Foreign Keys:
activity_group_id→activities_activity_groups.idpermission_id→permissions.id(RBAC)grants_role_id→roles.id(optional role assignment)
Entity Properties
Core Configuration Properties
name
- Type:
string - Required: Yes
- Max Length: 255
- Validation: Unique
- Description: Unique activity identifier and display name
- Examples: “Heavy Weapons Authorization”, “Marshal”, “Archery Authorizer”
term_length
- Type:
int - Required: Yes
- Units: Days
- Validation: Integer > 0
- Description: Authorization validity period in days
- Common Values:
- 365 = 1 year
- 730 = 2 years
- 1095 = 3 years
- Note: Previously documented as months but stored as days since migration
MakeTermMonthsNotYears
activity_group_id
- Type:
int - Required: Yes
- Relationship: belongsTo ActivityGroup
- Description: Categorical organization of related activities
- Validation: Must reference existing ActivityGroup
Age Restriction Properties
minimum_age
- Type:
int|null - Required: No
- Range: 0-127 (TINYINT limit)
- Description: Minimum age requirement for authorization eligibility
- Examples:
- 18 = Adult activities (heavy weapons, rapier)
- 13 = Youth activities with supervision
- NULL = No minimum age requirement
maximum_age
- Type:
int|null - Required: No
- Range: 0-127 (TINYINT limit)
- Description: Maximum age for authorization eligibility
- Examples:
- 17 = Youth-only activities
- NULL = No maximum age limit (typical for adult activities)
Approval Workflow Properties
num_required_authorizors
- Type:
int - Required: Yes
- Default: 1
- Range: 1-127 (TINYINT limit)
- Description: Number of approvers required for new authorization requests
- Examples:
- 1 = Single approver (simple activities)
- 2 = Dual approvers (combat activities)
- 3+ = Complex high-stakes activities
num_required_renewers
- Type:
int - Required: Yes
- Default: 1
- Range: 1-127 (TINYINT limit)
- Description: Number of approvers required for authorization renewals
- Usage: May differ from
num_required_authorizorsfor different renewal workflows
Integration Properties
permission_id
- Type:
int|null - Required: No
- Foreign Key:
permissions.id - Description: RBAC permission required to authorize this activity
- Integration: Used by
getApproversQuery()to discover members with approval authority - Note: Activities without permission_id cannot be authorized (approver discovery fails)
grants_role_id
- Type:
int|null - Required: No
- Foreign Key:
roles.id - Description: Role automatically granted when authorization becomes active
- Examples:
- MarshalRole for Marshal authorization
- FighterRole for Combat authorization
- NULL = No automatic role assignment
Audit Properties
created
- Type:
\Cake\I18n\DateTime - Required: Yes
- Set By: Timestamp behavior on insert
- Description: Activity creation timestamp
created_by
- Type:
int|null - Description: Member ID of activity creator
modified
- Type:
\Cake\I18n\DateTime|null - Set By: Timestamp behavior on update
- Description: Last activity modification timestamp
modified_by
- Type:
int|null - Description: Member ID of last modifier
deleted
- Type:
\Cake\I18n\DateTime|null - Description: Soft deletion timestamp (Trash behavior)
- Note: Non-deleted records have NULL value; filtered by default queries
Relationships
Parent Relationships (belongsTo)
activity_group
- Related Entity:
Activities\Model\Entity\ActivityGroup - Foreign Key:
activity_group_id - Required: Yes
- Description: Categorical organization for this activity
- Access:
$activity->activity_group->name
permission
- Related Entity:
App\Model\Entity\Permission - Foreign Key:
permission_id - Required: No
- Description: RBAC permission required to authorize this activity
- Access:
$activity->permission->name - Critical: Essential for approver discovery in authorization workflows
role
- Related Entity:
App\Model\Entity\Role - Foreign Key:
grants_role_id - Required: No
- Description: Role automatically granted upon authorization approval
- Access:
$activity->role->name - Related: Used by ActiveWindowManager for role assignment
Child Relationships (hasMany)
authorizations
- Related Entity:
Activities\Model\Entity\Authorization - Foreign Key:
activity_id - Description: All authorization records for this activity (all statuses)
- Query:
$activity->authorizations(includes all statuses and periods)
current_authorizations
- Related Entity:
Activities\Model\Entity\Authorization - Condition: Status = “Approved” AND start_on <= NOW() AND expires_on >= NOW()
- Description: Currently active and valid authorizations
- Use Case: Member participation eligibility checking
pending_authorizations
- Related Entity:
Activities\Model\Entity\Authorization - Condition: Status = “Pending”
- Description: Authorization requests awaiting approval
- Use Case: Approval queue management
upcoming_authorizations
- Related Entity:
Activities\Model\Entity\Authorization - Condition: Status = “Approved” AND start_on > NOW()
- Description: Approved authorizations with future start dates
- Use Case: Reporting and planning
previous_authorizations
- Related Entity:
Activities\Model\Entity\Authorization - Condition: Status IN (“Expired”, “Revoked”, “Denied”)
- Description: Historical authorization records
- Use Case: Member authorization history and compliance tracking
member_activities
- Legacy Relationship: Maintained for backward compatibility
- Status: Deprecated - use Authorization relationships instead
pending_authorizations
- Legacy Relationship: Maintained for backward compatibility
- Status: Deprecated - use Authorization relationships instead
Virtual Properties
Virtual properties are computed from related data and appear as entity properties but are not stored in the database.
activity_group_name
- Type:
string|null - Returns: Name of the associated activity group
- Formula: Retrieves
activity_group.namewhen loaded - Use Case: Grid display and reports without explicit joins
- Example:
<?= $activity->activity_group_name ?>
grants_role_name
- Type:
string - Returns: Name of granted role or “None”
- Formula:
$this->role->name ?? "None" - Use Case: Admin interfaces and configuration displays
- Example:
<?= $activity->grants_role_name ?>
length
- Type:
int - Returns: Authorization validity period in days
- Formula: Alias for
term_length - Note: Available for backward compatibility
- Use Case: When term_length property name is inconvenient
Mass Assignment Security
The Activity entity uses CakePHP’s accessible field system to prevent mass assignment vulnerabilities while allowing legitimate field updates.
Accessible Fields
The following fields can be safely mass assigned through newEntity() or patchEntity():
Core Activity Configuration:
name- Activity identifierterm_length- Authorization validity in daysactivity_group_id- Parent activity groupminimum_age- Age restriction lower boundmaximum_age- Age restriction upper boundnum_required_authorizors- Required approvers for new requestsnum_required_renewers- Required approvers for renewals
Administrative Fields:
deleted- Soft deletion timestamppermission_id- RBAC permission for approver discoverygrants_role_id- Role auto-grant configuration
Relationship Management:
activity_group- Parent ActivityGroup entityrole- Role entity for grantsmember_activities- Legacy authorization trackingpending_authorizations- Legacy pending requests
Protected Fields
The following fields are NOT accessible via mass assignment:
id- Primary key protectioncreated- Timestamp protection (set by behavior)modified- Timestamp protection (set by behavior)created_by- Creator tracking protectionmodified_by- Modifier tracking protection
Methods
getApproversQuery()
Discovers members who have permission to authorize this activity within a specified organizational branch.
Signature:
public function getApproversQuery(int $branch_id): \Cake\ORM\Query\SelectQuery
Parameters:
$branch_id(int): Branch scope for approver identification
Returns: SelectQuery for eligible approvers
Description:
This method is the core integration point between activities and the RBAC authorization system. It uses the activity’s permission_id to identify members with the required permission to authorize this activity within the specified branch.
Implementation Details:
- Uses
PermissionsLoader::getMembersWithPermissionsQuery()for optimized permission-based queries - Respects branch-level organizational scoping
- Leverages permission caching infrastructure for performance
- Only returns active members with valid permissions
Error Handling:
- Throws
\Exceptionifpermission_idis not set on the activity - Invalid branch_id results in empty query results (no error thrown)
Usage Examples:
// Get all potential approvers within a branch
$branchApprovers = $activity->getApproversQuery($member->branch_id)->toArray();
// Count available approvers
$approverCount = $activity->getApproversQuery($branchId)->count();
if ($approverCount < $activity->num_required_authorizors) {
throw new InsufficientApproversException(
"Insufficient approvers available for activity authorization"
);
}
// Chain additional query conditions
$seniorApprovers = $activity->getApproversQuery($branchId)
->innerJoinWith('MemberRoles.Roles', function ($q) {
return $q->where(['Roles.name LIKE' => '%Senior%']);
})
->distinct()
->toArray();
// Exclude specific approvers (e.g., requestor)
$availableApprovers = $activity->getApproversQuery($member->branch_id)
->where(['Members.id !=' => $requestingMember->id])
->toArray();
Integration Points:
- Called by
DefaultAuthorizationManager::request()to route approval requests - Used by notification systems to target eligible approvers
- Powers administrative interfaces for approver configuration
- Supports authorization workflow validation
Performance Notes:
- Result set size depends on permission distribution and branch membership
- Use
->count()instead of->toArray()when only count is needed - Consider query conditions to reduce result set before iteration
Virtual Property Accessors
The entity provides magic property access to virtual computed properties:
// Access virtual properties
$groupName = $activity->activity_group_name; // Calls _getActivityGroupName()
$roleName = $activity->grants_role_name; // Calls _getGrantsRoleName()
$days = $activity->length; // Alias for term_length
Magic Method:
_getActivityGroupName()- Implements activity_group_name virtual property_getGrantsRoleName()- Implements grants_role_name virtual property
Authorization Architecture
Approver Discovery Process
The Activity entity participates in the authorization approval workflow through the approver discovery mechanism:
- Activity Configuration:
- Activity defines required permission via
permission_id - Activity specifies required approver count:
num_required_authorizors
- Activity defines required permission via
- Authorization Request:
- Member requests authorization for activity
- System calls
getApproversQuery()to identify eligible approvers - Approvers filtered by permission and organizational branch
- Multi-Level Approval:
- AuthorizationApproval entities created for each required approver
- Workflow routes request sequentially through required approvals
- Final approval triggers authorization activation and role assignment
Age-Based Eligibility
Activities define age restrictions for member participation:
// Example: Youth activity (age 13-17)
$activity->minimum_age = 13;
$activity->maximum_age = 17;
// Example: Adult activity (age 18+)
$activity->minimum_age = 18;
$activity->maximum_age = null; // No upper limit
Validation Point: Authorization request workflow validates member age before processing
Role Grant Integration
Upon authorization approval, the Activity can automatically grant a role to the member:
- Configuration:
grants_role_idset to target Role - Activation: When authorization becomes active (status = “Approved”)
- Assignment: ActiveWindowManager grants role to member automatically
- Removal: Role removed if authorization revoked or expires
Note: Role assignment is automatic via ActiveWindow integration; no manual role management needed
Renewal vs. New Request
Activities support both new requests and renewal requests with potentially different approval requirements:
- New Request: Uses
num_required_authorizorsapprovers - Renewal Request: Uses
num_required_renewersapprovers (may differ) - Renewal Validation: System ensures valid existing authorization exists before renewal
Usage Examples
Activity Creation
// Create new heavy weapons combat activity
$activity = $activitiesTable->newEntity([
'name' => 'Heavy Weapons Authorization',
'term_length' => 1095, // 3 years in days
'activity_group_id' => $combatGroupId,
'minimum_age' => 18,
'maximum_age' => null, // No upper age limit
'num_required_authorizors' => 2, // Requires 2 approvers
'num_required_renewers' => 2, // Same for renewals
'permission_id' => $marshalPermissionId,
'grants_role_id' => $fighterRoleId
]);
$activitiesTable->save($activity);
Approver Discovery
// Find members authorized to approve activity within branch
$approversQuery = $activity->getApproversQuery($branchId);
$approvers = $approversQuery->toArray();
// Validate sufficient approvers available
if (count($approvers) < $activity->num_required_authorizors) {
throw new \Exception("Insufficient approvers for authorization workflow");
}
// Send approval requests
foreach ($approvers as $approver) {
$authorizationManager->sendApprovalRequest($authorization, $approver);
}
Member Eligibility Check
// Check age-based eligibility before allowing authorization request
if ($member->age !== null) {
$minimumMet = $activity->minimum_age === null ||
$member->age >= $activity->minimum_age;
$maximumMet = $activity->maximum_age === null ||
$member->age <= $activity->maximum_age;
if (!$minimumMet || !$maximumMet) {
throw new \Exception(
"Member age {$member->age} not eligible for {$activity->name}"
);
}
}
Activity Configuration Query
// Load activity with all relationships for admin interface
$activity = $activitiesTable->find()
->contain([
'ActivityGroup',
'Permission',
'Role',
'CurrentAuthorizations' => function ($q) {
return $q->contain(['Member']);
},
'PendingAuthorizations' => function ($q) {
return $q->contain(['Member']);
}
])
->where(['Activities.id' => $activityId])
->first();
Activity Statistics
// Get authorization statistics for activity
$activity = $activitiesTable->get($activityId, [
'contain' => [
'CurrentAuthorizations',
'PendingAuthorizations',
'UpcomingAuthorizations'
]
]);
$stats = [
'current_count' => count($activity->current_authorizations),
'pending_count' => count($activity->pending_authorizations),
'upcoming_count' => count($activity->upcoming_authorizations),
];
ActivitiesTable Utility Methods
The ActivitiesTable class provides static utility methods for authorization permission checking. These methods support navigation display, workflow entry validation, and access control.
canAuthorizeActivity()
Checks if a user can authorize a specific activity based on their permissions.
Signature:
public static function canAuthorizeActivity($user, int $activityId): bool
Parameters:
$user(\App\Model\Entity\Member): The user to check authorization permissions for$activityId(int): The activity ID to check authorization permissions against
Returns: bool - True if user can authorize the activity, false otherwise
Usage Examples:
// Controller authorization check
if (!ActivitiesTable::canAuthorizeActivity($this->getIdentity(), $activityId)) {
throw new ForbiddenException('Insufficient permissions');
}
// Workflow entry validation
if (ActivitiesTable::canAuthorizeActivity($currentUser, $activity->id)) {
$authorizationManager->processApproval($authorization, $currentUser);
}
// UI conditional display
if (ActivitiesTable::canAuthorizeActivity($this->identity, $activity->id)) {
echo $this->Html->link('Authorize', ['action' => 'authorize', $activity->id]);
}
canAuhtorizeAnyActivity()
Checks if a user has authorization permissions for any activities in the system.
Signature:
public static function canAuhtorizeAnyActivity($user): bool
Parameters:
$user(\App\Model\Entity\Member): The user to check general authorization permissions for
Returns: bool - True if user can authorize any activities, false otherwise
Usage Examples:
// Navigation queue display
if (ActivitiesTable::canAuhtorizeAnyActivity($this->getIdentity())) {
$pendingCount = $this->getIdentity()->getPendingApprovalsCount();
echo $this->Html->link("Authorization Queue ({$pendingCount})",
['plugin' => 'Activities', 'controller' => 'AuthorizationApprovals']);
}
// Dashboard widget conditional
if (ActivitiesTable::canAuhtorizeAnyActivity($currentUser)) {
echo $this->element('Activities.authorization_queue_widget');
}
// Redirect unauthorized users
if (!ActivitiesTable::canAuhtorizeAnyActivity($this->getIdentity())) {
$this->Flash->error('You do not have authorization permissions');
return $this->redirect(['controller' => 'Members', 'action' => 'view']);
}
Performance Notes:
- Early returns if user has no permissions (avoids database query)
- Uses count query with minimal field selection for efficiency
- Optimized for navigation and conditional display use cases
References and Related Documentation
Related Entities
- Authorization Entity - Activity authorizations
- ActivityGroup Entity - Activity categorization
- AuthorizationApproval Entity - Approval tracking
Related Documentation
- Activities Plugin Workflows - Authorization lifecycle workflows
- Activities Plugin Architecture - Plugin structure and initialization
- Activities Controller Reference - Controller implementation
- RBAC Security Architecture - Permission system
Source Files
- Entity:
plugins/Activities/src/Model/Entity/Activity.php - Table:
plugins/Activities/src/Model/Table/ActivitiesTable.php - Controller:
plugins/Activities/src/Controller/ActivitiesController.php - Migration:
plugins/Activities/config/Migrations/20240614001010_InitActivities.php