Skip to the content.

← Back to Activities Plugin

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

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:

Technology Stack:

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:

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:

  1. User navigates to /activities/activity-groups/
  2. Index view renders with empty grid container and current user context
  3. Turbo Frame automatically requests gridData action
  4. 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:

Request Handling:

  1. Query Construction: Builds base query of all activity groups
    $baseQuery = $this->ActivityGroups->find();
    
  2. 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,
    ]);
    
  3. CSV Export Handling: Detects CSV export requests via Turbo-Frame header
    if (!empty($result['isCsvExport'])) {
        return $this->handleCsvExport($result, $csvExportService, 'activity-groups');
    }
    
  4. View Variable Population: Sets all necessary data for grid rendering

  5. 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

Grid Features:

View Variables:

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:

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:

Authorization:

Error Handling:

View Variables:

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):

  1. Creates empty entity
  2. Patches entity with form data
  3. Validates data through ActivityGroupsTable validation rules
  4. Saves entity if validation succeeds
  5. Redirects to view on success or re-displays form with errors

Data Validation:

Authorization:

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:

Edit Action

Purpose: Display and process modification of existing activity groups.

Signature:

public function edit(?string $id = null): void

Parameters:

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):

  1. Loads existing group entity
  2. Validates existence
  3. Validates authorization
  4. Displays form with existing data

POST/PATCH/PUT Request (Form Processing):

  1. Loads existing group entity
  2. Patches entity with form data
  3. Validates data through ActivityGroupsTable validation rules
  4. Saves entity if validation succeeds
  5. Redirects to view on success or re-displays form with errors

Authorization:

Error Handling:

Data Validation:

Response:

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:

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:

  1. HTTP Method Validation: Restricts to POST or DELETE only
    $this->request->allowMethod(["post", "delete"]);
    

    Prevents CSRF attacks and accidental deletions

  2. Entity Loading: Loads group with associated activities
    $authorizationGroup = $this->ActivityGroups->get($id, contain: ["Activities"]);
    
  3. Existence Validation: Throws 404 if group not found

  4. Referential Integrity Check: Prevents deletion if activities associated
    if ($authorizationGroup->activities) {
        // Redirect with error message
    }
    
  5. Authorization Check: Validates user permission to delete specific group

  6. Soft Deletion: Implements soft deletion pattern by prefixing name
    $authorizationGroup->name = "Deleted: " . $authorizationGroup->name;
    
  7. Deletion Execution: Attempts deletion with error handling

Authorization:

Error Handling:

Business Logic Protection:

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:

Default Authorization Behavior:

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:

Validation Triggers:

Field-Specific Validation

Name Field:

Description Field (if implemented):

Integration Patterns

Activities Plugin Integration

Activity Association:

Grid Integration:

Navigation Support:

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:

Partial Updates:

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:

Relationship Loading:

Index Utilization:

Memory Management

Grid Performance

Caching Opportunities

Known Issues and Notes

Variable Naming Inconsistency

The edit action contains a variable naming inconsistency:

This inconsistency should be corrected for clarity and consistency with other actions.

See Also