5.6.2 Activities Controller Reference
Last Updated: December 3, 2025
Status: Complete
Scope: Activities Plugin Controller Implementation
This document provides comprehensive technical reference documentation for the Activities plugin controller, covering all action methods, API endpoints, architectural patterns, and integration points.
Table of Contents
- Overview
- Controller Architecture
- Index and Grid Methods
- CRUD Operations
- API Endpoints
- Authorization Framework
- Performance Considerations
- Integration Patterns
Overview
The Activities controller manages the complete lifecycle of Activity entities (authorization types that members can request). It provides administrative interfaces for activity definition, configuration, and operational management, along with API endpoints for integration with authorization workflows.
Key Responsibilities:
- Activity definition and configuration management
- Administrative approval workflow oversight
- Authorization statistics and monitoring
- Dynamic approver discovery for authorization requests
- CSV export functionality for administrative reporting
Technology Stack:
- CakePHP 5.x MVC framework
- DataverseGridTrait for advanced table rendering
- Permission-based authorization system
- Turbo Frame integration for efficient partial page updates
Controller Architecture
Class Declaration
namespace Activities\Controller;
use App\Controller\DataverseGridTrait;
use App\Services\CsvExportService;
use Cake\ORM\TableRegistry;
use Cake\I18n\DateTime;
use Cake\ORM\Query\SelectQuery;
use Activities\Model\Entity\Authorization;
class ActivitiesController extends AppController
{
use DataverseGridTrait;
}
Initialization
Model-level authorization is configured during controller initialization:
public function initialize(): void
{
parent::initialize();
$this->Authorization->authorizeModel("index", "add", "gridData");
}
Authorization Strategy:
index,add, andgridDatamethods subject to model-level authorization- Entity-level authorization applied in individual action methods
approversListskips authorization (relies on calling interface for security)
Property Access
@property \Activities\Model\Table\ActivitiesTable $Activities
The Activities table is accessed automatically through CakePHP conventions.
Index and Grid Methods
Index Action
Purpose: Display activity listing page with lazy-loaded data.
public function index(): void
{
// Simple index page - renders dv_grid element
// The dv_grid element lazy-loads actual data via gridData action
}
Response: Renders view with empty dv_grid container element that loads data via Turbo Frame.
User Flow:
- User navigates to
/activities/activities/ - Index view renders with empty grid container
- Turbo Frame automatically requests gridData action
- Grid populates with data via AJAX
Grid Data Action
Purpose: Provide Dataverse grid data with toolbar, filtering, and export capabilities.
Signature:
public function gridData(CsvExportService $csvExportService)
Parameters:
CsvExportService $csvExportService- Injected CSV export service for export handling
Request Handling:
- Query Construction: Builds base query with activity relationships
- Grid Processing: Uses
DataverseGridTrait::processDataverseGrid()for unified grid rendering - CSV Export: Handles CSV export requests via Turbo-Frame header detection
- View Selection: Renders different templates based on Turbo-Frame context
Configuration:
$result = $this->processDataverseGrid([
'gridKey' => 'Activities.Activities.index.main',
'gridColumnsClass' => \Activities\KMP\GridColumns\ActivitiesGridColumns::class,
'baseQuery' => $baseQuery,
'tableName' => 'Activities',
'defaultSort' => ['Activities.name' => 'asc'],
'defaultPageSize' => 25,
'showAllTab' => false,
'canAddViews' => false,
'canFilter' => true,
'canExportCsv' => true,
]);
Grid Features:
- Filtering: Member-configured search and filter options
- Sorting: Default sort by activity name ascending
- Pagination: 25 activities per page
- CSV Export: Full grid data export
- Column Configuration: Dynamically controlled via ActivitiesGridColumns
View Variables:
activities- Activity entities for displaycolumns- Column configuration metadatavisibleColumns- User-selected visible columnsdropdownFilterColumns- Columns with dropdown filtersfilterOptions- Available filter valuescurrentFilters- Applied filter criteriacurrentSearch- Search query textgridState- Serialized grid state for state preservation
Response Templates:
- Outer Frame (Turbo-Frame: not activities-grid-table)
- Renders:
dv_grid_contentelement with toolbar and table frame - Template:
templates/element/dv_grid_content.php
- Renders:
- Inner Frame (Turbo-Frame: activities-grid-table)
- Renders:
dv_grid_tableelement with table data only - Template:
templates/element/dv_grid_table.php
- Renders:
CRUD Operations
View Action
Purpose: Display comprehensive activity details with authorization statistics and administrative data.
Signature:
public function view($id = null)
Entity Loading:
$activity = $this->Activities->get($id, contain: [
"Permissions" => fn($q) => $q->select(["id", "name"]),
"ActivityGroups" => fn($q) => $q->select(["id", "name"]),
"Roles" => fn($q) => $q->select(["id", "name"])
]);
Data Retrieval:
- Core Activity Data: Activity entity with configuration and relationships
- Authorization Statistics: Count queries for active, pending, and previous authorizations
- Approver Roles: Roles with permission to approve for this activity
- Dropdown Data: Available activity groups, roles, and permissions for administrative reference
Authorization Statistics:
$activeCount = $this->Activities->CurrentAuthorizations
->find()
->where(["activity_id" => $id])
->count();
$pendingCount = $this->Activities->PendingAuthorizations
->find()
->where(["activity_id" => $id])
->count();
$previousCount = $this->Activities->PreviousAuthorizations
->find()
->where(["activity_id" => $id])
->count();
$isEmpty = ($activeCount + $pendingCount + $previousCount) === 0;
Approver Role Discovery:
if ($activity->permission_id) {
$roles = $this->Activities->Permissions->Roles
->find()
->innerJoinWith("Permissions", function ($q) use ($activity) {
return $q->where([
"OR" => [
"Permissions.id" => $activity->permission_id,
"Permissions.is_super_user" => true,
]
]);
})
->distinct()
->all();
} else {
$roles = [];
}
View Variables:
activity- Complete Activity entity with relationshipsactivityGroup- Available activity groups for referenceroles- Roles with approval authority for this activityauthAssignableRoles- All assignable rolesauthByPermissions- All available permissionsactiveCount- Number of current active authorizationspendingCount- Number of pending authorization requestsisEmpty- Boolean indicating no authorization historyid- Activity identifier
Exception Handling:
NotFoundExceptionthrown if activity not found
Add Action
Purpose: Create new Activity with comprehensive configuration management.
Signature:
public function add()
Request Handling:
GET Request:
$activity = $this->Activities->newEmptyEntity();
$activityGroup = $this->Activities->ActivityGroups
->find("list", limit: 200)
->all();
$authAssignableRoles = $this->Activities->Roles
->find("list")
->all();
$authByPermissions = $this->Activities->Permissions
->find("list")
->all();
POST Request:
if ($this->request->is("post")) {
$activity = $this->Activities->patchEntity(
$activity,
$this->request->getData()
);
if ($this->Activities->save($activity)) {
$this->Flash->success(__("The authorization type has been saved."));
return $this->redirect(["action" => "view", $activity->id]);
}
$this->Flash->error(
__("The authorization type could not be saved. Please, try again.")
);
}
Form Configuration Data:
- Activity Groups (limit 200 for performance)
- Available Roles for approval authority assignment
- Available Permissions for approval requirement configuration
Validation: Business rule validation enforced at Model level through Activity entity validators:
- Name uniqueness within organizational scope
- Age restriction logic (minimum ≤ maximum)
- Positive integer validation for approval counts
- Required field validation for operational readiness
Success Flow:
- Flash success message
- Redirect to created activity view
Error Flow:
- Flash error message
- Form redisplayed with validation errors
Edit Action
Purpose: Modify existing Activity configuration and relationships.
Signature:
public function edit($id = null)
Entity Loading:
$activity = $this->Activities->get($id, contain: []);
Authorization Validation:
$this->Authorization->authorize($activity);
Request Handling:
GET Request: Renders edit form with current activity data
PATCH/POST/PUT Request:
if ($this->request->is(["patch", "post", "put"])) {
$activity = $this->Activities->patchEntity(
$activity,
$this->request->getData()
);
if ($this->Activities->save($activity)) {
$this->Flash->success(__("The authorization type has been saved."));
return $this->redirect($this->referer());
}
$this->Flash->error(
__("The authorization type could not be saved. Please, try again.")
);
return $this->redirect($this->referer());
}
return $this->redirect($this->referer());
Navigation:
- Success/error redirection uses
$this->referer()to maintain workflow context - Enables both view detail and grid-based editing workflows
Business Considerations:
- Modifications may affect existing authorization workflows
- Age restriction changes affect member eligibility
- Approval count changes impact pending workflows
- Permission changes affect approval authority
Delete Action
Purpose: Securely delete Activity with audit trail and soft deletion pattern.
Signature:
public function delete($id = null)
Security Controls:
$this->request->allowMethod(["post", "delete"]);
Deletion Process:
- Load activity entity
- Validate entity existence
- Authorize deletion operation
- Prefix name with “Deleted: “ for soft deletion
- Execute database delete
- Provide user feedback
Soft Deletion Pattern:
$activity->name = "Deleted: " . $activity->name;
if ($this->Activities->delete($activity)) {
$this->Flash->success(__("The activity has been deleted."));
} else {
$this->Flash->error(
__("The activity could not be deleted. Please, try again.")
);
}
Data Preservation:
- Activity record retained for historical authorization reference
- Prevents foreign key constraint violations
- Maintains audit trail and historical data integrity
- Enables potential restoration through administrative name modification
Navigation:
- Redirects to activity index after deletion
API Endpoints
Approvers List (AJAX Endpoint)
Purpose: Provide dynamic approver discovery for authorization request forms.
Signature:
public function approversList($activityId = null, $memberId = null)
Parameters:
$activityId- Activity ID for permission-based approver discovery$memberId- Member ID for organizational context and self-exclusion
HTTP Method: GET only
Response Type: JSON
Authorization:
$this->Authorization->skipAuthorization();
Authorization skipped due to limited data exposure and API use case; relies on calling interface for security.
Query Construction:
$activity = $this->Activities->get($activityId);
$member = TableRegistry::getTableLocator()->get('Members')->get($memberId);
$query = $activity->getApproversQuery($member->branch_id);
$result = $query
->contain(["Branches"])
->where(["Members.id !=" => $memberId])
->orderBy(["Branches.name", "Members.sca_name"])
->select(["Members.id", "Members.sca_name", "Branches.name"])
->distinct()
->all()
->toArray();
Query Features:
- Permission-based: Uses Activity’s
getApproversQuery()for permission validation - Branch-scoped: Filters approvers within member’s organizational context
- Self-exclusion: Excludes requesting member to prevent self-approval
- Distinct: Prevents duplicate entries in complex permission scenarios
- Efficient: Loads only essential fields (id, sca_name)
Response Format:
[
{
"id": 123,
"sca_name": "East Kingdom: John Smith"
},
{
"id": 456,
"sca_name": "Meridies: Jane Doe"
}
]
Response Data Construction:
$responseData = [];
foreach ($result as $member) {
$responseData[] = [
"id" => $member->id,
"sca_name" => $member->branch->name . ": " . $member->sca_name,
];
}
Exception Handling:
NotFoundExceptionif activity not found
Client Integration Example:
// Fetch approvers for activity and member
const response = await fetch(
`/activities/activities/approversList/${activityId}/${memberId}`
);
const approvers = await response.json();
// Populate approver selection interface
approvers.forEach(approver => {
const option = document.createElement('option');
option.value = approver.id;
option.textContent = approver.sca_name;
approverSelect.appendChild(option);
});
Authorization Framework
Authorization Strategy
The Activities controller implements a two-level authorization approach:
Model-Level Authorization:
- Configured in
initialize()forindex,add,gridDataactions - Provides baseline permission checking
- Subject to Activities plugin authorization policies
Entity-Level Authorization:
- Applied in view, edit, delete actions
- Uses CakePHP Authorization component
- Leverages ActivityPolicy for fine-grained control
- Considers administrative permissions and organizational boundaries
Authorization Policies
Entity Access Control:
- Activity view requires entity authorization
- Activity modification requires entity authorization with edit permission
- Activity deletion requires entity authorization with delete permission
Organization Boundaries:
- Branch-scoped access control for multi-organizational deployments
- Administrative override through super-user permissions
- Permission-based approval authority validation
Performance Considerations
Query Optimization
Index/Grid Performance:
- Limited field selection for relationship data (id, name only)
- Strategic containment to minimize database queries
- Pagination with 25 activities per page
- Efficient list queries for dropdown interfaces
View Action Optimization:
- Targeted authorization statistics queries
- Distinct operations to prevent duplicate role entries
- Efficient approver role discovery through permission system
- Selective field loading for relationships
Database Strategies
Soft Deletion Impact:
- Deleted activities prefixed with “Deleted: “ remain in database
- Soft deletion prevents foreign key constraint violations
- Historical data preserved for audit and compliance requirements
Caching Opportunities:
- Activity groups relatively stable (suitable for caching)
- Approver lists cacheable by activity and branch combination
- Permission-to-role mappings suitable for caching
Integration Patterns
Dataverse Grid Integration
The Activities controller integrates with the Dataverse grid system for advanced table rendering:
- Grid Key:
Activities.Activities.index.main - Column Configuration:
ActivitiesGridColumnsclass defines available columns - Turbo Frame Support: Efficient partial page updates via Turbo Frame
- CSV Export: Unified export handling through DataverseGridTrait
Permission System Integration
Deep integration with KMP’s RBAC system:
- Activity Permission Linkage: Activities linked to specific permissions
- Approver Discovery: Permission-based validation of approval authority
- Permission Validation: Real-time validation of approver permissions
- Branch Scoping: Organizational hierarchy respected in approval authority
Email Notification Integration
Authorization workflows trigger email notifications through service layer:
- Approval Requests: Notifications sent to identified approvers
- Status Updates: Notifications sent to requesting member
- Secure Tokens: Email-based approval access with secure tokens
CSV Export Integration
Unified export handling through DataverseGridTrait:
- Grid-Based Export: All grid data exported with filters applied
- CSV Format: Standard comma-separated values format
- Filename:
activities_YYYY-MM-DD.csvor similar - Service Integration: Uses injected CsvExportService
Usage Examples
Creating an Activity via API
Administrative task to create new activity:
// POST /activities/activities/add
$data = [
'name' => 'Marshal',
'description' => 'Activity staff member managing activity safety',
'activity_group_id' => 1,
'minimum_age' => 18,
'maximum_age' => null,
'num_required_authorizors' => 2,
'num_required_renewers' => 1,
'permission_id' => 5
];
Discovering Approvers for Authorization Request
Client-side JavaScript for dynamic approver discovery:
// When member and activity selected in authorization form
const activityId = form.querySelector('[name="activity_id"]').value;
const memberId = form.querySelector('[name="member_id"]').value;
if (activityId && memberId) {
const response = await fetch(
`/activities/activities/approversList/${activityId}/${memberId}`
);
const approvers = await response.json();
// Populate approver dropdown with discovered approvers
populateApproverDropdown(approvers);
}
Administrative Activity Management
View and manage activity details:
1. Navigate to /activities/activities/
2. Grid displays all activities with filtering and search
3. Click activity name to view details
4. Detailed view shows:
- Activity configuration
- Current authorization statistics
- Roles with approval authority
- Edit/Delete controls
Extension Patterns
Custom Grid Columns
Extend grid functionality by modifying ActivitiesGridColumns:
// In ActivitiesGridColumns class
public static function getColumns(): array
{
return [
// ... existing columns
'custom_field' => [
'label' => 'Custom Field',
'type' => 'string',
'searchable' => true,
'filterable' => false,
]
];
}
Enhanced Validation
Implement custom business rule validation:
public function add()
{
if ($this->request->is("post")) {
$activity = $this->Activities->patchEntity($activity, $this->request->getData());
// Add custom validation
if (!$this->_validateBusinessRules($activity)) {
$this->Flash->error(__("Custom validation failed."));
} elseif ($this->Activities->save($activity)) {
// ... success flow
}
}
}
References
- Activity Entity Reference - Activity entity properties and methods
- Activity Security & Authorization Patterns - Security, mass assignment, and approver discovery
- Activities Plugin Architecture
- Activities Plugin Workflows
- RBAC Security Architecture
- Dataverse Grid System
Document Information:
- Last Updated: December 3, 2025
- Accuracy Verification: Based on ActivitiesController.php source code
- Status: Complete and comprehensive