Files
Cloud-CMS/lib/Report/LibraryUsage.php

637 lines
21 KiB
PHP
Raw Normal View History

2025-12-02 10:32:59 -05:00
<?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
);
}
}