395 lines
14 KiB
PHP
395 lines
14 KiB
PHP
<?php
|
|
/*
|
|
* Copyright (C) 2022-2024 Xibo Signage Ltd
|
|
*
|
|
* Xibo - Digital Signage - https://xibosignage.com
|
|
*
|
|
* This file is part of Xibo.
|
|
*
|
|
* Xibo is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* any later version.
|
|
*
|
|
* Xibo is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
namespace Xibo\Report;
|
|
|
|
use Carbon\Carbon;
|
|
use Psr\Container\ContainerInterface;
|
|
use Xibo\Entity\ReportForm;
|
|
use Xibo\Entity\ReportResult;
|
|
use Xibo\Entity\ReportSchedule;
|
|
use Xibo\Factory\DisplayFactory;
|
|
use Xibo\Factory\DisplayGroupFactory;
|
|
use Xibo\Helper\DateFormatHelper;
|
|
use Xibo\Helper\Translate;
|
|
use Xibo\Service\ReportServiceInterface;
|
|
use Xibo\Support\Exception\InvalidArgumentException;
|
|
use Xibo\Support\Sanitizer\SanitizerInterface;
|
|
|
|
/**
|
|
* Class TimeConnected
|
|
* @package Xibo\Report
|
|
*/
|
|
class TimeConnected implements ReportInterface
|
|
{
|
|
use ReportDefaultTrait;
|
|
|
|
/**
|
|
* @var DisplayFactory
|
|
*/
|
|
private $displayFactory;
|
|
|
|
/**
|
|
* @var DisplayGroupFactory
|
|
*/
|
|
private $displayGroupFactory;
|
|
|
|
/**
|
|
* @var ReportServiceInterface
|
|
*/
|
|
private $reportService;
|
|
|
|
/** @inheritdoc */
|
|
public function setFactories(ContainerInterface $container)
|
|
{
|
|
$this->displayFactory = $container->get('displayFactory');
|
|
$this->displayGroupFactory = $container->get('displayGroupFactory');
|
|
$this->reportService = $container->get('reportService');
|
|
|
|
return $this;
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function getReportEmailTemplate()
|
|
{
|
|
return 'timeconnected-email-template.twig';
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function getSavedReportTemplate()
|
|
{
|
|
return 'timeconnected-report-preview';
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function getReportForm()
|
|
{
|
|
$groups = [];
|
|
$displays = [];
|
|
|
|
foreach ($this->displayGroupFactory->query(['displayGroup'], ['isDisplaySpecific' => -1]) as $displayGroup) {
|
|
/* @var \Xibo\Entity\DisplayGroup $displayGroup */
|
|
|
|
if ($displayGroup->isDisplaySpecific == 1) {
|
|
$displays[] = $displayGroup;
|
|
} else {
|
|
$groups[] = $displayGroup;
|
|
}
|
|
}
|
|
|
|
return new ReportForm(
|
|
'timeconnected-report-form',
|
|
'timeconnected',
|
|
'Display',
|
|
[
|
|
'displays' => $displays,
|
|
'displayGroups' => $groups,
|
|
'fromDateOneDay' => Carbon::now()->subSeconds(86400)->format(DateFormatHelper::getSystemFormat()),
|
|
'toDate' => Carbon::now()->format(DateFormatHelper::getSystemFormat()),
|
|
],
|
|
__('Select a type and an item (i.e., layout/media/tag)')
|
|
);
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function getReportScheduleFormData(SanitizerInterface $sanitizedParams)
|
|
{
|
|
$data['hiddenFields'] = '{}';
|
|
$data['reportName'] = 'timeconnected';
|
|
|
|
$groups = [];
|
|
$displays = [];
|
|
foreach ($this->displayGroupFactory->query(['displayGroup'], ['isDisplaySpecific' => -1]) as $displayGroup) {
|
|
/* @var \Xibo\Entity\DisplayGroup $displayGroup */
|
|
|
|
if ($displayGroup->isDisplaySpecific == 1) {
|
|
$displays[] = $displayGroup;
|
|
} else {
|
|
$groups[] = $displayGroup;
|
|
}
|
|
}
|
|
$data['displays'] = $displays;
|
|
$data['displayGroups'] = $groups;
|
|
|
|
return [
|
|
'template' => 'timeconnected-schedule-form-add',
|
|
'data' => $data
|
|
];
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function setReportScheduleFormData(SanitizerInterface $sanitizedParams)
|
|
{
|
|
$filter = $sanitizedParams->getString('filter');
|
|
$groupByFilter = $sanitizedParams->getString('groupByFilter');
|
|
$displayGroupIds = $sanitizedParams->getIntArray('displayGroupIds');
|
|
$hiddenFields = json_decode($sanitizedParams->getString('hiddenFields'), true);
|
|
|
|
$filterCriteria['displayGroupIds'] = $displayGroupIds;
|
|
$filterCriteria['filter'] = $filter;
|
|
|
|
$schedule = '';
|
|
if ($filter == 'daily') {
|
|
$schedule = ReportSchedule::$SCHEDULE_DAILY;
|
|
$filterCriteria['reportFilter'] = 'yesterday';
|
|
$filterCriteria['groupByFilter'] = $groupByFilter;
|
|
} elseif ($filter == 'weekly') {
|
|
$schedule = ReportSchedule::$SCHEDULE_WEEKLY;
|
|
$filterCriteria['reportFilter'] = 'lastweek';
|
|
$filterCriteria['groupByFilter'] = $groupByFilter;
|
|
} elseif ($filter == 'monthly') {
|
|
$schedule = ReportSchedule::$SCHEDULE_MONTHLY;
|
|
$filterCriteria['reportFilter'] = 'lastmonth';
|
|
$filterCriteria['groupByFilter'] = $groupByFilter;
|
|
} elseif ($filter == 'yearly') {
|
|
$schedule = ReportSchedule::$SCHEDULE_YEARLY;
|
|
$filterCriteria['reportFilter'] = 'lastyear';
|
|
$filterCriteria['groupByFilter'] = $groupByFilter;
|
|
}
|
|
|
|
$filterCriteria['sendEmail'] = $sanitizedParams->getCheckbox('sendEmail');
|
|
$filterCriteria['nonusers'] = $sanitizedParams->getString('nonusers');
|
|
|
|
// Return
|
|
return [
|
|
'filterCriteria' => json_encode($filterCriteria),
|
|
'schedule' => $schedule
|
|
];
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function generateSavedReportName(SanitizerInterface $sanitizedParams)
|
|
{
|
|
return sprintf(__('%s report for Display', ucfirst($sanitizedParams->getString('filter'))));
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function restructureSavedReportOldJson($result)
|
|
{
|
|
return $result;
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function getSavedReportResults($json, $savedReport)
|
|
{
|
|
$metadata = [
|
|
'periodStart' => $json['metadata']['periodStart'],
|
|
'periodEnd' => $json['metadata']['periodEnd'],
|
|
'generatedOn' => Carbon::createFromTimestamp($savedReport->generatedOn)
|
|
->format(DateFormatHelper::getSystemFormat()),
|
|
'title' => $savedReport->saveAs,
|
|
];
|
|
|
|
// Report result object
|
|
return new ReportResult(
|
|
$metadata,
|
|
$json['table'],
|
|
$json['recordsTotal'],
|
|
$json['chart']
|
|
);
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function getResults(SanitizerInterface $sanitizedParams)
|
|
{
|
|
// Get an array of display id this user has access to.
|
|
$displayIds = $this->getDisplayIdFilter($sanitizedParams);
|
|
|
|
// From and To Date Selection
|
|
// --------------------------
|
|
// The report uses a custom range filter that automatically calculates the from/to dates
|
|
// depending on the date range selected.
|
|
$fromDt = $sanitizedParams->getDate('fromDt');
|
|
$toDt = $sanitizedParams->getDate('toDt');
|
|
|
|
// Use the group by filter provided
|
|
// NB: this differs from the Summary Report where we set the group by according to the range selected
|
|
$groupByFilter = $sanitizedParams->getString('groupByFilter');
|
|
|
|
//
|
|
// Get Results!
|
|
// with keys "result", "periods", "periodStart", "periodEnd"
|
|
// -------------
|
|
$result = $this->getTimeDisconnectedMySql($fromDt, $toDt, $groupByFilter, $displayIds);
|
|
|
|
//
|
|
// Output Results
|
|
// --------------
|
|
if ($this->getUser()->isSuperAdmin()) {
|
|
$sql = 'SELECT displayId, display FROM display WHERE 1 = 1';
|
|
if (count($displayIds) > 0) {
|
|
$sql .= ' AND displayId IN (' . implode(',', $displayIds) . ')';
|
|
}
|
|
}
|
|
|
|
$timeConnected = [];
|
|
$displays = [];
|
|
$i = 0;
|
|
$key = 0;
|
|
foreach ($this->store->select($sql, []) as $row) {
|
|
$displayId = intval($row['displayId']);
|
|
$displayName = $row['display'];
|
|
|
|
// Set the display name for the displays in this row.
|
|
$displays[$key][$displayId] = $displayName;
|
|
|
|
// Go through each period
|
|
foreach ($result['periods'] as $resPeriods) {
|
|
//
|
|
$temp = $resPeriods['customLabel'];
|
|
if (empty($timeConnected[$temp][$displayId]['percent'])) {
|
|
$timeConnected[$key][$temp][$displayId]['percent'] = 100;
|
|
}
|
|
if (empty($timeConnected[$temp][$displayId]['label'])) {
|
|
$timeConnected[$key][$temp][$displayId]['label'] = $resPeriods['customLabel'];
|
|
}
|
|
|
|
foreach ($result['result'] as $res) {
|
|
if ($res['displayId'] == $displayId && $res['customLabel'] == $resPeriods['customLabel']) {
|
|
$timeConnected[$key][$temp][$displayId]['percent'] = 100 - round($res['percent'], 2);
|
|
$timeConnected[$key][$temp][$displayId]['label'] = $resPeriods['customLabel'];
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
$i++;
|
|
if ($i >= 3) {
|
|
$i = 0;
|
|
$key++;
|
|
}
|
|
}
|
|
|
|
// ----
|
|
// No grid
|
|
// Return data to build chart/table
|
|
// This will get saved to a json file when schedule runs
|
|
return new ReportResult(
|
|
[
|
|
'periodStart' => Carbon::createFromTimestamp($fromDt->toDateTime()->format('U'))->format(DateFormatHelper::getSystemFormat()),
|
|
'periodEnd' => Carbon::createFromTimestamp($toDt->toDateTime()->format('U'))->format(DateFormatHelper::getSystemFormat()),
|
|
],
|
|
[
|
|
'timeConnected' => $timeConnected,
|
|
'displays' => $displays
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* MySQL distribution report
|
|
* @param Carbon $fromDt The filter range from date
|
|
* @param Carbon $toDt The filter range to date
|
|
* @param string $groupByFilter Grouping, byhour, bydayofweek and bydayofmonth
|
|
* @param array $displayIds
|
|
* @return array
|
|
*/
|
|
private function getTimeDisconnectedMySql($fromDt, $toDt, $groupByFilter, $displayIds)
|
|
{
|
|
|
|
if ($groupByFilter == 'bydayofmonth') {
|
|
$customLabel = 'Y-m-d (D)';
|
|
} else {
|
|
$customLabel = 'Y-m-d g:i A';
|
|
}
|
|
|
|
// Create periods covering the from/to dates
|
|
// -----------------------------------------
|
|
try {
|
|
$periods = $this->getTemporaryPeriodsTable($fromDt, $toDt, $groupByFilter, 'temp_periods', $customLabel);
|
|
} catch (InvalidArgumentException $invalidArgumentException) {
|
|
return [];
|
|
}
|
|
try {
|
|
$periods2 = $this->getTemporaryPeriodsTable($fromDt, $toDt, $groupByFilter, 'temp_periods2', $customLabel);
|
|
} catch (InvalidArgumentException $invalidArgumentException) {
|
|
return [];
|
|
}
|
|
|
|
// Join in stats
|
|
// -------------
|
|
$query = '
|
|
SELECT periods.id,
|
|
periods.start,
|
|
periods.end,
|
|
periods.label,
|
|
periods.customLabel,
|
|
display,
|
|
displayId,
|
|
SUM(duration) AS downtime,
|
|
periods.end - periods.start AS periodDuration,
|
|
SUM(duration) / (periods.end - periods.start) * 100 AS percent
|
|
FROM ' . $periods2 . ' AS periods
|
|
INNER JOIN (
|
|
SELECT id,
|
|
label,
|
|
customLabel,
|
|
display,
|
|
displayId,
|
|
GREATEST(periods.start, down.start) AS actualStart,
|
|
LEAST(periods.end, down.end) AS actualEnd,
|
|
LEAST(periods.end, down.end) - GREATEST(periods.start, down.start) AS duration,
|
|
periods.end - periods.start AS periodDuration,
|
|
(LEAST(periods.end, down.end) - GREATEST(periods.start, down.start)) / (periods.end - periods.start) * 100 AS percent
|
|
FROM ' . $periods . ' AS periods
|
|
LEFT OUTER JOIN (
|
|
SELECT start,
|
|
IFNULL(end, UNIX_TIMESTAMP()) AS end,
|
|
displayevent.displayId,
|
|
display.display
|
|
FROM displayevent
|
|
INNER JOIN display
|
|
ON display.displayId = displayevent.displayId
|
|
WHERE `displayevent`.eventTypeId = 1
|
|
';
|
|
|
|
// Displays
|
|
if (count($displayIds) > 0) {
|
|
$query .= ' AND display.displayID IN (' . implode(',', $displayIds) . ') ';
|
|
}
|
|
|
|
$query .= '
|
|
) down
|
|
ON down.start < periods.`end`
|
|
AND down.end > periods.`start`
|
|
) joined
|
|
ON joined.customLabel = periods.customLabel
|
|
GROUP BY periods.id,
|
|
periods.start,
|
|
periods.end,
|
|
periods.label,
|
|
periods.customLabel,
|
|
joined.display,
|
|
joined.displayId
|
|
ORDER BY id, display
|
|
';
|
|
|
|
return [
|
|
'result' => $this->getStore()->select($query, []),
|
|
'periods' => $this->getStore()->select('SELECT * from ' . $periods, []),
|
|
'periodStart' => $fromDt->format('Y-m-d H:i:s'),
|
|
'periodEnd' => $toDt->format('Y-m-d H:i:s')
|
|
];
|
|
}
|
|
}
|