Skip to the content.

← Back to Activities Plugin

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

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:

Technology Stack:

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:

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:

  1. User navigates to /activities/activities/
  2. Index view renders with empty grid container
  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 export capabilities.

Signature:

public function gridData(CsvExportService $csvExportService)

Parameters:

Request Handling:

  1. Query Construction: Builds base query with activity relationships
  2. Grid Processing: Uses DataverseGridTrait::processDataverseGrid() for unified grid rendering
  3. CSV Export: Handles CSV export requests via Turbo-Frame header detection
  4. 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:

View Variables:

Response Templates:

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:

  1. Core Activity Data: Activity entity with configuration and relationships
  2. Authorization Statistics: Count queries for active, pending, and previous authorizations
  3. Approver Roles: Roles with permission to approve for this activity
  4. 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:

Exception Handling:

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:

Validation: Business rule validation enforced at Model level through Activity entity validators:

Success Flow:

Error Flow:

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:

Business Considerations:

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:

  1. Load activity entity
  2. Validate entity existence
  3. Authorize deletion operation
  4. Prefix name with “Deleted: “ for soft deletion
  5. Execute database delete
  6. 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:

Navigation:

API Endpoints

Approvers List (AJAX Endpoint)

Purpose: Provide dynamic approver discovery for authorization request forms.

Signature:

public function approversList($activityId = null, $memberId = null)

Parameters:

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:

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:

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:

Entity-Level Authorization:

Authorization Policies

Entity Access Control:

Organization Boundaries:

Performance Considerations

Query Optimization

Index/Grid Performance:

View Action Optimization:

Database Strategies

Soft Deletion Impact:

Caching Opportunities:

Integration Patterns

Dataverse Grid Integration

The Activities controller integrates with the Dataverse grid system for advanced table rendering:

Permission System Integration

Deep integration with KMP’s RBAC system:

Email Notification Integration

Authorization workflows trigger email notifications through service layer:

CSV Export Integration

Unified export handling through DataverseGridTrait:

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


Document Information: