Files
MokoSuiteCross/source/packages/com_mokosuitecross/src/Model/AnalyticsModel.php
T

170 lines
6.4 KiB
PHP
Raw Normal View History

<?php
/**
* @package MokoSuiteCross
* @subpackage com_mokosuitecross
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace Joomla\Component\MokoSuiteCross\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
class AnalyticsModel extends BaseDatabaseModel
{
public function getHeatmap(int $days = 90, ?int $serviceId = null): array
{
$db = $this->getDatabase();
$cutoff = Factory::getDate('now - ' . $days . ' days')->format('Y-m-d H:i:s');
$query = $db->getQuery(true)
->select([
'DAYOFWEEK(' . $db->quoteName('posted_at') . ') AS dow',
'HOUR(' . $db->quoteName('posted_at') . ') AS hour_of_day',
'COUNT(*) AS total',
'SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('posted') . ' THEN 1 ELSE 0 END) AS success',
])
->from($db->quoteName('#__mokosuitecross_posts'))
->where($db->quoteName('posted_at') . ' IS NOT NULL')
->where($db->quoteName('posted_at') . ' >= ' . $db->quote($cutoff))
->group('DAYOFWEEK(' . $db->quoteName('posted_at') . '), HOUR(' . $db->quoteName('posted_at') . ')')
->order('dow ASC, hour_of_day ASC');
if ($serviceId !== null && $serviceId > 0) {
$query->where($db->quoteName('service_id') . ' = ' . (int) $serviceId);
}
$db->setQuery($query);
$rows = $db->loadAssocList() ?: [];
$grid = [];
for ($d = 1; $d <= 7; $d++) {
for ($h = 0; $h < 24; $h++) {
$grid[$d][$h] = ['total' => 0, 'success' => 0, 'rate' => 0];
}
}
foreach ($rows as $row) {
$d = (int) $row['dow'];
$h = (int) $row['hour_of_day'];
$grid[$d][$h] = [
'total' => (int) $row['total'],
'success' => (int) $row['success'],
'rate' => (int) $row['total'] > 0
? round(((int) $row['success'] / (int) $row['total']) * 100)
: 0,
];
}
return $grid;
}
public function getBestTimes(int $days = 90, ?int $serviceId = null, int $limit = 5): array
{
$db = $this->getDatabase();
$cutoff = Factory::getDate('now - ' . $days . ' days')->format('Y-m-d H:i:s');
$query = $db->getQuery(true)
->select([
'DAYOFWEEK(' . $db->quoteName('posted_at') . ') AS dow',
'HOUR(' . $db->quoteName('posted_at') . ') AS hour_of_day',
'COUNT(*) AS total',
'SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('posted') . ' THEN 1 ELSE 0 END) AS success',
])
->from($db->quoteName('#__mokosuitecross_posts'))
->where($db->quoteName('posted_at') . ' IS NOT NULL')
->where($db->quoteName('posted_at') . ' >= ' . $db->quote($cutoff))
->group('DAYOFWEEK(' . $db->quoteName('posted_at') . '), HOUR(' . $db->quoteName('posted_at') . ')')
->having('COUNT(*) >= 3')
->order('success DESC, total DESC');
if ($serviceId !== null && $serviceId > 0) {
$query->where($db->quoteName('service_id') . ' = ' . (int) $serviceId);
}
$db->setQuery($query, 0, $limit);
return $db->loadAssocList() ?: [];
}
public function getHourlyDistribution(int $days = 90, ?int $serviceId = null): array
{
$db = $this->getDatabase();
$cutoff = Factory::getDate('now - ' . $days . ' days')->format('Y-m-d H:i:s');
$query = $db->getQuery(true)
->select([
'HOUR(' . $db->quoteName('posted_at') . ') AS hour_of_day',
'COUNT(*) AS total',
'SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('posted') . ' THEN 1 ELSE 0 END) AS success',
'SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('failed') . ' THEN 1 ELSE 0 END) AS failed',
])
->from($db->quoteName('#__mokosuitecross_posts'))
->where($db->quoteName('posted_at') . ' IS NOT NULL')
->where($db->quoteName('posted_at') . ' >= ' . $db->quote($cutoff))
->group('HOUR(' . $db->quoteName('posted_at') . ')')
->order('hour_of_day ASC');
if ($serviceId !== null && $serviceId > 0) {
$query->where($db->quoteName('service_id') . ' = ' . (int) $serviceId);
}
$db->setQuery($query);
return $db->loadAssocList() ?: [];
}
public function getDayOfWeekDistribution(int $days = 90, ?int $serviceId = null): array
{
$db = $this->getDatabase();
$cutoff = Factory::getDate('now - ' . $days . ' days')->format('Y-m-d H:i:s');
$query = $db->getQuery(true)
->select([
'DAYOFWEEK(' . $db->quoteName('posted_at') . ') AS dow',
'COUNT(*) AS total',
'SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('posted') . ' THEN 1 ELSE 0 END) AS success',
'SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('failed') . ' THEN 1 ELSE 0 END) AS failed',
])
->from($db->quoteName('#__mokosuitecross_posts'))
->where($db->quoteName('posted_at') . ' IS NOT NULL')
->where($db->quoteName('posted_at') . ' >= ' . $db->quote($cutoff))
->group('DAYOFWEEK(' . $db->quoteName('posted_at') . ')')
->order('dow ASC');
if ($serviceId !== null && $serviceId > 0) {
$query->where($db->quoteName('service_id') . ' = ' . (int) $serviceId);
}
$db->setQuery($query);
return $db->loadAssocList() ?: [];
}
public function getServices(): array
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select([$db->quoteName('id'), $db->quoteName('title'), $db->quoteName('service_type')])
->from($db->quoteName('#__mokosuitecross_services'))
->where($db->quoteName('published') . ' = 1')
->order($db->quoteName('title') . ' ASC');
$db->setQuery($query);
return $db->loadAssocList() ?: [];
}
}