e3c15979b8
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Generic: Repo Health / Access control (pull_request) Has been cancelled
Generic: Repo Health / Site Health (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Has been cancelled
Rename top-level src/ directory to source/ and update all references in .gitignore, CLAUDE.md, manifest.xml, docs, and PATH comments. Internal namespace path="src" attributes within extension packages are unchanged (they refer to the package-internal src/ folder). Closes #188 Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
613 lines
17 KiB
PHP
613 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* @package MokoWaaS
|
|
* @subpackage com_mokowaas
|
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
|
* @license GNU General Public License version 3 or later; see LICENSE
|
|
*/
|
|
|
|
namespace Moko\Component\MokoWaaS\Administrator\Model;
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
use Joomla\CMS\Factory;
|
|
use Joomla\CMS\Log\Log;
|
|
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
|
|
|
|
class PrivacyModel extends BaseDatabaseModel
|
|
{
|
|
/**
|
|
* Get all pending data requests.
|
|
*/
|
|
public function getDataRequests(string $filterStatus = ''): array
|
|
{
|
|
$db = $this->getDatabase();
|
|
$query = $db->getQuery(true)
|
|
->select([
|
|
$db->quoteName('r') . '.*',
|
|
$db->quoteName('u.name', 'user_name'),
|
|
$db->quoteName('u.email', 'user_email'),
|
|
$db->quoteName('u.username'),
|
|
$db->quoteName('p.name', 'processed_by_name'),
|
|
])
|
|
->from($db->quoteName('#__mokowaas_data_requests', 'r'))
|
|
->leftJoin($db->quoteName('#__users', 'u') . ' ON u.id = r.user_id')
|
|
->leftJoin($db->quoteName('#__users', 'p') . ' ON p.id = r.processed_by');
|
|
|
|
if ($filterStatus)
|
|
{
|
|
$query->where($db->quoteName('r.status') . ' = ' . $db->quote($filterStatus));
|
|
}
|
|
|
|
$query->order($db->quoteName('r.created') . ' DESC')->setLimit(50);
|
|
$db->setQuery($query);
|
|
|
|
return $db->loadObjectList() ?: [];
|
|
}
|
|
|
|
/**
|
|
* Create a data request (from admin or user self-service).
|
|
*/
|
|
public function createRequest(int $userId, string $type, string $notes = ''): array
|
|
{
|
|
$validTypes = ['export', 'delete', 'anonymize'];
|
|
|
|
if (!\in_array($type, $validTypes, true))
|
|
{
|
|
return ['success' => false, 'message' => 'Invalid request type.'];
|
|
}
|
|
|
|
try
|
|
{
|
|
$db = $this->getDatabase();
|
|
$row = (object) [
|
|
'user_id' => $userId,
|
|
'type' => $type,
|
|
'status' => 'pending',
|
|
'notes' => $notes,
|
|
'created' => Factory::getDate()->toSql(),
|
|
];
|
|
|
|
$db->insertObject('#__mokowaas_data_requests', $row, 'id');
|
|
|
|
return ['success' => true, 'message' => ucfirst($type) . ' request #' . $row->id . ' created.', 'id' => (int) $row->id];
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
return ['success' => false, 'message' => 'Failed: ' . $e->getMessage()];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a data request (approve and execute).
|
|
*/
|
|
public function processRequest(int $requestId, string $action): array
|
|
{
|
|
$db = $this->getDatabase();
|
|
|
|
try
|
|
{
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select('*')
|
|
->from($db->quoteName('#__mokowaas_data_requests'))
|
|
->where($db->quoteName('id') . ' = ' . $requestId)
|
|
);
|
|
$request = $db->loadObject();
|
|
|
|
if (!$request)
|
|
{
|
|
return ['success' => false, 'message' => 'Request not found.'];
|
|
}
|
|
|
|
if ($action === 'deny')
|
|
{
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__mokowaas_data_requests'))
|
|
->set($db->quoteName('status') . ' = ' . $db->quote('denied'))
|
|
->set($db->quoteName('processed_by') . ' = ' . (int) Factory::getApplication()->getIdentity()->id)
|
|
->set($db->quoteName('processed') . ' = ' . $db->quote(Factory::getDate()->toSql()))
|
|
->where($db->quoteName('id') . ' = ' . $requestId)
|
|
)->execute();
|
|
|
|
return ['success' => true, 'message' => 'Request denied.'];
|
|
}
|
|
|
|
// Mark as processing
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__mokowaas_data_requests'))
|
|
->set($db->quoteName('status') . ' = ' . $db->quote('processing'))
|
|
->where($db->quoteName('id') . ' = ' . $requestId)
|
|
)->execute();
|
|
|
|
// Execute the request
|
|
$result = null;
|
|
|
|
switch ($request->type)
|
|
{
|
|
case 'export':
|
|
$result = $this->exportUserData((int) $request->user_id);
|
|
break;
|
|
|
|
case 'delete':
|
|
$result = $this->deleteUserData((int) $request->user_id);
|
|
break;
|
|
|
|
case 'anonymize':
|
|
$result = $this->anonymizeUserData((int) $request->user_id);
|
|
break;
|
|
}
|
|
|
|
// Mark completed
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__mokowaas_data_requests'))
|
|
->set($db->quoteName('status') . ' = ' . $db->quote('completed'))
|
|
->set($db->quoteName('processed_by') . ' = ' . (int) Factory::getApplication()->getIdentity()->id)
|
|
->set($db->quoteName('processed') . ' = ' . $db->quote(Factory::getDate()->toSql()))
|
|
->where($db->quoteName('id') . ' = ' . $requestId)
|
|
)->execute();
|
|
|
|
return $result ?? ['success' => true, 'message' => 'Request processed.'];
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
return ['success' => false, 'message' => 'Processing failed: ' . $e->getMessage()];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export all data for a user as a structured array.
|
|
*/
|
|
public function exportUserData(int $userId): array
|
|
{
|
|
$db = $this->getDatabase();
|
|
$data = ['user_id' => $userId, 'exported' => gmdate('Y-m-d\TH:i:s\Z')];
|
|
|
|
try
|
|
{
|
|
// User profile
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select(['id', 'name', 'username', 'email', 'registerDate', 'lastvisitDate', 'params'])
|
|
->from($db->quoteName('#__users'))
|
|
->where($db->quoteName('id') . ' = ' . $userId)
|
|
);
|
|
$data['profile'] = $db->loadObject();
|
|
|
|
// Content (articles)
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select(['id', 'title', 'alias', 'created', 'modified', 'hits'])
|
|
->from($db->quoteName('#__content'))
|
|
->where($db->quoteName('created_by') . ' = ' . $userId)
|
|
);
|
|
$data['articles'] = $db->loadObjectList() ?: [];
|
|
|
|
// Action logs
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select(['message', 'log_date', 'ip_address'])
|
|
->from($db->quoteName('#__action_logs'))
|
|
->where($db->quoteName('user_id') . ' = ' . $userId)
|
|
->order('log_date DESC')
|
|
->setLimit(100)
|
|
);
|
|
$data['action_logs'] = $db->loadObjectList() ?: [];
|
|
|
|
// Support tickets
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select(['id', 'subject', 'body', 'status', 'priority', 'created'])
|
|
->from($db->quoteName('#__mokowaas_tickets'))
|
|
->where($db->quoteName('created_by') . ' = ' . $userId)
|
|
);
|
|
$data['tickets'] = $db->loadObjectList() ?: [];
|
|
|
|
// Ticket replies
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select(['r.id', 'r.ticket_id', 'r.body', 'r.created'])
|
|
->from($db->quoteName('#__mokowaas_ticket_replies', 'r'))
|
|
->where($db->quoteName('r.user_id') . ' = ' . $userId)
|
|
);
|
|
$data['ticket_replies'] = $db->loadObjectList() ?: [];
|
|
|
|
// Consent log
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select('*')
|
|
->from($db->quoteName('#__mokowaas_consent_log'))
|
|
->where($db->quoteName('user_id') . ' = ' . $userId)
|
|
->order('created ASC')
|
|
);
|
|
$data['consent_history'] = $db->loadObjectList() ?: [];
|
|
|
|
// Community Builder profile (if table exists)
|
|
try
|
|
{
|
|
$db->setQuery('SHOW TABLES LIKE ' . $db->quote('%comprofiler%'));
|
|
|
|
if ($db->loadResult())
|
|
{
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select('*')
|
|
->from($db->quoteName('#__comprofiler'))
|
|
->where($db->quoteName('user_id') . ' = ' . $userId)
|
|
);
|
|
$data['community_builder'] = $db->loadObject();
|
|
}
|
|
}
|
|
catch (\Throwable $e) {}
|
|
|
|
return ['success' => true, 'message' => 'Data exported.', 'data' => $data];
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
return ['success' => false, 'message' => 'Export failed: ' . $e->getMessage()];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Anonymize a user's data (GDPR right to be forgotten — soft).
|
|
*/
|
|
public function anonymizeUserData(int $userId): array
|
|
{
|
|
$db = $this->getDatabase();
|
|
$now = Factory::getDate()->toSql();
|
|
$anon = 'Anonymous User #' . $userId;
|
|
|
|
try
|
|
{
|
|
// Anonymize user record
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__users'))
|
|
->set([
|
|
$db->quoteName('name') . ' = ' . $db->quote($anon),
|
|
$db->quoteName('username') . ' = ' . $db->quote('anon_' . $userId),
|
|
$db->quoteName('email') . ' = ' . $db->quote('anon_' . $userId . '@deleted.local'),
|
|
$db->quoteName('password') . ' = ' . $db->quote(''),
|
|
$db->quoteName('block') . ' = 1',
|
|
$db->quoteName('params') . ' = ' . $db->quote('{}'),
|
|
])
|
|
->where($db->quoteName('id') . ' = ' . $userId)
|
|
)->execute();
|
|
|
|
// Anonymize article authorship
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__content'))
|
|
->set($db->quoteName('created_by_alias') . ' = ' . $db->quote($anon))
|
|
->where($db->quoteName('created_by') . ' = ' . $userId)
|
|
)->execute();
|
|
|
|
// Delete action logs
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->delete($db->quoteName('#__action_logs'))
|
|
->where($db->quoteName('user_id') . ' = ' . $userId)
|
|
)->execute();
|
|
|
|
// Anonymize ticket replies
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__mokowaas_ticket_replies'))
|
|
->set($db->quoteName('body') . ' = ' . $db->quote('[Content removed per data request]'))
|
|
->where($db->quoteName('user_id') . ' = ' . $userId)
|
|
)->execute();
|
|
|
|
// Community Builder
|
|
try
|
|
{
|
|
$db->setQuery('SHOW TABLES LIKE ' . $db->quote('%comprofiler%'));
|
|
|
|
if ($db->loadResult())
|
|
{
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__comprofiler'))
|
|
->set([
|
|
$db->quoteName('firstname') . ' = ' . $db->quote('Anonymous'),
|
|
$db->quoteName('lastname') . ' = ' . $db->quote('User'),
|
|
$db->quoteName('middlename') . ' = ' . $db->quote(''),
|
|
])
|
|
->where($db->quoteName('user_id') . ' = ' . $userId)
|
|
)->execute();
|
|
}
|
|
}
|
|
catch (\Throwable $e) {}
|
|
|
|
// Clear Joomla user profile fields (#7)
|
|
try
|
|
{
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->delete($db->quoteName('#__user_profiles'))
|
|
->where($db->quoteName('user_id') . ' = ' . $userId)
|
|
)->execute();
|
|
}
|
|
catch (\Throwable $e) {}
|
|
|
|
// Clear contact details if linked
|
|
try
|
|
{
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->delete($db->quoteName('#__contact_details'))
|
|
->where($db->quoteName('user_id') . ' = ' . $userId)
|
|
)->execute();
|
|
}
|
|
catch (\Throwable $e) {}
|
|
|
|
// Log the anonymization
|
|
$this->logConsent($userId, 'account_anonymized', 'granted');
|
|
|
|
return ['success' => true, 'message' => 'User #' . $userId . ' data anonymized.'];
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
return ['success' => false, 'message' => 'Anonymization failed: ' . $e->getMessage()];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete a user's data completely (hard delete).
|
|
*/
|
|
public function deleteUserData(int $userId): array
|
|
{
|
|
$result = $this->anonymizeUserData($userId);
|
|
|
|
if (!$result['success'])
|
|
{
|
|
return $result;
|
|
}
|
|
|
|
$db = $this->getDatabase();
|
|
|
|
try
|
|
{
|
|
// Delete tickets and replies
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select($db->quoteName('id'))
|
|
->from($db->quoteName('#__mokowaas_tickets'))
|
|
->where($db->quoteName('created_by') . ' = ' . $userId)
|
|
);
|
|
$ticketIds = $db->loadColumn() ?: [];
|
|
|
|
if (!empty($ticketIds))
|
|
{
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->delete($db->quoteName('#__mokowaas_ticket_replies'))
|
|
->where($db->quoteName('ticket_id') . ' IN (' . implode(',', $ticketIds) . ')')
|
|
)->execute();
|
|
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->delete($db->quoteName('#__mokowaas_tickets'))
|
|
->where($db->quoteName('created_by') . ' = ' . $userId)
|
|
)->execute();
|
|
}
|
|
|
|
// Delete consent log
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->delete($db->quoteName('#__mokowaas_consent_log'))
|
|
->where($db->quoteName('user_id') . ' = ' . $userId)
|
|
)->execute();
|
|
|
|
// Delete user record
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->delete($db->quoteName('#__users'))
|
|
->where($db->quoteName('id') . ' = ' . $userId)
|
|
)->execute();
|
|
|
|
return ['success' => true, 'message' => 'User #' . $userId . ' data permanently deleted.'];
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
return ['success' => false, 'message' => 'Deletion failed: ' . $e->getMessage()];
|
|
}
|
|
}
|
|
|
|
// ==================================================================
|
|
// Consent Management
|
|
// ==================================================================
|
|
|
|
/**
|
|
* Get consent status for a user.
|
|
*/
|
|
public function getUserConsent(int $userId): array
|
|
{
|
|
$db = $this->getDatabase();
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select('*')
|
|
->from($db->quoteName('#__mokowaas_consent_log'))
|
|
->where($db->quoteName('user_id') . ' = ' . $userId)
|
|
->order($db->quoteName('created') . ' DESC')
|
|
);
|
|
|
|
return $db->loadObjectList() ?: [];
|
|
}
|
|
|
|
/**
|
|
* Record a consent action.
|
|
*/
|
|
public function logConsent(int $userId, string $category, string $action): void
|
|
{
|
|
$db = $this->getDatabase();
|
|
$row = (object) [
|
|
'user_id' => $userId,
|
|
'category' => $category,
|
|
'action' => $action === 'revoked' ? 'revoked' : 'granted',
|
|
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '',
|
|
'created' => Factory::getDate()->toSql(),
|
|
];
|
|
$db->insertObject('#__mokowaas_consent_log', $row, 'id');
|
|
}
|
|
|
|
// ==================================================================
|
|
// Retention Policy Enforcement
|
|
// ==================================================================
|
|
|
|
/**
|
|
* Get all retention policies.
|
|
*/
|
|
public function getRetentionPolicies(): array
|
|
{
|
|
$db = $this->getDatabase();
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select('*')
|
|
->from($db->quoteName('#__mokowaas_retention_policies'))
|
|
->order($db->quoteName('id') . ' ASC')
|
|
);
|
|
|
|
return $db->loadObjectList() ?: [];
|
|
}
|
|
|
|
/**
|
|
* Run retention policy enforcement (called by scheduled task).
|
|
*/
|
|
public function enforceRetentionPolicies(): array
|
|
{
|
|
$db = $this->getDatabase();
|
|
$results = ['policies_run' => 0, 'items_affected' => 0];
|
|
$policies = $this->getRetentionPolicies();
|
|
|
|
foreach ($policies as $policy)
|
|
{
|
|
if (!(int) $policy->enabled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$cutoff = Factory::getDate('-' . (int) $policy->retention_days . ' days')->toSql();
|
|
$count = 0;
|
|
|
|
try
|
|
{
|
|
switch ($policy->content_type)
|
|
{
|
|
case 'action_logs':
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->delete($db->quoteName('#__action_logs'))
|
|
->where($db->quoteName('log_date') . ' < ' . $db->quote($cutoff))
|
|
)->execute();
|
|
$count = $db->getAffectedRows();
|
|
break;
|
|
|
|
case 'waf_logs':
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->delete($db->quoteName('#__mokowaas_waf_log'))
|
|
->where($db->quoteName('created') . ' < ' . $db->quote($cutoff))
|
|
)->execute();
|
|
$count = $db->getAffectedRows();
|
|
break;
|
|
|
|
case 'sessions':
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->delete($db->quoteName('#__session'))
|
|
->where($db->quoteName('time') . ' < ' . (int) strtotime($cutoff))
|
|
)->execute();
|
|
$count = $db->getAffectedRows();
|
|
break;
|
|
|
|
case 'closed_tickets':
|
|
if ($policy->action === 'anonymize')
|
|
{
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__mokowaas_tickets'))
|
|
->set($db->quoteName('body') . ' = ' . $db->quote('[Removed per retention policy]'))
|
|
->where($db->quoteName('status') . ' = ' . $db->quote('closed'))
|
|
->where($db->quoteName('closed') . ' < ' . $db->quote($cutoff))
|
|
->where($db->quoteName('body') . ' != ' . $db->quote('[Removed per retention policy]'))
|
|
)->execute();
|
|
$count = $db->getAffectedRows();
|
|
}
|
|
break;
|
|
|
|
case 'inactive_users':
|
|
if ($policy->action === 'anonymize')
|
|
{
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select($db->quoteName('id'))
|
|
->from($db->quoteName('#__users'))
|
|
->where($db->quoteName('lastvisitDate') . ' < ' . $db->quote($cutoff))
|
|
->where($db->quoteName('lastvisitDate') . ' != ' . $db->quote('0000-00-00 00:00:00'))
|
|
->where($db->quoteName('block') . ' = 0')
|
|
->where($db->quoteName('username') . ' NOT LIKE ' . $db->quote('anon_%'))
|
|
);
|
|
$userIds = $db->loadColumn() ?: [];
|
|
|
|
foreach ($userIds as $uid)
|
|
{
|
|
$this->anonymizeUserData((int) $uid);
|
|
$count++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ($count > 0)
|
|
{
|
|
$results['policies_run']++;
|
|
$results['items_affected'] += $count;
|
|
Log::add(\sprintf('Retention: %s — %d items affected', $policy->content_type, $count), Log::INFO, 'mokowaas');
|
|
}
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
Log::add('Retention policy error (' . $policy->content_type . '): ' . $e->getMessage(), Log::WARNING, 'mokowaas');
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Get privacy dashboard summary counts.
|
|
*/
|
|
public function getDashboardSummary(): object
|
|
{
|
|
$db = $this->getDatabase();
|
|
|
|
$summary = (object) [
|
|
'pending_requests' => 0,
|
|
'total_requests' => 0,
|
|
'consent_entries' => 0,
|
|
'policies_active' => 0,
|
|
];
|
|
|
|
try
|
|
{
|
|
$db->setQuery('SELECT COUNT(*) FROM #__mokowaas_data_requests WHERE status = ' . $db->quote('pending'));
|
|
$summary->pending_requests = (int) $db->loadResult();
|
|
|
|
$db->setQuery('SELECT COUNT(*) FROM #__mokowaas_data_requests');
|
|
$summary->total_requests = (int) $db->loadResult();
|
|
|
|
$db->setQuery('SELECT COUNT(*) FROM #__mokowaas_consent_log');
|
|
$summary->consent_entries = (int) $db->loadResult();
|
|
|
|
$db->setQuery('SELECT COUNT(*) FROM #__mokowaas_retention_policies WHERE enabled = 1');
|
|
$summary->policies_active = (int) $db->loadResult();
|
|
}
|
|
catch (\Throwable $e) {}
|
|
|
|
return $summary;
|
|
}
|
|
}
|