Initial Upload

This commit is contained in:
Matt Batchelder
2025-12-02 10:32:59 -05:00
commit 05ce0da296
2240 changed files with 467811 additions and 0 deletions

457
lib/Report/ApiRequests.php Normal file
View File

@@ -0,0 +1,457 @@
<?php
/*
* Copyright (C) 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\Controller\DataTablesDotNetTrait;
use Xibo\Entity\ReportForm;
use Xibo\Entity\ReportResult;
use Xibo\Entity\ReportSchedule;
use Xibo\Factory\ApplicationRequestsFactory;
use Xibo\Factory\AuditLogFactory;
use Xibo\Factory\LogFactory;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Translate;
use Xibo\Support\Exception\AccessDeniedException;
use Xibo\Support\Sanitizer\SanitizerInterface;
class ApiRequests implements ReportInterface
{
use ReportDefaultTrait, DataTablesDotNetTrait;
/** @var LogFactory */
private $logFactory;
/** @var AuditLogFactory */
private $auditLogFactory;
/** @var ApplicationRequestsFactory */
private $apiRequestsFactory;
/** @inheritdoc */
public function setFactories(ContainerInterface $container)
{
$this->logFactory = $container->get('logFactory');
$this->auditLogFactory = $container->get('auditLogFactory');
$this->apiRequestsFactory = $container->get('apiRequestsFactory');
return $this;
}
/** @inheritdoc */
public function getReportEmailTemplate()
{
return 'apirequests-email-template.twig';
}
/** @inheritdoc */
public function getSavedReportTemplate()
{
return 'apirequests-report-preview';
}
/** @inheritdoc */
public function getReportForm(): ReportForm
{
return new ReportForm(
'apirequests-report-form',
'apirequests',
'Audit',
[
'fromDate' => Carbon::now()->startOfMonth()->format(DateFormatHelper::getSystemFormat()),
'toDate' => Carbon::now()->format(DateFormatHelper::getSystemFormat()),
]
);
}
/** @inheritdoc */
public function getReportScheduleFormData(SanitizerInterface $sanitizedParams): array
{
$data = [];
$data['reportName'] = 'apirequests';
return [
'template' => 'apirequests-schedule-form-add',
'data' => $data
];
}
/** @inheritdoc */
public function setReportScheduleFormData(SanitizerInterface $sanitizedParams): array
{
$filter = $sanitizedParams->getString('filter');
$filterCriteria['userId'] = $sanitizedParams->getInt('userId');
$filterCriteria['type'] = $sanitizedParams->getString('type');
$filterCriteria['scheduledReport'] = true;
$filterCriteria['filter'] = $filter;
$schedule = '';
if ($filter == 'daily') {
$schedule = ReportSchedule::$SCHEDULE_DAILY;
$filterCriteria['reportFilter'] = 'yesterday';
} elseif ($filter == 'weekly') {
$schedule = ReportSchedule::$SCHEDULE_WEEKLY;
$filterCriteria['reportFilter'] = 'lastweek';
} elseif ($filter == 'monthly') {
$schedule = ReportSchedule::$SCHEDULE_MONTHLY;
$filterCriteria['reportFilter'] = 'lastmonth';
} elseif ($filter == 'yearly') {
$schedule = ReportSchedule::$SCHEDULE_YEARLY;
$filterCriteria['reportFilter'] = 'lastyear';
}
$filterCriteria['sendEmail'] = $sanitizedParams->getCheckbox('sendEmail');
$filterCriteria['nonusers'] = $sanitizedParams->getString('nonusers');
// Return
return [
'filterCriteria' => json_encode($filterCriteria),
'schedule' => $schedule
];
}
public function generateSavedReportName(SanitizerInterface $sanitizedParams): string
{
return sprintf(
__('%s API requests %s log report for User'),
ucfirst($sanitizedParams->getString('filter')),
ucfirst($sanitizedParams->getString('type'))
);
}
/** @inheritdoc */
public function restructureSavedReportOldJson($json)
{
return $json;
}
/** @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,
'logType' => $json['metadata']['logType']
];
// Report result object
return new ReportResult(
$metadata,
$json['table'],
$json['recordsTotal'],
);
}
/** @inheritdoc */
public function getResults(SanitizerInterface $sanitizedParams)
{
if (!$this->getUser()->isSuperAdmin()) {
throw new AccessDeniedException();
}
//
// 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.
$reportFilter = $sanitizedParams->getString('reportFilter');
// Use the current date as a helper
$now = Carbon::now();
// This calculation will be retained as it is used for scheduled reports
switch ($reportFilter) {
case 'yesterday':
$fromDt = $now->copy()->startOfDay()->subDay();
$toDt = $now->copy()->startOfDay();
break;
case 'lastweek':
$fromDt = $now->copy()->locale(Translate::GetLocale())->startOfWeek()->subWeek();
$toDt = $fromDt->copy()->addWeek();
break;
case 'lastmonth':
$fromDt = $now->copy()->startOfMonth()->subMonth();
$toDt = $fromDt->copy()->addMonth();
break;
case 'lastyear':
$fromDt = $now->copy()->startOfYear()->subYear();
$toDt = $fromDt->copy()->addYear();
break;
case '':
default:
// fromDt will always be from start of day ie 00:00
$fromDt = $sanitizedParams->getDate('fromDt') ?? $now->copy()->startOfDay();
$toDt = $sanitizedParams->getDate('toDt') ?? $now;
break;
}
$type = $sanitizedParams->getString('type');
$metadata = [
'periodStart' => Carbon::createFromTimestamp($fromDt->toDateTime()->format('U'))
->format(DateFormatHelper::getSystemFormat()),
'periodEnd' => Carbon::createFromTimestamp($toDt->toDateTime()->format('U'))
->format(DateFormatHelper::getSystemFormat()),
'logType' => $type,
];
if ($type === 'audit') {
$params = [
'fromDt' => $fromDt->format('U'),
'toDt' => $toDt->format('U'),
];
$sql = 'SELECT
`auditlog`.`logId`,
`auditlog`.`logDate`,
`user`.`userName`,
`auditlog`.`message`,
`auditlog`.`objectAfter`,
`auditlog`.`entity`,
`auditlog`.`entityId`,
`auditlog`.userId,
`auditlog`.ipAddress,
`auditlog`.requestId,
`application_requests_history`.applicationId,
`application_requests_history`.url,
`application_requests_history`.method,
`application_requests_history`.startTime,
`oauth_clients`.name AS applicationName
FROM `auditlog`
INNER JOIN `user`
ON `user`.`userId` = `auditlog`.`userId`
INNER JOIN `application_requests_history`
ON `application_requests_history`.`requestId` = `auditlog`.`requestId`
INNER JOIN `oauth_clients`
ON `oauth_clients`.id = `application_requests_history`.applicationId
WHERE `auditlog`.logDate BETWEEN :fromDt AND :toDt
';
if ($sanitizedParams->getInt('userId') !== null) {
$sql .= ' AND `auditlog`.`userId` = :userId';
$params['userId'] = $sanitizedParams->getInt('userId');
}
if ($sanitizedParams->getInt('requestId') !== null) {
$sql .= ' AND `auditlog`.`requestId` = :requestId';
$params['requestId'] = $sanitizedParams->getInt('requestId');
}
// Sorting?
$sortOrder = $this->gridRenderSort($sanitizedParams);
if (is_array($sortOrder)) {
$sql .= ' ORDER BY ' . implode(',', $sortOrder);
}
$rows = [];
foreach ($this->store->select($sql, $params) as $row) {
$auditRecord = $this->auditLogFactory->create()->hydrate($row);
$auditRecord->setUnmatchedProperty(
'applicationId',
$row['applicationId']
);
$auditRecord->setUnmatchedProperty(
'applicationName',
$row['applicationName']
);
$auditRecord->setUnmatchedProperty(
'url',
$row['url']
);
$auditRecord->setUnmatchedProperty(
'method',
$row['method']
);
// decode for grid view, leave as json for email/preview.
if (!$sanitizedParams->getCheckbox('scheduledReport')) {
$auditRecord->objectAfter = json_decode($auditRecord->objectAfter);
}
$auditRecord->logDate = Carbon::createFromTimestamp($auditRecord->logDate)
->format(DateFormatHelper::getSystemFormat());
$rows[] = $auditRecord;
}
return new ReportResult(
$metadata,
$rows,
count($rows),
);
} else if ($type === 'debug') {
$params = [
'fromDt' => $fromDt->format(DateFormatHelper::getSystemFormat()),
'toDt' => $toDt->format(DateFormatHelper::getSystemFormat()),
];
$sql = 'SELECT
`log`.`logId`,
`log`.`logDate`,
`log`.`runNo`,
`log`.`channel`,
`log`.`page`,
`log`.`function`,
`log`.`type`,
`log`.`message`,
`log`.`userId`,
`log`.`requestId`,
`user`.`userName`,
`application_requests_history`.applicationId,
`application_requests_history`.url,
`application_requests_history`.method,
`application_requests_history`.startTime,
`oauth_clients`.name AS applicationName
FROM `log`
INNER JOIN `user`
ON `user`.`userId` = `log`.`userId`
INNER JOIN `application_requests_history`
ON `application_requests_history`.`requestId` = `log`.`requestId`
INNER JOIN `oauth_clients`
ON `oauth_clients`.id = `application_requests_history`.applicationId
WHERE `log`.logDate BETWEEN :fromDt AND :toDt
';
if ($sanitizedParams->getInt('userId') !== null) {
$sql .= ' AND `log`.`userId` = :userId';
$params['userId'] = $sanitizedParams->getInt('userId');
}
if ($sanitizedParams->getInt('requestId') !== null) {
$sql .= ' AND `log`.`requestId` = :requestId';
$params['requestId'] = $sanitizedParams->getInt('requestId');
}
// Sorting?
$sortOrder = $this->gridRenderSort($sanitizedParams);
if (is_array($sortOrder)) {
$sql .= ' ORDER BY ' . implode(',', $sortOrder);
}
$rows = [];
foreach ($this->store->select($sql, $params) as $row) {
$logRecord = $this->logFactory->createEmpty()->hydrate($row, ['htmlStringProperties' => ['message']]);
$logRecord->setUnmatchedProperty(
'applicationId',
$row['applicationId']
);
$logRecord->setUnmatchedProperty(
'applicationName',
$row['applicationName']
);
$logRecord->setUnmatchedProperty(
'url',
$row['url']
);
$logRecord->setUnmatchedProperty(
'method',
$row['method']
);
$rows[] = $logRecord;
}
return new ReportResult(
$metadata,
$rows,
count($rows),
);
} else {
$params = [
'fromDt' => $fromDt->format(DateFormatHelper::getSystemFormat()),
'toDt' => $toDt->format(DateFormatHelper::getSystemFormat()),
];
$sql = 'SELECT
`application_requests_history`.applicationId,
`application_requests_history`.requestId,
`application_requests_history`.userId,
`application_requests_history`.url,
`application_requests_history`.method,
`application_requests_history`.startTime,
`oauth_clients`.name AS applicationName,
`user`.`userName`
FROM `application_requests_history`
INNER JOIN `user`
ON `user`.`userId` = `application_requests_history`.`userId`
INNER JOIN `oauth_clients`
ON `oauth_clients`.id = `application_requests_history`.applicationId
WHERE `application_requests_history`.startTime BETWEEN :fromDt AND :toDt
';
if ($sanitizedParams->getInt('userId') !== null) {
$sql .= ' AND `application_requests_history`.`userId` = :userId';
$params['userId'] = $sanitizedParams->getInt('userId');
}
// Sorting?
$sortOrder = $this->gridRenderSort($sanitizedParams);
if (is_array($sortOrder)) {
$sql .= ' ORDER BY ' . implode(',', $sortOrder);
}
$rows = [];
foreach ($this->store->select($sql, $params) as $row) {
$apiRequestRecord = $this->apiRequestsFactory->createEmpty()->hydrate($row);
$apiRequestRecord->setUnmatchedProperty(
'userName',
$row['userName']
);
$apiRequestRecord->setUnmatchedProperty(
'applicationName',
$row['applicationName']
);
$rows[] = $apiRequestRecord;
}
return new ReportResult(
$metadata,
$rows,
count($rows),
);
}
}
}

365
lib/Report/Bandwidth.php Normal file
View File

@@ -0,0 +1,365 @@
<?php
/*
* Copyright (C) 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\Helper\DateFormatHelper;
use Xibo\Support\Sanitizer\SanitizerInterface;
/**
* Class Bandwidth
* @package Xibo\Report
*/
class Bandwidth implements ReportInterface
{
use ReportDefaultTrait;
/**
* @var DisplayFactory
*/
private $displayFactory;
/** @inheritdoc */
public function setFactories(ContainerInterface $container)
{
$this->displayFactory = $container->get('displayFactory');
return $this;
}
/** @inheritdoc */
public function getReportChartScript($results)
{
return json_encode($results->chart);
}
/** @inheritdoc */
public function getReportEmailTemplate()
{
return 'bandwidth-email-template.twig';
}
/** @inheritdoc */
public function getSavedReportTemplate()
{
return 'bandwidth-report-preview';
}
/** @inheritdoc */
public function getReportForm()
{
return new ReportForm(
'bandwidth-report-form',
'bandwidth',
'Display',
[
'toDate' => Carbon::now()->format(DateFormatHelper::getSystemFormat()),
]
);
}
/** @inheritdoc */
public function getReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$data = [];
$data['reportName'] = 'bandwidth';
return [
'template' => 'bandwidth-schedule-form-add',
'data' => $data
];
}
/** @inheritdoc */
public function setReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$filter = $sanitizedParams->getString('filter');
$displayId = $sanitizedParams->getInt('displayId');
$filterCriteria['displayId'] = $displayId;
$filterCriteria['filter'] = $filter;
// Bandwidth report does not support weekly as bandwidth has monthly records in DB
$schedule = '';
if ($filter == 'daily') {
$schedule = ReportSchedule::$SCHEDULE_DAILY;
$filterCriteria['reportFilter'] = 'yesterday';
} elseif ($filter == 'monthly') {
$schedule = ReportSchedule::$SCHEDULE_MONTHLY;
$filterCriteria['reportFilter'] = 'lastmonth';
} elseif ($filter == 'yearly') {
$schedule = ReportSchedule::$SCHEDULE_YEARLY;
$filterCriteria['reportFilter'] = 'lastyear';
}
$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 bandwidth report', 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)
{
//
// From and To Date Selection
// --------------------------
// Our report has a range filter which determins whether or not the user has to enter their own from / to dates
// check the range filter first and set from/to dates accordingly.
$reportFilter = $sanitizedParams->getString('reportFilter');
// Use the current date as a helper
$now = Carbon::now();
// Bandwidth report does not support weekly as bandwidth has monthly records in DB
switch ($reportFilter) {
// Daily report if setup which has reportfilter = yesterday will be daily progression of bandwidth usage
// It always starts from the start of the month so we get the month usage
case 'yesterday':
$fromDt = $now->copy()->startOfDay()->subDay();
$fromDt->startOfMonth();
$toDt = $now->copy()->startOfDay();
break;
case 'lastmonth':
$fromDt = $now->copy()->startOfMonth()->subMonth();
$toDt = $fromDt->copy()->addMonth();
break;
case 'lastyear':
$fromDt = $now->copy()->startOfYear()->subYear();
$toDt = $fromDt->copy()->addYear();
break;
case '':
default:
// Expect dates to be provided.
$fromDt = $sanitizedParams->getDate('fromDt', ['default' => $sanitizedParams->getDate('bandwidthFromDt')]);
$fromDt->startOfMonth();
$toDt = $sanitizedParams->getDate('toDt', ['default' => $sanitizedParams->getDate('bandwidthToDt')]);
$toDt->addMonth();
break;
}
// Get an array of display id this user has access to.
$displayIds = $this->getDisplayIdFilter($sanitizedParams);
// Get some data for a bandwidth chart
$dbh = $this->store->getConnection();
$displayId = $sanitizedParams->getInt('displayId');
$params = [
'month' => $fromDt->copy()->format('U'),
'month2' => $toDt->copy()->format('U')
];
$SQL = 'SELECT display.display, IFNULL(SUM(Size), 0) AS size ';
if ($displayId != 0) {
$SQL .= ', bandwidthtype.name AS type ';
}
// For user with limited access, return only data for displays this user has permissions to.
$joinType = ($this->getUser()->isSuperAdmin()) ? 'LEFT OUTER JOIN' : 'INNER JOIN';
$SQL .= ' FROM `bandwidth` ' .
$joinType . ' `display`
ON display.displayid = bandwidth.displayid ';
// Displays
if (count($displayIds) > 0) {
$SQL .= ' AND display.displayId IN (' . implode(',', $displayIds) . ') ';
}
if ($displayId != 0) {
$SQL .= '
INNER JOIN bandwidthtype
ON bandwidthtype.bandwidthtypeid = bandwidth.type
';
}
$SQL .= ' WHERE month > :month
AND month < :month2 ';
if ($displayId != 0) {
$SQL .= ' AND display.displayid = :displayid ';
$params['displayid'] = $displayId;
}
$SQL .= 'GROUP BY display.displayId, display.display ';
if ($displayId != 0) {
$SQL .= ' , bandwidthtype.name ';
}
$SQL .= 'ORDER BY display.display';
$sth = $dbh->prepare($SQL);
$sth->execute($params);
// Get the results
$results = $sth->fetchAll();
$maxSize = 0;
foreach ($results as $library) {
$maxSize = ($library['size'] > $maxSize) ? $library['size'] : $maxSize;
}
// Decide what our units are going to be, based on the size
// We need to put a fallback value in case it returns an infinite value
$base = !is_infinite(floor(log($maxSize) / log(1024))) ? floor(log($maxSize) / log(1024)) : 0;
$labels = [];
$data = [];
$backgroundColor = [];
$rows = [];
// Set up some suffixes
$suffixes = array('bytes', 'k', 'M', 'G', 'T');
foreach ($results as $row) {
// label depends whether we are filtered by display
if ($displayId != 0) {
$label = $row['type'];
$labels[] = $label;
} else {
$label = $row['display'] === null ? __('Deleted Displays') : $row['display'];
$labels[] = $label;
}
$backgroundColor[] = ($row['display'] === null) ? 'rgb(255,0,0)' : 'rgb(11, 98, 164)';
$bandwidth = round((double)$row['size'] / (pow(1024, $base)), 2);
$data[] = $bandwidth;
// ----
// Build Tabular data
$entry = [];
$entry['label'] = $label;
$entry['bandwidth'] = $bandwidth;
$entry['unit'] = (isset($suffixes[$base]) ? $suffixes[$base] : '');
$rows[] = $entry;
}
//
// Output Results
// --------------
$chart = [
'type' => 'bar',
'data' => [
'labels' => $labels,
'datasets' => [
[
'label' => __('Bandwidth'),
'backgroundColor' => $backgroundColor,
'data' => $data
]
]
],
'options' => [
'scales' => [
'yAxes' => [
[
'scaleLabel' => [
'display' => true,
'labelString' => (isset($suffixes[$base]) ? $suffixes[$base] : '')
]
]
]
],
'legend' => [
'display' => false
],
'maintainAspectRatio' => true
]
];
$metadata = [
'periodStart' => $fromDt->format(DateFormatHelper::getSystemFormat()),
'periodEnd' => $toDt->format(DateFormatHelper::getSystemFormat()),
];
// Total records
$recordsTotal = count($rows);
// ----
// Chart Only
// Return data to build chart/table
// This will get saved to a json file when schedule runs
return new ReportResult(
$metadata,
$rows,
$recordsTotal,
$chart
);
}
}

View File

@@ -0,0 +1,385 @@
<?php
/*
* Copyright (C) 2022 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* 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 MongoDB\BSON\UTCDateTime;
use Psr\Container\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Xibo\Controller\DataTablesDotNetTrait;
use Xibo\Entity\ReportForm;
use Xibo\Entity\ReportResult;
use Xibo\Entity\ReportSchedule;
use Xibo\Event\ReportDataEvent;
use Xibo\Factory\CampaignFactory;
use Xibo\Factory\DisplayFactory;
use Xibo\Factory\LayoutFactory;
use Xibo\Factory\MediaFactory;
use Xibo\Factory\ReportScheduleFactory;
use Xibo\Helper\ApplicationState;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\SanitizerService;
use Xibo\Helper\Translate;
use Xibo\Support\Exception\GeneralException;
use Xibo\Support\Exception\NotFoundException;
use Xibo\Support\Sanitizer\SanitizerInterface;
/**
* Class CampaignProofOfPlay
* @package Xibo\Report
*/
class CampaignProofOfPlay implements ReportInterface
{
use ReportDefaultTrait, DataTablesDotNetTrait;
/**
* @var CampaignFactory
*/
private $campaignFactory;
/**
* @var DisplayFactory
*/
private $displayFactory;
/**
* @var LayoutFactory
*/
private $layoutFactory;
/**
* @var ReportScheduleFactory
*/
private $reportScheduleFactory;
/**
* @var SanitizerService
*/
private $sanitizer;
/**
* @var EventDispatcher
*/
private $dispatcher;
/**
* @var ApplicationState
*/
private $state;
/** @inheritdoc */
public function setFactories(ContainerInterface $container)
{
$this->campaignFactory = $container->get('campaignFactory');
$this->displayFactory = $container->get('displayFactory');
$this->reportScheduleFactory = $container->get('reportScheduleFactory');
$this->sanitizer = $container->get('sanitizerService');
$this->dispatcher = $container->get('dispatcher');
return $this;
}
/** @inheritdoc */
public function getReportEmailTemplate()
{
return 'campaign-proofofplay-email-template.twig';
}
/** @inheritdoc */
public function getSavedReportTemplate()
{
return 'campaign-proofofplay-report-preview';
}
/** @inheritdoc */
public function getReportForm()
{
return new ReportForm(
'campaign-proofofplay-report-form',
'campaignProofOfPlay',
'Connector Reports',
[
'fromDateOneDay' => Carbon::now()->subSeconds(86400)->format(DateFormatHelper::getSystemFormat()),
'toDate' => Carbon::now()->format(DateFormatHelper::getSystemFormat())
],
__('Select a display')
);
}
/** @inheritdoc */
public function getReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$data = [];
$data['hiddenFields'] = '';
$data['reportName'] = 'campaignProofOfPlay';
return [
'template' => 'campaign-proofofplay-schedule-form-add',
'data' => $data
];
}
/** @inheritdoc */
public function setReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$filter = $sanitizedParams->getString('filter');
$filterCriteria = [
'filter' => $filter,
'displayId' => $sanitizedParams->getInt('displayId'),
'displayIds' => $sanitizedParams->getIntArray('displayIds'),
];
$schedule = '';
if ($filter == 'daily') {
$schedule = ReportSchedule::$SCHEDULE_DAILY;
$filterCriteria['reportFilter'] = 'yesterday';
} elseif ($filter == 'weekly') {
$schedule = ReportSchedule::$SCHEDULE_WEEKLY;
$filterCriteria['reportFilter'] = 'lastweek';
} elseif ($filter == 'monthly') {
$schedule = ReportSchedule::$SCHEDULE_MONTHLY;
$filterCriteria['reportFilter'] = 'lastmonth';
} elseif ($filter == 'yearly') {
$schedule = ReportSchedule::$SCHEDULE_YEARLY;
$filterCriteria['reportFilter'] = 'lastyear';
}
$filterCriteria['sendEmail'] = $sanitizedParams->getCheckbox('sendEmail');
$filterCriteria['nonusers'] = $sanitizedParams->getString('nonusers');
// Return
return [
'filterCriteria' => json_encode($filterCriteria),
'schedule' => $schedule
];
}
/** @inheritdoc */
public function generateSavedReportName(SanitizerInterface $sanitizedParams)
{
$saveAs = sprintf(__('%s report for ', ucfirst($sanitizedParams->getString('filter'))));
$displayId = $sanitizedParams->getInt('displayId');
if (!empty($displayId)) {
// Get display
try {
$displayName = $this->displayFactory->getById($displayId)->display;
$saveAs .= '(Display: '. $displayName . ')';
} catch (NotFoundException $error) {
$saveAs .= '(DisplayId: Not Found )';
}
}
return $saveAs;
}
/** @inheritdoc */
public function restructureSavedReportOldJson($result)
{
return [
'periodStart' => $result['periodStart'],
'periodEnd' => $result['periodEnd'],
'table' => $result['result'],
];
}
/** @inheritdoc */
public function getSavedReportResults($json, $savedReport)
{
// Get filter criteria
$rs = $this->reportScheduleFactory->getById($savedReport->reportScheduleId, 1)->filterCriteria;
$filterCriteria = json_decode($rs, true);
// Show filter criteria
$metadata = [];
// Get Meta data
$metadata['periodStart'] = $json['metadata']['periodStart'];
$metadata['periodEnd'] = $json['metadata']['periodEnd'];
$metadata['generatedOn'] = Carbon::createFromTimestamp($savedReport->generatedOn)
->format(DateFormatHelper::getSystemFormat());
$metadata['title'] = $savedReport->saveAs;
// Report result object
return new ReportResult(
$metadata,
$json['table'],
$json['recordsTotal']
);
}
/** @inheritdoc */
public function getResults(SanitizerInterface $sanitizedParams)
{
$parentCampaignId = $sanitizedParams->getInt('parentCampaignId');
// Get campaign
if (!empty($parentCampaignId)) {
$campaign = $this->campaignFactory->getById($parentCampaignId);
}
// Display filter.
try {
// Get an array of display id this user has access to.
$displayIds = $this->getDisplayIdFilter($sanitizedParams);
} catch (GeneralException $exception) {
// stop the query
return new ReportResult();
}
//
// From and To Date Selection
// --------------------------
// Our report has a range filter which determines whether the user has to enter their own from / to dates
// check the range filter first and set from/to dates accordingly.
$reportFilter = $sanitizedParams->getString('reportFilter');
// Use the current date as a helper
$now = Carbon::now();
switch ($reportFilter) {
case 'today':
$fromDt = $now->copy()->startOfDay();
$toDt = $fromDt->copy()->addDay();
break;
case 'yesterday':
$fromDt = $now->copy()->startOfDay()->subDay();
$toDt = $now->copy()->startOfDay();
break;
case 'thisweek':
$fromDt = $now->copy()->locale(Translate::GetLocale())->startOfWeek();
$toDt = $fromDt->copy()->addWeek();
break;
case 'thismonth':
$fromDt = $now->copy()->startOfMonth();
$toDt = $fromDt->copy()->addMonth();
break;
case 'thisyear':
$fromDt = $now->copy()->startOfYear();
$toDt = $fromDt->copy()->addYear();
break;
case 'lastweek':
$fromDt = $now->copy()->locale(Translate::GetLocale())->startOfWeek()->subWeek();
$toDt = $fromDt->copy()->addWeek();
break;
case 'lastmonth':
$fromDt = $now->copy()->startOfMonth()->subMonth();
$toDt = $fromDt->copy()->addMonth();
break;
case 'lastyear':
$fromDt = $now->copy()->startOfYear()->subYear();
$toDt = $fromDt->copy()->addYear();
break;
case '':
default:
// Expect dates to be provided.
$fromDt = $sanitizedParams->getDate('statsFromDt', ['default' => Carbon::now()->subDay()]);
$fromDt->startOfDay();
$toDt = $sanitizedParams->getDate('statsToDt', ['default' => Carbon::now()]);
$toDt->endOfDay();
// What if the fromdt and todt are exactly the same?
// in this case assume an entire day from midnight on the fromdt to midnight on the todt (i.e. add a day to the todt)
if ($fromDt == $toDt) {
$toDt->addDay();
}
break;
}
$params = [
'campaignId' => $parentCampaignId,
'displayIds' => $displayIds,
'groupBy' => $sanitizedParams->getString('groupBy')
];
// when the reportfilter is wholecampaign take campaign start/end as form/to date
if (!empty($parentCampaignId) && $sanitizedParams->getString('reportFilter') === 'wholecampaign') {
$params['fromDt'] = !empty($campaign->getStartDt()) ? $campaign->getStartDt()->format('Y-m-d H:i:s') : null;
$params['toDt'] = !empty($campaign->getEndDt()) ? $campaign->getEndDt()->format('Y-m-d H:i:s') : null;
if (empty($campaign->getStartDt()) || empty($campaign->getEndDt())) {
return new ReportResult();
}
} else {
$params['fromDt'] = $fromDt->format('Y-m-d H:i:s');
$params['toDt'] = $toDt->format('Y-m-d H:i:s');
}
// --------
// ReportDataEvent
$event = new ReportDataEvent('campaignProofofplay');
// Set query params for audience proof of play report
$event->setParams($params);
// Dispatch the event - listened by Audience Report Connector
$this->dispatcher->dispatch($event, ReportDataEvent::$NAME);
$results = $event->getResults();
$result['periodStart'] = $params['fromDt'];
$result['periodEnd'] = $params['toDt'];
// Sanitize results??
$rows = [];
foreach ($results['json'] as $row) {
$entry = [];
$entry['labelDate'] = $row['labelDate'];
$entry['adPlays'] = $row['adPlays'];
$entry['adDuration'] = $row['adDuration'];
$entry['impressions'] = $row['impressions'];
$entry['spend'] = $row['spend'];
$rows[] = $entry;
}
// Set Meta data
$metadata = [
'periodStart' => $result['periodStart'],
'periodEnd' => $result['periodEnd'],
];
$recordsTotal = count($rows);
// ----
// Table Only
// Return data to build chart/table
// This will get saved to a json file when schedule runs
return new ReportResult(
$metadata,
$rows,
$recordsTotal,
[],
$results['error'] ?? null
);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* Copyright (C) 2021 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* 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;
/**
* Trait DefaultReportEmailTrait
* @package Xibo\Report
*/
trait DefaultReportEmailTrait
{
/** @inheritdoc */
public function getReportEmailTemplate()
{
return 'default-email-template.twig';
}
}

View File

@@ -0,0 +1,492 @@
<?php
/*
* Copyright (C) 2022 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* 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 MongoDB\BSON\UTCDateTime;
use Psr\Container\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Xibo\Controller\DataTablesDotNetTrait;
use Xibo\Entity\ReportForm;
use Xibo\Entity\ReportResult;
use Xibo\Entity\ReportSchedule;
use Xibo\Event\ReportDataEvent;
use Xibo\Factory\CampaignFactory;
use Xibo\Factory\DisplayFactory;
use Xibo\Factory\LayoutFactory;
use Xibo\Factory\ReportScheduleFactory;
use Xibo\Helper\ApplicationState;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\SanitizerService;
use Xibo\Helper\Translate;
use Xibo\Support\Exception\GeneralException;
use Xibo\Support\Exception\NotFoundException;
use Xibo\Support\Sanitizer\SanitizerInterface;
/**
* Class DisplayAdPlay
* @package Xibo\Report
*/
class DisplayAdPlay implements ReportInterface
{
use ReportDefaultTrait, DataTablesDotNetTrait;
/**
* @var CampaignFactory
*/
private $campaignFactory;
/**
* @var DisplayFactory
*/
private $displayFactory;
/**
* @var LayoutFactory
*/
private $layoutFactory;
/**
* @var ReportScheduleFactory
*/
private $reportScheduleFactory;
/**
* @var SanitizerService
*/
private $sanitizer;
/**
* @var EventDispatcher
*/
private $dispatcher;
/**
* @var ApplicationState
*/
private $state;
/** @inheritdoc */
public function setFactories(ContainerInterface $container)
{
$this->campaignFactory = $container->get('campaignFactory');
$this->displayFactory = $container->get('displayFactory');
$this->reportScheduleFactory = $container->get('reportScheduleFactory');
$this->sanitizer = $container->get('sanitizerService');
$this->dispatcher = $container->get('dispatcher');
return $this;
}
/** @inheritdoc */
public function getReportChartScript($results)
{
return json_encode($results->chart);
}
/** @inheritdoc */
public function getReportEmailTemplate()
{
return 'display-adplays-email-template.twig';
}
/** @inheritdoc */
public function getSavedReportTemplate()
{
return 'display-adplays-report-preview';
}
/** @inheritdoc */
public function getReportForm()
{
return new ReportForm(
'display-adplays-report-form',
'displayAdPlay',
'Connector Reports',
[
'fromDateOneDay' => Carbon::now()->subSeconds(86400)->format(DateFormatHelper::getSystemFormat()),
'toDate' => Carbon::now()->format(DateFormatHelper::getSystemFormat()),
],
__('Select a display')
);
}
/** @inheritdoc */
public function getReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$data = [];
$data['hiddenFields'] = '';
$data['reportName'] = 'displayAdPlay';
return [
'template' => 'display-adplays-schedule-form-add',
'data' => $data
];
}
/** @inheritdoc */
public function setReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$filter = $sanitizedParams->getString('filter');
$filterCriteria = [
'filter' => $filter,
'displayId' => $sanitizedParams->getInt('displayId'),
'displayIds' => $sanitizedParams->getIntArray('displayIds'),
];
$schedule = '';
if ($filter == 'daily') {
$schedule = ReportSchedule::$SCHEDULE_DAILY;
$filterCriteria['reportFilter'] = 'yesterday';
} elseif ($filter == 'weekly') {
$schedule = ReportSchedule::$SCHEDULE_WEEKLY;
$filterCriteria['reportFilter'] = 'lastweek';
} elseif ($filter == 'monthly') {
$schedule = ReportSchedule::$SCHEDULE_MONTHLY;
$filterCriteria['reportFilter'] = 'lastmonth';
$filterCriteria['groupByFilter'] = 'byweek';
} elseif ($filter == 'yearly') {
$schedule = ReportSchedule::$SCHEDULE_YEARLY;
$filterCriteria['reportFilter'] = 'lastyear';
$filterCriteria['groupByFilter'] = 'bymonth';
}
$filterCriteria['sendEmail'] = $sanitizedParams->getCheckbox('sendEmail');
$filterCriteria['nonusers'] = $sanitizedParams->getString('nonusers');
// Return
return [
'filterCriteria' => json_encode($filterCriteria),
'schedule' => $schedule
];
}
/** @inheritdoc */
public function generateSavedReportName(SanitizerInterface $sanitizedParams)
{
$saveAs = sprintf(__('%s report for ', ucfirst($sanitizedParams->getString('filter'))));
$displayId = $sanitizedParams->getInt('displayId');
if (!empty($displayId)) {
// Get display
try {
$displayName = $this->displayFactory->getById($displayId)->display;
$saveAs .= '(Display: '. $displayName . ')';
} catch (NotFoundException $error) {
$saveAs .= '(DisplayId: Not Found )';
}
}
return $saveAs;
}
/** @inheritdoc */
public function restructureSavedReportOldJson($result)
{
return [
'periodStart' => $result['periodStart'],
'periodEnd' => $result['periodEnd'],
'table' => $result['result'],
];
}
/** @inheritdoc */
public function getSavedReportResults($json, $savedReport)
{
// Get filter criteria
$rs = $this->reportScheduleFactory->getById($savedReport->reportScheduleId, 1)->filterCriteria;
$filterCriteria = json_decode($rs, true);
// Show filter criteria
$metadata = [];
// Get Meta data
$metadata['periodStart'] = $json['metadata']['periodStart'];
$metadata['periodEnd'] = $json['metadata']['periodEnd'];
$metadata['generatedOn'] = Carbon::createFromTimestamp($savedReport->generatedOn)
->format(DateFormatHelper::getSystemFormat());
$metadata['title'] = $savedReport->saveAs;
// Report result object
return new ReportResult(
$metadata,
$json['table'],
$json['recordsTotal'],
$json['chart']
);
}
/** @inheritDoc */
public function getResults(SanitizerInterface $sanitizedParams)
{
$layoutId = $sanitizedParams->getInt('layoutId');
$parentCampaignId = $sanitizedParams->getInt('parentCampaignId');
// Get campaign
if (!empty($parentCampaignId)) {
$campaign = $this->campaignFactory->getById($parentCampaignId);
}
// Display filter.
try {
// Get an array of display id this user has access to.
$displayIds = $this->getDisplayIdFilter($sanitizedParams);
} catch (GeneralException $exception) {
// stop the query
return new ReportResult();
}
//
// From and To Date Selection
// --------------------------
// Our report has a range filter which determines whether the user has to enter their own from / to dates
// check the range filter first and set from/to dates accordingly.
$reportFilter = $sanitizedParams->getString('reportFilter');
// Use the current date as a helper
$now = Carbon::now();
switch ($reportFilter) {
case 'today':
$fromDt = $now->copy()->startOfDay();
$toDt = $fromDt->copy()->addDay();
break;
case 'yesterday':
$fromDt = $now->copy()->startOfDay()->subDay();
$toDt = $now->copy()->startOfDay();
break;
case 'thisweek':
$fromDt = $now->copy()->locale(Translate::GetLocale())->startOfWeek();
$toDt = $fromDt->copy()->addWeek();
break;
case 'thismonth':
$fromDt = $now->copy()->startOfMonth();
$toDt = $fromDt->copy()->addMonth();
break;
case 'thisyear':
$fromDt = $now->copy()->startOfYear();
$toDt = $fromDt->copy()->addYear();
break;
case 'lastweek':
$fromDt = $now->copy()->locale(Translate::GetLocale())->startOfWeek()->subWeek();
$toDt = $fromDt->copy()->addWeek();
break;
case 'lastmonth':
$fromDt = $now->copy()->startOfMonth()->subMonth();
$toDt = $fromDt->copy()->addMonth();
break;
case 'lastyear':
$fromDt = $now->copy()->startOfYear()->subYear();
$toDt = $fromDt->copy()->addYear();
break;
case '':
default:
// Expect dates to be provided.
$fromDt = $sanitizedParams->getDate('fromDt', ['default' => Carbon::now()->subDay()]);
$fromDt->startOfDay();
$toDt = $sanitizedParams->getDate('toDt', ['default' => Carbon::now()]);
$toDt->endOfDay();
// What if the fromdt and todt are exactly the same?
// in this case assume an entire day from midnight on the fromdt to midnight on the todt (i.e. add a day to the todt)
if ($fromDt == $toDt) {
$toDt->addDay();
}
break;
}
$params = [
'campaignId' => $parentCampaignId,
'layoutId' => $layoutId,
'displayIds' => $displayIds,
'groupBy' => $sanitizedParams->getString('groupBy')
];
// when the reportfilter is wholecampaign take campaign start/end as form/to date
if (!empty($parentCampaignId) && $sanitizedParams->getString('reportFilter') === 'wholecampaign') {
$params['fromDt'] = !empty($campaign->getStartDt()) ? $campaign->getStartDt()->format('Y-m-d H:i:s') : null;
$params['toDt'] = !empty($campaign->getEndDt()) ? $campaign->getEndDt()->format('Y-m-d H:i:s') : null;
if (empty($campaign->getStartDt()) || empty($campaign->getEndDt())) {
return new ReportResult();
}
} else {
$params['fromDt'] = $fromDt->format('Y-m-d H:i:s');
$params['toDt'] = $toDt->format('Y-m-d H:i:s');
}
// --------
// ReportDataEvent
$event = new ReportDataEvent('displayAdPlay');
// Set query params for report
$event->setParams($params);
// Dispatch the event - listened by Audience Report Connector
$this->dispatcher->dispatch($event, ReportDataEvent::$NAME);
$results = $event->getResults();
$result['periodStart'] = $params['fromDt'];
$result['periodEnd'] = $params['toDt'];
$rows = [];
$labels = [];
$adPlaysData = [];
$impressionsData = [];
$spendData = [];
$backgroundColor = [];
foreach ($results['json'] as $row) {
// ----
// Build Chart data
$labels[] = $row['labelDate'];
$backgroundColor[] = 'rgb(34, 207, 207, 0.7)';
$adPlays = $row['adPlays'];
$adPlaysData[] = ($adPlays == '') ? 0 : $adPlays;
$impressions = $row['impressions'];
$impressionsData[] = ($impressions == '') ? 0 : $impressions;
$spend = $row['spend'];
$spendData[] = ($spend == '') ? 0 : $spend;
// ----
// Build Tabular data
$entry = [];
$entry['labelDate'] = $row['labelDate'];
$entry['adPlays'] = $row['adPlays'];
$entry['adDuration'] = $row['adDuration'];
$entry['impressions'] = $row['impressions'];
$entry['spend'] = $row['spend'];
$rows[] = $entry;
}
// Build Chart to pass in twig file chart.js
$chart = [
'type' => 'bar',
'data' => [
'labels' => $labels,
'datasets' => [
[
'label' => __('Total ad plays'),
'yAxisID' => 'AdPlay',
'backgroundColor' => $backgroundColor,
'data' => $adPlaysData
],
[
'label' => __('Total impressions'),
'yAxisID' => 'Impression',
'borderColor' => 'rgba(255,159,64,255)',
'type' => 'line',
'fill' => false,
'data' => $impressionsData
],
[
'label' => __('Total spend'),
'yAxisID' => 'Spend',
'borderColor' => 'rgba(255,99,132,255)',
'type' => 'line',
'fill' => false,
'data' => $spendData
]
]
],
'options' => [
'scales' => [
'yAxes' => [
[
'id' => 'AdPlay',
'type' => 'linear',
'position' => 'left',
'display' => true,
'scaleLabel' => [
'display' => true,
'labelString' => __('Ad Play(s)')
],
'ticks' => [
'beginAtZero' => true
]
], [
'id' => 'Impression',
'type' => 'linear',
'position' => 'right',
'display' => true,
'scaleLabel' => [
'display' => true,
'labelString' => __('Impression(s)')
],
'ticks' => [
'beginAtZero' => true
]
], [
'id' => 'Spend',
'type' => 'linear',
'position' => 'right',
'display' => true,
'scaleLabel' => [
'display' => true,
'labelString' => __('Spend')
],
'ticks' => [
'beginAtZero' => true
]
]
]
]
]
];
// Set Meta data
$metadata = [
'periodStart' => $result['periodStart'],
'periodEnd' => $result['periodEnd'],
];
$recordsTotal = count($rows);
// ----
// Table Only
// Return data to build chart/table
// This will get saved to a json file when schedule runs
return new ReportResult(
$metadata,
$rows,
$recordsTotal,
$chart,
$results['error'] ?? null
);
}
}

View File

@@ -0,0 +1,323 @@
<?php
/*
* Copyright (C) 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\Controller\DataTablesDotNetTrait;
use Xibo\Entity\ReportForm;
use Xibo\Entity\ReportResult;
use Xibo\Entity\ReportSchedule;
use Xibo\Factory\DisplayEventFactory;
use Xibo\Factory\DisplayFactory;
use Xibo\Factory\DisplayGroupFactory;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Translate;
use Xibo\Support\Sanitizer\SanitizerInterface;
/**
* Class DisplayAlerts
* @package Xibo\Report
*/
class DisplayAlerts implements ReportInterface
{
use ReportDefaultTrait, DataTablesDotNetTrait;
/** @var DisplayFactory */
private $displayFactory;
/** @var DisplayGroupFactory */
private $displayGroupFactory;
/** @var DisplayEventFactory */
private $displayEventFactory;
public function setFactories(ContainerInterface $container)
{
$this->displayFactory = $container->get('displayFactory');
$this->displayGroupFactory = $container->get('displayGroupFactory');
$this->displayEventFactory = $container->get('displayEventFactory');
return $this;
}
public function getReportEmailTemplate()
{
return 'displayalerts-email-template.twig';
}
public function getSavedReportTemplate()
{
return 'displayalerts-report-preview';
}
public function getReportForm()
{
return new ReportForm(
'displayalerts-report-form',
'displayalerts',
'Display',
[
'fromDate' => Carbon::now()->startOfMonth()->format(DateFormatHelper::getSystemFormat()),
'toDate' => Carbon::now()->format(DateFormatHelper::getSystemFormat()),
]
);
}
public function getReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$data = [];
$data['reportName'] = 'displayalerts';
return [
'template' => 'displayalerts-schedule-form-add',
'data' => $data
];
}
public function setReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$filter = $sanitizedParams->getString('filter');
$displayId = $sanitizedParams->getInt('displayId');
$displayGroupIds = $sanitizedParams->getIntArray('displayGroupId', ['default' => []]);
$filterCriteria['displayId'] = $displayId;
if (empty($displayId) && count($displayGroupIds) > 0) {
$filterCriteria['displayGroupId'] = $displayGroupIds;
}
$filterCriteria['filter'] = $filter;
$schedule = '';
if ($filter == 'daily') {
$schedule = ReportSchedule::$SCHEDULE_DAILY;
$filterCriteria['reportFilter'] = 'yesterday';
} elseif ($filter == 'weekly') {
$schedule = ReportSchedule::$SCHEDULE_WEEKLY;
$filterCriteria['reportFilter'] = 'lastweek';
} elseif ($filter == 'monthly') {
$schedule = ReportSchedule::$SCHEDULE_MONTHLY;
$filterCriteria['reportFilter'] = 'lastmonth';
} elseif ($filter == 'yearly') {
$schedule = ReportSchedule::$SCHEDULE_YEARLY;
$filterCriteria['reportFilter'] = 'lastyear';
}
$filterCriteria['sendEmail'] = $sanitizedParams->getCheckbox('sendEmail');
$filterCriteria['nonusers'] = $sanitizedParams->getString('nonusers');
// Return
return [
'filterCriteria' => json_encode($filterCriteria),
'schedule' => $schedule
];
}
public function generateSavedReportName(SanitizerInterface $sanitizedParams)
{
return sprintf(__('%s report for Display'), ucfirst($sanitizedParams->getString('filter')));
}
public function restructureSavedReportOldJson($json)
{
return $json;
}
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'],
);
}
public function getResults(SanitizerInterface $sanitizedParams)
{
$displayIds = $this->getDisplayIdFilter($sanitizedParams);
$onlyLoggedIn = $sanitizedParams->getCheckbox('onlyLoggedIn') == 1;
//
// 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');
$currentDate = Carbon::now()->startOfDay();
// If toDt is current date then make it current datetime
if ($toDt->format('Y-m-d') == $currentDate->format('Y-m-d')) {
$toDt = Carbon::now();
}
$metadata = [
'periodStart' => Carbon::createFromTimestamp($fromDt->toDateTime()->format('U'))
->format(DateFormatHelper::getSystemFormat()),
'periodEnd' => Carbon::createFromTimestamp($toDt->toDateTime()->format('U'))
->format(DateFormatHelper::getSystemFormat()),
];
$params = [
'start' => $fromDt->format('U'),
'end' => $toDt->format('U')
];
$sql = 'SELECT
`displayevent`.displayId,
`display`.display,
`displayevent`.start,
`displayevent`.end,
`displayevent`.eventTypeId,
`displayevent`.refId,
`displayevent`.detail
FROM `displayevent`
INNER JOIN `display` ON `display`.displayId = `displayevent`.displayId
INNER JOIN `lkdisplaydg` ON `display`.displayId = `lkdisplaydg`.displayId
INNER JOIN `displaygroup` ON `displaygroup`.displayGroupId = `lkdisplaydg`.displayGroupId
AND `displaygroup`.isDisplaySpecific = 1
WHERE `displayevent`.eventDate BETWEEN :start AND :end ';
$eventTypeIdFilter = $sanitizedParams->getString('eventType');
if ($eventTypeIdFilter != -1) {
$params['eventTypeId'] = $eventTypeIdFilter;
$sql .= 'AND `displayevent`.eventTypeId = :eventTypeId ';
}
if (count($displayIds) > 0) {
$sql .= 'AND `displayevent`.displayId IN (' . implode(',', $displayIds) . ')';
}
if ($onlyLoggedIn) {
$sql .= ' AND `display`.loggedIn = 1 ';
}
// Tags
if (!empty($sanitizedParams->getString('tags'))) {
$tagFilter = $sanitizedParams->getString('tags');
if (trim($tagFilter) === '--no-tag') {
$sql .= ' AND `displaygroup`.displaygroupId NOT IN (
SELECT `lktagdisplaygroup`.displaygroupId
FROM tag
INNER JOIN `lktagdisplaygroup`
ON `lktagdisplaygroup`.tagId = tag.tagId
)
';
} else {
$operator = $sanitizedParams->getCheckbox('exactTags') == 1 ? '=' : 'LIKE';
$logicalOperator = $sanitizedParams->getString('logicalOperator', ['default' => 'OR']);
$allTags = explode(',', $tagFilter);
$notTags = [];
$tags = [];
foreach ($allTags as $tag) {
if (str_starts_with($tag, '-')) {
$notTags[] = ltrim(($tag), '-');
} else {
$tags[] = $tag;
}
}
if (!empty($notTags)) {
$sql .= ' AND `displaygroup`.displaygroupId NOT IN (
SELECT `lktagdisplaygroup`.displaygroupId
FROM tag
INNER JOIN `lktagdisplaygroup`
ON `lktagdisplaygroup`.tagId = tag.tagId
';
$this->displayFactory->tagFilter(
$notTags,
'lktagdisplaygroup',
'lkTagDisplayGroupId',
'displayGroupId',
$logicalOperator,
$operator,
true,
$sql,
$params
);
}
if (!empty($tags)) {
$sql .= ' AND `displaygroup`.displaygroupId IN (
SELECT `lktagdisplaygroup`.displaygroupId
FROM tag
INNER JOIN `lktagdisplaygroup`
ON `lktagdisplaygroup`.tagId = tag.tagId
';
$this->displayFactory->tagFilter(
$tags,
'lktagdisplaygroup',
'lkTagDisplayGroupId',
'displayGroupId',
$logicalOperator,
$operator,
false,
$sql,
$params
);
}
}
}
// Sorting?
$sortOrder = $this->gridRenderSort($sanitizedParams);
if (is_array($sortOrder)) {
$sql .= 'ORDER BY ' . implode(',', $sortOrder);
}
$rows = [];
foreach ($this->store->select($sql, $params) as $row) {
$displayEvent = $this->displayEventFactory->createEmpty()->hydrate($row);
$displayEvent->setUnmatchedProperty(
'eventType',
$displayEvent->getEventNameFromId($displayEvent->eventTypeId)
);
$displayEvent->setUnmatchedProperty(
'display',
$row['display']
);
$rows[] = $displayEvent;
}
return new ReportResult(
$metadata,
$rows,
count($rows),
);
}
}

View File

@@ -0,0 +1,316 @@
<?php
/*
* Copyright (C) 2022 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* 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 MongoDB\BSON\UTCDateTime;
use Psr\Container\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Xibo\Controller\DataTablesDotNetTrait;
use Xibo\Entity\ReportForm;
use Xibo\Entity\ReportResult;
use Xibo\Entity\ReportSchedule;
use Xibo\Event\ReportDataEvent;
use Xibo\Factory\CampaignFactory;
use Xibo\Factory\DisplayFactory;
use Xibo\Factory\LayoutFactory;
use Xibo\Factory\ReportScheduleFactory;
use Xibo\Helper\ApplicationState;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\SanitizerService;
use Xibo\Helper\Translate;
use Xibo\Support\Exception\GeneralException;
use Xibo\Support\Exception\NotFoundException;
use Xibo\Support\Sanitizer\SanitizerInterface;
/**
* Class DisplayPercentage
* @package Xibo\Report
*/
class DisplayPercentage implements ReportInterface
{
use ReportDefaultTrait, DataTablesDotNetTrait;
/**
* @var CampaignFactory
*/
private $campaignFactory;
/**
* @var DisplayFactory
*/
private $displayFactory;
/**
* @var LayoutFactory
*/
private $layoutFactory;
/**
* @var ReportScheduleFactory
*/
private $reportScheduleFactory;
/**
* @var SanitizerService
*/
private $sanitizer;
/**
* @var EventDispatcher
*/
private $dispatcher;
/**
* @var ApplicationState
*/
private $state;
/** @inheritdoc */
public function setFactories(ContainerInterface $container)
{
$this->campaignFactory = $container->get('campaignFactory');
$this->displayFactory = $container->get('displayFactory');
$this->reportScheduleFactory = $container->get('reportScheduleFactory');
$this->sanitizer = $container->get('sanitizerService');
$this->dispatcher = $container->get('dispatcher');
return $this;
}
/** @inheritdoc */
public function getReportChartScript($results)
{
return json_encode($results->chart);
}
/** @inheritdoc */
public function getReportEmailTemplate()
{
return 'display-percentage-email-template.twig';
}
/** @inheritdoc */
public function getSavedReportTemplate()
{
return 'display-percentage-report-preview';
}
/** @inheritdoc */
public function getReportForm()
{
return new ReportForm(
'display-percentage-report-form',
'displayPercentage',
'Connector Reports',
[
'fromDateOneDay' => Carbon::now()->subSeconds(86400)->format(DateFormatHelper::getSystemFormat()),
'toDate' => Carbon::now()->format(DateFormatHelper::getSystemFormat()),
],
__('Select a campaign')
);
}
/** @inheritdoc */
public function getReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$data = [];
$data['hiddenFields'] = json_encode([
'parentCampaignId' => $sanitizedParams->getInt('parentCampaignId')
]);
$data['reportName'] = 'displayPercentage';
return [
'template' => 'display-percentage-schedule-form-add',
'data' => $data
];
}
/** @inheritdoc */
public function setReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$filter = $sanitizedParams->getString('filter');
$hiddenFields = json_decode($sanitizedParams->getString('hiddenFields'), true);
$filterCriteria = [
'filter' => $filter,
'parentCampaignId' => $hiddenFields['parentCampaignId']
];
$schedule = '';
if ($filter == 'daily') {
$schedule = ReportSchedule::$SCHEDULE_DAILY;
$filterCriteria['reportFilter'] = 'yesterday';
} elseif ($filter == 'weekly') {
$schedule = ReportSchedule::$SCHEDULE_WEEKLY;
$filterCriteria['reportFilter'] = 'lastweek';
} elseif ($filter == 'monthly') {
$schedule = ReportSchedule::$SCHEDULE_MONTHLY;
$filterCriteria['reportFilter'] = 'lastmonth';
$filterCriteria['groupByFilter'] = 'byweek';
} elseif ($filter == 'yearly') {
$schedule = ReportSchedule::$SCHEDULE_YEARLY;
$filterCriteria['reportFilter'] = 'lastyear';
$filterCriteria['groupByFilter'] = 'bymonth';
}
$filterCriteria['sendEmail'] = $sanitizedParams->getCheckbox('sendEmail');
$filterCriteria['nonusers'] = $sanitizedParams->getString('nonusers');
// Return
return [
'filterCriteria' => json_encode($filterCriteria),
'schedule' => $schedule
];
}
/** @inheritdoc */
public function generateSavedReportName(SanitizerInterface $sanitizedParams)
{
$saveAs = sprintf(__('%s report for ', ucfirst($sanitizedParams->getString('filter'))));
$parentCampaignId = $sanitizedParams->getInt('parentCampaignId');
if (!empty($parentCampaignId)) {
// Get display
try {
$parentCampaignName = $this->campaignFactory->getById($parentCampaignId)->campaign;
$saveAs .= '(Campaign: '. $parentCampaignName . ')';
} catch (NotFoundException $error) {
$saveAs .= '(Campaign: Not Found )';
}
}
return $saveAs;
}
/** @inheritdoc */
public function restructureSavedReportOldJson($result)
{
return [
'periodStart' => $result['periodStart'],
'periodEnd' => $result['periodEnd'],
'table' => $result['result'],
];
}
/** @inheritdoc */
public function getSavedReportResults($json, $savedReport)
{
// Get filter criteria
$rs = $this->reportScheduleFactory->getById($savedReport->reportScheduleId, 1)->filterCriteria;
$filterCriteria = json_decode($rs, true);
// Show filter criteria
$metadata = [];
// Get Meta data
$metadata['periodStart'] = $json['metadata']['periodStart'];
$metadata['periodEnd'] = $json['metadata']['periodEnd'];
$metadata['generatedOn'] = Carbon::createFromTimestamp($savedReport->generatedOn)
->format(DateFormatHelper::getSystemFormat());
$metadata['title'] = $savedReport->saveAs;
// Report result object
return new ReportResult(
$metadata,
$json['table'],
$json['recordsTotal'],
$json['chart']
);
}
/** @inheritDoc */
public function getResults(SanitizerInterface $sanitizedParams)
{
$params = [
'parentCampaignId' => $sanitizedParams->getInt('parentCampaignId')
];
// --------
// ReportDataEvent
$event = new ReportDataEvent('displayPercentage');
// Set query params for report
$event->setParams($params);
// Dispatch the event - listened by Audience Report Connector
$this->dispatcher->dispatch($event, ReportDataEvent::$NAME);
$results = $event->getResults();
// TODO
$result['periodStart'] = Carbon::now()->format('Y-m-d H:i:s');
$result['periodEnd'] = Carbon::now()->format('Y-m-d H:i:s');
$rows = [];
$displayCache = [];
foreach ($results['json'] as $row) {
// ----
// Build Chart data
// ----
// Build Tabular data
$entry = [];
// --------
// Get Display
try {
if (!array_key_exists($row['displayId'], $displayCache)) {
$display = $this->displayFactory->getById($row['displayId']);
$displayCache[$row['displayId']] = $display->display;
}
$entry['label'] = $displayCache[$row['displayId']] ?? '';
} catch (\Exception $e) {
$entry['label'] = __('Not found');
}
$entry['spendData'] = $row['spendData'];
$entry['playtimeDuration'] = $row['playtimeDuration'];
$entry['backgroundColor'] = '#'.substr(md5($row['displayId']), 0, 6);
$rows[] = $entry;
}
// Build Chart to pass in twig file chart.js
$chart = [];
// Set Meta data
$metadata = [
'periodStart' => $result['periodStart'],
'periodEnd' => $result['periodEnd'],
];
$recordsTotal = count($rows);
// ----
// Table Only
// Return data to build chart/table
// This will get saved to a json file when schedule runs
return new ReportResult(
$metadata,
$rows,
$recordsTotal,
$chart,
$results['error'] ?? null
);
}
}

File diff suppressed because it is too large Load Diff

636
lib/Report/LibraryUsage.php Normal file
View File

@@ -0,0 +1,636 @@
<?php
namespace Xibo\Report;
use Carbon\Carbon;
use Psr\Container\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Controller\DataTablesDotNetTrait;
use Xibo\Entity\ReportForm;
use Xibo\Entity\ReportResult;
use Xibo\Entity\ReportSchedule;
use Xibo\Factory\DisplayFactory;
use Xibo\Factory\DisplayGroupFactory;
use Xibo\Factory\LayoutFactory;
use Xibo\Factory\MediaFactory;
use Xibo\Factory\SavedReportFactory;
use Xibo\Factory\UserFactory;
use Xibo\Factory\UserGroupFactory;
use Xibo\Helper\ApplicationState;
use Xibo\Helper\ByteFormatter;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\SanitizerService;
use Xibo\Helper\Translate;
use Xibo\Service\ConfigServiceInterface;
use Xibo\Service\ReportServiceInterface;
use Xibo\Support\Sanitizer\SanitizerInterface;
/**
* Class LibraryUsage
* @package Xibo\Report
*/
class LibraryUsage implements ReportInterface
{
use ReportDefaultTrait, DataTablesDotNetTrait;
/**
* @var DisplayFactory
*/
private $displayFactory;
/**
* @var MediaFactory
*/
private $mediaFactory;
/**
* @var LayoutFactory
*/
private $layoutFactory;
/**
* @var SavedReportFactory
*/
private $savedReportFactory;
/**
* @var UserFactory
*/
private $userFactory;
/**
* @var UserGroupFactory
*/
private $userGroupFactory;
/**
* @var DisplayGroupFactory
*/
private $displayGroupFactory;
/**
* @var ReportServiceInterface
*/
private $reportService;
/**
* @var ConfigServiceInterface
*/
private $configService;
/**
* @var SanitizerService
*/
private $sanitizer;
/**
* @var ApplicationState
*/
private $state;
/**
* @var EventDispatcherInterface
*/
private $dispatcher;
/** @inheritdoc */
public function setFactories(ContainerInterface $container)
{
$this->mediaFactory = $container->get('mediaFactory');
$this->userFactory = $container->get('userFactory');
$this->userGroupFactory = $container->get('userGroupFactory');
$this->reportService = $container->get('reportService');
$this->configService = $container->get('configService');
$this->sanitizer = $container->get('sanitizerService');
$this->dispatcher = $container->get('dispatcher');
return $this;
}
public function getDispatcher()
{
return $this->dispatcher;
}
/** @inheritdoc */
public function getReportChartScript($results)
{
return json_encode($results->chart);
}
/** @inheritdoc */
public function getReportEmailTemplate()
{
return 'libraryusage-email-template.twig';
}
/** @inheritdoc */
public function getSavedReportTemplate()
{
return 'libraryusage-report-preview';
}
/** @inheritdoc */
public function getReportForm()
{
$data = [];
// Set up some suffixes
$suffixes = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB');
// Widget for the library usage pie chart
try {
if ($this->getUser()->libraryQuota != 0) {
$libraryLimit = $this->getUser()->libraryQuota * 1024;
} else {
$libraryLimit = $this->configService->getSetting('LIBRARY_SIZE_LIMIT_KB') * 1024;
}
// Library Size in Bytes
$params = [];
$sql = 'SELECT IFNULL(SUM(FileSize), 0) AS SumSize, type FROM `media` WHERE 1 = 1 ';
$this->mediaFactory->viewPermissionSql(
'Xibo\Entity\Media',
$sql,
$params,
'`media`.mediaId',
'`media`.userId',
[
'userCheckUserId' => $this->getUser()->userId
]
);
$sql .= ' GROUP BY type ';
$sth = $this->store->getConnection()->prepare($sql);
$sth->execute($params);
$results = $sth->fetchAll();
// add any dependencies fonts, player software etc to the results
$event = new \Xibo\Event\DependencyFileSizeEvent($results);
$this->getDispatcher()->dispatch($event, $event::$NAME);
$results = $event->getResults();
// Do we base the units on the maximum size or the library limit
$maxSize = 0;
if ($libraryLimit > 0) {
$maxSize = $libraryLimit;
} else {
// Find the maximum sized chunk of the items in the library
foreach ($results as $library) {
$maxSize = ($library['SumSize'] > $maxSize) ? $library['SumSize'] : $maxSize;
}
}
// Decide what our units are going to be, based on the size
$base = ($maxSize == 0) ? 0 : floor(log($maxSize) / log(1024));
$libraryUsage = [];
$libraryLabels = [];
$totalSize = 0;
foreach ($results as $library) {
$libraryUsage[] = round((double)$library['SumSize'] / (pow(1024, $base)), 2);
$libraryLabels[] = ucfirst($library['type']) . ' ' . $suffixes[$base];
$totalSize = $totalSize + $library['SumSize'];
}
// Do we need to add the library remaining?
if ($libraryLimit > 0) {
$remaining = round(($libraryLimit - $totalSize) / (pow(1024, $base)), 2);
$libraryUsage[] = $remaining;
$libraryLabels[] = __('Free') . ' ' . $suffixes[$base];
}
// What if we are empty?
if (count($results) == 0 && $libraryLimit <= 0) {
$libraryUsage[] = 0;
$libraryLabels[] = __('Empty');
}
$data['libraryLimitSet'] = ($libraryLimit > 0);
$data['libraryLimit'] = (round((double)$libraryLimit / (pow(1024, $base)), 2)) . ' ' . $suffixes[$base];
$data['librarySize'] = ByteFormatter::format($totalSize, 1);
$data['librarySuffix'] = $suffixes[$base];
$data['libraryWidgetLabels'] = json_encode($libraryLabels);
$data['libraryWidgetData'] = json_encode($libraryUsage);
} catch (\Exception $exception) {
$this->getLog()->error('Error rendering the library stats page widget');
}
// Note: getReportForm is only run by the web UI and therefore the logged-in users permissions are checked here
$data['users'] = $this->userFactory->query();
$data['groups'] = $this->userGroupFactory->query();
$data['availableReports'] = $this->reportService->listReports();
return new ReportForm(
'libraryusage-report-form',
'libraryusage',
'Library',
$data
);
}
/** @inheritdoc */
public function getReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$data = [];
$data['reportName'] = 'libraryusage';
// Note: getReportScheduleFormData is only run by the web UI and therefore the logged-in users permissions
// are checked here
$data['users'] = $this->userFactory->query();
$data['groups'] = $this->userGroupFactory->query();
return [
'template' => 'libraryusage-schedule-form-add',
'data' => $data
];
}
/** @inheritdoc */
public function setReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$filter = $sanitizedParams->getString('filter');
$userId = $sanitizedParams->getInt('userId');
$filterCriteria['userId'] = $userId;
$groupId = $sanitizedParams->getInt('groupId');
$filterCriteria['groupId'] = $groupId;
$filterCriteria['filter'] = $filter;
$schedule = '';
if ($filter == 'daily') {
$schedule = ReportSchedule::$SCHEDULE_DAILY;
$filterCriteria['reportFilter'] = 'yesterday';
} elseif ($filter == 'weekly') {
$schedule = ReportSchedule::$SCHEDULE_WEEKLY;
$filterCriteria['reportFilter'] = 'lastweek';
} elseif ($filter == 'monthly') {
$schedule = ReportSchedule::$SCHEDULE_MONTHLY;
$filterCriteria['reportFilter'] = 'lastmonth';
} elseif ($filter == 'yearly') {
$schedule = ReportSchedule::$SCHEDULE_YEARLY;
$filterCriteria['reportFilter'] = 'lastyear';
}
$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 library usage report', 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)
{
$filter = [
'userId' => $sanitizedParams->getInt('userId'),
'groupId' => $sanitizedParams->getInt('groupId'),
'start' => $sanitizedParams->getInt('start'),
'length' => $sanitizedParams->getInt('length'),
];
//
// From and To Date Selection
// --------------------------
// Our report has a range filter which determins whether or not the user has to enter their own from / to dates
// check the range filter first and set from/to dates accordingly.
$reportFilter = $sanitizedParams->getString('reportFilter');
// Use the current date as a helper
$now = Carbon::now();
switch ($reportFilter) {
// the monthly data starts from yesterday
case 'yesterday':
$fromDt = $now->copy()->startOfDay()->subDay();
$toDt = $now->copy()->startOfDay();
break;
case 'lastweek':
$fromDt = $now->copy()->locale(Translate::GetLocale())->startOfWeek()->subWeek();
$toDt = $fromDt->copy()->addWeek();
break;
case 'lastmonth':
$fromDt = $now->copy()->startOfMonth()->subMonth();
$toDt = $fromDt->copy()->addMonth();
break;
case 'lastyear':
$fromDt = $now->copy()->startOfYear()->subYear();
$toDt = $fromDt->copy()->addYear();
break;
case '':
default:
// Expect dates to be provided.
$fromDt= $now;
$toDt = $now;
break;
}
$params = [];
$select = '
SELECT `user`.userId,
`user`.userName,
IFNULL(SUM(`media`.FileSize), 0) AS bytesUsed,
COUNT(`media`.mediaId) AS numFiles
';
$body = '
FROM `user`
LEFT OUTER JOIN `media`
ON `media`.userID = `user`.UserID
WHERE 1 = 1
';
// Restrict on the users we have permission to see
// Normal users can only see themselves
$permissions = '';
if ($this->getUser()->userTypeId == 3) {
$permissions .= ' AND user.userId = :currentUserId ';
$filterBy['currentUserId'] = $this->getUser()->userId;
} elseif ($this->getUser()->userTypeId == 2) {
// Group admins can only see users from their groups.
$permissions .= '
AND user.userId IN (
SELECT `otherUserLinks`.userId
FROM `lkusergroup`
INNER JOIN `group`
ON `group`.groupId = `lkusergroup`.groupId
AND `group`.isUserSpecific = 0
INNER JOIN `lkusergroup` `otherUserLinks`
ON `otherUserLinks`.groupId = `group`.groupId
WHERE `lkusergroup`.userId = :currentUserId
)
';
$params['currentUserId'] = $this->getUser()->userId;
}
// Filter by userId
if ($sanitizedParams->getInt('userId') !== null) {
$body .= ' AND user.userId = :userId ';
$params['userId'] = $sanitizedParams->getInt('userId');
}
// Filter by groupId
if ($sanitizedParams->getInt('groupId') !== null) {
$body .= ' AND user.userId IN (SELECT userId FROM `lkusergroup` WHERE groupId = :groupId) ';
$params['groupId'] = $sanitizedParams->getInt('groupId');
}
$body .= $permissions;
$body .= '
GROUP BY `user`.userId,
`user`.userName
';
// Sorting?
$filterBy = $this->gridRenderFilter($filter);
$sortOrder = $this->gridRenderSort($sanitizedParams);
$order = '';
if (is_array($sortOrder)) {
$newSortOrder = [];
foreach ($sortOrder as $sort) {
if ($sort == '`bytesUsedFormatted`') {
$newSortOrder[] = '`bytesUsed`';
continue;
}
if ($sort == '`bytesUsedFormatted` DESC') {
$newSortOrder[] = '`bytesUsed` DESC';
continue;
}
$newSortOrder[] = $sort;
}
$sortOrder = $newSortOrder;
$order .= 'ORDER BY ' . implode(',', $sortOrder);
}
$limit = '';
// Paging
if ($filterBy !== null
&& $sanitizedParams->getInt('start') !== null
&& $sanitizedParams->getInt('length') !== null
) {
$limit = ' LIMIT ' . $sanitizedParams->getInt('start', ['default' => 0])
. ', ' . $sanitizedParams->getInt('length', ['default' => 10]);
}
$sql = $select . $body . $order . $limit;
$rows = [];
foreach ($this->store->select($sql, $params) as $row) {
$entry = [];
$sanitizedRow = $this->sanitizer->getSanitizer($row);
$entry['userId'] = $sanitizedRow->getInt('userId');
$entry['userName'] = $sanitizedRow->getString('userName');
$entry['bytesUsed'] = $sanitizedRow->getInt('bytesUsed');
$entry['bytesUsedFormatted'] = ByteFormatter::format($sanitizedRow->getInt('bytesUsed'), 2);
$entry['numFiles'] = $sanitizedRow->getInt('numFiles');
$rows[] = $entry;
}
// Paging
$recordsTotal = 0;
if ($limit != '' && count($rows) > 0) {
$results = $this->store->select('SELECT COUNT(*) AS total FROM `user` ' . $permissions, $params);
$recordsTotal = intval($results[0]['total']);
}
// Get the Library widget labels and Widget Data
$libraryWidgetLabels = [];
$libraryWidgetData = [];
$suffixes = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
// Widget for the library usage pie chart
try {
if ($this->getUser()->libraryQuota != 0) {
$libraryLimit = $this->userFactory->getUser()->libraryQuota * 1024;
} else {
$libraryLimit = $this->configService->getSetting('LIBRARY_SIZE_LIMIT_KB') * 1024;
}
// Library Size in Bytes
$params = [];
$sql = 'SELECT IFNULL(SUM(FileSize), 0) AS SumSize, type FROM `media` WHERE 1 = 1 ';
$this->mediaFactory->viewPermissionSql(
'Xibo\Entity\Media',
$sql,
$params,
'`media`.mediaId',
'`media`.userId',
[
'userCheckUserId' => $this->getUser()->userId
]
);
$sql .= ' GROUP BY type ';
$sth = $this->store->getConnection()->prepare($sql);
$sth->execute($params);
$results = $sth->fetchAll();
// Do we base the units on the maximum size or the library limit
$maxSize = 0;
if ($libraryLimit > 0) {
$maxSize = $libraryLimit;
} else {
// Find the maximum sized chunk of the items in the library
foreach ($results as $library) {
$maxSize = ($library['SumSize'] > $maxSize) ? $library['SumSize'] : $maxSize;
}
}
// Decide what our units are going to be, based on the size
$base = ($maxSize == 0) ? 0 : floor(log($maxSize) / log(1024));
$libraryUsage = [];
$libraryLabels = [];
$totalSize = 0;
foreach ($results as $library) {
$libraryUsage[] = round((double)$library['SumSize'] / (pow(1024, $base)), 2);
$libraryLabels[] = ucfirst($library['type']) . ' ' . $suffixes[$base];
$totalSize = $totalSize + $library['SumSize'];
}
// Do we need to add the library remaining?
if ($libraryLimit > 0) {
$remaining = round(($libraryLimit - $totalSize) / (pow(1024, $base)), 2);
$libraryUsage[] = $remaining;
$libraryLabels[] = __('Free') . ' ' . $suffixes[$base];
}
// What if we are empty?
if (count($results) == 0 && $libraryLimit <= 0) {
$libraryUsage[] = 0;
$libraryLabels[] = __('Empty');
}
$libraryWidgetLabels = $libraryLabels;
$libraryWidgetData = $libraryUsage;
} catch (\Exception $exception) {
$this->getLog()->error('Error rendering the library stats page widget');
}
// Build the Library Usage and User Percentage Usage chart data
$totalSize = 0;
foreach ($rows as $row) {
$totalSize += $row['bytesUsed'];
}
$userData = [];
$userLabels = [];
foreach ($rows as $row) {
$userData[] = ($row['bytesUsed']/$totalSize)*100;
$userLabels[] = $row['userName'];
}
$colours = [];
foreach ($userData as $userDatum) {
$colours[] = 'rgb(' . mt_rand(0, 255).','. mt_rand(0, 255).',' . mt_rand(0, 255) .')';
}
$libraryColours = [];
foreach ($libraryWidgetData as $libraryDatum) {
$libraryColours[] = 'rgb(' . mt_rand(0, 255).','. mt_rand(0, 255).',' . mt_rand(0, 255) .')';
}
$chart = [
// we will use User_Percentage_Usage as report name when we export/email pdf
'User_Percentage_Usage' => [
'type' => 'pie',
'data' => [
'labels' => $userLabels,
'datasets' => [
[
'backgroundColor' => $colours,
'data' => $userData
]
]
],
'options' => [
'maintainAspectRatio' => false
]
],
'Library_Usage' => [
'type' => 'pie',
'data' => [
'labels' => $libraryWidgetLabels,
'datasets' => [
[
'backgroundColor' => $libraryColours,
'data' => $libraryWidgetData
]
]
],
'options' => [
'maintainAspectRatio' => false
]
]
];
// ----
// Both Chart and Table
// Return data to build chart/table
// This will get saved to a json file when schedule runs
return new ReportResult(
[
'periodStart' => $fromDt->format(DateFormatHelper::getSystemFormat()),
'periodEnd' => $toDt->format(DateFormatHelper::getSystemFormat()),
],
$rows,
$recordsTotal,
$chart
);
}
}

View File

@@ -0,0 +1,418 @@
<?php
/*
* Copyright (C) 2022 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* 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 Symfony\Component\EventDispatcher\EventDispatcher;
use Xibo\Controller\DataTablesDotNetTrait;
use Xibo\Entity\ReportForm;
use Xibo\Entity\ReportResult;
use Xibo\Entity\ReportSchedule;
use Xibo\Event\ReportDataEvent;
use Xibo\Factory\CampaignFactory;
use Xibo\Factory\DisplayFactory;
use Xibo\Factory\LayoutFactory;
use Xibo\Factory\ReportScheduleFactory;
use Xibo\Helper\ApplicationState;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\SanitizerService;
use Xibo\Helper\Translate;
use Xibo\Support\Exception\GeneralException;
use Xibo\Support\Exception\NotFoundException;
use Xibo\Support\Sanitizer\SanitizerInterface;
/**
* Class MobileProofOfPlay
* @package Xibo\Report
*/
class MobileProofOfPlay implements ReportInterface
{
use ReportDefaultTrait, DataTablesDotNetTrait;
/**
* @var CampaignFactory
*/
private $campaignFactory;
/**
* @var DisplayFactory
*/
private $displayFactory;
/**
* @var LayoutFactory
*/
private $layoutFactory;
/**
* @var ReportScheduleFactory
*/
private $reportScheduleFactory;
/**
* @var SanitizerService
*/
private $sanitizer;
/**
* @var EventDispatcher
*/
private $dispatcher;
/**
* @var ApplicationState
*/
private $state;
/** @inheritdoc */
public function setFactories(ContainerInterface $container)
{
$this->campaignFactory = $container->get('campaignFactory');
$this->displayFactory = $container->get('displayFactory');
$this->layoutFactory = $container->get('layoutFactory');
$this->reportScheduleFactory = $container->get('reportScheduleFactory');
$this->sanitizer = $container->get('sanitizerService');
$this->dispatcher = $container->get('dispatcher');
return $this;
}
/** @inheritdoc */
public function getReportEmailTemplate()
{
return 'mobile-proofofplay-email-template.twig';
}
/** @inheritdoc */
public function getSavedReportTemplate()
{
return 'mobile-proofofplay-report-preview';
}
/** @inheritdoc */
public function getReportForm()
{
return new ReportForm(
'mobile-proofofplay-report-form',
'mobileProofOfPlay',
'Connector Reports',
[
'fromDateOneDay' => Carbon::now()->subSeconds(86400)->format(DateFormatHelper::getSystemFormat()),
'toDate' => Carbon::now()->format(DateFormatHelper::getSystemFormat())
],
__('Select a display')
);
}
/** @inheritdoc */
public function getReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$data = [];
$data['hiddenFields'] = '';
$data['reportName'] = 'mobileProofOfPlay';
return [
'template' => 'mobile-proofofplay-schedule-form-add',
'data' => $data
];
}
/** @inheritdoc */
public function setReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$filter = $sanitizedParams->getString('filter');
$filterCriteria = [
'filter' => $filter,
'displayId' => $sanitizedParams->getInt('displayId'),
'displayIds' => $sanitizedParams->getIntArray('displayIds'),
];
$schedule = '';
if ($filter == 'daily') {
$schedule = ReportSchedule::$SCHEDULE_DAILY;
$filterCriteria['reportFilter'] = 'yesterday';
} elseif ($filter == 'weekly') {
$schedule = ReportSchedule::$SCHEDULE_WEEKLY;
$filterCriteria['reportFilter'] = 'lastweek';
} elseif ($filter == 'monthly') {
$schedule = ReportSchedule::$SCHEDULE_MONTHLY;
$filterCriteria['reportFilter'] = 'lastmonth';
} elseif ($filter == 'yearly') {
$schedule = ReportSchedule::$SCHEDULE_YEARLY;
$filterCriteria['reportFilter'] = 'lastyear';
}
$filterCriteria['sendEmail'] = $sanitizedParams->getCheckbox('sendEmail');
$filterCriteria['nonusers'] = $sanitizedParams->getString('nonusers');
// Return
return [
'filterCriteria' => json_encode($filterCriteria),
'schedule' => $schedule
];
}
/** @inheritdoc */
public function generateSavedReportName(SanitizerInterface $sanitizedParams)
{
$saveAs = sprintf(__('%s report for ', ucfirst($sanitizedParams->getString('filter'))));
$displayId = $sanitizedParams->getInt('displayId');
if (!empty($displayId)) {
// Get display
try {
$displayName = $this->displayFactory->getById($displayId)->display;
$saveAs .= '(Display: '. $displayName . ')';
} catch (NotFoundException $error) {
$saveAs .= '(DisplayId: Not Found )';
}
}
return $saveAs;
}
/** @inheritdoc */
public function restructureSavedReportOldJson($result)
{
return [
'periodStart' => $result['periodStart'],
'periodEnd' => $result['periodEnd'],
'table' => $result['result'],
];
}
/** @inheritdoc */
public function getSavedReportResults($json, $savedReport)
{
// Get filter criteria
$rs = $this->reportScheduleFactory->getById($savedReport->reportScheduleId, 1)->filterCriteria;
$filterCriteria = json_decode($rs, true);
// Show filter criteria
$metadata = [];
// Get Meta data
$metadata['periodStart'] = $json['metadata']['periodStart'];
$metadata['periodEnd'] = $json['metadata']['periodEnd'];
$metadata['generatedOn'] = Carbon::createFromTimestamp($savedReport->generatedOn)
->format(DateFormatHelper::getSystemFormat());
$metadata['title'] = $savedReport->saveAs;
// Report result object
return new ReportResult(
$metadata,
$json['table'],
$json['recordsTotal']
);
}
/** @inheritdoc */
public function getResults(SanitizerInterface $sanitizedParams)
{
$parentCampaignId = $sanitizedParams->getInt('parentCampaignId');
$layoutId = $sanitizedParams->getInt('layoutId');
// Get campaign
if (!empty($parentCampaignId)) {
$campaign = $this->campaignFactory->getById($parentCampaignId);
}
// Display filter.
try {
// Get an array of display id this user has access to.
$displayIds = $this->getDisplayIdFilter($sanitizedParams);
} catch (GeneralException $exception) {
// stop the query
return new ReportResult();
}
//
// From and To Date Selection
// --------------------------
// Our report has a range filter which determines whether the user has to enter their own from / to dates
// check the range filter first and set from/to dates accordingly.
$reportFilter = $sanitizedParams->getString('reportFilter');
// Use the current date as a helper
$now = Carbon::now();
switch ($reportFilter) {
case 'today':
$fromDt = $now->copy()->startOfDay();
$toDt = $fromDt->copy()->addDay();
break;
case 'yesterday':
$fromDt = $now->copy()->startOfDay()->subDay();
$toDt = $now->copy()->startOfDay();
break;
case 'thisweek':
$fromDt = $now->copy()->locale(Translate::GetLocale())->startOfWeek();
$toDt = $fromDt->copy()->addWeek();
break;
case 'thismonth':
$fromDt = $now->copy()->startOfMonth();
$toDt = $fromDt->copy()->addMonth();
break;
case 'thisyear':
$fromDt = $now->copy()->startOfYear();
$toDt = $fromDt->copy()->addYear();
break;
case 'lastweek':
$fromDt = $now->copy()->locale(Translate::GetLocale())->startOfWeek()->subWeek();
$toDt = $fromDt->copy()->addWeek();
break;
case 'lastmonth':
$fromDt = $now->copy()->startOfMonth()->subMonth();
$toDt = $fromDt->copy()->addMonth();
break;
case 'lastyear':
$fromDt = $now->copy()->startOfYear()->subYear();
$toDt = $fromDt->copy()->addYear();
break;
case '':
default:
// Expect dates to be provided.
$fromDt = $sanitizedParams->getDate('fromDt', ['default' => Carbon::now()->subDay()]);
$fromDt->startOfDay();
$toDt = $sanitizedParams->getDate('toDt', ['default' => Carbon::now()]);
$toDt->endOfDay();
// What if the fromdt and todt are exactly the same?
// in this case assume an entire day from midnight on the fromdt to midnight on the todt (i.e. add a day to the todt)
if ($fromDt == $toDt) {
$toDt->addDay();
}
break;
}
$params = [
'campaignId' => $parentCampaignId,
'layoutId' => $layoutId,
'displayIds' => $displayIds,
];
// when the reportfilter is wholecampaign take campaign start/end as form/to date
if (!empty($parentCampaignId) && $sanitizedParams->getString('reportFilter') === 'wholecampaign') {
$params['from'] = !empty($campaign->getStartDt()) ? $campaign->getStartDt()->format('Y-m-d H:i:s') : null;
$params['to'] = !empty($campaign->getEndDt()) ? $campaign->getEndDt()->format('Y-m-d H:i:s') : null;
if (empty($campaign->getStartDt()) || empty($campaign->getEndDt())) {
return new ReportResult();
}
} else {
$params['from'] = $fromDt->format('Y-m-d H:i:s');
$params['to'] = $toDt->format('Y-m-d H:i:s');
}
// --------
// ReportDataEvent
$event = new ReportDataEvent('mobileProofofplay');
// Set query params for report
$event->setParams($params);
// Dispatch the event - listened by Audience Report Connector
$this->dispatcher->dispatch($event, ReportDataEvent::$NAME);
$results = $event->getResults();
$result['periodStart'] = $params['from'];
$result['periodEnd'] = $params['to'];
$rows = [];
$displayCache = [];
$layoutCache = [];
foreach ($results['json'] as $row) {
$entry = [];
$entry['from'] = $row['from'];
$entry['to'] = $row['to'];
// --------
// Get Display
$entry['displayId'] = $row['displayId'];
try {
if (!empty($entry['displayId'])) {
if (!array_key_exists($row['displayId'], $displayCache)) {
$display = $this->displayFactory->getById($row['displayId']);
$displayCache[$row['displayId']] = $display->display;
}
}
$entry['display'] = $displayCache[$row['displayId']] ?? '';
} catch (\Exception $e) {
$entry['display'] = __('Not found');
}
// --------
// Get layout
$entry['layoutId'] = $row['layoutId'];
try {
if (!empty($entry['layoutId'])) {
if (!array_key_exists($row['layoutId'], $layoutCache)) {
$layout = $this->layoutFactory->getById($row['layoutId']);
$layoutCache[$row['layoutId']] = $layout->layout;
}
}
$entry['layout'] = $layoutCache[$row['layoutId']] ?? '';
} catch (\Exception $e) {
$entry['layout'] = __('Not found');
}
$entry['startLat'] = $row['startLat'];
$entry['startLong'] = $row['startLong'];
$entry['endLat'] = $row['endLat'];
$entry['endLong'] = $row['endLong'];
$entry['duration'] = $row['duration'];
$rows[] = $entry;
}
// Set Meta data
$metadata = [
'periodStart' => $result['periodStart'],
'periodEnd' => $result['periodEnd'],
];
$recordsTotal = count($rows);
// ----
// Table Only
// Return data to build chart/table
// This will get saved to a json file when schedule runs
return new ReportResult(
$metadata,
$rows,
$recordsTotal,
[],
$results['error'] ?? null
);
}
}

103
lib/Report/PeriodTrait.php Normal file
View File

@@ -0,0 +1,103 @@
<?php
/*
* Copyright (C) 2021 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* 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;
/**
* Trait PeriodTrait
* @package Xibo\Report
*/
trait PeriodTrait
{
public function generateHourPeriods($filterRangeStart, $filterRangeEnd, $start, $end, $ranges)
{
$periodData = []; // to generate periods table
// Generate all hours of the period
foreach ($ranges as $range) {
$startHour = $start->addHour()->format('U');
// Remove the period which crossed the end range
if ($startHour >= $filterRangeEnd) {
continue;
}
// Period start
$periodData[$range]['start'] = $startHour;
if ($periodData[$range]['start'] < $filterRangeStart) {
$periodData[$range]['start'] = $filterRangeStart;
}
// Period end
$periodData[$range]['end'] = $end->addHour()->format('U');
if ($periodData[$range]['end'] > $filterRangeEnd) {
$periodData[$range]['end'] = $filterRangeEnd;
}
$hourofday = Carbon::createFromTimestamp($periodData[$range]['start'])->hour;
// groupbycol = hour
$periodData[$range]['groupbycol'] = $hourofday;
}
return $periodData;
}
public function generateDayPeriods($filterRangeStart, $filterRangeEnd, $start, $end, $ranges, $groupByFilter = null)
{
$periodData = []; // to generate periods table
// Generate all days of the period
foreach ($ranges as $range) {
$startDay = $start->addDay()->format('U');
// Remove the period which crossed the end range
if ($startDay >= $filterRangeEnd) {
continue;
}
// Period start
$periodData[$range]['start'] = $startDay;
if ($periodData[$range]['start'] < $filterRangeStart) {
$periodData[$range]['start'] = $filterRangeStart;
}
// Period end
$periodData[$range]['end'] = $end->addDay()->format('U');
if ($periodData[$range]['end'] > $filterRangeEnd) {
$periodData[$range]['end'] = $filterRangeEnd;
}
if ($groupByFilter == 'bydayofweek') {
$groupbycol = Carbon::createFromTimestamp($periodData[$range]['start'])->dayOfWeekIso;
} else {
$groupbycol = Carbon::createFromTimestamp($periodData[$range]['start'])->day;
}
// groupbycol = dayofweek
$periodData[$range]['groupbycol'] = $groupbycol;
}
return $periodData;
}
}

1368
lib/Report/ProofOfPlay.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,334 @@
<?php
/*
* Copyright (C) 2022 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* 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 http\Exception\RuntimeException;
use Psr\Log\NullLogger;
use Slim\Http\ServerRequest as Request;
use Xibo\Entity\ReportResult;
use Xibo\Helper\Translate;
use Xibo\Service\LogServiceInterface;
use Xibo\Storage\StorageServiceInterface;
use Xibo\Storage\TimeSeriesStoreInterface;
use Xibo\Support\Exception\InvalidArgumentException;
use Xibo\Support\Sanitizer\SanitizerInterface;
/**
* Trait ReportDefaultTrait
* @package Xibo\Report
*/
trait ReportDefaultTrait
{
/**
* @var StorageServiceInterface
*/
private $store;
/**
* @var TimeSeriesStoreInterface
*/
private $timeSeriesStore;
/**
* @var LogServiceInterface
*/
private $logService;
/**
* @var Request
*/
private $request;
/** @var \Xibo\Entity\User */
private $user;
/**
* Set common dependencies.
* @param StorageServiceInterface $store
* @param TimeSeriesStoreInterface $timeSeriesStore
* @return $this
*/
public function setCommonDependencies($store, $timeSeriesStore)
{
$this->store = $store;
$this->timeSeriesStore = $timeSeriesStore;
$this->logService = new NullLogger();
return $this;
}
/**
* @param LogServiceInterface $logService
* @return $this
*/
public function useLogger(LogServiceInterface $logService)
{
$this->logService = $logService;
return $this;
}
/**
* Get Store
* @return StorageServiceInterface
*/
protected function getStore()
{
return $this->store;
}
/**
* Get TimeSeriesStore
* @return TimeSeriesStoreInterface
*/
protected function getTimeSeriesStore()
{
return $this->timeSeriesStore;
}
/**
* Get Log
* @return LogServiceInterface
*/
protected function getLog()
{
return $this->logService;
}
/**
* @return \Xibo\Entity\User
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function getUser()
{
return $this->user;
}
/**
* Set user Id
* @param \Xibo\Entity\User $user
* @return $this
*/
public function setUser($user)
{
$this->user = $user;
return $this;
}
/**
* Get chart script
* @param ReportResult $results
* @return string
*/
public function getReportChartScript($results)
{
return null;
}
/**
* Generate saved report name
* @param SanitizerInterface $sanitizedParams
* @return string
*/
public function generateSavedReportName(SanitizerInterface $sanitizedParams)
{
$saveAs = sprintf(__('%s report'), ucfirst($sanitizedParams->getString('filter')));
return $saveAs. ' '. Carbon::now()->format('Y-m-d');
}
/**
* Get a temporary table representing the periods covered
* @param Carbon $fromDt
* @param Carbon $toDt
* @param string $groupByFilter
* @param string $table
* @param string $customLabel Custom Label
* @return string
* @throws InvalidArgumentException
*/
public function getTemporaryPeriodsTable($fromDt, $toDt, $groupByFilter, $table = 'temp_periods', $customLabel = 'Y-m-d H:i:s')
{
// My from/to dt represent the entire range we're interested in.
// we need to generate periods according to our grouping, within that range.
// Clone them so as to not effect the calling object
$fromDt = $fromDt->copy();
$toDt = $toDt->copy();
// our from/to dates might not sit nicely inside our period groupings
// for example if we look at June, by week, the 1st of June is a Saturday, week 22.
// NB:
// FromDT/ToDt should always be at the start of the day.
switch ($groupByFilter) {
case 'byweek':
$fromDt->locale(Translate::GetLocale())->startOfWeek();
break;
case 'bymonth':
$fromDt->startOfMonth();
break;
}
// Temporary Periods Table
// -----------------------
// we will use a temporary table for this.
// Drop table if exists
$this->getStore()->getConnection()->exec('
DROP TABLE IF EXISTS `' . $table . '`');
$this->getStore()->getConnection()->exec('
CREATE TEMPORARY TABLE `' . $table . '` (
id INT,
customLabel VARCHAR(20),
label VARCHAR(20),
start INT,
end INT
);
');
// Prepare an insert statement
$periods = $this->getStore()->getConnection()->prepare('
INSERT INTO `' . $table . '` (id, customLabel, label, start, end)
VALUES (:id, :customLabel, :label, :start, :end)
');
// Loop until we've covered all periods needed
$loopDate = $fromDt->copy();
while ($toDt > $loopDate) {
// We add different periods for each type of grouping
if ($groupByFilter == 'byhour') {
$periods->execute([
'id' => $loopDate->hour,
'customLabel' => $loopDate->format($customLabel),
'label' => $loopDate->format('g:i A'),
'start' => $loopDate->format('U'),
'end' => $loopDate->addHour()->format('U')
]);
} elseif ($groupByFilter == 'byday') {
$periods->execute([
'id' => $loopDate->year . $loopDate->month . $loopDate->day,
'customLabel' => $loopDate->format($customLabel),
'label' => $loopDate->format('Y-m-d'),
'start' => $loopDate->format('U'),
'end' => $loopDate->addDay()->format('U')
]);
} elseif ($groupByFilter == 'byweek') {
$weekNo = $loopDate->locale(Translate::GetLocale())->week();
$periods->execute([
'id' => $loopDate->weekOfYear . $loopDate->year,
'customLabel' => $loopDate->format($customLabel),
'label' => $loopDate->format('Y-m-d') . '(w' . $weekNo . ')',
'start' => $loopDate->format('U'),
'end' => $loopDate->addWeek()->format('U')
]);
} elseif ($groupByFilter == 'bymonth') {
$periods->execute([
'id' => $loopDate->year . $loopDate->month,
'customLabel' => $loopDate->format($customLabel),
'label' => $loopDate->format('M'),
'start' => $loopDate->format('U'),
'end' => $loopDate->addMonth()->format('U')
]);
} elseif ($groupByFilter == 'bydayofweek') {
$periods->execute([
'id' => $loopDate->dayOfWeek,
'customLabel' => $loopDate->format($customLabel),
'label' => $loopDate->format('D'),
'start' => $loopDate->format('U'),
'end' => $loopDate->addDay()->format('U')
]);
} elseif ($groupByFilter == 'bydayofmonth') {
$periods->execute([
'id' => $loopDate->day,
'customLabel' => $loopDate->format($customLabel),
'label' => $loopDate->format('d'),
'start' => $loopDate->format('U'),
'end' => $loopDate->addDay()->format('U')
]);
} else {
$this->getLog()->error('Unknown Grouping Selected ' . $groupByFilter);
throw new InvalidArgumentException(__('Unknown Grouping ') . $groupByFilter, 'groupByFilter');
}
}
$this->getLog()->debug(json_encode($this->store->select('SELECT * FROM ' . $table, []), JSON_PRETTY_PRINT));
return $table;
}
/**
* Get an array of displayIds we should pass into the query,
* if an exception is thrown, we should stop the report and return no results.
* @param \Xibo\Support\Sanitizer\SanitizerInterface $params
* @return array displayIds
* @throws \Xibo\Support\Exception\GeneralException
*/
private function getDisplayIdFilter(SanitizerInterface $params): array
{
$displayIds = [];
// Filters
$displayId = $params->getInt('displayId');
$displayGroupIds = $params->getIntArray('displayGroupId', ['default' => null]);
if ($displayId !== null) {
// Don't bother checking if we are a super admin
if (!$this->getUser()->isSuperAdmin()) {
$display = $this->displayFactory->getById($displayId);
if ($this->getUser()->checkViewable($display)) {
$displayIds[] = $displayId;
}
} else {
$displayIds[] = $displayId;
}
} else {
// If we are NOT a super admin OR we have some display group filters
// get an array of display id this user has access to.
// we cannot rely on the logged-in user because this will be run by the task runner which is a sysadmin
if (!$this->getUser()->isSuperAdmin() || $displayGroupIds !== null) {
// This will be the displayIds the user has access to, and are in the displayGroupIds provided.
foreach ($this->displayFactory->query(
null,
[
'userCheckUserId' => $this->getUser()->userId,
'displayGroupIds' => $displayGroupIds,
]
) as $display) {
$displayIds[] = $display->displayId;
}
}
}
// If we are a super admin without anything filtered, the object of this method is to return an empty
// array.
// If we are any other user, we must return something in the array.
if (!$this->getUser()->isSuperAdmin() && count($displayIds) <= 0) {
throw new InvalidArgumentException(__('No displays with View permissions'), 'displays');
}
return $displayIds;
}
}

View File

@@ -0,0 +1,127 @@
<?php
/**
* Copyright (C) 2019 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* 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 Psr\Container\ContainerInterface;
use Xibo\Entity\ReportForm;
use Xibo\Entity\ReportResult;
use Xibo\Support\Sanitizer\SanitizerInterface;
/**
* Interface ReportInterface
* @package Xibo\Report
*/
interface ReportInterface
{
/**
* Set factories
* @param ContainerInterface $container
* @return $this
*/
public function setFactories(ContainerInterface $container);
/**
* Set user Id
* @param \Xibo\Entity\User $user
* @return $this
*/
public function setUser($user);
/**
* Get the user
* @return \Xibo\Entity\User
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function getUser();
/**
* Get chart script
* @param ReportResult $results
* @return string
*/
public function getReportChartScript($results);
/**
* Return the twig file name of the saved report email and export template
* @return string
*/
public function getReportEmailTemplate();
/**
* Return the twig file name of the saved report preview template
* @return string
*/
public function getSavedReportTemplate();
/**
* Return the twig file name of the report form
* Load the report form
* @return ReportForm
*/
public function getReportForm();
/**
* Populate form title and hidden fields
* @param SanitizerInterface $sanitizedParams
* @return array
*/
public function getReportScheduleFormData(SanitizerInterface $sanitizedParams);
/**
* Set Report Schedule form data
* @param SanitizerInterface $sanitizedParams
* @return array
*/
public function setReportScheduleFormData(SanitizerInterface $sanitizedParams);
/**
* Generate saved report name
* @param SanitizerInterface $sanitizedParams
* @return string
*/
public function generateSavedReportName(SanitizerInterface $sanitizedParams);
/**
* Resrtucture old saved report's json file to support schema version 2
* @param $json
* @return array
*/
public function restructureSavedReportOldJson($json);
/**
* Return data from saved json file to build chart/table for saved report
* @param array $json
* @param object $savedReport
* @return ReportResult
*/
public function getSavedReportResults($json, $savedReport);
/**
* Get results when on demand report runs and
* This result will get saved to a json if schedule report runs
* @param SanitizerInterface $sanitizedParams
* @return ReportResult
* @throws \Xibo\Support\Exception\GeneralException
*/
public function getResults(SanitizerInterface $sanitizedParams);
}

View File

@@ -0,0 +1,438 @@
<?php
/*
* Copyright (C) 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\Controller\DataTablesDotNetTrait;
use Xibo\Entity\ReportForm;
use Xibo\Entity\ReportResult;
use Xibo\Entity\ReportSchedule;
use Xibo\Factory\AuditLogFactory;
use Xibo\Factory\LogFactory;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Translate;
use Xibo\Support\Exception\AccessDeniedException;
use Xibo\Support\Sanitizer\SanitizerInterface;
class SessionHistory implements ReportInterface
{
use ReportDefaultTrait, DataTablesDotNetTrait;
/** @var LogFactory */
private $logFactory;
/** @var AuditLogFactory */
private $auditLogFactory;
/** @inheritdoc */
public function setFactories(ContainerInterface $container)
{
$this->logFactory = $container->get('logFactory');
$this->auditLogFactory = $container->get('auditLogFactory');
return $this;
}
/** @inheritdoc */
public function getReportEmailTemplate()
{
return 'sessionhistory-email-template.twig';
}
/** @inheritdoc */
public function getSavedReportTemplate()
{
return 'sessionhistory-report-preview';
}
/** @inheritdoc */
public function getReportForm(): ReportForm
{
return new ReportForm(
'sessionhistory-report-form',
'sessionhistory',
'Audit',
[
'fromDate' => Carbon::now()->startOfMonth()->format(DateFormatHelper::getSystemFormat()),
'toDate' => Carbon::now()->format(DateFormatHelper::getSystemFormat()),
]
);
}
/** @inheritdoc */
public function getReportScheduleFormData(SanitizerInterface $sanitizedParams): array
{
$data = [];
$data['reportName'] = 'sessionhistory';
return [
'template' => 'sessionhistory-schedule-form-add',
'data' => $data
];
}
/** @inheritdoc */
public function setReportScheduleFormData(SanitizerInterface $sanitizedParams): array
{
$filter = $sanitizedParams->getString('filter');
$filterCriteria['userId'] = $sanitizedParams->getInt('userId');
$filterCriteria['type'] = $sanitizedParams->getString('type');
$filterCriteria['scheduledReport'] = true;
$filterCriteria['filter'] = $filter;
$schedule = '';
if ($filter == 'daily') {
$schedule = ReportSchedule::$SCHEDULE_DAILY;
$filterCriteria['reportFilter'] = 'yesterday';
} elseif ($filter == 'weekly') {
$schedule = ReportSchedule::$SCHEDULE_WEEKLY;
$filterCriteria['reportFilter'] = 'lastweek';
} elseif ($filter == 'monthly') {
$schedule = ReportSchedule::$SCHEDULE_MONTHLY;
$filterCriteria['reportFilter'] = 'lastmonth';
} elseif ($filter == 'yearly') {
$schedule = ReportSchedule::$SCHEDULE_YEARLY;
$filterCriteria['reportFilter'] = 'lastyear';
}
$filterCriteria['sendEmail'] = $sanitizedParams->getCheckbox('sendEmail');
$filterCriteria['nonusers'] = $sanitizedParams->getString('nonusers');
// Return
return [
'filterCriteria' => json_encode($filterCriteria),
'schedule' => $schedule
];
}
public function generateSavedReportName(SanitizerInterface $sanitizedParams): string
{
return sprintf(
__('%s Session %s log report for User'),
ucfirst($sanitizedParams->getString('filter')),
ucfirst($sanitizedParams->getString('type'))
);
}
/** @inheritdoc */
public function restructureSavedReportOldJson($json)
{
return $json;
}
/** @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'],
);
}
/** @inheritdoc */
public function getResults(SanitizerInterface $sanitizedParams)
{
if (!$this->getUser()->isSuperAdmin()) {
throw new AccessDeniedException();
}
//
// 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.
$reportFilter = $sanitizedParams->getString('reportFilter');
// Use the current date as a helper
$now = Carbon::now();
// This calculation will be retained as it is used for scheduled reports
switch ($reportFilter) {
case 'yesterday':
$fromDt = $now->copy()->startOfDay()->subDay();
$toDt = $now->copy()->startOfDay();
break;
case 'lastweek':
$fromDt = $now->copy()->locale(Translate::GetLocale())->startOfWeek()->subWeek();
$toDt = $fromDt->copy()->addWeek();
break;
case 'lastmonth':
$fromDt = $now->copy()->startOfMonth()->subMonth();
$toDt = $fromDt->copy()->addMonth();
break;
case 'lastyear':
$fromDt = $now->copy()->startOfYear()->subYear();
$toDt = $fromDt->copy()->addYear();
break;
case '':
default:
// fromDt will always be from start of day ie 00:00
$fromDt = $sanitizedParams->getDate('fromDt') ?? $now->copy()->startOfDay();
$toDt = $sanitizedParams->getDate('toDt') ?? $now;
break;
}
$metadata = [
'periodStart' => Carbon::createFromTimestamp($fromDt->toDateTime()->format('U'))
->format(DateFormatHelper::getSystemFormat()),
'periodEnd' => Carbon::createFromTimestamp($toDt->toDateTime()->format('U'))
->format(DateFormatHelper::getSystemFormat()),
];
$type = $sanitizedParams->getString('type');
if ($type === 'audit') {
$params = [
'fromDt' => $fromDt->format('U'),
'toDt' => $toDt->format('U'),
];
$sql = 'SELECT
`auditlog`.`logId`,
`auditlog`.`logDate`,
`user`.`userName`,
`auditlog`.`message`,
`auditlog`.`objectAfter`,
`auditlog`.`entity`,
`auditlog`.`entityId`,
`auditlog`.userId,
`auditlog`.ipAddress,
`auditlog`.sessionHistoryId,
`session_history`.userAgent
FROM `auditlog`
INNER JOIN `user` ON `user`.`userId` = `auditlog`.`userId`
INNER JOIN `session_history` ON `session_history`.`sessionId` = `auditlog`.`sessionHistoryId`
WHERE `auditlog`.logDate BETWEEN :fromDt AND :toDt
';
if ($sanitizedParams->getInt('userId') !== null) {
$sql .= ' AND `auditlog`.`userId` = :userId';
$params['userId'] = $sanitizedParams->getInt('userId');
}
if ($sanitizedParams->getInt('sessionHistoryId') !== null) {
$sql .= ' AND `auditlog`.`sessionHistoryId` = :sessionHistoryId';
$params['sessionHistoryId'] = $sanitizedParams->getInt('sessionHistoryId');
}
// Sorting?
$sortOrder = $this->gridRenderSort($sanitizedParams);
if (is_array($sortOrder)) {
$sql .= ' ORDER BY ' . implode(',', $sortOrder);
}
$rows = [];
foreach ($this->store->select($sql, $params) as $row) {
$auditRecord = $this->auditLogFactory->create()->hydrate($row);
$auditRecord->setUnmatchedProperty(
'userAgent',
$row['userAgent']
);
// decode for grid view, leave as json for email/preview.
if (!$sanitizedParams->getCheckbox('scheduledReport')) {
$auditRecord->objectAfter = json_decode($auditRecord->objectAfter);
}
$auditRecord->logDate = Carbon::createFromTimestamp($auditRecord->logDate)
->format(DateFormatHelper::getSystemFormat());
$rows[] = $auditRecord;
}
return new ReportResult(
$metadata,
$rows,
count($rows),
);
} else if ($type === 'debug') {
$params = [
'fromDt' => $fromDt->format(DateFormatHelper::getSystemFormat()),
'toDt' => $toDt->format(DateFormatHelper::getSystemFormat()),
];
$sql = 'SELECT
`log`.`logId`,
`log`.`logDate`,
`log`.`runNo`,
`log`.`channel`,
`log`.`page`,
`log`.`function`,
`log`.`type`,
`log`.`message`,
`log`.`userId`,
`log`.`sessionHistoryId`,
`user`.`userName`,
`display`.`displayId`,
`display`.`display`,
`session_history`.ipAddress,
`session_history`.userAgent
FROM `log`
LEFT OUTER JOIN `display` ON `display`.`displayid` = `log`.`displayid`
INNER JOIN `user` ON `user`.`userId` = `log`.`userId`
INNER JOIN `session_history` ON `session_history`.`sessionId` = `log`.`sessionHistoryId`
WHERE `log`.logDate BETWEEN :fromDt AND :toDt
';
if ($sanitizedParams->getInt('userId') !== null) {
$sql .= ' AND `log`.`userId` = :userId';
$params['userId'] = $sanitizedParams->getInt('userId');
}
if ($sanitizedParams->getInt('sessionHistoryId') !== null) {
$sql .= ' AND `log`.`sessionHistoryId` = :sessionHistoryId';
$params['sessionHistoryId'] = $sanitizedParams->getInt('sessionHistoryId');
}
// Sorting?
$sortOrder = $this->gridRenderSort($sanitizedParams);
if (is_array($sortOrder)) {
$sql .= ' ORDER BY ' . implode(',', $sortOrder);
}
$rows = [];
foreach ($this->store->select($sql, $params) as $row) {
$logRecord = $this->logFactory->createEmpty()->hydrate($row, ['htmlStringProperties' => ['message']]);
$logRecord->setUnmatchedProperty(
'userAgent',
$row['userAgent']
);
$logRecord->setUnmatchedProperty(
'ipAddress',
$row['ipAddress']
);
$logRecord->setUnmatchedProperty(
'userName',
$row['userName']
);
$rows[] = $logRecord;
}
return new ReportResult(
$metadata,
$rows,
count($rows),
);
} else {
$params = [
'fromDt' => $fromDt->format(DateFormatHelper::getSystemFormat()),
'toDt' => $toDt->format(DateFormatHelper::getSystemFormat()),
];
$sql = 'SELECT
`session_history`.`sessionId`,
`session_history`.`startTime`,
`session_history`.`userId`,
`session_history`.`userAgent`,
`session_history`.`ipAddress`,
`session_history`.`lastUsedTime`,
`user`.`userName`,
`usertype`.`userType`
FROM `session_history`
LEFT OUTER JOIN `user` ON `user`.`userId` = `session_history`.`userId`
LEFT OUTER JOIN `usertype` ON `usertype`.`userTypeId` = `user`.`userTypeId`
WHERE `session_history`.startTime BETWEEN :fromDt AND :toDt
';
if ($sanitizedParams->getInt('userId') !== null) {
$sql .= ' AND `session_history`.`userId` = :userId';
$params['userId'] = $sanitizedParams->getInt('userId');
}
if ($sanitizedParams->getInt('sessionHistoryId') !== null) {
$sql .= ' AND `session_history`.`sessionId` = :sessionHistoryId';
$params['sessionHistoryId'] = $sanitizedParams->getInt('sessionHistoryId');
}
// Sorting?
$sortOrder = $this->gridRenderSort($sanitizedParams);
if (is_array($sortOrder)) {
$sql .= ' ORDER BY ' . implode(',', $sortOrder);
}
$rows = [];
foreach ($this->store->select($sql, $params) as $row) {
$sessionRecord = $this->logFactory->createEmpty()->hydrate($row, ['htmlStringProperties' => ['message']]);
$duration = isset($row['lastUsedTime'])
? date_diff(date_create($row['startTime']), date_create($row['lastUsedTime']))->format('%H:%I:%S')
: null;
$sessionRecord->setUnmatchedProperty(
'userAgent',
$row['userAgent']
);
$sessionRecord->setUnmatchedProperty(
'ipAddress',
$row['ipAddress']
);
$sessionRecord->setUnmatchedProperty(
'userName',
$row['userName']
);
$sessionRecord->setUnmatchedProperty(
'endTime',
$row['lastUsedTime']
);
$sessionRecord->setUnmatchedProperty(
'duration',
$duration
);
$rows[] = $sessionRecord;
}
return new ReportResult(
$metadata,
$rows,
count($rows),
);
}
}
}

View File

@@ -0,0 +1,145 @@
<?php
/*
* Copyright (C) 2022 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* 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 Xibo\Support\Exception\InvalidArgumentException;
use Xibo\Support\Sanitizer\SanitizerInterface;
/**
* Common function between the Summary and Distribution reports
*/
trait SummaryDistributionCommonTrait
{
/** @inheritdoc */
public function restructureSavedReportOldJson($result)
{
$durationData = $result['durationData'];
$countData = $result['countData'];
$labels = $result['labels'];
$backgroundColor = $result['backgroundColor'];
$borderColor = $result['borderColor'];
$periodStart = $result['periodStart'];
$periodEnd = $result['periodEnd'];
return [
'hasData' => count($durationData) > 0 && count($countData) > 0,
'chart' => [
'type' => 'bar',
'data' => [
'labels' => $labels,
'datasets' => [
[
'label' => __('Total duration'),
'yAxisID' => 'Duration',
'backgroundColor' => $backgroundColor,
'data' => $durationData
],
[
'label' => __('Total count'),
'yAxisID' => 'Count',
'borderColor' => $borderColor,
'type' => 'line',
'fill' => false,
'data' => $countData
]
]
],
'options' => [
'scales' => [
'yAxes' => [
[
'id' => 'Duration',
'type' => 'linear',
'position' => 'left',
'display' => true,
'scaleLabel' => [
'display' => true,
'labelString' => __('Duration(s)')
],
'ticks' => [
'beginAtZero' => true
]
], [
'id' => 'Count',
'type' => 'linear',
'position' => 'right',
'display' => true,
'scaleLabel' => [
'display' => true,
'labelString' => __('Count')
],
'ticks' => [
'beginAtZero' => true
]
]
]
]
]
],
'periodStart' => $periodStart,
'periodEnd' => $periodEnd,
];
}
/**
* @param \Xibo\Support\Sanitizer\SanitizerInterface $sanitizedParams
* @return array
* @throws \Xibo\Support\Exception\InvalidArgumentException
* @throws \Xibo\Support\Exception\NotFoundException
*/
private function getReportScheduleFormTitle(SanitizerInterface $sanitizedParams): array
{
$type = $sanitizedParams->getString('type');
if ($type == 'layout') {
$selectedId = $sanitizedParams->getInt('layoutId');
$title = sprintf(
__('Add Report Schedule for %s - %s'),
$type,
$this->layoutFactory->getById($selectedId)->layout
);
} elseif ($type == 'media') {
$selectedId = $sanitizedParams->getInt('mediaId');
$title = sprintf(
__('Add Report Schedule for %s - %s'),
$type,
$this->mediaFactory->getById($selectedId)->name
);
} elseif ($type == 'event') {
$selectedId = 0; // we only need eventTag
$eventTag = $sanitizedParams->getString('eventTag');
$title = sprintf(
__('Add Report Schedule for %s - %s'),
$type,
$eventTag
);
} else {
throw new InvalidArgumentException(__('Unknown type ') . $type, 'type');
}
return [
'title' => $title,
'selectedId' => $selectedId
];
}
}

1141
lib/Report/SummaryReport.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,394 @@
<?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')
];
}
}

View File

@@ -0,0 +1,564 @@
<?php
/*
* Copyright (C) 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\Controller\DataTablesDotNetTrait;
use Xibo\Entity\ReportForm;
use Xibo\Entity\ReportResult;
use Xibo\Entity\ReportSchedule;
use Xibo\Factory\DisplayFactory;
use Xibo\Factory\DisplayGroupFactory;
use Xibo\Helper\ApplicationState;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\SanitizerService;
use Xibo\Helper\Translate;
use Xibo\Support\Exception\InvalidArgumentException;
use Xibo\Support\Sanitizer\SanitizerInterface;
/**
* Class TimeDisconnectedSummary
* @package Xibo\Report
*/
class TimeDisconnectedSummary implements ReportInterface
{
use ReportDefaultTrait, DataTablesDotNetTrait;
/**
* @var DisplayFactory
*/
private $displayFactory;
/**
* @var DisplayGroupFactory
*/
private $displayGroupFactory;
/**
* @var SanitizerService
*/
private $sanitizer;
/**
* @var ApplicationState
*/
private $state;
/** @inheritdoc */
public function setFactories(ContainerInterface $container)
{
$this->displayFactory = $container->get('displayFactory');
$this->displayGroupFactory = $container->get('displayGroupFactory');
$this->sanitizer = $container->get('sanitizerService');
$this->state = $container->get('state');
return $this;
}
/** @inheritdoc */
public function getReportChartScript($results)
{
return json_encode($results->chart);
}
/** @inheritdoc */
public function getReportEmailTemplate()
{
return 'timedisconnectedsummary-email-template.twig';
}
/** @inheritdoc */
public function getSavedReportTemplate()
{
return 'timedisconnectedsummary-report-preview';
}
/** @inheritdoc */
public function getReportForm()
{
return new ReportForm(
'timedisconnectedsummary-report-form',
'timedisconnectedsummary',
'Display',
[
'fromDate' => Carbon::now()->subSeconds(86400 * 35)->format(DateFormatHelper::getSystemFormat()),
'toDate' => Carbon::now()->format(DateFormatHelper::getSystemFormat()),
]
);
}
/** @inheritdoc */
public function getReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$data = [];
$data['reportName'] = 'timedisconnectedsummary';
return [
'template' => 'timedisconnectedsummary-schedule-form-add',
'data' => $data
];
}
/** @inheritdoc */
public function setReportScheduleFormData(SanitizerInterface $sanitizedParams)
{
$filter = $sanitizedParams->getString('filter');
$displayId = $sanitizedParams->getInt('displayId');
$displayGroupIds = $sanitizedParams->getIntArray('displayGroupId', ['default' => []]);
$filterCriteria['displayId'] = $displayId;
if (empty($displayId) && count($displayGroupIds) > 0) {
$filterCriteria['displayGroupId'] = $displayGroupIds;
}
$filterCriteria['filter'] = $filter;
$schedule = '';
if ($filter == 'daily') {
$schedule = ReportSchedule::$SCHEDULE_DAILY;
$filterCriteria['reportFilter'] = 'yesterday';
} elseif ($filter == 'weekly') {
$schedule = ReportSchedule::$SCHEDULE_WEEKLY;
$filterCriteria['reportFilter'] = 'lastweek';
} elseif ($filter == 'monthly') {
$schedule = ReportSchedule::$SCHEDULE_MONTHLY;
$filterCriteria['reportFilter'] = 'lastmonth';
} elseif ($filter == 'yearly') {
$schedule = ReportSchedule::$SCHEDULE_YEARLY;
$filterCriteria['reportFilter'] = 'lastyear';
}
$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 time disconnected summary report', 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)
{
// Filter by displayId?
$displayIds = $this->getDisplayIdFilter($sanitizedParams);
$tags = $sanitizedParams->getString('tags');
$onlyLoggedIn = $sanitizedParams->getCheckbox('onlyLoggedIn') == 1;
$groupBy = $sanitizedParams->getString('groupBy');
//
// 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');
$currentDate = Carbon::now()->startOfDay();
// If toDt is current date then make it current datetime
if ($toDt->format('Y-m-d') == $currentDate->format('Y-m-d')) {
$toDt = Carbon::now();
}
// Get an array of display groups this user has access to
$displayGroupIds = [];
foreach ($this->displayGroupFactory->query(null, [
'isDisplaySpecific' => -1,
'userCheckUserId' => $this->getUser()->userId
]) as $displayGroup) {
$displayGroupIds[] = $displayGroup->displayGroupId;
}
if (count($displayGroupIds) <= 0) {
throw new InvalidArgumentException(__('No display groups with View permissions'), 'displayGroup');
}
$params = array(
'start' => $fromDt->format('U'),
'end' => $toDt->format('U')
);
// Disconnected Displays Query
$select = '
SELECT display.display, display.displayId,
SUM(LEAST(IFNULL(`end`, :end), :end) - GREATEST(`start`, :start)) AS duration,
:end - :start as filter ';
$body = 'FROM `displayevent`
INNER JOIN `display`
ON display.displayId = `displayevent`.displayId
WHERE `start` <= :end
AND IFNULL(`end`, :end) >= :start
AND :end <= UNIX_TIMESTAMP(NOW())
AND `displayevent`.eventTypeId = 1 ';
if (count($displayIds) > 0) {
$body .= 'AND display.displayId IN (' . implode(',', $displayIds) . ') ';
}
if ($onlyLoggedIn) {
$body .= ' AND `display`.loggedIn = 1 ';
}
$body .= '
GROUP BY display.display, display.displayId
';
$sql = $select . $body;
$maxDuration = 0;
foreach ($this->store->select($sql, $params) as $row) {
$maxDuration = $maxDuration + $this->sanitizer->getSanitizer($row)->getDouble('duration');
}
if ($maxDuration > 86400) {
$postUnits = __('Days');
$divisor = 86400;
} elseif ($maxDuration > 3600) {
$postUnits = __('Hours');
$divisor = 3600;
} else {
$postUnits = __('Minutes');
$divisor = 60;
}
// Tabular Data
$disconnectedDisplays = [];
foreach ($this->store->select($sql, $params) as $row) {
$sanitizedRow = $this->sanitizer->getSanitizer($row);
$entry = [];
$entry['timeDisconnected'] = round($sanitizedRow->getDouble('duration') / $divisor, 2);
$entry['timeConnected'] = round($sanitizedRow->getDouble('filter') / $divisor - $entry['timeDisconnected'], 2);
$disconnectedDisplays[$sanitizedRow->getInt(('displayId'))] = $entry;
}
// Displays with filters such as tags
$displaySelect = '
SELECT display.display, display.displayId ';
if ($tags != '') {
$displaySelect .= ', (SELECT GROUP_CONCAT(DISTINCT tag)
FROM tag
INNER JOIN lktagdisplaygroup
ON lktagdisplaygroup.tagId = tag.tagId
WHERE lktagdisplaygroup.displayGroupId = displaygroup.DisplayGroupID
GROUP BY lktagdisplaygroup.displayGroupId) AS tags ';
}
$displayBody = 'FROM `display` ';
if ($groupBy === 'displayGroup') {
$displaySelect .= ', displaydg.displayGroup, displaydg.displayGroupId ';
}
if ($tags != '') {
$displayBody .= 'INNER JOIN `lkdisplaydg`
ON lkdisplaydg.DisplayID = display.displayid
INNER JOIN `displaygroup`
ON displaygroup.displaygroupId = lkdisplaydg.displaygroupId
AND `displaygroup`.isDisplaySpecific = 1 ';
}
// Grouping Option
if ($groupBy === 'displayGroup') {
$displayBody .= 'INNER JOIN `lkdisplaydg` AS linkdg
ON linkdg.DisplayID = display.displayid
INNER JOIN `displaygroup` AS displaydg
ON displaydg.displaygroupId = linkdg.displaygroupId
AND `displaydg`.isDisplaySpecific = 0 ';
}
$displayBody .= 'WHERE 1 = 1 ';
if (count($displayIds) > 0) {
$displayBody .= 'AND display.displayId IN (' . implode(',', $displayIds) . ') ';
}
$tagParams = [];
if ($tags != '') {
if (trim($tags) === '--no-tag') {
$displayBody .= ' AND `displaygroup`.displaygroupId NOT IN (
SELECT `lktagdisplaygroup`.displaygroupId
FROM tag
INNER JOIN `lktagdisplaygroup`
ON `lktagdisplaygroup`.tagId = tag.tagId
)
';
} else {
$operator = $sanitizedParams->getCheckbox('exactTags') == 1 ? '=' : 'LIKE';
$displayBody .= ' AND `displaygroup`.displaygroupId IN (
SELECT `lktagdisplaygroup`.displaygroupId
FROM tag
INNER JOIN `lktagdisplaygroup`
ON `lktagdisplaygroup`.tagId = tag.tagId
';
$i = 0;
foreach (explode(',', $tags) as $tag) {
$i++;
if ($i == 1) {
$displayBody .= ' WHERE `tag` ' . $operator . ' :tags' . $i;
} else {
$displayBody .= ' OR `tag` ' . $operator . ' :tags' . $i;
}
if ($operator === '=') {
$tagParams['tags' . $i] = $tag;
} else {
$tagParams['tags' . $i] = '%' . $tag . '%';
}
}
$displayBody .= ' ) ';
}
}
if ($onlyLoggedIn) {
$displayBody .= ' AND `display`.loggedIn = 1 ';
}
$displayBody .= '
GROUP BY display.display, display.displayId
';
if ($tags != '') {
$displayBody .= ', displaygroup.displayGroupId ';
}
if ($groupBy === 'displayGroup') {
$displayBody .= ', displaydg.displayGroupId ';
}
// Sorting?
$sortOrder = $this->gridRenderSort($sanitizedParams);
$order = '';
if (is_array($sortOrder)) {
$order .= 'ORDER BY ' . implode(',', $sortOrder);
}
// Get a list of displays by filters
$displaySql = $displaySelect . $displayBody . $order;
$rows = [];
// Retrieve the disconnected/connected time from the $disconnectedDisplays array into displays
foreach ($this->store->select($displaySql, $tagParams) as $displayRow) {
$sanitizedDisplayRow = $this->sanitizer->getSanitizer($displayRow);
$entry = [];
$displayId = $sanitizedDisplayRow->getInt(('displayId'));
$entry['displayId'] = $displayId;
$entry['display'] = $sanitizedDisplayRow->getString(('display'));
$entry['timeDisconnected'] = $disconnectedDisplays[$displayId]['timeDisconnected'] ?? 0 ;
$entry['timeConnected'] = $disconnectedDisplays[$displayId]['timeConnected'] ?? round(($toDt->format('U') - $fromDt->format('U')) / $divisor, 2);
$entry['postUnits'] = $postUnits;
$entry['displayGroupId'] = $sanitizedDisplayRow->getInt(('displayGroupId'));
$entry['displayGroup'] = $sanitizedDisplayRow->getString(('displayGroup'));
$entry['avgTimeDisconnected'] = 0;
$entry['avgTimeConnected'] = 0;
$entry['availabilityPercentage'] = $this->getAvailabilityPercentage(
$entry['timeConnected'],
$entry['timeDisconnected']
) . '%';
$rows[] = $entry;
}
//
// Output Results
// --------------
$availabilityData = [];
$availabilityDataConnected = [];
$availabilityLabels = [];
$postUnits = '';
if ($groupBy === 'displayGroup') {
$rows = $this->getByDisplayGroup($rows, $sanitizedParams->getIntArray('displayGroupId', ['default' => []]));
}
foreach ($rows as $row) {
$availabilityData[] = $row['timeDisconnected'];
$availabilityDataConnected[] = $row['timeConnected'];
$availabilityLabels[] = ($groupBy === 'displayGroup')
? $row['displayGroup']
: $row['display'];
$postUnits = $row['postUnits'];
}
// Build Chart to pass in twig file chart.js
$chart = [
'type' => 'bar',
'data' => [
'labels' => $availabilityLabels,
'datasets' => [
[
'backgroundColor' => 'rgb(11, 98, 164)',
'data' => $availabilityData,
'label' => __('Downtime')
],
[
'backgroundColor' => 'rgb(0, 255, 0)',
'data' => $availabilityDataConnected,
'label' => __('Uptime')
]
]
],
'options' => [
'scales' => [
'xAxes' => [
[
'stacked' => true
]
],
'yAxes' => [
[
'stacked' => true,
'scaleLabel' => [
'display' => true,
'labelString' => $postUnits
]
]
]
],
'legend' => [
'display' => false
],
'maintainAspectRatio' => false
]
];
$metadata = [
'periodStart' => Carbon::createFromTimestamp($fromDt->toDateTime()->format('U'))->format(DateFormatHelper::getSystemFormat()),
'periodEnd' => Carbon::createFromTimestamp($toDt->toDateTime()->format('U'))->format(DateFormatHelper::getSystemFormat()),
];
// ----
// Return data to build chart/table
// This will get saved to a json file when schedule runs
return new ReportResult(
$metadata,
$rows,
count($rows),
$chart
);
}
/**
* Get the Availability Percentage
* @param float $connectedTime
* @param float $disconnectedTime
* @return float
*/
private function getAvailabilityPercentage(float $connectedTime, float $disconnectedTime) : float
{
$connectedPercentage = $connectedTime/($connectedTime + $disconnectedTime ?: 1);
return abs(round($connectedPercentage * 100, 2));
}
/**
* Get the accumulated value by display groups
* @param array $rows
* @param array $displayGroupIds
* @return array
*/
private function getByDisplayGroup(array $rows, array $displayGroupIds = []) : array
{
$data = [];
$displayGroups = [];
// Get the accumulated values by displayGroupId
foreach ($rows as $row) {
$displayGroupId = $row['displayGroupId'];
if (isset($displayGroups[$displayGroupId])) {
$displayGroups[$displayGroupId]['timeDisconnected'] += $row['timeDisconnected'];
$displayGroups[$displayGroupId]['timeConnected'] += $row['timeConnected'];
$displayGroups[$displayGroupId]['count'] += 1;
} else {
$row['count'] = 1;
$displayGroups[$displayGroupId] = $row;
}
}
// Get all display groups or selected display groups only
foreach ($displayGroups as $displayGroup) {
if (!$displayGroupIds || in_array($displayGroup['displayGroupId'], $displayGroupIds)) {
$displayGroup['timeConnected'] = round($displayGroup['timeConnected'], 2);
$displayGroup['timeDisconnected'] = round($displayGroup['timeDisconnected'], 2);
$displayGroup['availabilityPercentage'] = $this->getAvailabilityPercentage(
$displayGroup['timeConnected'],
$displayGroup['timeDisconnected']
) . '%';
// Calculate the average values
$displayGroup['avgTimeConnected'] = round($displayGroup['timeConnected'] / $displayGroup['count'], 2);
$displayGroup['avgTimeDisconnected'] = round($displayGroup['timeDisconnected'] / $displayGroup['count'], 2);
$data[] = $displayGroup;
}
}
return $data;
}
}