5.6.3 Activity Groups Controller Reference
Last Updated: December 3, 2025
Status: Complete
Scope: Activities Plugin ActivityGroupsController Implementation
This document provides comprehensive technical reference documentation for the ActivityGroupsController, covering all action methods, architectural patterns, authorization framework, and integration points.
Table of Contents
- Overview
- Controller Architecture
- Index and Grid Methods
- CRUD Operations
- Authorization Framework
- Data Validation
- Integration Patterns
- Performance Considerations
Overview
The ActivityGroupsController manages the complete lifecycle of Activity Group entities, which provide categorical organization for related activities within the Activities plugin. It enables administrative definition, configuration, and operational management of activity grouping.
Key Responsibilities:
- Activity group definition and categorical organization
- Administrative CRUD operations for activity groups
- Grid-based data display with filtering and export
- Authorization management for group operations
- Data validation and integrity protection
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;
class ActivityGroupsController 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 CRUD action methods (
view,edit,delete) - Authorization determined by ActivityGroupPolicy
Property Access
@property \Activities\Model\Table\ActivityGroupsTable $ActivityGroups
The ActivityGroups table is accessed automatically through CakePHP conventions.
Index and Grid Methods
Index Action
Purpose: Display activity group listing page with lazy-loaded data.
Signature:
public function index(): void
Implementation:
public function index(): void
{
$this->set('user', $this->request->getAttribute('identity'));
}
Response: Renders view with empty dv_grid container element that loads data via Turbo Frame.
User Flow:
- User navigates to
/activities/activity-groups/ - Index view renders with empty grid container and current user context
- Turbo Frame automatically requests gridData action
- Grid populates with data via AJAX
Grid Data Action
Purpose: Provide Dataverse grid data with toolbar, filtering, and CSV 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 of all activity groups
$baseQuery = $this->ActivityGroups->find(); - Grid Processing: Uses
DataverseGridTrait::processDataverseGrid()for unified grid rendering$result = $this->processDataverseGrid([ 'gridKey' => 'Activities.ActivityGroups.index.main', 'gridColumnsClass' => \Activities\KMP\GridColumns\ActivityGroupsGridColumns::class, 'baseQuery' => $baseQuery, 'tableName' => 'ActivityGroups', 'defaultSort' => ['ActivityGroups.name' => 'asc'], 'defaultPageSize' => 25, 'showAllTab' => false, 'canAddViews' => false, 'canFilter' => true, 'canExportCsv' => true, ]); - CSV Export Handling: Detects CSV export requests via Turbo-Frame header
if (!empty($result['isCsvExport'])) { return $this->handleCsvExport($result, $csvExportService, 'activity-groups'); } -
View Variable Population: Sets all necessary data for grid rendering
- Template Selection: Renders different templates based on Turbo-Frame context
- Inner Frame Request (
activity-groups-grid-table): Renders table data only - Outer Frame Request: Renders toolbar + table frame
- Inner Frame Request (
Grid Features:
- Filtering: Member-configured search and filter options
- Sorting: Default sort by activity group name ascending
- Pagination: 25 groups per page
- CSV Export: Full grid data export capability
- Column Configuration: Dynamically controlled via ActivityGroupsGridColumns
View Variables:
activityGroups- Collection of activity group entitiesgridState- Current grid state (sort, filters, page, etc.)columns- Column metadata for UI renderingvisibleColumns- Columns selected by user for displaysearchableColumns- Columns available for searchdropdownFilterColumns- Columns with dropdown filter optionsfilterOptions- Available filter valuescurrentFilters- Currently applied filterscurrentSearch- Active search querycurrentView- Active saved viewavailableViews- User-created saved grid viewsgridKey- Unique identifier for grid configurationcurrentSort- Current sorting configurationcurrentMember- Authenticated user entity
CRUD Operations
View Action
Purpose: Display detailed view of a specific activity group with associated activities.
Signature:
public function view(?string $id = null): void
Parameters:
$id- Activity Group ID for detailed view
Implementation:
public function view($id = null)
{
$authorizationGroup = $this->ActivityGroups->get(
$id,
contain: ["Activities"],
);
if (!$authorizationGroup) {
throw new \Cake\Http\Exception\NotFoundException();
}
$this->Authorization->authorize($authorizationGroup);
$this->set(compact("authorizationGroup"));
}
Data Loading Strategy:
- Eager loads associated activities to prevent N+1 queries
- Single database query retrieves group and all related activities
- Complete activity entities with all properties available
Authorization:
- Entity-level authorization check via ActivityGroupPolicy
- Ensures users can only view authorized groups
- Throws ForbiddenException if user lacks view permission
Error Handling:
- Throws NotFoundException if group does not exist
- Throws ForbiddenException if user lacks view permission
View Variables:
authorizationGroup- Activity group entity with associated activities
Response: Renders plugins/Activities/templates/ActivityGroups/view.php with group details and associated activities list.
Add Action
Purpose: Display and process creation of new activity groups.
Signature:
public function add(): void
Request Handling:
GET Request (Form Display):
$authorizationGroup = $this->ActivityGroups->newEmptyEntity();
$this->set(compact("authorizationGroup"));
POST Request (Form Processing):
- Creates empty entity
- Patches entity with form data
- Validates data through ActivityGroupsTable validation rules
- Saves entity if validation succeeds
- Redirects to view on success or re-displays form with errors
Data Validation:
- Validation rules enforced through ActivityGroupsTable
- Required field validation
- Unique constraint validation
- Business rule validation
Authorization:
- Model-level authorization via ActivityGroupPolicy
- Configured in initialize() method
Success Workflow:
$this->Flash->success(__("The Activity Group has been saved."));
return $this->redirect(['action' => 'view', $authorizationGroup->id]);
Error Workflow:
$this->Flash->error(__("The Activity Group could not be saved. Please, try again."));
// Form re-displayed with validation errors
Response:
- Success: Redirects to view action
- Validation failure: Renders form with error messages
Edit Action
Purpose: Display and process modification of existing activity groups.
Signature:
public function edit(?string $id = null): void
Parameters:
$id- Activity Group ID for editing
Implementation:
public function edit($id = null)
{
$authorizationGroup = $this->ActivityGroups->get($id, contain: []);
if (!$authorizationGroup) {
throw new \Cake\Http\Exception\NotFoundException();
}
$this->Authorization->authorize($authorizationGroup);
if ($this->request->is(["patch", "post", "put"])) {
$authorizationGroup = $this->ActivityGroups->patchEntity(
$authorizationGroup,
$this->request->getData(),
);
if ($this->ActivityGroups->save($authorizationGroup)) {
$this->Flash->success(__("The Activity Group has been saved."));
return $this->redirect(['action' => 'view', $authorizationGroup->id]);
}
$this->Flash->error(__("The Activity Group could not be saved. Please, try again."));
}
$this->set(compact("ActivityGroup"));
}
Request Handling:
GET Request (Form Display):
- Loads existing group entity
- Validates existence
- Validates authorization
- Displays form with existing data
POST/PATCH/PUT Request (Form Processing):
- Loads existing group entity
- Patches entity with form data
- Validates data through ActivityGroupsTable validation rules
- Saves entity if validation succeeds
- Redirects to view on success or re-displays form with errors
Authorization:
- Entity-level authorization via ActivityGroupPolicy
- Ensures user can edit specific group
- Throws ForbiddenException if user lacks edit permission
Error Handling:
- Throws NotFoundException if group does not exist
- Throws ForbiddenException if user lacks edit permission
- Displays validation errors on form re-display
Data Validation:
- Validation rules enforced through ActivityGroupsTable
- Validates updated data against business rules
- Maintains unique constraints
Response:
- Success: Redirects to view action
- Validation failure: Renders form with error messages
Note: Current implementation sets template variable as ActivityGroup (inconsistent with entity variable name $authorizationGroup). This should be corrected to compact("authorizationGroup") for consistency.
Delete Action
Purpose: Secure deletion of activity groups with referential integrity protection.
Signature:
public function delete(?string $id = null): \Cake\Http\Response|null
Parameters:
$id- Activity Group ID for deletion
Implementation:
public function delete($id = null)
{
$this->request->allowMethod(["post", "delete"]);
$authorizationGroup = $this->ActivityGroups->get(
$id,
contain: ["Activities"]
);
if (!$authorizationGroup) {
throw new \Cake\Http\Exception\NotFoundException();
}
if ($authorizationGroup->activities) {
$this->Flash->error(
__("The Activity Group could not be deleted because it has associated Activities."),
);
return $this->redirect(["action" => "index"]);
}
$this->Authorization->authorize($authorizationGroup);
$authorizationGroup->name = "Deleted: " . $authorizationGroup->name;
if ($this->ActivityGroups->delete($authorizationGroup)) {
$this->Flash->success(__("The Activity Group has been deleted."));
} else {
$this->Flash->error(
__("The Activity Group could not be deleted. Please, try again."),
);
}
return $this->redirect(["action" => "index"]);
}
Request Handling:
- HTTP Method Validation: Restricts to POST or DELETE only
$this->request->allowMethod(["post", "delete"]);Prevents CSRF attacks and accidental deletions
- Entity Loading: Loads group with associated activities
$authorizationGroup = $this->ActivityGroups->get($id, contain: ["Activities"]); -
Existence Validation: Throws 404 if group not found
- Referential Integrity Check: Prevents deletion if activities associated
if ($authorizationGroup->activities) { // Redirect with error message } -
Authorization Check: Validates user permission to delete specific group
- Soft Deletion: Implements soft deletion pattern by prefixing name
$authorizationGroup->name = "Deleted: " . $authorizationGroup->name; - Deletion Execution: Attempts deletion with error handling
Authorization:
- Entity-level authorization via ActivityGroupPolicy
- Ensures user can delete specific group
- Throws ForbiddenException if user lacks delete permission
Error Handling:
- Throws NotFoundException if group does not exist
- Throws MethodNotAllowedException if invalid HTTP method used
- Throws ForbiddenException if user lacks delete permission
- Displays error message if group has associated activities
- Displays error message if deletion fails
Business Logic Protection:
- Referential Integrity: Prevents orphaning activities
- Soft Deletion: Maintains audit trail through name modification
- Activity Association Check: Prevents cascade deletion
Response: Always redirects to index action with appropriate success or error message
Authorization Framework
Authorization Policy
All actions are subject to authorization through the ActivityGroupPolicy:
Policy File: plugins/Activities/src/Policy/ActivityGroupPolicy.php
Authorization Points:
index/gridData: Model-level authorization (configured in initialize)view: Entity-level authorizationadd: Model-level authorization (configured in initialize)edit: Entity-level authorizationdelete: Entity-level authorization
Default Authorization Behavior:
- Typically requires administrative role or specific permission
- Policy determines which users can perform each action
- Integrated with KMP’s RBAC system
Authorization Integration
public function initialize(): void
{
parent::initialize();
$this->Authorization->authorizeModel("index", "add", "gridData");
}
Model-level authorization ensures general access control for listing and creation operations.
$this->Authorization->authorize($authorizationGroup);
Entity-level authorization ensures specific group access control in CRUD operations.
Data Validation
ActivityGroupsTable Validation
All validation is enforced through ActivityGroupsTable:
File: plugins/Activities/src/Model/Table/ActivityGroupsTable.php
Validation Rules:
- Required Fields: Mandatory group information validation
- Unique Constraints: Ensures group names remain unique
- Business Rules: Custom validation for group-specific requirements
- Referential Integrity: Validates activity associations
Validation Triggers:
- Form submissions (add/edit actions)
- Manual save operations
- Data patch operations
Field-Specific Validation
Name Field:
- Required validation
- Unique constraint validation
- String format validation
Description Field (if implemented):
- Optional field validation
- String format validation
Integration Patterns
Activities Plugin Integration
Activity Association:
- Activity groups link to activities via
activity_group_id - One-to-many relationship: group has many activities
- Provides categorical organization for activity browsing
Grid Integration:
- Uses DataverseGridTrait for consistent UI
- Integrated with Dataverse grid toolbar and filtering
- Supports CSV export for reporting
Navigation Support:
- Activity groups appear in plugin navigation
- Links to activity listing filtered by group
- Breadcrumb support for navigation hierarchy
View Templates
Index Template:
plugins/Activities/templates/ActivityGroups/index.php
Displays empty grid container that loads data via gridData action.
View Template:
plugins/Activities/templates/ActivityGroups/view.php
Displays group details with associated activities list.
Add Template:
plugins/Activities/templates/ActivityGroups/add.php
Displays form for creating new activity group.
Edit Template:
plugins/Activities/templates/ActivityGroups/edit.php
Displays form for modifying existing activity group.
Turbo Frame Integration
Lazy Loading:
- Index action renders empty container
- Turbo Frame automatically requests gridData
- Grid populates without full page reload
Partial Updates:
- gridData renders different templates based on Turbo-Frame header
- Inner frame request: Table data only
- Outer frame request: Toolbar + table frame
Plugin Templates:
// Use main app's element templates (not plugin templates)
$this->viewBuilder()->setPlugin(null);
if ($turboFrame === 'activity-groups-grid-table') {
$this->viewBuilder()->setTemplate('element/dv_grid_table');
} else {
$this->viewBuilder()->setTemplate('element/dv_grid_content');
}
Performance Considerations
Database Optimization
Query Efficiency:
- Single query retrieves group and activities (view action)
- Eager loading prevents N+1 queries
- Strategic relationship loading minimizes database hits
Relationship Loading:
viewaction: Loads activities with group (contain: ["Activities"])editaction: Minimal loading (contain: [])deleteaction: Loads activities for validation (contain: ["Activities"])
Index Utilization:
- Leverages primary key indexes for fast retrieval
- Optimized queries for grid data retrieval
Memory Management
- Lightweight entity loading for list operations
- Selective loading of relationships based on operation
- Efficient entity processing for form operations
Grid Performance
- Pagination limits rows per page (default: 25)
- Column configuration prevents excessive data loading
- Filter optimization reduces data set size
Caching Opportunities
- Activity groups relatively stable (suitable for caching)
- Grid toolbar data could be cached
- Filter options could be cached
Known Issues and Notes
Variable Naming Inconsistency
The edit action contains a variable naming inconsistency:
- Entity loaded as:
$authorizationGroup - Template variable set as:
$ActivityGroup(should be$authorizationGroup)
This inconsistency should be corrected for clarity and consistency with other actions.
Related Documentation
- Activities Plugin Overview
- Activities Plugin Architecture
- Activities Controller Reference
- Activity Entity Documentation