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

View File

@@ -0,0 +1,171 @@
<?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\Listener;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Event\DayPartDeleteEvent;
use Xibo\Event\FolderMovingEvent;
use Xibo\Event\ParsePermissionEntityEvent;
use Xibo\Event\TagDeleteEvent;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\CampaignFactory;
use Xibo\Storage\StorageServiceInterface;
use Xibo\Support\Exception\InvalidArgumentException;
/**
* Campaign events
*/
class CampaignListener
{
use ListenerLoggerTrait;
/** @var \Xibo\Factory\CampaignFactory */
private $campaignFactory;
/** @var \Xibo\Storage\StorageServiceInterface */
private $storageService;
public function __construct(
CampaignFactory $campaignFactory,
StorageServiceInterface $storageService
) {
$this->campaignFactory = $campaignFactory;
$this->storageService = $storageService;
}
public function registerWithDispatcher(EventDispatcherInterface $dispatcher): CampaignListener
{
$dispatcher->addListener(ParsePermissionEntityEvent::$NAME . 'campaign', [$this, 'onParsePermissions']);
$dispatcher->addListener(FolderMovingEvent::$NAME, [$this, 'onFolderMoving']);
$dispatcher->addListener(UserDeleteEvent::$NAME, [$this, 'onUserDelete']);
$dispatcher->addListener(DayPartDeleteEvent::$NAME, [$this, 'onDayPartDelete']);
$dispatcher->addListener(TagDeleteEvent::$NAME, [$this, 'onTagDelete']);
return $this;
}
/**
* Parse permissions
* @param \Xibo\Event\ParsePermissionEntityEvent $event
* @return void
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onParsePermissions(ParsePermissionEntityEvent $event)
{
$this->getLogger()->debug('onParsePermissions');
$event->setObject($this->campaignFactory->getById($event->getObjectId()));
}
/**
* When we're moving a folder, update our folderId/permissions folder id
* @param \Xibo\Event\FolderMovingEvent $event
* @return void
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onFolderMoving(FolderMovingEvent $event)
{
$folder = $event->getFolder();
$newFolder = $event->getNewFolder();
foreach ($this->campaignFactory->getByFolderId($folder->getId()) as $campaign) {
// update campaign record
$campaign->folderId = $newFolder->id;
$campaign->permissionsFolderId = $newFolder->getPermissionFolderIdOrThis();
$campaign->updateFolders('campaign');
}
}
/**
* User is being deleted, tidy up their campaigns
* @param \Xibo\Event\UserDeleteEvent $event
* @return void
* @throws \Xibo\Support\Exception\DuplicateEntityException
* @throws \Xibo\Support\Exception\InvalidArgumentException
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onUserDelete(UserDeleteEvent $event)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
if ($function === 'delete') {
// Delete any Campaigns
foreach ($this->campaignFactory->getByOwnerId($user->userId) as $campaign) {
$campaign->delete();
}
} else if ($function === 'reassignAll') {
// Reassign campaigns
$this->storageService->update('UPDATE `campaign` SET userId = :userId WHERE userId = :oldUserId', [
'userId' => $newUser->userId,
'oldUserId' => $user->userId
]);
} else if ($function === 'countChildren') {
$campaigns = $this->campaignFactory->getByOwnerId($user->userId);
$count = count($campaigns);
$this->getLogger()->debug(
sprintf(
'Counted Children Campaign on User ID %d, there are %d',
$user->userId,
$count
)
);
$event->setReturnValue($event->getReturnValue() + $count);
}
}
/**
* Days parts might be assigned to lkcampaignlayout records.
* @param \Xibo\Event\DayPartDeleteEvent $event
* @return void
* @throws \Xibo\Support\Exception\InvalidArgumentException
*/
public function onDayPartDelete(DayPartDeleteEvent $event)
{
// We can't delete dayparts that are in-use on advertising campaigns.
if ($this->storageService->exists('
SELECT lkCampaignLayoutId
FROM `lkcampaignlayout`
WHERE dayPartId = :dayPartId
LIMIT 1
', [
'dayPartId' => $event->getDayPart()->dayPartId,
])) {
throw new InvalidArgumentException(__('This is inuse and cannot be deleted.'), 'dayPartId');
}
}
/**
* When Tag gets deleted, remove any campaign links from it.
* @param TagDeleteEvent $event
* @return void
*/
public function onTagDelete(TagDeleteEvent $event)
{
$this->storageService->update(
'DELETE FROM `lktagcampaign` WHERE `lktagcampaign`.tagId = :tagId',
['tagId' => $event->getTagId()]
);
}
}

View File

@@ -0,0 +1,372 @@
<?php
/*
* Copyright (C) 2025 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\Listener;
use Carbon\Carbon;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\DataSet;
use Xibo\Event\DataSetDataRequestEvent;
use Xibo\Event\DataSetDataTypeRequestEvent;
use Xibo\Event\DataSetModifiedDtRequestEvent;
use Xibo\Factory\DataSetFactory;
use Xibo\Factory\DisplayFactory;
use Xibo\Service\ConfigServiceInterface;
use Xibo\Storage\StorageServiceInterface;
use Xibo\Support\Exception\NotFoundException;
use Xibo\Widget\Definition\DataType;
use Xibo\Widget\Provider\DataProviderInterface;
/**
* Listens to requests for data from DataSets.
*/
class DataSetDataProviderListener
{
use ListenerLoggerTrait;
/** @var \Xibo\Storage\StorageServiceInterface */
private $store;
/** @var \Xibo\Service\ConfigServiceInterface */
private $config;
/** @var \Xibo\Factory\DataSetFactory */
private $dataSetFactory;
/** @var \Xibo\Factory\DisplayFactory */
private $displayFactory;
public function __construct(
StorageServiceInterface $store,
ConfigServiceInterface $config,
DataSetFactory $dataSetFactory,
DisplayFactory $displayFactory
) {
$this->store = $store;
$this->config = $config;
$this->dataSetFactory = $dataSetFactory;
$this->displayFactory = $displayFactory;
}
public function registerWithDispatcher(EventDispatcherInterface $dispatcher): DataSetDataProviderListener
{
$dispatcher->addListener(DataSetDataRequestEvent::$NAME, [$this, 'onDataRequest']);
$dispatcher->addListener(DataSetDataTypeRequestEvent::$NAME, [$this, 'onDataTypeRequest']);
$dispatcher->addListener(DataSetModifiedDtRequestEvent::$NAME, [$this, 'onModifiedDtRequest']);
return $this;
}
public function onDataRequest(DataSetDataRequestEvent $event)
{
$this->getLogger()->debug('onDataRequest: data source is ' . $event->getDataProvider()->getDataSource());
$dataProvider = $event->getDataProvider();
// We must have a dataSetId configured.
$dataSetId = $dataProvider->getProperty('dataSetId', 0);
if (empty($dataSetId)) {
$this->getLogger()->debug('onDataRequest: no dataSetId.');
return;
}
// Get this dataset
try {
$dataSet = $this->dataSetFactory->getById($dataSetId);
} catch (NotFoundException $notFoundException) {
$this->getLogger()->error('onDataRequest: dataSetId ' . $dataSetId . ' not found.');
return;
}
$this->getData($dataSet, $dataProvider);
// Cache timeout
$dataProvider->setCacheTtl($dataProvider->getProperty('updateInterval', 60) * 60);
}
/**
* @param \Xibo\Event\DataSetDataTypeRequestEvent $event
* @return void
*/
public function onDataTypeRequest(DataSetDataTypeRequestEvent $event)
{
// We must have a dataSetId configured.
$dataSetId = $event->getDataSetId();
if (empty($dataSetId)) {
$this->getLogger()->debug('onDataTypeRequest: no dataSetId.');
return;
}
$this->getLogger()->debug('onDataTypeRequest: with dataSetId: ' . $dataSetId);
// Get this dataset
try {
$dataSet = $this->dataSetFactory->getById($dataSetId);
// Create a new DataType for this DataSet
$dataType = new DataType();
$dataType->id = 'dataset';
$dataType->name = $dataSet->dataSet;
// Get the columns for this dataset and return a list of them
foreach ($dataSet->getColumn() as $column) {
$dataType->addField(
$column->heading . '|' . $column->dataSetColumnId,
$column->heading,
$column->dataType
);
}
$event->setDataType($dataType);
} catch (NotFoundException $notFoundException) {
$this->getLogger()->error('onDataTypeRequest: dataSetId ' . $dataSetId . ' not found.');
return;
}
}
public function onModifiedDtRequest(DataSetModifiedDtRequestEvent $event)
{
$this->getLogger()->debug('onModifiedDtRequest: get modifiedDt with dataSetId: ' . $event->getDataSetId());
try {
$dataSet = $this->dataSetFactory->getById($event->getDataSetId());
$event->setModifiedDt(Carbon::createFromTimestamp($dataSet->lastDataEdit));
// Remote dataSets are kept "active" by required files
$dataSet->setActive();
} catch (NotFoundException $notFoundException) {
$this->getLogger()->error('onModifiedDtRequest: dataSetId ' . $event->getDataSetId() . ' not found.');
}
}
private function getData(DataSet $dataSet, DataProviderInterface $dataProvider): void
{
// Load the dataSet
$dataSet->load();
// Columns
// Build a list of column mappings we will make available as metadata
$mappings = [];
$columnIds = $dataProvider->getProperty('columns');
$columnIds = empty($columnIds) ? null : explode(',', $columnIds);
$this->getLogger()->debug('getData: loaded dataSetId ' . $dataSet->dataSetId . ', there are '
. count($dataSet->columns) . '. We have selected ' . ($columnIds !== null ? count($columnIds) : 'all')
. ' of them');
foreach ($dataSet->columns as $column) {
if ($columnIds === null || in_array($column->dataSetColumnId, $columnIds)) {
$mappings[] = [
'dataSetColumnId' => $column->dataSetColumnId,
'heading' => $column->heading,
'dataTypeId' => $column->dataTypeId
];
}
}
$this->getLogger()->debug('getData: resolved ' . count($mappings) . ' column mappings');
// Build filter, order and limit parameters to pass to the DataSet entity
// Ordering
$ordering = '';
if ($dataProvider->getProperty('useOrderingClause', 1) == 1) {
$ordering = $dataProvider->getProperty('ordering');
} else {
// Build an order string
foreach (json_decode($dataProvider->getProperty('orderClauses', '[]'), true) as $clause) {
$ordering .= $clause['orderClause'] . ' ' . $clause['orderClauseDirection'] . ',';
}
$ordering = rtrim($ordering, ',');
}
// Build a filter to pass to the dataset
$filter = [
'filter' => $this->buildFilterClause($dataProvider),
'order' => $ordering,
'displayId' => $dataProvider->getDisplayId(),
];
// limits?
$upperLimit = $dataProvider->getProperty('upperLimit', 0);
$lowerLimit = $dataProvider->getProperty('lowerLimit', 0);
if ($lowerLimit !== 0 || $upperLimit !== 0) {
// Start should be the lower limit
// Size should be the distance between upper and lower
$filter['start'] = $lowerLimit;
$filter['size'] = $upperLimit - $lowerLimit;
$this->getLogger()->debug('getData: applied limits, start: '
. $filter['start'] . ', size: ' . $filter['size']);
}
// Expiry time for any images
$expires = Carbon::now()
->addSeconds($dataProvider->getProperty('updateInterval', 3600) * 60)
->format('U');
try {
$this->setTimezone($dataProvider);
$dataSetResults = $dataSet->getData($filter);
$this->getLogger()->debug('getData: finished getting data. There are '
. count($dataSetResults) . ' records returned');
foreach ($dataSetResults as $row) {
// Add an item containing the columns we have selected
$item = [];
foreach ($mappings as $mapping) {
// This column is selected
$cellValue = $row[$mapping['heading']] ?? null;
if ($mapping['dataTypeId'] === 4) {
// Grab the external image
$item[$mapping['heading']] = $dataProvider->addImage(
'dataset_' . md5($dataSet->dataSetId . $mapping['dataSetColumnId'] . $cellValue),
str_replace(' ', '%20', htmlspecialchars_decode($cellValue)),
$expires
);
} else if ($mapping['dataTypeId'] === 5) {
// Library Image
$this->getLogger()->debug('getData: Library media reference found: ' . $cellValue);
// The content is the ID of the image
try {
$item[$mapping['heading']] = $dataProvider->addLibraryFile(intval($cellValue));
} catch (NotFoundException $notFoundException) {
$this->getLogger()->error('getData: Invalid library media reference: ' . $cellValue);
$item[$mapping['heading']] = '';
}
} else {
// Just a normal column
$item[$mapping['heading']] = $cellValue;
}
}
$dataProvider->addItem($item);
}
// Add the mapping we've generated to the metadata
$dataProvider->addOrUpdateMeta('mapping', $mappings);
$dataProvider->setIsHandled();
} catch (\Exception $exception) {
$this->getLogger()->debug('onDataRequest: ' . $exception->getTraceAsString());
$this->getLogger()->error('onDataRequest: unable to get data for dataSetId ' . $dataSet->dataSetId
. ' e: ' . $exception->getMessage());
$dataProvider->addError(__('DataSet Invalid'));
}
}
private function buildFilterClause(DataProviderInterface $dataProvider): ?string
{
$filter = '';
if ($dataProvider->getProperty('useFilteringClause', 1) == 1) {
$filter = $dataProvider->getProperty('filter');
} else {
// Build
$i = 0;
foreach (json_decode($dataProvider->getProperty('filterClauses', '[]'), true) as $clause) {
$i++;
switch ($clause['filterClauseCriteria']) {
case 'starts-with':
$criteria = 'LIKE \'' . $clause['filterClauseValue'] . '%\'';
break;
case 'ends-with':
$criteria = 'LIKE \'%' . $clause['filterClauseValue'] . '\'';
break;
case 'contains':
$criteria = 'LIKE \'%' . $clause['filterClauseValue'] . '%\'';
break;
case 'equals':
$criteria = '= \'' . $clause['filterClauseValue'] . '\'';
break;
case 'not-contains':
$criteria = 'NOT LIKE \'%' . $clause['filterClauseValue'] . '%\'';
break;
case 'not-starts-with':
$criteria = 'NOT LIKE \'' . $clause['filterClauseValue'] . '%\'';
break;
case 'not-ends-with':
$criteria = 'NOT LIKE \'%' . $clause['filterClauseValue'] . '\'';
break;
case 'not-equals':
$criteria = '<> \'' . $clause['filterClauseValue'] . '\'';
break;
case 'greater-than':
$criteria = '> \'' . $clause['filterClauseValue'] . '\'';
break;
case 'less-than':
$criteria = '< \'' . $clause['filterClauseValue'] . '\'';
break;
default:
continue 2;
}
if ($i > 1) {
$filter .= ' ' . $clause['filterClauseOperator'] . ' ';
}
$filter .= $clause['filterClause'] . ' ' . $criteria;
}
}
return $filter;
}
/**
* @param \Xibo\Widget\Provider\DataProviderInterface $dataProvider
* @return void
* @throws \Xibo\Support\Exception\NotFoundException
*/
private function setTimezone(DataProviderInterface $dataProvider)
{
// Set the timezone for SQL
$dateNow = Carbon::now();
if ($dataProvider->getDisplayId() != 0) {
$display = $this->displayFactory->getById($dataProvider->getDisplayId());
$timeZone = $display->getSetting('displayTimeZone', '');
$timeZone = ($timeZone == '') ? $this->config->getSetting('defaultTimezone') : $timeZone;
$dateNow->timezone($timeZone);
$this->logger->debug(sprintf(
'Display Timezone Resolved: %s. Time: %s.',
$timeZone,
$dateNow->toDateTimeString()
));
}
// Run this command on a new connection so that we do not interfere with any other queries on this connection.
$this->store->setTimeZone($dateNow->format('P'), 'dataset');
$this->getLogger()->debug('setTimezone: finished setting timezone');
}
}

View File

@@ -0,0 +1,277 @@
<?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\Listener;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Event\DisplayGroupLoadEvent;
use Xibo\Event\FolderMovingEvent;
use Xibo\Event\MediaDeleteEvent;
use Xibo\Event\MediaFullLoadEvent;
use Xibo\Event\ParsePermissionEntityEvent;
use Xibo\Event\TagDeleteEvent;
use Xibo\Event\TagEditEvent;
use Xibo\Event\TriggerTaskEvent;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\DisplayFactory;
use Xibo\Factory\DisplayGroupFactory;
use Xibo\Storage\StorageServiceInterface;
use Xibo\Support\Exception\GeneralException;
use Xibo\Support\Exception\NotFoundException;
/**
* DisplayGroup events
*/
class DisplayGroupListener
{
use ListenerLoggerTrait;
/**
* @var DisplayGroupFactory
*/
private $displayGroupFactory;
/**
* @var DisplayFactory
*/
private $displayFactory;
/**
* @var StorageServiceInterface
*/
private $storageService;
/**
* @param DisplayGroupFactory $displayGroupFactory
* @param DisplayFactory $displayFactory
* @param StorageServiceInterface $storageService
*/
public function __construct(
DisplayGroupFactory $displayGroupFactory,
DisplayFactory $displayFactory,
StorageServiceInterface $storageService
) {
$this->displayGroupFactory = $displayGroupFactory;
$this->displayFactory = $displayFactory;
$this->storageService = $storageService;
}
/**
* @param EventDispatcherInterface $dispatcher
* @return $this
*/
public function registerWithDispatcher(EventDispatcherInterface $dispatcher): DisplayGroupListener
{
$dispatcher->addListener(MediaDeleteEvent::$NAME, [$this, 'onMediaDelete']);
$dispatcher->addListener(UserDeleteEvent::$NAME, [$this, 'onUserDelete']);
$dispatcher->addListener(MediaFullLoadEvent::$NAME, [$this, 'onMediaLoad']);
$dispatcher->addListener(ParsePermissionEntityEvent::$NAME . 'displayGroup', [$this, 'onParsePermissions']);
$dispatcher->addListener(FolderMovingEvent::$NAME, [$this, 'onFolderMoving']);
$dispatcher->addListener(TagDeleteEvent::$NAME, [$this, 'onTagDelete']);
$dispatcher->addListener(TagEditEvent::$NAME, [$this, 'onTagEdit']);
return $this;
}
/**
* @param MediaDeleteEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
* @return void
* @throws \Xibo\Support\Exception\GeneralException
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onMediaDelete(MediaDeleteEvent $event, string $eventName, EventDispatcherInterface $dispatcher)
{
$media = $event->getMedia();
$parentMedia = $event->getParentMedia();
foreach ($this->displayGroupFactory->getByMediaId($media->mediaId) as $displayGroup) {
$dispatcher->dispatch(new DisplayGroupLoadEvent($displayGroup), DisplayGroupLoadEvent::$NAME);
$displayGroup->load();
$displayGroup->unassignMedia($media);
if ($parentMedia != null) {
$displayGroup->assignMedia($parentMedia);
}
$displayGroup->save(['validate' => false]);
}
}
/**
* @param UserDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @return void
* @throws \Xibo\Support\Exception\GeneralException
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onUserDelete(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
// we do not want to delete Display specific Display Groups, reassign to systemUser instead.
foreach ($this->displayGroupFactory->getByOwnerId($user->userId, -1) as $displayGroup) {
if ($displayGroup->isDisplaySpecific === 1) {
$displayGroup->setOwner($systemUser->userId);
$displayGroup->save(['saveTags' => false, 'manageDynamicDisplayLinks' => false]);
} else {
$displayGroup->load();
$dispatcher->dispatch(new DisplayGroupLoadEvent($displayGroup), DisplayGroupLoadEvent::$NAME);
$displayGroup->delete();
}
}
} else if ($function === 'reassignAll') {
foreach ($this->displayGroupFactory->getByOwnerId($user->userId, -1) as $displayGroup) {
($displayGroup->isDisplaySpecific === 1) ? $displayGroup->setOwner($systemUser->userId) : $displayGroup->setOwner($newUser->getOwnerId());
$displayGroup->save(['saveTags' => false, 'manageDynamicDisplayLinks' => false]);
}
} else if ($function === 'countChildren') {
$displayGroups = $this->displayGroupFactory->getByOwnerId($user->userId, -1);
$count = count($displayGroups);
$this->getLogger()->debug(
sprintf(
'Counted Children Display Groups on User ID %d, there are %d',
$user->userId,
$count
)
);
$event->setReturnValue($event->getReturnValue() + $count);
}
}
/**
* @param MediaFullLoadEvent $event
* @return void
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onMediaLoad(MediaFullLoadEvent $event)
{
$media = $event->getMedia();
$media->displayGroups = $this->displayGroupFactory->getByMediaId($media->mediaId);
}
/**
* @param ParsePermissionEntityEvent $event
* @return void
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onParsePermissions(ParsePermissionEntityEvent $event)
{
$this->getLogger()->debug('onParsePermissions');
$event->setObject($this->displayGroupFactory->getById($event->getObjectId()));
}
/**
* @param FolderMovingEvent $event
* @return void
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onFolderMoving(FolderMovingEvent $event)
{
$folder = $event->getFolder();
$newFolder = $event->getNewFolder();
foreach ($this->displayGroupFactory->getbyFolderId($folder->getId()) as $displayGroup) {
$displayGroup->folderId = $newFolder->getId();
$displayGroup->permissionsFolderId = $newFolder->getPermissionFolderIdOrThis();
$displayGroup->updateFolders('displaygroup');
}
}
/**
* @param TagDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @return void
*/
public function onTagDelete(TagDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher): void
{
$displays = $this->storageService->select('
SELECT lktagdisplaygroup.displayGroupId
FROM `lktagdisplaygroup`
INNER JOIN `displaygroup`
ON `lktagdisplaygroup`.displayGroupId = `displaygroup`.displayGroupId
AND `displaygroup`.isDisplaySpecific = 1
WHERE `lktagdisplaygroup`.tagId = :tagId', [
'tagId' => $event->getTagId()
]);
$this->storageService->update(
'DELETE FROM `lktagdisplaygroup` WHERE `lktagdisplaygroup`.tagId = :tagId',
['tagId' => $event->getTagId()]
);
if (count($displays) > 0) {
$dispatcher->dispatch(
new TriggerTaskEvent('\Xibo\XTR\MaintenanceRegularTask', 'DYNAMIC_DISPLAY_GROUP_ASSESSED'),
TriggerTaskEvent::$NAME
);
}
}
/**
* Update dynamic display groups' dynamicCriteriaTags when a tag is edited from the tag administration.
*
* @param TagEditEvent $event
* @return void
* @throws NotFoundException
* @throws GeneralException
*/
public function onTagEdit(TagEditEvent $event): void
{
// Retrieve all dynamic display groups
$displayGroups = $this->displayGroupFactory->getByIsDynamic(1);
foreach ($displayGroups as $displayGroup) {
// Convert the tag string into an array for easier processing
$tags = explode(',', $displayGroup->dynamicCriteriaTags);
$displayGroup->setDisplayFactory($this->displayFactory);
foreach ($tags as &$tag) {
$tag = trim($tag);
// Split tag into name and value (e.g. "tagName|tagValue")
$parts = explode('|', $tag, 2);
$tagName = $parts[0];
$tagValue = $parts[1] ?? null;
// If tag name matches the old tag, update the name while keeping the value (if any)
if ($tagName == $event->getOldTag()) {
$tagName = $event->getNewTag();
$tag = $tagValue !== null ? $tagName . '|' . $tagValue : $tagName;
$displayGroup->dynamicCriteriaTags = implode(',', $tags);
$displayGroup->save();
}
}
}
}
}

View File

@@ -0,0 +1,271 @@
<?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\Listener;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\Layout;
use Xibo\Entity\Region;
use Xibo\Event\DisplayGroupLoadEvent;
use Xibo\Event\LayoutOwnerChangeEvent;
use Xibo\Event\LayoutSharingChangeEvent;
use Xibo\Event\MediaDeleteEvent;
use Xibo\Event\MediaFullLoadEvent;
use Xibo\Event\PlaylistDeleteEvent;
use Xibo\Event\RegionAddedEvent;
use Xibo\Event\TagDeleteEvent;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\LayoutFactory;
use Xibo\Factory\PermissionFactory;
use Xibo\Storage\StorageServiceInterface;
/**
* Layout events
*/
class LayoutListener
{
use ListenerLoggerTrait;
/**
* @param LayoutFactory $layoutFactory
* @param StorageServiceInterface $storageService
* @param \Xibo\Factory\PermissionFactory $permissionFactory
*/
public function __construct(
private readonly LayoutFactory $layoutFactory,
private readonly StorageServiceInterface $storageService,
private readonly PermissionFactory $permissionFactory
) {
}
/**
* @param EventDispatcherInterface $dispatcher
* @return $this
*/
public function registerWithDispatcher(EventDispatcherInterface $dispatcher) : LayoutListener
{
$dispatcher->addListener(MediaDeleteEvent::$NAME, [$this, 'onMediaDelete']);
$dispatcher->addListener(UserDeleteEvent::$NAME, [$this, 'onUserDelete']);
$dispatcher->addListener(DisplayGroupLoadEvent::$NAME, [$this, 'onDisplayGroupLoad']);
$dispatcher->addListener(MediaFullLoadEvent::$NAME, [$this, 'onMediaLoad']);
$dispatcher->addListener(LayoutOwnerChangeEvent::$NAME, [$this, 'onOwnerChange']);
$dispatcher->addListener(TagDeleteEvent::$NAME, [$this, 'onTagDelete']);
$dispatcher->addListener(PlaylistDeleteEvent::$NAME, [$this, 'onPlaylistDelete']);
$dispatcher->addListener(LayoutSharingChangeEvent::$NAME, [$this, 'onLayoutSharingChange']);
$dispatcher->addListener(RegionAddedEvent::$NAME, [$this, 'onRegionAdded']);
return $this;
}
/**
* @param MediaDeleteEvent $event
* @return void
* @throws \Xibo\Support\Exception\GeneralException
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onMediaDelete(MediaDeleteEvent $event)
{
$media = $event->getMedia();
$parentMedia = $event->getParentMedia();
foreach ($this->layoutFactory->getByBackgroundImageId($media->mediaId) as $layout) {
if ($media->mediaType == 'image' && $parentMedia != null) {
$this->getLogger()->debug(sprintf(
'Updating layouts with the old media %d as the background image.',
$media->mediaId
));
$this->getLogger()->debug(sprintf(
'Found layout that needs updating. ID = %d. Setting background image id to %d',
$layout->layoutId,
$parentMedia->mediaId
));
$layout->backgroundImageId = $parentMedia->mediaId;
} else {
$layout->backgroundImageId = null;
}
$layout->save(Layout::$saveOptionsMinimum);
}
// do we have any full screen Layout linked to this Media item?
$linkedLayout = $this->layoutFactory->getLinkedFullScreenLayout('media', $media->mediaId);
if (!empty($linkedLayout)) {
$linkedLayout->delete();
}
}
/**
* @param UserDeleteEvent $event
* @return void
* @throws \Xibo\Support\Exception\GeneralException
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onUserDelete(UserDeleteEvent $event)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
if ($function === 'delete') {
// Delete any Layouts
foreach ($this->layoutFactory->getByOwnerId($user->userId) as $layout) {
$layout->delete();
}
} else if ($function === 'reassignAll') {
// Reassign layouts, regions, region Playlists and Widgets.
foreach ($this->layoutFactory->getByOwnerId($user->userId) as $layout) {
$layout->setOwner($newUser->userId, true);
$layout->save(['notify' => false, 'saveTags' => false, 'setBuildRequired' => false]);
}
} else if ($function === 'countChildren') {
$layouts = $this->layoutFactory->getByOwnerId($user->userId);
$count = count($layouts);
$this->getLogger()->debug(
sprintf(
'Counted Children Layouts on User ID %d, there are %d',
$user->userId,
$count
)
);
$event->setReturnValue($event->getReturnValue() + $count);
}
}
/**
* @param DisplayGroupLoadEvent $event
* @return void
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onDisplayGroupLoad(DisplayGroupLoadEvent $event)
{
$displayGroup = $event->getDisplayGroup();
$displayGroup->layouts = ($displayGroup->displayGroupId != null)
? $this->layoutFactory->getByDisplayGroupId($displayGroup->displayGroupId)
: [];
}
/**
* @param MediaFullLoadEvent $event
* @return void
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onMediaLoad(MediaFullLoadEvent $event)
{
$media = $event->getMedia();
$media->layoutBackgroundImages = $this->layoutFactory->getByBackgroundImageId($media->mediaId);
}
/**
* @param LayoutOwnerChangeEvent $event
* @return void
* @throws \Xibo\Support\Exception\GeneralException
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onOwnerChange(LayoutOwnerChangeEvent $event)
{
$campaignId = $event->getCampaignId();
$ownerId = $event->getOwnerId();
foreach ($this->layoutFactory->getByCampaignId($campaignId, true, true) as $layout) {
$layout->setOwner($ownerId, true);
$layout->save(['notify' => false]);
}
}
/**
* @param TagDeleteEvent $event
* @return void
*/
public function onTagDelete(TagDeleteEvent $event)
{
$this->storageService->update(
'DELETE FROM `lktaglayout` WHERE `lktaglayout`.tagId = :tagId',
['tagId' => $event->getTagId()]
);
}
/**
* @param PlaylistDeleteEvent $event
* @return void
*/
public function onPlaylistDelete(PlaylistDeleteEvent $event)
{
$playlist = $event->getPlaylist();
// do we have any full screen Layout linked to this playlist?
$layout = $this->layoutFactory->getLinkedFullScreenLayout('playlist', $playlist->playlistId);
if (!empty($layout)) {
$layout->delete();
}
}
/**
* @param \Xibo\Event\LayoutSharingChangeEvent $event
* @return void
* @throws \Xibo\Support\Exception\GeneralException
*/
public function onLayoutSharingChange(LayoutSharingChangeEvent $event): void
{
// Check to see if this Campaign has any Canvas regions
$layouts = $this->layoutFactory->getByCampaignId($event->getCampaignId(), false, true);
foreach ($layouts as $layout) {
$layout->load([
'loadPlaylists' => false,
'loadPermissions' => false,
'loadCampaigns' => false,
'loadActions' => false,
]);
foreach ($layout->regions as $region) {
if ($region->type === 'canvas') {
$event->addCanvasRegionId($region->getId());
}
}
}
}
/**
* @param \Xibo\Event\RegionAddedEvent $event
* @return void
* @throws \Xibo\Support\Exception\InvalidArgumentException
*/
public function onRegionAdded(RegionAddedEvent $event): void
{
if ($event->getRegion()->type === 'canvas') {
// Set this layout's permissions on the canvas region
$entityId = $this->permissionFactory->getEntityId(Region::class);
foreach ($event->getLayout()->permissions as $permission) {
$new = clone $permission;
$new->entityId = $entityId;
$new->objectId = $event->getRegion()->getId();
$new->save();
}
}
}
}

View File

@@ -0,0 +1,49 @@
<?php
/*
* Copyright (C) 2023 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\Listener;
use Xibo\Service\ConfigServiceInterface;
trait ListenerConfigTrait
{
/** @var ConfigServiceInterface */
private $config;
/**
* @param ConfigServiceInterface $config
* @return $this
*/
public function useConfig(ConfigServiceInterface $config)
{
$this->config = $config;
return $this;
}
/**
* @return ConfigServiceInterface
*/
protected function getConfig()
{
return $this->config;
}
}

View File

@@ -0,0 +1,53 @@
<?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\Listener;
use Psr\Log\NullLogger;
trait ListenerLoggerTrait
{
/** @var \Psr\Log\LoggerInterface */
private $logger;
/**
* @param \Psr\Log\LoggerInterface $logger
* @return $this
*/
public function useLogger($logger)
{
$this->logger = $logger;
return $this;
}
/**
* @return \Psr\Log\LoggerInterface
*/
protected function getLogger()
{
if ($this->logger === null) {
$this->logger = new NullLogger();
}
return $this->logger;
}
}

View File

@@ -0,0 +1,194 @@
<?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\Listener;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Event\DisplayGroupLoadEvent;
use Xibo\Event\FolderMovingEvent;
use Xibo\Event\MediaDeleteEvent;
use Xibo\Event\ParsePermissionEntityEvent;
use Xibo\Event\TagDeleteEvent;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\MediaFactory;
use Xibo\Storage\StorageServiceInterface;
use Xibo\Support\Exception\NotFoundException;
/**
* Media events
*/
class MediaListener
{
use ListenerLoggerTrait;
/**
* @var MediaFactory
*/
private $mediaFactory;
/**
* @var StorageServiceInterface
*/
private $storageService;
/**
* @param MediaFactory $mediaFactory
* @param StorageServiceInterface $storageService
*/
public function __construct(
MediaFactory $mediaFactory,
StorageServiceInterface $storageService
) {
$this->mediaFactory = $mediaFactory;
$this->storageService = $storageService;
}
/**
* @param EventDispatcherInterface $dispatcher
* @return $this
*/
public function registerWithDispatcher(EventDispatcherInterface $dispatcher) : MediaListener
{
$dispatcher->addListener(UserDeleteEvent::$NAME, [$this, 'onUserDelete']);
$dispatcher->addListener(DisplayGroupLoadEvent::$NAME, [$this, 'onDisplayGroupLoad']);
$dispatcher->addListener(ParsePermissionEntityEvent::$NAME . 'media', [$this, 'onParsePermissions']);
$dispatcher->addListener(FolderMovingEvent::$NAME, [$this, 'onFolderMoving']);
$dispatcher->addListener(TagDeleteEvent::$NAME, [$this, 'onTagDelete']);
return $this;
}
/**
* @param UserDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @return void
* @throws NotFoundException
* @throws \Xibo\Support\Exception\ConfigurationException
* @throws \Xibo\Support\Exception\DuplicateEntityException
* @throws \Xibo\Support\Exception\GeneralException
* @throws \Xibo\Support\Exception\InvalidArgumentException
*/
public function onUserDelete(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
// Delete any media
foreach ($this->mediaFactory->getByOwnerId($user->userId, 1) as $media) {
// If there is a parent, bring it back
try {
$parentMedia = $this->mediaFactory->getParentById($media->mediaId);
$parentMedia->isEdited = 0;
$parentMedia->parentId = null;
$parentMedia->save(['validate' => false]);
} catch (NotFoundException $e) {
// This is fine, no parent
$parentMedia = null;
}
// if this User owns any module files, reassign to systemUser instead of deleting.
if ($media->mediaType === 'module') {
$media->setOwner($systemUser->userId);
$media->save();
} else {
$dispatcher->dispatch(new MediaDeleteEvent($media, $parentMedia, true), MediaDeleteEvent::$NAME);
$media->delete();
}
}
} else if ($function === 'reassignAll') {
foreach ($this->mediaFactory->getByOwnerId($user->userId, 1) as $media) {
($media->mediaType === 'module') ? $media->setOwner($systemUser->userId) : $media->setOwner($newUser->getOwnerId());
$media->save();
}
} else if ($function === 'countChildren') {
$media = $this->mediaFactory->getByOwnerId($user->userId, 1);
$count = count($media);
$this->getLogger()->debug(
sprintf(
'Counted Children Media on User ID %d, there are %d',
$user->userId,
$count
)
);
$event->setReturnValue($event->getReturnValue() + $count);
}
}
/**
* @param DisplayGroupLoadEvent $event
* @return void
* @throws NotFoundException
*/
public function onDisplayGroupLoad(DisplayGroupLoadEvent $event)
{
$displayGroup = $event->getDisplayGroup();
$displayGroup->media = ($displayGroup->displayGroupId != null)
? $this->mediaFactory->getByDisplayGroupId($displayGroup->displayGroupId)
: [];
}
/**
* @param ParsePermissionEntityEvent $event
* @return void
* @throws NotFoundException
*/
public function onParsePermissions(ParsePermissionEntityEvent $event)
{
$this->getLogger()->debug('onParsePermissions');
$event->setObject($this->mediaFactory->getById($event->getObjectId()));
}
/**
* @param FolderMovingEvent $event
* @return void
* @throws NotFoundException
*/
public function onFolderMoving(FolderMovingEvent $event)
{
$folder = $event->getFolder();
$newFolder = $event->getNewFolder();
foreach ($this->mediaFactory->getByFolderId($folder->getId()) as $media) {
$media->folderId = $newFolder->getId();
$media->permissionsFolderId = $newFolder->getPermissionFolderIdOrThis();
$media->updateFolders('media');
}
}
/**
* @param TagDeleteEvent $event
* @return void
*/
public function onTagDelete(TagDeleteEvent $event)
{
$this->storageService->update(
'DELETE FROM `lktagmedia` WHERE `lktagmedia`.tagId = :tagId',
['tagId' => $event->getTagId()]
);
}
}

View File

@@ -0,0 +1,167 @@
<?php
/*
* Copyright (C) 2023 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\Listener;
use Carbon\Carbon;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Event\MenuBoardCategoryRequest;
use Xibo\Event\MenuBoardModifiedDtRequest;
use Xibo\Event\MenuBoardProductRequest;
use Xibo\Factory\MenuBoardCategoryFactory;
use Xibo\Factory\MenuBoardFactory;
use Xibo\Support\Exception\NotFoundException;
/**
* Listener for dealing with a menu board provider
*/
class MenuBoardProviderListener
{
use ListenerLoggerTrait;
private MenuBoardFactory $menuBoardFactory;
private MenuBoardCategoryFactory $menuBoardCategoryFactory;
public function __construct(MenuBoardFactory $menuBoardFactory, MenuBoardCategoryFactory $menuBoardCategoryFactory)
{
$this->menuBoardFactory = $menuBoardFactory;
$this->menuBoardCategoryFactory = $menuBoardCategoryFactory;
}
public function registerWithDispatcher(EventDispatcherInterface $dispatcher): MenuBoardProviderListener
{
$dispatcher->addListener(MenuBoardProductRequest::$NAME, [$this, 'onProductRequest']);
$dispatcher->addListener(MenuBoardCategoryRequest::$NAME, [$this, 'onCategoryRequest']);
$dispatcher->addListener(MenuBoardModifiedDtRequest::$NAME, [$this, 'onModifiedDtRequest']);
return $this;
}
public function onProductRequest(MenuBoardProductRequest $event): void
{
$this->getLogger()->debug('onProductRequest: data source is ' . $event->getDataProvider()->getDataSource());
$dataProvider = $event->getDataProvider();
$menuId = $dataProvider->getProperty('menuId', 0);
if (empty($menuId)) {
$this->getLogger()->debug('onProductRequest: no menuId.');
return;
}
// Sorting
$desc = $dataProvider->getProperty('sortDescending') == 1 ? ' DESC' : '';
$sort = match ($dataProvider->getProperty('sortField')) {
'name' => '`name`' . $desc,
'price' => '`price`' . $desc,
'id' => '`menuProductId`' . $desc,
default => '`displayOrder`' . $desc,
};
// Build a filter
$filter = [
'menuId' => $menuId,
];
$categoryId = $dataProvider->getProperty('categoryId');
$this->getLogger()->debug('onProductRequest: $categoryId: ' . $categoryId);
if ($categoryId !== null && $categoryId !== '') {
$filter['menuCategoryId'] = intval($categoryId);
}
// Show Unavailable?
if ($dataProvider->getProperty('showUnavailable', 0) === 0) {
$filter['availability'] = 1;
}
// limits?
$lowerLimit = $dataProvider->getProperty('lowerLimit', 0);
$upperLimit = $dataProvider->getProperty('upperLimit', 0);
if ($lowerLimit !== 0 || $upperLimit !== 0) {
// Start should be the lower limit
// Size should be the distance between upper and lower
$filter['start'] = $lowerLimit;
$filter['length'] = $upperLimit - $lowerLimit;
$this->getLogger()->debug('onProductRequest: applied limits, start: '
. $filter['start'] . ', length: ' . $filter['length']);
}
$products = $this->menuBoardCategoryFactory->getProductData([$sort], $filter);
foreach ($products as $menuBoardProduct) {
$menuBoardProduct->productOptions = $menuBoardProduct->getOptions();
$product = $menuBoardProduct->toProduct();
// Convert the image to a library image?
if ($product->image !== null) {
// The content is the ID of the image
try {
$product->image = $dataProvider->addLibraryFile(intval($product->image));
} catch (NotFoundException $notFoundException) {
$this->getLogger()->error('onProductRequest: Invalid library media reference: ' . $product->image);
$product->image = null;
}
}
$dataProvider->addItem($product);
}
$dataProvider->setIsHandled();
}
public function onCategoryRequest(MenuBoardCategoryRequest $event): void
{
$this->getLogger()->debug('onCategoryRequest: data source is ' . $event->getDataProvider()->getDataSource());
$dataProvider = $event->getDataProvider();
$menuId = $dataProvider->getProperty('menuId', 0);
if (empty($menuId)) {
$this->getLogger()->debug('onCategoryRequest: no menuId.');
return;
}
$categoryId = $dataProvider->getProperty('categoryId', 0);
if (empty($categoryId)) {
$this->getLogger()->debug('onCategoryRequest: no categoryId.');
return;
}
$category = $this->menuBoardCategoryFactory->getById($categoryId)->toProductCategory();
// Convert the image to a library image?
if ($category->image !== null) {
// The content is the ID of the image
try {
$category->image = $dataProvider->addLibraryFile(intval($category->image));
} catch (NotFoundException $notFoundException) {
$this->getLogger()->error('onCategoryRequest: Invalid library media reference: ' . $category->image);
$category->image = null;
}
}
$dataProvider->addItem($category);
$dataProvider->setIsHandled();
}
public function onModifiedDtRequest(MenuBoardModifiedDtRequest $event): void
{
$menu = $this->menuBoardFactory->getById($event->getDataSetId());
$event->setModifiedDt(Carbon::createFromTimestamp($menu->modifiedDt));
}
}

View File

@@ -0,0 +1,58 @@
<?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\Listener;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Event\ParsePermissionEntityEvent;
use Xibo\Factory\ModuleTemplateFactory;
class ModuleTemplateListener
{
use ListenerLoggerTrait;
/** @var ModuleTemplateFactory */
private ModuleTemplateFactory $moduleTemplateFactory;
public function __construct(ModuleTemplateFactory $moduleTemplateFactory)
{
$this->moduleTemplateFactory = $moduleTemplateFactory;
}
public function registerWithDispatcher(EventDispatcherInterface $dispatcher): ModuleTemplateListener
{
$dispatcher->addListener(ParsePermissionEntityEvent::$NAME . 'moduleTemplate', [$this, 'onParsePermissions']);
return $this;
}
/**
* @param ParsePermissionEntityEvent $event
* @return void
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onParsePermissions(ParsePermissionEntityEvent $event)
{
$this->getLogger()->debug('onParsePermissions');
$event->setObject($this->moduleTemplateFactory->getUserTemplateById($event->getObjectId()));
}
}

View File

@@ -0,0 +1,131 @@
<?php
/*
* Copyright (C) 2023 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\Listener;
use Carbon\Carbon;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Event\NotificationCacheKeyRequestEvent;
use Xibo\Event\NotificationDataRequestEvent;
use Xibo\Event\NotificationModifiedDtRequestEvent;
use Xibo\Factory\NotificationFactory;
use Xibo\Service\ConfigServiceInterface;
use Xibo\Widget\Provider\DataProviderInterface;
/**
* Listens to request for data from Notification.
*/
class NotificationDataProviderListener
{
use ListenerLoggerTrait;
/** @var \Xibo\Service\ConfigServiceInterface */
private $config;
/** @var \Xibo\Factory\NotificationFactory */
private $notificationFactory;
/**
* @var User
*/
private $user;
public function __construct(
ConfigServiceInterface $config,
NotificationFactory $notificationFactory,
User $user
) {
$this->config = $config;
$this->notificationFactory = $notificationFactory;
$this->user = $user;
}
public function registerWithDispatcher(EventDispatcherInterface $dispatcher): NotificationDataProviderListener
{
$dispatcher->addListener(NotificationDataRequestEvent::$NAME, [$this, 'onDataRequest']);
$dispatcher->addListener(NotificationModifiedDtRequestEvent::$NAME, [$this, 'onModifiedDtRequest']);
return $this;
}
public function onDataRequest(NotificationDataRequestEvent $event)
{
$this->getData($event->getDataProvider());
}
public function getData(DataProviderInterface $dataProvider)
{
$age = $dataProvider->getProperty('age', 0);
$filter = [
'releaseDt' => ($age === 0) ? null : Carbon::now()->subMinutes($age)->unix(),
'onlyReleased' => 1,
];
if ($dataProvider->isPreview()) {
$filter['userId'] = $this->user->getId();
} else {
$filter['displayId'] = $dataProvider->getDisplayId();
}
$sort = ['releaseDt DESC', 'createDt DESC', 'subject'];
$notifications = $this->notificationFactory->query($sort, $filter);
foreach ($notifications as $notification) {
$item = [];
$item['subject'] = $notification->subject;
$item['body'] = strip_tags($notification->body);
$item['date'] = Carbon::createFromTimestamp($notification->releaseDt)->format('c');
$item['createdAt'] = Carbon::createFromTimestamp($notification->createDt)->format('c');
$dataProvider->addItem($item);
}
$dataProvider->setIsHandled();
}
public function onModifiedDtRequest(NotificationModifiedDtRequestEvent $event)
{
$this->getLogger()->debug('onModifiedDtRequest');
// Get the latest notification according to the filter provided.
$displayId = $event->getDisplayId();
// If we're a user, we should always refresh
if ($displayId === 0) {
$event->setModifiedDt(Carbon::maxValue());
return;
}
$notifications = $this->notificationFactory->query(['releaseDt DESC'], [
'onlyReleased' => 1,
'displayId' => $displayId,
'length' => 1,
]);
if (count($notifications) > 0) {
$event->setModifiedDt(Carbon::createFromTimestamp($notifications[0]->releaseDt));
}
}
}

View File

@@ -0,0 +1,53 @@
<?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\Listener;
use Xibo\Event\CommandDeleteEvent;
use Xibo\Factory\DisplayProfileFactory;
class OnCommandDelete
{
/**
* @var DisplayProfileFactory
*/
private $displayProfileFactory;
public function __construct(DisplayProfileFactory $displayProfileFactory)
{
$this->displayProfileFactory = $displayProfileFactory;
}
/**
* @throws \Xibo\Support\Exception\NotFoundException
* @throws \Xibo\Support\Exception\InvalidArgumentException
*/
public function __invoke(CommandDeleteEvent $event)
{
$command = $event->getCommand();
foreach ($this->displayProfileFactory->getByCommandId($command->commandId) as $displayProfile) {
$displayProfile->unassignCommand($command);
$displayProfile->save(['validate' => false]);
}
}
}

View File

@@ -0,0 +1,47 @@
<?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\Listener\OnDisplayGroupLoad;
use Xibo\Event\DisplayGroupLoadEvent;
use Xibo\Factory\DisplayFactory;
class DisplayGroupDisplayListener
{
/**
* @var DisplayFactory
*/
private $displayFactory;
public function __construct(DisplayFactory $displayFactory)
{
$this->displayFactory = $displayFactory;
}
public function __invoke(DisplayGroupLoadEvent $event)
{
$displayGroup = $event->getDisplayGroup();
$displayGroup->setDisplayFactory($this->displayFactory);
$displayGroup->displays = $this->displayFactory->getByDisplayGroupId($displayGroup->displayGroupId);
}
}

View File

@@ -0,0 +1,48 @@
<?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\Listener\OnDisplayGroupLoad;
use Xibo\Event\DisplayGroupLoadEvent;
use Xibo\Factory\ScheduleFactory;
class DisplayGroupScheduleListener
{
/**
* @var ScheduleFactory
*/
private $scheduleFactory;
public function __construct(ScheduleFactory $scheduleFactory)
{
$this->scheduleFactory = $scheduleFactory;
}
public function __invoke(DisplayGroupLoadEvent $event)
{
$displayGroup = $event->getDisplayGroup();
$displayGroup->events = ($displayGroup->displayGroupId != null)
? $this->scheduleFactory->getByDisplayGroupId($displayGroup->displayGroupId)
: [];
}
}

View File

@@ -0,0 +1,50 @@
<?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\Listener\OnFolderMoving;
use Xibo\Event\FolderMovingEvent;
use Xibo\Factory\DataSetFactory;
class DataSetListener
{
/**
* @var DataSetFactory
*/
private $dataSetFactory;
public function __construct(DataSetFactory $dataSetFactory)
{
$this->dataSetFactory = $dataSetFactory;
}
public function __invoke(FolderMovingEvent $event)
{
$folder = $event->getFolder();
$newFolder = $event->getNewFolder();
foreach ($this->dataSetFactory->getByFolderId($folder->getId()) as $dataSet) {
$dataSet->folderId = $newFolder->getId();
$dataSet->permissionsFolderId = $newFolder->getPermissionFolderIdOrThis();
$dataSet->updateFolders('dataset');
}
}
}

View File

@@ -0,0 +1,55 @@
<?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\Listener\OnFolderMoving;
use Xibo\Event\FolderMovingEvent;
use Xibo\Factory\FolderFactory;
class FolderListener
{
/**
* @var FolderFactory
*/
private $folderFactory;
public function __construct(FolderFactory $folderFactory)
{
$this->folderFactory = $folderFactory;
}
public function __invoke(FolderMovingEvent $event)
{
$merge = $event->getIsMerge();
if ($merge) {
$folder = $event->getFolder();
$newFolder = $event->getNewFolder();
// on merge we delete the original Folder and move its content to the new selected folder
// sub-folders are moved to their new parent as well
foreach (array_filter(explode(',', $folder->children)) as $childFolderId) {
$childFolder = $this->folderFactory->getById($childFolderId, 0);
$childFolder->updateFoldersAfterMove($folder->getId(), $newFolder->getId());
}
}
}
}

View File

@@ -0,0 +1,50 @@
<?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\Listener\OnFolderMoving;
use Xibo\Event\FolderMovingEvent;
use Xibo\Factory\MenuBoardFactory;
class MenuBoardListener
{
/**
* @var MenuBoardFactory
*/
private $menuBoardFactory;
public function __construct(MenuBoardFactory $menuBoardFactory)
{
$this->menuBoardFactory = $menuBoardFactory;
}
public function __invoke(FolderMovingEvent $event)
{
$folder = $event->getFolder();
$newFolder = $event->getNewFolder();
foreach ($this->menuBoardFactory->getbyFolderId($folder->getId()) as $menuBoard) {
$menuBoard->folderId = $newFolder->getId();
$menuBoard->permissionsFolderId = $newFolder->getPermissionFolderIdOrThis();
$menuBoard->updateFolders('menu_board');
}
}
}

View File

@@ -0,0 +1,58 @@
<?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\Listener\OnFolderMoving;
use Xibo\Event\FolderMovingEvent;
use Xibo\Factory\UserFactory;
use Xibo\Storage\StorageServiceInterface;
class UserListener
{
/**
* @var UserFactory
*/
private $userFactory;
/**
* @var StorageServiceInterface
*/
private $store;
public function __construct(UserFactory $userFactory, StorageServiceInterface $store)
{
$this->userFactory = $userFactory;
$this->store = $store;
}
public function __invoke(FolderMovingEvent $event)
{
$folder = $event->getFolder();
$newFolder = $event->getNewFolder();
foreach ($this->userFactory->getByHomeFolderId($folder->getId()) as $user) {
$this->store->update('UPDATE `user` SET homeFolderId = :newFolderId WHERE homeFolderId = :oldFolderId AND userId = :userId', [
'newFolderId' => $newFolder->getId(),
'oldFolderId' => $folder->getId(),
'userId' => $user->getId()
]);
}
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Xibo\Listener\OnGettingDependencyFileSize;
use Xibo\Event\DependencyFileSizeEvent;
use Xibo\Factory\FontFactory;
class FontsListener
{
/**
* @var FontFactory
*/
private $fontFactory;
public function __construct(FontFactory $fontFactory)
{
$this->fontFactory = $fontFactory;
}
public function __invoke(DependencyFileSizeEvent $event)
{
$fontsSize = $this->fontFactory->getFontsSizeAndCount();
$event->addResult([
'SumSize' => $fontsSize['SumSize'],
'type' => 'font',
'count' => $fontsSize['totalCount']
]);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Xibo\Listener\OnGettingDependencyFileSize;
use Xibo\Event\DependencyFileSizeEvent;
use Xibo\Factory\PlayerVersionFactory;
class PlayerVersionListener
{
/**
* @var PlayerVersionFactory
*/
private $playerVersionFactory;
public function __construct(PlayerVersionFactory $playerVersionFactory)
{
$this->playerVersionFactory = $playerVersionFactory;
}
public function __invoke(DependencyFileSizeEvent $event)
{
$versionSize = $this->playerVersionFactory->getSizeAndCount();
$event->addResult([
'SumSize' => $versionSize['SumSize'],
'type' => 'playersoftware',
'count' => $versionSize['totalCount']
]);
}
}

View File

@@ -0,0 +1,49 @@
<?php
/*
* Copyright (C) 2023 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\Listener\OnGettingDependencyFileSize;
use Xibo\Event\DependencyFileSizeEvent;
use Xibo\Factory\SavedReportFactory;
class SavedReportListener
{
/**
* @var SavedReportFactory
*/
private $savedReportFactory;
public function __construct(SavedReportFactory $savedReportFactory)
{
$this->savedReportFactory = $savedReportFactory;
}
public function __invoke(DependencyFileSizeEvent $event)
{
$versionSize = $this->savedReportFactory->getSizeAndCount();
$event->addResult([
'SumSize' => $versionSize['SumSize'],
'type' => 'savedreport',
'count' => $versionSize['totalCount']
]);
}
}

View File

@@ -0,0 +1,60 @@
<?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\Listener\OnMediaDelete;
use Xibo\Event\MediaDeleteEvent;
use Xibo\Factory\MenuBoardCategoryFactory;
use Xibo\Listener\ListenerLoggerTrait;
use Xibo\Support\Exception\InvalidArgumentException;
class MenuBoardListener
{
use ListenerLoggerTrait;
/** @var MenuBoardCategoryFactory */
private $menuBoardCategoryFactory;
public function __construct($menuBoardCategoryFactory)
{
$this->menuBoardCategoryFactory = $menuBoardCategoryFactory;
}
/**
* @param MediaDeleteEvent $event
* @throws InvalidArgumentException
*/
public function __invoke(MediaDeleteEvent $event)
{
$media = $event->getMedia();
foreach ($this->menuBoardCategoryFactory->query(null, ['mediaId' => $media->mediaId]) as $category) {
$category->mediaId = null;
$category->save();
}
foreach ($this->menuBoardCategoryFactory->getProductData(null, ['mediaId' => $media->mediaId]) as $product) {
$product->mediaId = null;
$product->save();
}
}
}

View File

@@ -0,0 +1,62 @@
<?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\Listener\OnMediaDelete;
use Carbon\Carbon;
use Xibo\Event\MediaDeleteEvent;
use Xibo\Helper\DateFormatHelper;
use Xibo\Listener\ListenerLoggerTrait;
use Xibo\Service\ConfigServiceInterface;
use Xibo\Storage\StorageServiceInterface;
class PurgeListListener
{
use ListenerLoggerTrait;
/**
* @var StorageServiceInterface
*/
private $store;
/**
* @var ConfigServiceInterface
*/
private $configService;
public function __construct(StorageServiceInterface $store, ConfigServiceInterface $configService)
{
$this->store = $store;
$this->configService = $configService;
}
public function __invoke(MediaDeleteEvent $event)
{
// storedAs
if ($event->isSetToPurge()) {
$this->store->insert('INSERT INTO `purge_list` (mediaId, storedAs, expiryDate) VALUES (:mediaId, :storedAs, :expiryDate)', [
'mediaId' => $event->getMedia()->mediaId,
'storedAs' => $event->getMedia()->storedAs,
'expiryDate' => Carbon::now()->addDays($this->configService->getSetting('DEFAULT_PURGE_LIST_TTL'))->format(DateFormatHelper::getSystemFormat())
]);
}
}
}

View File

@@ -0,0 +1,97 @@
<?php
/*
* Copyright (C) 2023 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\Listener\OnMediaDelete;
use Xibo\Event\MediaDeleteEvent;
use Xibo\Factory\ModuleFactory;
use Xibo\Factory\WidgetFactory;
use Xibo\Listener\ListenerLoggerTrait;
use Xibo\Storage\StorageServiceInterface;
class WidgetListener
{
use ListenerLoggerTrait;
/** @var WidgetFactory */
private $widgetFactory;
/** @var \Xibo\Factory\ModuleFactory */
private $moduleFactory;
/** @var StorageServiceInterface */
private $storageService;
public function __construct(
StorageServiceInterface $storageService,
WidgetFactory $widgetFactory,
ModuleFactory $moduleFactory
) {
$this->storageService = $storageService;
$this->widgetFactory = $widgetFactory;
$this->moduleFactory = $moduleFactory;
}
/**
* @param MediaDeleteEvent $event
* @throws \Xibo\Support\Exception\GeneralException
*/
public function __invoke(MediaDeleteEvent $event)
{
$media = $event->getMedia();
$parentMedia = $event->getParentMedia();
foreach ($this->widgetFactory->getByMediaId($media->mediaId) as $widget) {
$widget->unassignMedia($media->mediaId);
if ($parentMedia != null) {
// Assign the parent media to the widget instead
$widget->assignMedia($parentMedia->mediaId);
// Swap any audio nodes over to this new widget media assignment.
$this->storageService->update('
UPDATE `lkwidgetaudio` SET mediaId = :mediaId WHERE widgetId = :widgetId AND mediaId = :oldMediaId
', [
'mediaId' => $parentMedia->mediaId,
'widgetId' => $widget->widgetId,
'oldMediaId' => $media->mediaId
]);
} else {
// Also delete the `lkwidgetaudio`
foreach ($widget->audio as $audio) {
$widget->unassignAudioById($audio->mediaId);
$audio->delete();
}
}
// This action might result in us deleting a widget (unless we are a temporary file with an expiry date)
if ($media->mediaType != 'module'
&& $this->moduleFactory->getByType($widget->type)->regionSpecific === 0
&& count($widget->mediaIds) <= 0
) {
$widget->delete();
} else {
$widget->save(['saveWidgetOptions' => false]);
}
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Xibo\Listener\OnMediaLoad;
use Xibo\Event\MediaFullLoadEvent;
use Xibo\Factory\WidgetFactory;
class WidgetListener
{
/**
* @var WidgetFactory
*/
private $widgetFactory;
public function __construct(WidgetFactory $widgetFactory)
{
$this->widgetFactory = $widgetFactory;
}
public function __invoke(MediaFullLoadEvent $event)
{
$media = $event->getMedia();
$media->widgets = $this->widgetFactory->getByMediaId($media->mediaId);
}
}

View File

@@ -0,0 +1,44 @@
<?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\Listener\OnParsePermissions;
use Xibo\Event\ParsePermissionEntityEvent;
use Xibo\Factory\CommandFactory;
class PermissionsCommandListener
{
/**
* @var CommandFactory
*/
private $commandFactory;
public function __construct(CommandFactory $commandFactory)
{
$this->commandFactory = $commandFactory;
}
public function __invoke(ParsePermissionEntityEvent $event)
{
$event->setObject($this->commandFactory->getById($event->getObjectId()));
}
}

View File

@@ -0,0 +1,44 @@
<?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\Listener\OnParsePermissions;
use Xibo\Event\ParsePermissionEntityEvent;
use Xibo\Factory\DataSetFactory;
class PermissionsDataSetListener
{
/**
* @var DataSetFactory
*/
private $dataSetFactory;
public function __construct(DataSetFactory $dataSetFactory)
{
$this->dataSetFactory = $dataSetFactory;
}
public function __invoke(ParsePermissionEntityEvent $event)
{
$event->setObject($this->dataSetFactory->getById($event->getObjectId()));
}
}

View File

@@ -0,0 +1,44 @@
<?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\Listener\OnParsePermissions;
use Xibo\Event\ParsePermissionEntityEvent;
use Xibo\Factory\DayPartFactory;
class PermissionsDayPartListener
{
/**
* @var DayPartFactory
*/
private $dayPartFactory;
public function __construct(DayPartFactory $dayPartFactory)
{
$this->dayPartFactory = $dayPartFactory;
}
public function __invoke(ParsePermissionEntityEvent $event)
{
$event->setObject($this->dayPartFactory->getById($event->getObjectId()));
}
}

View File

@@ -0,0 +1,44 @@
<?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\Listener\OnParsePermissions;
use Xibo\Event\ParsePermissionEntityEvent;
use Xibo\Factory\FolderFactory;
class PermissionsFolderListener
{
/**
* @var FolderFactory
*/
private $folderFactory;
public function __construct(FolderFactory $folderFactory)
{
$this->folderFactory = $folderFactory;
}
public function __invoke(ParsePermissionEntityEvent $event)
{
$event->setObject($this->folderFactory->getById($event->getObjectId()));
}
}

View File

@@ -0,0 +1,44 @@
<?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\Listener\OnParsePermissions;
use Xibo\Event\ParsePermissionEntityEvent;
use Xibo\Factory\MenuBoardFactory;
class PermissionsMenuBoardListener
{
/**
* @var MenuBoardFactory
*/
private $menuBoardFactory;
public function __construct(MenuBoardFactory $menuBoardFactory)
{
$this->menuBoardFactory = $menuBoardFactory;
}
public function __invoke(ParsePermissionEntityEvent $event)
{
$event->setObject($this->menuBoardFactory->getById($event->getObjectId()));
}
}

View File

@@ -0,0 +1,44 @@
<?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\Listener\OnParsePermissions;
use Xibo\Event\ParsePermissionEntityEvent;
use Xibo\Factory\NotificationFactory;
class PermissionsNotificationListener
{
/**
* @var NotificationFactory
*/
private $notificationFactory;
public function __construct(NotificationFactory $notificationFactory)
{
$this->notificationFactory = $notificationFactory;
}
public function __invoke(ParsePermissionEntityEvent $event)
{
$event->setObject($this->notificationFactory->getById($event->getObjectId()));
}
}

View File

@@ -0,0 +1,44 @@
<?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\Listener\OnParsePermissions;
use Xibo\Event\ParsePermissionEntityEvent;
use Xibo\Factory\RegionFactory;
class PermissionsRegionListener
{
/**
* @var RegionFactory
*/
private $regionFactory;
public function __construct(RegionFactory $regionFactory)
{
$this->regionFactory = $regionFactory;
}
public function __invoke(ParsePermissionEntityEvent $event)
{
$event->setObject($this->regionFactory->getById($event->getObjectId()));
}
}

View File

@@ -0,0 +1,44 @@
<?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\Listener\OnParsePermissions;
use Xibo\Event\ParsePermissionEntityEvent;
use Xibo\Factory\WidgetFactory;
class PermissionsWidgetListener
{
/**
* @var WidgetFactory
*/
private $widgetFactory;
public function __construct(WidgetFactory $widgetFactory)
{
$this->widgetFactory = $widgetFactory;
}
public function __invoke(ParsePermissionEntityEvent $event)
{
$event->setObject($this->widgetFactory->getById($event->getObjectId()));
}
}

View File

@@ -0,0 +1,46 @@
<?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\Listener;
use Xibo\Event\PlaylistMaxNumberChangedEvent;
use Xibo\Storage\StorageServiceInterface;
class OnPlaylistMaxNumberChange
{
/**
* @var StorageServiceInterface
*/
private $storageService;
public function __construct(StorageServiceInterface $storageService)
{
$this->storageService = $storageService;
}
public function __invoke(PlaylistMaxNumberChangedEvent $event)
{
$this->storageService->update('UPDATE `playlist` SET maxNumberOfItems = :newLimit WHERE isDynamic = 1 AND maxNumberOfItems > :newLimit', [
'newLimit' => $event->getNewLimit()
]);
}
}

View File

@@ -0,0 +1,60 @@
<?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\Listener;
use Xibo\Event\SystemUserChangedEvent;
use Xibo\Storage\StorageServiceInterface;
class OnSystemUserChange
{
/**
* @var StorageServiceInterface
*/
private $storageService;
public function __construct(StorageServiceInterface $storageService)
{
$this->storageService = $storageService;
}
public function __invoke(SystemUserChangedEvent $event)
{
// Reassign Module files
$this->storageService->update('UPDATE `media` SET userId = :userId WHERE userId = :oldUserId AND type = \'module\'', [
'userId' => $event->getNewSystemUser()->userId,
'oldUserId' => $event->getOldSystemUser()->userId
]);
// Reassign Display specific Display Groups
$this->storageService->update('UPDATE `displaygroup` SET userId = :userId WHERE userId = :oldUserId AND isDisplaySpecific = 1', [
'userId' => $event->getNewSystemUser()->userId,
'oldUserId' => $event->getOldSystemUser()->userId
]);
// Reassign system dayparts
$this->storageService->update('UPDATE `daypart` SET userId = :userId WHERE userId = :oldUserId AND (isCustom = 1 OR isAlways = 1)', [
'userId' => $event->getNewSystemUser()->userId,
'oldUserId' => $event->getOldSystemUser()->userId
]);
}
}

View File

@@ -0,0 +1,94 @@
<?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\Listener\OnUserDelete;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\ActionFactory;
use Xibo\Listener\ListenerLoggerTrait;
use Xibo\Storage\StorageServiceInterface;
class ActionListener implements OnUserDeleteInterface
{
/**
* @var ActionFactory
*/
private $actionFactory;
/**
* @var StorageServiceInterface
*/
private $store;
use ListenerLoggerTrait;
public function __construct(StorageServiceInterface $store, ActionFactory $actionFactory)
{
$this->store = $store;
$this->actionFactory = $actionFactory;
}
public function __invoke(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
$this->deleteChildren($user, $dispatcher, $systemUser);
} elseif ($function === 'reassignAll') {
$this->reassignAllTo($user, $newUser, $systemUser);
} elseif ($function === 'countChildren') {
$event->setReturnValue($event->getReturnValue() + $this->countChildren($user));
}
}
/* @inheritDoc */
public function deleteChildren(User $user, EventDispatcherInterface $dispatcher, User $systemUser)
{
// Delete any Actions
foreach ($this->actionFactory->getByOwnerId($user->userId) as $action) {
$action->delete();
}
}
/* @inheritDoc */
public function reassignAllTo(User $user, User $newUser, User $systemUser)
{
// Reassign Actions
$this->store->update('UPDATE `action` SET ownerId = :userId WHERE ownerId = :oldUserId', [
'userId' => $newUser->userId,
'oldUserId' => $user->userId
]);
}
/* @inheritDoc */
public function countChildren(User $user)
{
$actions = $this->actionFactory->getByOwnerId($user->userId);
$this->getLogger()->debug(sprintf('Counted Children Actions on User ID %d, there are %d', $user->userId, count($actions)));
return count($actions);
}
}

View File

@@ -0,0 +1,91 @@
<?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\Listener\OnUserDelete;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Event\CommandDeleteEvent;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\CommandFactory;
use Xibo\Listener\ListenerLoggerTrait;
use Xibo\Storage\StorageServiceInterface;
class CommandListener implements OnUserDeleteInterface
{
use ListenerLoggerTrait;
/**
* @var CommandFactory
*/
private $commandFactory;
/**
* @var StorageServiceInterface
*/
private $store;
public function __construct(StorageServiceInterface $store, CommandFactory $commandFactory)
{
$this->store = $store;
$this->commandFactory = $commandFactory;
}
public function __invoke(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
$this->deleteChildren($user, $dispatcher, $systemUser);
} elseif ($function === 'reassignAll') {
$this->reassignAllTo($user, $newUser, $systemUser);
} elseif ($function === 'countChildren') {
$event->setReturnValue($event->getReturnValue() + $this->countChildren($user));
}
}
public function deleteChildren(User $user, EventDispatcherInterface $dispatcher, User $systemUser)
{
foreach ($this->commandFactory->getByOwnerId($user->userId) as $command) {
$dispatcher->dispatch(CommandDeleteEvent::$NAME, new CommandDeleteEvent($command));
$command->delete();
}
}
public function reassignAllTo(User $user, User $newUser, User $systemUser)
{
$this->store->update('UPDATE `command` SET userId = :userId WHERE userId = :oldUserId', [
'userId' => $newUser->userId,
'oldUserId' => $user->userId
]);
}
public function countChildren(User $user)
{
$commands = $this->commandFactory->getByOwnerId($user->userId);
$this->getLogger()->debug(sprintf('Counted Children Command on User ID %d, there are %d', $user->userId, count($commands)));
return count($commands);
}
}

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\Listener\OnUserDelete;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\DataSetFactory;
use Xibo\Listener\ListenerLoggerTrait;
use Xibo\Storage\StorageServiceInterface;
class DataSetListener implements OnUserDeleteInterface
{
use ListenerLoggerTrait;
/**
* @var StorageServiceInterface
*/
private $storageService;
/**
* @var DataSetFactory
*/
private $dataSetFactory;
public function __construct(StorageServiceInterface $storageService, DataSetFactory $dataSetFactory)
{
$this->storageService = $storageService;
$this->dataSetFactory = $dataSetFactory;
}
/**
* @inheritDoc
*/
public function __invoke(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
$this->deleteChildren($user, $dispatcher, $systemUser);
} elseif ($function === 'reassignAll') {
$this->reassignAllTo($user, $newUser, $systemUser);
} elseif ($function === 'countChildren') {
$event->setReturnValue($event->getReturnValue() + $this->countChildren($user));
}
}
/**
* @inheritDoc
*/
public function deleteChildren(User $user, EventDispatcherInterface $dispatcher, User $systemUser)
{
foreach ($this->dataSetFactory->getByOwnerId($user->userId) as $dataSet) {
$dataSet->delete();
}
}
/**
* @inheritDoc
*/
public function reassignAllTo(User $user, User $newUser, User $systemUser)
{
// Reassign datasets
$this->storageService->update('UPDATE `dataset` SET userId = :userId WHERE userId = :oldUserId', [
'userId' => $newUser->userId,
'oldUserId' => $user->userId
]);
}
/**
* @inheritDoc
*/
public function countChildren(User $user)
{
$dataSets = $this->dataSetFactory->getByOwnerId($user->userId);
$count = count($dataSets);
$this->getLogger()->debug(sprintf('Counted Children DataSet on User ID %d, there are %d', $user->userId, $count));
return $count;
}
}

View File

@@ -0,0 +1,124 @@
<?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\Listener\OnUserDelete;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\DayPartFactory;
use Xibo\Factory\ScheduleFactory;
use Xibo\Listener\ListenerLoggerTrait;
use Xibo\Service\DisplayNotifyServiceInterface;
use Xibo\Storage\StorageServiceInterface;
class DayPartListener implements OnUserDeleteInterface
{
use ListenerLoggerTrait;
/**
* @var StorageServiceInterface
*/
private $storageService;
/**
* @var DayPartFactory
*/
private $dayPartFactory;
/**
* @var ScheduleFactory
*/
private $scheduleFactory;
/** @var DisplayNotifyServiceInterface */
private $displayNotifyService;
public function __construct(
StorageServiceInterface $storageService,
DayPartFactory $dayPartFactory,
ScheduleFactory $scheduleFactory,
DisplayNotifyServiceInterface $displayNotifyService
) {
$this->storageService = $storageService;
$this->dayPartFactory = $dayPartFactory;
$this->scheduleFactory = $scheduleFactory;
$this->displayNotifyService = $displayNotifyService;
}
/**
* @inheritDoc
*/
public function __invoke(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
$this->deleteChildren($user, $dispatcher, $systemUser);
} elseif ($function === 'reassignAll') {
$this->reassignAllTo($user, $newUser, $systemUser);
} elseif ($function === 'countChildren') {
$event->setReturnValue($event->getReturnValue() + $this->countChildren($user));
}
}
/**
* @inheritDoc
*/
public function deleteChildren(User $user, EventDispatcherInterface $dispatcher, User $systemUser)
{
// system dayParts cannot be deleted, if this user owns them reassign to systemUser
foreach ($this->dayPartFactory->getByOwnerId($user->userId) as $dayPart) {
if ($dayPart->isSystemDayPart()) {
$dayPart->setOwner($systemUser->userId);
$dayPart->save(['recalculateHash' => false]);
} else {
$dayPart->setScheduleFactory($this->scheduleFactory, $this->displayNotifyService)->delete();
}
}
}
/**
* @inheritDoc
*/
public function reassignAllTo(User $user, User $newUser, User $systemUser)
{
// Reassign Dayparts
foreach ($this->dayPartFactory->getByOwnerId($user->userId) as $dayPart) {
($dayPart->isSystemDayPart()) ? $dayPart->setOwner($systemUser->userId) : $dayPart->setOwner($newUser->getOwnerId());
$dayPart->save(['recalculateHash' => false]);
}
}
/**
* @inheritDoc
*/
public function countChildren(User $user)
{
$dayParts = $this->dayPartFactory->getByOwnerId($user->userId);
$count = count($dayParts);
$this->getLogger()->debug(sprintf('Counted Children DayParts on User ID %d, there are %d', $user->userId, $count));
return $count;
}
}

View File

@@ -0,0 +1,95 @@
<?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\Listener\OnUserDelete;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\DisplayProfileFactory;
use Xibo\Listener\ListenerLoggerTrait;
use Xibo\Storage\StorageServiceInterface;
class DisplayProfileListener implements OnUserDeleteInterface
{
use ListenerLoggerTrait;
/**
* @var StorageServiceInterface
*/
private $store;
/**
* @var DisplayProfileFactory
*/
private $displayProfileFactory;
public function __construct(StorageServiceInterface $store, DisplayProfileFactory $displayProfileFactory)
{
$this->store = $store;
$this->displayProfileFactory = $displayProfileFactory;
}
public function __invoke(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
$this->deleteChildren($user, $dispatcher, $systemUser);
} elseif ($function === 'reassignAll') {
$this->reassignAllTo($user, $newUser, $systemUser);
} elseif ($function === 'countChildren') {
$event->setReturnValue($event->getReturnValue() + $this->countChildren($user));
}
}
public function deleteChildren(User $user, EventDispatcherInterface $dispatcher, User $systemUser)
{
// Delete any Display Profiles, reassign any default profiles to systemUser
foreach ($this->displayProfileFactory->getByOwnerId($user->userId) as $displayProfile) {
if ($displayProfile->isDefault === 1) {
$displayProfile->setOwner($systemUser->userId);
$displayProfile->save();
} else {
$displayProfile->delete();
}
}
}
public function reassignAllTo(User $user, User $newUser, User $systemUser)
{
$this->store->update('UPDATE `displayprofile` SET userId = :userId WHERE userId = :oldUserId', [
'userId' => $newUser->userId,
'oldUserId' => $user->userId
]);
}
public function countChildren(User $user)
{
$profiles = $this->displayProfileFactory->getByOwnerId($user->userId);
$this->getLogger()->debug(sprintf('Counted Children Display Profiles on User ID %d, there are %d', $user->userId, count($profiles)));
return count($profiles);
}
}

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\Listener\OnUserDelete;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\MenuBoardFactory;
use Xibo\Listener\ListenerLoggerTrait;
use Xibo\Storage\StorageServiceInterface;
class MenuBoardListener implements OnUserDeleteInterface
{
use ListenerLoggerTrait;
/**
* @var StorageServiceInterface
*/
private $storageService;
/**
* @var MenuBoardFactory
*/
private $menuBoardFactory;
public function __construct(StorageServiceInterface $storageService, MenuBoardFactory $menuBoardFactory)
{
$this->storageService = $storageService;
$this->menuBoardFactory = $menuBoardFactory;
}
/**
* @inheritDoc
*/
public function __invoke(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
$this->deleteChildren($user, $dispatcher, $systemUser);
} elseif ($function === 'reassignAll') {
$this->reassignAllTo($user, $newUser, $systemUser);
} elseif ($function === 'countChildren') {
$event->setReturnValue($event->getReturnValue() + $this->countChildren($user));
}
}
/**
* @inheritDoc
*/
public function deleteChildren(User $user, EventDispatcherInterface $dispatcher, User $systemUser)
{
foreach ($this->menuBoardFactory->getByOwnerId($user->userId) as $menuBoard) {
$menuBoard->delete();
}
}
/**
* @inheritDoc
*/
public function reassignAllTo(User $user, User $newUser, User $systemUser)
{
// Reassign Menu Boards
$this->storageService->update('UPDATE `menu_board` SET userId = :userId WHERE userId = :oldUserId', [
'userId' => $newUser->userId,
'oldUserId' => $user->userId
]);
}
/**
* @inheritDoc
*/
public function countChildren(User $user)
{
$menuBoards = $this->menuBoardFactory->getByOwnerId($user->userId);
$count = count($menuBoards);
$this->getLogger()->debug(sprintf('Counted Children Menu Board on User ID %d, there are %d', $user->userId, $count));
return $count;
}
}

View File

@@ -0,0 +1,85 @@
<?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\Listener\OnUserDelete;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\NotificationFactory;
use Xibo\Listener\ListenerLoggerTrait;
class NotificationListener implements OnUserDeleteInterface
{
use ListenerLoggerTrait;
/**
* @var NotificationFactory
*/
private $notificationFactory;
public function __construct(NotificationFactory $notificationFactory)
{
$this->notificationFactory = $notificationFactory;
}
public function __invoke(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
$this->deleteChildren($user, $dispatcher, $systemUser);
} elseif ($function === 'reassignAll') {
$this->reassignAllTo($user, $newUser, $systemUser);
} elseif ($function === 'countChildren') {
$event->setReturnValue($event->getReturnValue() + $this->countChildren($user));
}
}
public function deleteChildren(User $user, EventDispatcherInterface $dispatcher, User $systemUser)
{
// Delete any Notifications
foreach ($this->notificationFactory->getByOwnerId($user->userId) as $notification) {
$notification->delete();
}
}
public function reassignAllTo(User $user, User $newUser, User $systemUser)
{
foreach ($this->notificationFactory->getByOwnerId($user->userId) as $notification) {
$notification->load();
$notification->userId = $newUser->userId;
$notification->save();
}
}
public function countChildren(User $user)
{
$notifications = $this->notificationFactory->getByOwnerId($user->userId);
$this->getLogger()->debug(sprintf('Counted Children Notifications on User ID %d, there are %d', $user->userId, count($notifications)));
return count($notifications);
}
}

View File

@@ -0,0 +1,63 @@
<?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\Listener\OnUserDelete;
use Xibo\Event\UserDeleteEvent;
use Xibo\Listener\ListenerLoggerTrait;
use Xibo\Storage\StorageServiceInterface;
class OnUserDelete
{
use ListenerLoggerTrait;
/** @var StorageServiceInterface */
private $store;
public function __construct(StorageServiceInterface $store)
{
$this->store = $store;
}
/**
* @inheritDoc
*/
public function __invoke(UserDeleteEvent $event)
{
$user = $event->getUser();
$function = $event->getFunction();
if ($function === 'delete' || $function === 'reassignAll') {
$this->deleteChildren($user);
}
}
// when we delete a User with or without reassign the session and oauth clients should always be removed
// other objects that can be owned by the user are deleted in their respective listeners.
private function deleteChildren($user)
{
// Delete oAuth clients
$this->store->update('DELETE FROM `oauth_clients` WHERE userId = :userId', ['userId' => $user->userId]);
$this->store->update('DELETE FROM `session` WHERE userId = :userId', ['userId' => $user->userId]);
}
}

View File

@@ -0,0 +1,63 @@
<?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\Listener\OnUserDelete;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Event\UserDeleteEvent;
interface OnUserDeleteInterface
{
/**
* Listen to the UserDeleteEvent
*
* @param UserDeleteEvent $event
* @return mixed
*/
public function __invoke(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher);
/**
* Delete Objects owned by the User we want to delete
*
* @param User $user
* @return mixed
*/
public function deleteChildren(User $user, EventDispatcherInterface $dispatcher, User $systemUser);
/**
* Reassign objects to a new User
*
* @param User $user
* @param User $newUser
* @return mixed
*/
public function reassignAllTo(User $user, User $newUser, User $systemUser);
/**
* Count Children, return count of objects owned by the User we want to delete
*
* @param User $user
* @return mixed
*/
public function countChildren(User $user);
}

View File

@@ -0,0 +1,94 @@
<?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\Listener\OnUserDelete;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\RegionFactory;
use Xibo\Listener\ListenerLoggerTrait;
class RegionListener implements OnUserDeleteInterface
{
use ListenerLoggerTrait;
/**
* @var RegionFactory
*/
private $regionFactory;
public function __construct(RegionFactory $regionFactory)
{
$this->regionFactory = $regionFactory;
}
public function __invoke(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
$this->deleteChildren($user, $dispatcher, $systemUser);
} elseif ($function === 'reassignAll') {
$this->reassignAllTo($user, $newUser, $systemUser);
} elseif ($function === 'countChildren') {
$event->setReturnValue($event->getReturnValue() + $this->countChildren($user));
}
}
public function deleteChildren(User $user, EventDispatcherInterface $dispatcher, User $systemUser)
{
foreach ($this->regionFactory->getbyOwnerId($user->userId) as $region) {
$region->delete();
}
}
public function reassignAllTo(User $user, User $newUser, User $systemUser)
{
$regions = $this->regionFactory->getbyOwnerId($user->userId);
$this->getLogger()->debug(sprintf('Counted Children Regions on User ID %d, there are %d', $user->userId, count($regions)));
foreach ($regions as $region) {
$region->setOwner($newUser->userId, true);
$region->save([
'validate' => false,
'audit' => false,
'notify' => false
]);
}
$this->getLogger()->debug(sprintf('Finished reassign Regions, there are %d children', $this->countChildren($user)));
}
public function countChildren(User $user)
{
$regions = $this->regionFactory->getbyOwnerId($user->userId);
$this->getLogger()->debug(sprintf('Counted Children Regions on User ID %d, there are %d', $user->userId, count($regions)));
return count($regions);
}
}

View File

@@ -0,0 +1,89 @@
<?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\Listener\OnUserDelete;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\ReportScheduleFactory;
use Xibo\Listener\ListenerLoggerTrait;
use Xibo\Storage\StorageServiceInterface;
class ReportScheduleListener implements OnUserDeleteInterface
{
use ListenerLoggerTrait;
/**
* @var StorageServiceInterface
*/
private $store;
/**
* @var ReportScheduleFactory
*/
private $reportScheduleFactory;
public function __construct(StorageServiceInterface $store, ReportScheduleFactory $reportScheduleFactory)
{
$this->store = $store;
$this->reportScheduleFactory = $reportScheduleFactory;
}
public function __invoke(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
$this->deleteChildren($user, $dispatcher, $systemUser);
} elseif ($function === 'reassignAll') {
$this->reassignAllTo($user, $newUser, $systemUser);
} elseif ($function === 'countChildren') {
$event->setReturnValue($event->getReturnValue() + $this->countChildren($user));
}
}
public function deleteChildren(User $user, EventDispatcherInterface $dispatcher, User $systemUser)
{
foreach ($this->reportScheduleFactory->getByOwnerId($user->userId) as $reportSchedule) {
$reportSchedule->delete();
}
}
public function reassignAllTo(User $user, User $newUser, User $systemUser)
{
$this->store->update('UPDATE `reportschedule` SET userId = :userId WHERE userId = :oldUserId', [
'userId' => $newUser->userId,
'oldUserId' => $user->userId
]);
}
public function countChildren(User $user)
{
$reportSchedules = $this->reportScheduleFactory->getByOwnerId($user->userId);
$this->getLogger()->debug(sprintf('Counted Children Report Schedules on User ID %d, there are %d', $user->userId, count($reportSchedules)));
return count($reportSchedules);
}
}

View File

@@ -0,0 +1,94 @@
<?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\Listener\OnUserDelete;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\ResolutionFactory;
use Xibo\Listener\ListenerLoggerTrait;
use Xibo\Storage\StorageServiceInterface;
class ResolutionListener implements OnUserDeleteInterface
{
use ListenerLoggerTrait;
/**
* @var StorageServiceInterface
*/
private $store;
/**
* @var ResolutionFactory
*/
private $resolutionFactory;
public function __construct(StorageServiceInterface $store, ResolutionFactory $resolutionFactory)
{
$this->store = $store;
$this->resolutionFactory = $resolutionFactory;
}
public function __invoke(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
$this->deleteChildren($user, $dispatcher, $systemUser);
} elseif ($function === 'reassignAll') {
$this->reassignAllTo($user, $newUser, $systemUser);
} elseif ($function === 'countChildren') {
$event->setReturnValue($event->getReturnValue() + $this->countChildren($user));
}
}
/* @inheritDoc */
public function deleteChildren(User $user, EventDispatcherInterface $dispatcher, User $systemUser)
{
// Delete any Resolutions
foreach ($this->resolutionFactory->getByOwnerId($user->userId) as $resolution) {
$resolution->delete();
}
}
/* @inheritDoc */
public function reassignAllTo(User $user, User $newUser, User $systemUser)
{
// Reassign Resolutions
$this->store->update('UPDATE `resolution` SET userId = :userId WHERE userId = :oldUserId', [
'userId' => $newUser->userId,
'oldUserId' => $user->userId
]);
}
/* @inheritDoc */
public function countChildren(User $user)
{
$resolutions = $this->resolutionFactory->getByOwnerId($user->userId);
$this->getLogger()->debug(sprintf('Counted Children Resolution on User ID %d, there are %d', $user->userId, count($resolutions)));
return count($resolutions);
}
}

View File

@@ -0,0 +1,89 @@
<?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\Listener\OnUserDelete;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\SavedReportFactory;
use Xibo\Listener\ListenerLoggerTrait;
use Xibo\Storage\StorageServiceInterface;
class SavedReportListener implements OnUserDeleteInterface
{
use ListenerLoggerTrait;
/**
* @var StorageServiceInterface
*/
private $store;
/**
* @var SavedReportFactory
*/
private $savedReportFactory;
public function __construct(StorageServiceInterface $store, SavedReportFactory $savedReportFactory)
{
$this->store = $store;
$this->savedReportFactory = $savedReportFactory;
}
public function __invoke(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
$this->deleteChildren($user, $dispatcher, $systemUser);
} elseif ($function === 'reassignAll') {
$this->reassignAllTo($user, $newUser, $systemUser);
} elseif ($function === 'countChildren') {
$event->setReturnValue($event->getReturnValue() + $this->countChildren($user));
}
}
public function deleteChildren(User $user, EventDispatcherInterface $dispatcher, User $systemUser)
{
foreach ($this->savedReportFactory->getByOwnerId($user->userId) as $savedReport) {
$savedReport->delete();
}
}
public function reassignAllTo(User $user, User $newUser, User $systemUser)
{
$this->store->update('UPDATE `saved_report` SET userId = :userId WHERE userId = :oldUserId', [
'userId' => $newUser->userId,
'oldUserId' => $user->userId
]);
}
public function countChildren(User $user)
{
$savedReports = $this->savedReportFactory->getByOwnerId($user->userId);
$this->getLogger()->debug(sprintf('Counted Children Saved Report on User ID %d, there are %d', $user->userId, count($savedReports)));
return count($savedReports);
}
}

View File

@@ -0,0 +1,102 @@
<?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\Listener\OnUserDelete;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\Schedule;
use Xibo\Entity\User;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\ScheduleFactory;
use Xibo\Listener\ListenerLoggerTrait;
use Xibo\Storage\StorageServiceInterface;
class ScheduleListener implements OnUserDeleteInterface
{
use ListenerLoggerTrait;
/** @var StorageServiceInterface */
private $storageService;
/** @var ScheduleFactory */
private $scheduleFactory;
public function __construct(StorageServiceInterface $storageService, ScheduleFactory $scheduleFactory)
{
$this->storageService = $storageService;
$this->scheduleFactory = $scheduleFactory;
}
/**
* @inheritDoc
*/
public function __invoke(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
$this->deleteChildren($user, $dispatcher, $systemUser);
} elseif ($function === 'reassignAll') {
$this->reassignAllTo($user, $newUser, $systemUser);
} elseif ($function === 'countChildren') {
$event->setReturnValue($event->getReturnValue() + $this->countChildren($user));
}
}
/**
* @inheritDoc
*/
public function deleteChildren($user, EventDispatcherInterface $dispatcher, User $systemUser)
{
// Delete any scheduled events
foreach ($this->scheduleFactory->getByOwnerId($user->userId) as $event) {
/* @var Schedule $event */
$event->delete();
}
}
/**
* @inheritDoc
*/
public function reassignAllTo(User $user, User $newUser, User $systemUser)
{
// Reassign events
$this->storageService->update('UPDATE `schedule` SET userId = :userId WHERE userId = :oldUserId', [
'userId' => $newUser->userId,
'oldUserId' => $user->userId
]);
}
/**
* @inheritDoc
*/
public function countChildren(User $user)
{
$events = $this->scheduleFactory->getByOwnerId($user->userId);
$count = count($events);
$this->getLogger()->debug(sprintf('Counted Children Event on User ID %d, there are %d', $user->userId, $count));
return $count;
}
}

View File

@@ -0,0 +1,93 @@
<?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\Listener\OnUserDelete;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\WidgetFactory;
use Xibo\Listener\ListenerLoggerTrait;
class WidgetListener implements OnUserDeleteInterface
{
use ListenerLoggerTrait;
/**
* @var WidgetFactory
*/
private $widgetFactory;
public function __construct(WidgetFactory $widgetFactory)
{
$this->widgetFactory = $widgetFactory;
}
public function __invoke(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
$systemUser = $event->getSystemUser();
if ($function === 'delete') {
$this->deleteChildren($user, $dispatcher, $systemUser);
} elseif ($function === 'reassignAll') {
$this->reassignAllTo($user, $newUser, $systemUser);
} elseif ($function === 'countChildren') {
$event->setReturnValue($event->getReturnValue() + $this->countChildren($user));
}
}
public function deleteChildren(User $user, EventDispatcherInterface $dispatcher, User $systemUser)
{
foreach ($this->widgetFactory->getByOwnerId($user->userId) as $widget) {
$widget->delete();
}
}
public function reassignAllTo(User $user, User $newUser, User $systemUser)
{
foreach ($this->widgetFactory->getByOwnerId($user->userId) as $widget) {
$widget->setOwner($newUser->userId);
$widget->save([
'saveWidgetOptions' => false,
'saveWidgetAudio' => false,
'saveWidgetMedia' => false,
'notify' => false,
'notifyPlaylists' => false,
'notifyDisplays' => false,
'audit' => true,
'alwaysUpdate' => true
]);
}
}
public function countChildren(User $user)
{
$widgets = $this->widgetFactory->getByOwnerId($user->userId);
$this->getLogger()->debug(sprintf('Counted Children Widgets on User ID %d, there are %d', $user->userId, count($widgets)));
return count($widgets);
}
}

View File

@@ -0,0 +1,159 @@
<?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\Listener;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Event\FolderMovingEvent;
use Xibo\Event\ParsePermissionEntityEvent;
use Xibo\Event\TagDeleteEvent;
use Xibo\Event\TriggerTaskEvent;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\PlaylistFactory;
use Xibo\Storage\StorageServiceInterface;
/**
* Playlist events
*/
class PlaylistListener
{
use ListenerLoggerTrait;
/**
* @var PlaylistFactory
*/
private $playlistFactory;
/**
* @var StorageServiceInterface
*/
private $storageService;
/**
* @param PlaylistFactory $playlistFactory
* @param StorageServiceInterface $storageService
*/
public function __construct(
PlaylistFactory $playlistFactory,
StorageServiceInterface $storageService
) {
$this->playlistFactory = $playlistFactory;
$this->storageService = $storageService;
}
/**
* @param EventDispatcherInterface $dispatcher
* @return $this
*/
public function registerWithDispatcher(EventDispatcherInterface $dispatcher) : PlaylistListener
{
$dispatcher->addListener(UserDeleteEvent::$NAME, [$this, 'onUserDelete']);
$dispatcher->addListener(ParsePermissionEntityEvent::$NAME . 'playlist', [$this, 'onParsePermissions']);
$dispatcher->addListener(FolderMovingEvent::$NAME, [$this, 'onFolderMoving']);
$dispatcher->addListener(TagDeleteEvent::$NAME, [$this, 'onTagDelete']);
return $this;
}
/**
* @param UserDeleteEvent $event
* @return void
* @throws \Xibo\Support\Exception\DuplicateEntityException
* @throws \Xibo\Support\Exception\InvalidArgumentException
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onUserDelete(UserDeleteEvent $event)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
if ($function === 'delete') {
// Delete Playlists owned by this user
foreach ($this->playlistFactory->getByOwnerId($user->userId) as $playlist) {
$playlist->delete();
}
} else if ($function === 'reassignAll') {
// Reassign playlists and widgets
foreach ($this->playlistFactory->getByOwnerId($user->userId) as $playlist) {
$playlist->setOwner($newUser->userId);
$playlist->save();
}
} else if ($function === 'countChildren') {
$playlists = $this->playlistFactory->getByOwnerId($user->userId);
$count = count($playlists);
$this->getLogger()->debug(
sprintf(
'Counted Children Playlists on User ID %d, there are %d',
$user->userId,
$count
)
);
$event->setReturnValue($event->getReturnValue() + $count);
}
}
/**
* @param ParsePermissionEntityEvent $event
* @return void
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onParsePermissions(ParsePermissionEntityEvent $event)
{
$this->getLogger()->debug('onParsePermissions');
$event->setObject($this->playlistFactory->getById($event->getObjectId()));
}
/**
* @param FolderMovingEvent $event
* @return void
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onFolderMoving(FolderMovingEvent $event)
{
$folder = $event->getFolder();
$newFolder = $event->getNewFolder();
foreach ($this->playlistFactory->getbyFolderId($folder->getId()) as $playlist) {
$playlist->folderId = $newFolder->getId();
$playlist->permissionsFolderId = $newFolder->getPermissionFolderIdOrThis();
$playlist->updateFolders('playlist');
}
}
/**
* @param TagDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @return void
*/
public function onTagDelete(TagDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->storageService->update(
'DELETE FROM `lktagplaylist` WHERE `lktagplaylist`.tagId = :tagId',
['tagId' => $event->getTagId()]
);
$dispatcher->dispatch(new TriggerTaskEvent('\Xibo\XTR\DynamicPlaylistSyncTask'), TriggerTaskEvent::$NAME);
}
}

View File

@@ -0,0 +1,133 @@
<?php
/*
* Copyright (C) 2023 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\Listener;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Event\FolderMovingEvent;
use Xibo\Event\ParsePermissionEntityEvent;
use Xibo\Event\UserDeleteEvent;
use Xibo\Factory\SyncGroupFactory;
use Xibo\Storage\StorageServiceInterface;
/**
* SyncGroup events
*/
class SyncGroupListener
{
use ListenerLoggerTrait;
private SyncGroupFactory $syncGroupFactory;
private StorageServiceInterface $storageService;
/**
* @param SyncGroupFactory $syncGroupFactory
* @param StorageServiceInterface $storageService
*/
public function __construct(
SyncGroupFactory $syncGroupFactory,
StorageServiceInterface $storageService
) {
$this->syncGroupFactory = $syncGroupFactory;
$this->storageService = $storageService;
}
/**
* @param EventDispatcherInterface $dispatcher
* @return $this
*/
public function registerWithDispatcher(EventDispatcherInterface $dispatcher): SyncGroupListener
{
$dispatcher->addListener(UserDeleteEvent::$NAME, [$this, 'onUserDelete']);
$dispatcher->addListener(ParsePermissionEntityEvent::$NAME . 'syncGroup', [$this, 'onParsePermissions']);
$dispatcher->addListener(FolderMovingEvent::$NAME, [$this, 'onFolderMoving']);
return $this;
}
/**
* @param UserDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @return void
* @throws \Xibo\Support\Exception\GeneralException
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onUserDelete(UserDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$user = $event->getUser();
$function = $event->getFunction();
$newUser = $event->getNewUser();
if ($function === 'delete') {
// we do not want to delete Display specific Display Groups, reassign to systemUser instead.
foreach ($this->syncGroupFactory->getByOwnerId($user->userId) as $syncGroup) {
$syncGroup->delete();
}
} else if ($function === 'reassignAll') {
foreach ($this->syncGroupFactory->getByOwnerId($user->userId) as $syncGroup) {
$syncGroup->setOwner($newUser->getOwnerId());
$syncGroup->save();
}
} else if ($function === 'countChildren') {
$syncGroups = $this->syncGroupFactory->getByOwnerId($user->userId);
$count = count($syncGroups);
$this->getLogger()->debug(
sprintf(
'Counted Children Sync Groups on User ID %d, there are %d',
$user->userId,
$count
)
);
$event->setReturnValue($event->getReturnValue() + $count);
}
}
/**
* @param ParsePermissionEntityEvent $event
* @return void
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onParsePermissions(ParsePermissionEntityEvent $event)
{
$this->getLogger()->debug('onParsePermissions');
$event->setObject($this->syncGroupFactory->getById($event->getObjectId()));
}
/**
* @param FolderMovingEvent $event
* @return void
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onFolderMoving(FolderMovingEvent $event)
{
$folder = $event->getFolder();
$newFolder = $event->getNewFolder();
foreach ($this->syncGroupFactory->getByFolderId($folder->getId()) as $syncGroup) {
$syncGroup->folderId = $newFolder->getId();
$syncGroup->permissionsFolderId = $newFolder->getPermissionFolderIdOrThis();
$syncGroup->updateFolders('syncgroup');
}
}
}

View File

@@ -0,0 +1,74 @@
<?php
/*
* Copyright (C) 2025 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\Listener;
use Stash\Interfaces\PoolInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Event\TriggerTaskEvent;
use Xibo\Factory\TaskFactory;
use Xibo\Service\ConfigServiceInterface;
/**
* A listener for events related to tasks
*/
class TaskListener
{
use ListenerLoggerTrait;
public function __construct(
private readonly TaskFactory $taskFactory,
private readonly ConfigServiceInterface $configService,
private readonly PoolInterface $pool
) {
}
/**
* @param EventDispatcherInterface $dispatcher
* @return $this
*/
public function registerWithDispatcher(EventDispatcherInterface $dispatcher) : TaskListener
{
$dispatcher->addListener(TriggerTaskEvent::$NAME, [$this, 'onTriggerTask']);
return $this;
}
/**
* @param TriggerTaskEvent $event
* @return void
* @throws \Xibo\Support\Exception\InvalidArgumentException
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onTriggerTask(TriggerTaskEvent $event): void
{
if (!empty($event->getKey())) {
// Drop this setting from the cache
$this->pool->deleteItem($event->getKey());
}
// Mark the task to run now
$task = $this->taskFactory->getByClass($event->getClassName());
$task->runNow = 1;
$task->save(['validate' => false]);
}
}

View File

@@ -0,0 +1,768 @@
<?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\Listener;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\Widget;
use Xibo\Event\RegionAddedEvent;
use Xibo\Event\SubPlaylistDurationEvent;
use Xibo\Event\SubPlaylistItemsEvent;
use Xibo\Event\SubPlaylistValidityEvent;
use Xibo\Event\SubPlaylistWidgetsEvent;
use Xibo\Event\WidgetDeleteEvent;
use Xibo\Event\WidgetEditEvent;
use Xibo\Factory\ModuleFactory;
use Xibo\Factory\PlaylistFactory;
use Xibo\Factory\WidgetFactory;
use Xibo\Service\ConfigServiceInterface;
use Xibo\Storage\StorageServiceInterface;
use Xibo\Support\Exception\GeneralException;
use Xibo\Support\Exception\InvalidArgumentException;
use Xibo\Support\Exception\NotFoundException;
use Xibo\Widget\SubPlaylistItem;
/**
* Widget Listener.
*
* Sub Playlist Events
* -------------------
* Sub Playlists are a special case in that they resolve to multiple widgets
* This is handled by the standard widget edit/delete events and a special event to calculate the duration
* These events are processed by a SubPlaylistListener included with core.
*
* Region Events
* -------------
* We listen for a region being added and if its a canvas we add a "global" widget to it.
*/
class WidgetListener
{
use ListenerLoggerTrait;
/** @var PlaylistFactory */
private $playlistFactory;
/** @var \Xibo\Factory\ModuleFactory */
private $moduleFactory;
/** @var WidgetFactory */
private $widgetFactory;
/** @var StorageServiceInterface */
private $storageService;
/** @var \Xibo\Service\ConfigServiceInterface */
private $configService;
/**
* @param PlaylistFactory $playlistFactory
* @param \Xibo\Factory\ModuleFactory $moduleFactory
* @param StorageServiceInterface $storageService
* @param \Xibo\Service\ConfigServiceInterface $configService
*/
public function __construct(
PlaylistFactory $playlistFactory,
ModuleFactory $moduleFactory,
WidgetFactory $widgetFactory,
StorageServiceInterface $storageService,
ConfigServiceInterface $configService
) {
$this->playlistFactory = $playlistFactory;
$this->moduleFactory = $moduleFactory;
$this->widgetFactory = $widgetFactory;
$this->storageService = $storageService;
$this->configService = $configService;
}
/**
* @param EventDispatcherInterface $dispatcher
* @return $this
*/
public function registerWithDispatcher(EventDispatcherInterface $dispatcher) : WidgetListener
{
$dispatcher->addListener(WidgetEditEvent::$NAME, [$this, 'onWidgetEdit']);
$dispatcher->addListener(WidgetDeleteEvent::$NAME, [$this, 'onWidgetDelete']);
$dispatcher->addListener(SubPlaylistDurationEvent::$NAME, [$this, 'onDuration']);
$dispatcher->addListener(SubPlaylistWidgetsEvent::$NAME, [$this, 'onWidgets']);
$dispatcher->addListener(SubPlaylistItemsEvent::$NAME, [$this, 'onSubPlaylistItems']);
$dispatcher->addListener(SubPlaylistValidityEvent::$NAME, [$this, 'onSubPlaylistValid']);
$dispatcher->addListener(RegionAddedEvent::$NAME, [$this, 'onRegionAdded']);
return $this;
}
/**
* Widget Edit
* @param \Xibo\Event\WidgetEditEvent $event
* @throws \Xibo\Support\Exception\InvalidArgumentException
*/
public function onWidgetEdit(WidgetEditEvent $event)
{
$widget = $event->getWidget();
if ($widget->type !== 'subplaylist') {
return;
}
$this->getLogger()->debug('onWidgetEdit: processing subplaylist for widgetId ' . $widget->widgetId);
// Get the IDs we had before the edit and work out the difference between then and now.
$existingSubPlaylistIds = [];
foreach ($this->getAssignedPlaylists($widget, true) as $assignedPlaylist) {
if (!in_array($assignedPlaylist->playlistId, $existingSubPlaylistIds)) {
$existingSubPlaylistIds[] = $assignedPlaylist->playlistId;
}
}
$this->getLogger()->debug('onWidgetEdit: there are ' . count($existingSubPlaylistIds) . ' existing playlists');
// Make up a companion setting which maps the playlistIds to the options
$subPlaylists = $this->getAssignedPlaylists($widget);
$subPlaylistIds = [];
foreach ($subPlaylists as $playlist) {
if ($playlist->spots < 0) {
throw new InvalidArgumentException(
__('Number of spots must be empty, 0 or a positive number'),
'subPlaylistIdSpots'
);
}
if ($playlist->spotLength < 0) {
throw new InvalidArgumentException(
__('Spot length must be empty, 0 or a positive number'),
'subPlaylistIdSpotLength'
);
}
if (!in_array($playlist->playlistId, $subPlaylistIds)) {
$subPlaylistIds[] = $playlist->playlistId;
}
}
// Validation
if (count($subPlaylists) < 1) {
throw new InvalidArgumentException(__('Please select at least 1 Playlist to embed'), 'subPlaylistId');
}
// Work out whether we've added/removed
$addedEntries = array_diff($subPlaylistIds, $existingSubPlaylistIds);
$removedEntries = array_diff($existingSubPlaylistIds, $subPlaylistIds);
$this->logger->debug('onWidgetEdit Added ' . var_export($addedEntries, true));
$this->logger->debug('onWidgetEdit Removed ' . var_export($removedEntries, true));
// Remove items from closure table if necessary
foreach ($removedEntries as $entry) {
$this->logger->debug('Removing old link - existing link child is ' . $entry);
$this->storageService->update('
DELETE link
FROM `lkplaylistplaylist` p, `lkplaylistplaylist` link, `lkplaylistplaylist` c
WHERE p.parentId = link.parentId AND c.childId = link.childId
AND p.childId = :parentId AND c.parentId = :childId
', [
'parentId' => $widget->playlistId,
'childId' => $entry
]);
}
foreach ($addedEntries as $addedEntry) {
$this->logger->debug('Manage closure table for parent ' . $widget->playlistId
. ' and child ' . $addedEntry);
if ($this->storageService->exists('
SELECT parentId, childId, depth
FROM lkplaylistplaylist
WHERE childId = :childId AND parentId = :parentId
', [
'parentId' => $widget->playlistId,
'childId' => $addedEntry
])) {
throw new InvalidArgumentException(__('Cannot add the same SubPlaylist twice.'), 'playlistId');
}
$this->storageService->insert('
INSERT INTO `lkplaylistplaylist` (parentId, childId, depth)
SELECT p.parentId, c.childId, p.depth + c.depth + 1
FROM lkplaylistplaylist p, lkplaylistplaylist c
WHERE p.childId = :parentId AND c.parentId = :childId
', [
'parentId' => $widget->playlistId,
'childId' => $addedEntry
]);
}
// Make sure we've not created a circular reference
// this is a lazy last minute check as we can't really tell if there is a circular reference unless
// we've inserted the records already.
if ($this->storageService->exists('
SELECT depth
FROM `lkplaylistplaylist`
WHERE parentId = :parentId
AND childId = parentId
AND depth > 0
', ['parentId' => $widget->playlistId])) {
throw new InvalidArgumentException(
__('This assignment creates a loop because the Playlist being assigned contains the Playlist being worked on.'),//phpcs:ignore
'subPlaylistId'
);
}
}
/**
* @param \Xibo\Event\WidgetDeleteEvent $event
* @return void
*/
public function onWidgetDelete(WidgetDeleteEvent $event)
{
$widget = $event->getWidget();
$this->getLogger()->debug('onWidgetDelete: processing widgetId ' . $widget->widgetId);
// Clear cache
$renderer = $this->moduleFactory->createWidgetHtmlRenderer();
$renderer->clearWidgetCache($widget);
// Everything else relates to sub-playlists
if ($widget->type !== 'subplaylist') {
return;
}
$subPlaylists = $this->getAssignedPlaylists($widget);
// tidy up the closure table records.
foreach ($subPlaylists as $subPlaylist) {
$this->storageService->update('
DELETE link
FROM `lkplaylistplaylist` p, `lkplaylistplaylist` link, `lkplaylistplaylist` c
WHERE p.parentId = link.parentId AND c.childId = link.childId
AND p.childId = :parentId AND c.parentId = :childId
', [
'parentId' => $widget->playlistId,
'childId' => $subPlaylist->playlistId,
]);
}
}
/**
* @param \Xibo\Event\SubPlaylistDurationEvent $event
* @return void
* @throws \Xibo\Support\Exception\GeneralException
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onDuration(SubPlaylistDurationEvent $event)
{
$widget = $event->getWidget();
$this->getLogger()->debug('onDuration: for ' . $widget->type);
if ($widget->type !== 'subplaylist') {
return;
}
// We give our widgetId to the resolve method so that it resolves us as if we're a child.
// we only resolve top-level sub-playlists when we build the layout XLF
$duration = 0;
$countWidgets = 0;
foreach ($this->getSubPlaylistResolvedWidgets($widget, $widget->widgetId ?? 0) as $resolvedWidget) {
$duration += $resolvedWidget->calculatedDuration;
$countWidgets++;
}
if ($widget->getOptionValue('cyclePlaybackEnabled', 0) === 1 && $countWidgets > 0) {
$this->getLogger()->debug('onDuration: cycle playback is enabled and there are ' . $countWidgets
. ' widgets with a total of ' . $duration . ' seconds');
$duration = intval(ceil($duration / $countWidgets));
}
$event->appendDuration($duration);
}
/**
* @param \Xibo\Event\SubPlaylistWidgetsEvent $event
* @return void
* @throws \Xibo\Support\Exception\GeneralException
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function onWidgets(SubPlaylistWidgetsEvent $event)
{
$widget = $event->getWidget();
if ($widget->type !== 'subplaylist') {
return;
}
$event->setWidgets($this->getSubPlaylistResolvedWidgets($widget, $event->getTempId()));
}
/**
* @param SubPlaylistItemsEvent $event
* @return void
*/
public function onSubPlaylistItems(SubPlaylistItemsEvent $event)
{
$widget = $event->getWidget();
if ($widget->type !== 'subplaylist') {
return;
}
$event->setItems($this->getAssignedPlaylists($widget));
}
/**
* @param SubPlaylistValidityEvent $event
* @return void
*/
public function onSubPlaylistValid(SubPlaylistValidityEvent $event): void
{
$playlists = $this->getAssignedPlaylists($event->getWidget());
if (count($playlists) <= 0) {
$event->setIsValid(false);
return;
} else {
foreach ($playlists as $playlistItem) {
try {
$this->playlistFactory->getById($playlistItem->playlistId);
} catch (NotFoundException $e) {
$this->getLogger()->error('Misconfigured sub playlist, playlist ID '
. $playlistItem->playlistId . ' Not found');
$event->setIsValid(false);
return;
}
}
}
$event->setIsValid(true);
}
/**
* @return \Xibo\Widget\SubPlaylistItem[]
*/
private function getAssignedPlaylists(Widget $widget, bool $originalValue = false): array
{
$this->getLogger()->debug('getAssignedPlaylists: original value: ' . var_export($originalValue, true));
$playlistItems = [];
foreach (json_decode($widget->getOptionValue('subPlaylists', '[]', $originalValue), true) as $playlist) {
$item = new SubPlaylistItem();
$item->rowNo = intval($playlist['rowNo']);
$item->playlistId = $playlist['playlistId'];
$item->spotFill = $playlist['spotFill'] ?? null;
$item->spotLength = $playlist['spotLength'] !== '' ? intval($playlist['spotLength']) : null;
$item->spots = $playlist['spots'] !== '' ? intval($playlist['spots']) : null;
$playlistItems[] = $item;
}
return $playlistItems;
}
/**
* @param int $parentWidgetId this tracks the top level widgetId
* @return Widget[] $widgets
* @throws NotFoundException
* @throws GeneralException
*/
private function getSubPlaylistResolvedWidgets(Widget $widget, int $parentWidgetId = 0): array
{
$this->getLogger()->debug('getSubPlaylistResolvedWidgets: widgetId is ' . $widget->widgetId
. ', parentWidgetId is ' . $parentWidgetId);
$arrangement = $widget->getOptionValue('arrangement', 'none');
$remainder = $widget->getOptionValue('remainder', 'none');
$cyclePlayback = $widget->getOptionValue('cyclePlaybackEnabled', 0);
$playCount = $widget->getOptionValue('playCount', 0);
$isRandom = $widget->getOptionValue('cycleRandomWidget', 0);
$this->logger->debug('Resolve widgets for Sub-Playlist ' . $widget->widgetId
. ' with arrangement ' . $arrangement . ' and remainder ' . $remainder);
// As a first step, get all of our playlists widgets loaded into an array
/** @var Widget[] $resolvedWidgets */
$resolvedWidgets = [];
$widgets = [];
$firstList = null;
$firstListCount = 0;
$largestListKey = null;
$largestListCount = 0;
$smallestListCount = 0;
// Expand or Shrink each of our assigned lists according to the Spot options (if any)
// Expand all widgets from sub-playlists
foreach ($this->getAssignedPlaylists($widget) as $playlistItem) {
try {
$playlist = $this->playlistFactory->getById($playlistItem->playlistId)
->setModuleFactory($this->moduleFactory);
} catch (NotFoundException $notFoundException) {
$this->logger->error('getSubPlaylistResolvedWidgets: widget references a playlist which no longer exists. widgetId: '//phpcs:ignore
. $widget->widgetId . ', playlistId: ' . $playlistItem->playlistId);
continue;
}
$expanded = $playlist->expandWidgets($parentWidgetId);
$countExpanded = count($expanded);
// Assert top level options
// ------------------------
// options such as stats/cycle playback are asserted from the top down
// this is not a saved change, we assess this every time
$playlistEnableStat = empty($playlist->enableStat)
? $this->configService->getSetting('PLAYLIST_STATS_ENABLED_DEFAULT')
: $playlist->enableStat;
foreach ($expanded as $subPlaylistWidget) {
// Handle proof of play
// Go through widgets assigned to this Playlist, if their enableStat is set to Inherit alter that option
// in memory for this widget.
$subPlaylistWidgetEnableStat = $subPlaylistWidget->getOptionValue(
'enableStat',
$this->configService->getSetting('WIDGET_STATS_ENABLED_DEFAULT')
);
if ($subPlaylistWidgetEnableStat == 'Inherit') {
$this->logger->debug('For widget ID ' . $subPlaylistWidget->widgetId
. ' enableStat was Inherit, changed to Playlist enableStat value - ' . $playlistEnableStat);
$subPlaylistWidget->setOptionValue('enableStat', 'attrib', $playlistEnableStat);
}
// Cycle Playback
// --------------
// currently we only support cycle playback on the topmost level.
// https://github.com/xibosignage/xibo/issues/2869
$subPlaylistWidget->setOptionValue('cyclePlayback', 'attrib', $cyclePlayback);
$subPlaylistWidget->setOptionValue('playCount', 'attrib', $playCount);
$subPlaylistWidget->setOptionValue('isRandom', 'attrib', $isRandom);
}
// Do we have a number of spots set?
$this->logger->debug($playlistItem->spots . ' spots for playlistId ' . $playlistItem->playlistId);
// Do we need to expand or shrink our list to make our Spot length
if ($playlistItem->spots !== null && $playlistItem->spots != $countExpanded) {
// We do need to do something!
$this->logger->debug('There are ' . count($expanded) . ' Widgets in the list and we want '
. $playlistItem->spots . ' fill is ' . $playlistItem->spotFill);
// If our spot size is 0, then we deliberately do not add to the final widgets array
if ($playlistItem->spots == 0) {
if ($firstList === null && count($expanded) > 0) {
// If this is the first list, and it contains some values, then set it.
$firstList = $expanded;
}
// Skip over this one (we want to ignore it as it has spots = 0)
continue;
}
// If there are 0 items in the list, we need to fill
if (count($expanded) <= 0) {
// If this is the first list, then we need to skip it completely
if ($firstList === null) {
continue;
} else {
// Not the first list, so we can swap over to fill mode and use the first list instead
$playlistItem->spotFill = 'fill';
}
}
// Expand the list out, using the fill options.
$spotFillIndex = 0;
while (count($expanded) < $playlistItem->spots) {
$spotsToFill = $playlistItem->spots - count($expanded);
if ($playlistItem->spotFill == 'repeat' || $firstList === null) {
// Repeat the list to fill the spots
$expanded = array_merge($expanded, $expanded);
} else if ($playlistItem->spotFill == 'fill') {
// Get Playlist 1 and use it to fill
// Filling means taking playlist 1 and putting in on the end of the current list
// until we're full
$expanded = array_merge($expanded, $firstList);
} else if ($playlistItem->spotFill == 'pad') {
// Get Playlist 1 and use it to pad
// padding means taking playlist 1 and interleaving it with the current list, until we're
// full
$new = [];
$loops = $spotsToFill / count($expanded);
for ($i = 0; $i < count($expanded); $i++) {
// Take one from the playlist we're operating on
$new[] = $expanded[$i];
// Take $loops from the filler playlist (the first one)
for ($j = 0; $j < $loops; $j++) {
$new[] = $firstList[$spotFillIndex];
$spotFillIndex++;
// if we've gone around too far, then start from the beginning.
if ($spotFillIndex >= count($firstList)) {
$spotFillIndex = 0;
}
}
}
$expanded = $new;
}
}
if (count($expanded) > $playlistItem->spots) {
// Chop the list down to size.
$expanded = array_slice($expanded, 0, $playlistItem->spots);
}
// Update our count of expanded widgets to be the spots
$countExpanded = $playlistItem->spots;
} else if ($countExpanded <= 0) {
// No spots required and no content in this list.
continue;
}
// first watermark
if ($firstList === null) {
$firstList = $expanded;
}
if ($firstListCount === 0) {
$firstListCount = $countExpanded;
}
// high watermark
if ($countExpanded > $largestListCount) {
$largestListCount = $countExpanded;
$largestListKey = $playlistItem->playlistId . '_' . $playlistItem->rowNo;
}
// low watermark
if ($countExpanded < $smallestListCount || $smallestListCount === 0) {
$smallestListCount = $countExpanded;
}
// Adjust the widget duration if necessary
if ($playlistItem->spotLength !== null && $playlistItem->spotLength > 0) {
foreach ($expanded as $widget) {
$widget->useDuration = 1;
$widget->duration = $playlistItem->spotLength;
$widget->calculatedDuration = $playlistItem->spotLength;
}
}
$widgets[$playlistItem->playlistId . '_' . $playlistItem->rowNo] = $expanded;
}
$this->logger->debug('Finished parsing all sub-playlists, smallest list is ' . $smallestListCount
. ' widgets in size, largest is ' . $largestListCount);
if ($smallestListCount == 0 && $largestListCount == 0) {
$this->logger->debug('No Widgets to order');
return [];
}
// Enable for debugging only - large log
//$thislogger->debug(json_encode($widgets));
$takeIndices = [];
$lastTakeIndices = [];
// Arrangement first
if ($arrangement === 'even' && $smallestListCount > 0) {
// Evenly distributed by round-robin
$arrangement = 'roundrobin';
// We need to decide how frequently we take from the respective lists.
// this is different for each list.
foreach (array_keys($widgets) as $listKey) {
$takeIndices[$listKey] = intval(floor(count($widgets[$listKey]) / $smallestListCount));
$lastTakeIndices[$listKey] = -1;
}
} else {
// On a standard round-robin, we take every 1 item (i.e. one from each).
foreach (array_keys($widgets) as $listKey) {
$takeIndices[$listKey] = 1;
$lastTakeIndices[$listKey] = -1;
}
}
$this->logger->debug('Take Indices: ' . json_encode($takeIndices));
// Round-robin or sequentially
if ($arrangement === 'roundrobin') {
// Round Robin
// Take 1 from each until we have run out, use the smallest list as the "key"
$loopCount = $largestListCount / $takeIndices[$largestListKey];
$this->logger->debug('Round-Robin: We will loop a maximum of ' . $loopCount . ' times');
for ($i = 0; $i < $loopCount; $i++) {
$this->logger->debug('Loop number ' . $i);
foreach (array_keys($widgets) as $listKey) {
// How many items should we take from this list each time we go around?
$takeEvery = $takeIndices[$listKey];
$countInList = count($widgets[$listKey]);
$this->logger->debug('Assessing playlistId ' . $listKey . ' which has '
. $countInList . ' widgets.');
for ($count = 1; $count <= $takeEvery; $count++) {
// Increment the last index we consumed from this list each time
$index = $lastTakeIndices[$listKey] + 1;
// Does this key actually have this many items?
if ($index >= $countInList) {
// it does not :o
$this->logger->debug('Index ' . $index
. ' is higher than the count of widgets in the list ' . $countInList);
// what we do depends on our remainder setting
// if we drop, we stop, otherwise we skip
if ($remainder === 'drop') {
// Stop everything, we've got enough
break 3;
} else if ($remainder === 'repeat') {
// start this list again from the beginning.
$index = 0;
} else {
// Just skip this key
continue 2;
}
}
$this->logger->debug('Selecting widget at position ' . $index
. ' from playlistId ' . $listKey);
// Append the key at the position
$resolvedWidgets[] = $widgets[$listKey][$index];
// Update our last take index for this list.
$lastTakeIndices[$listKey] = $index;
}
}
}
} else {
// None
// If the arrangement is none we just add all the widgets together
// Merge the arrays together for returning
foreach ($widgets as $items) {
if ($remainder === 'drop') {
$this->logger->debug('Dropping list of ' . count($items)
. ' widgets down to ' . $smallestListCount);
// We trim all arrays down to the smallest of them
$items = array_slice($items, 0, $smallestListCount);
} else if ($remainder === 'repeat') {
$this->logger->debug('Expanding list of ' . count($items) . ' widgets to ' . $largestListCount);
while (count($items) < $largestListCount) {
$items = array_merge($items, $items);
}
// Finally trim (we might have added too many if the list sizes aren't exactly divisible)
$items = array_slice($items, 0, $largestListCount);
}
$resolvedWidgets = array_merge($resolvedWidgets, $items);
}
}
// At the end of it, log out what we've calculated
$log = 'Resolved: ';
foreach ($resolvedWidgets as $resolvedWidget) {
$log .= $resolvedWidget->playlistId . '-' . $resolvedWidget->widgetId . ',';
// Should my from/to dates be applied to the resolved widget?
// only if they are more restrictive.
// because this is recursive, we should end up with the top most widget being "ruler" of the from/to dates
if ($widget->fromDt > $resolvedWidget->fromDt) {
$resolvedWidget->fromDt = $widget->fromDt;
}
if ($widget->toDt < $resolvedWidget->toDt) {
$resolvedWidget->toDt = $widget->toDt;
}
}
$this->logger->debug($log);
return $resolvedWidgets;
}
/**
* TODO: we will need a way to upgrade from early v3 to late v3
* (this can replace convertOldPlaylistOptions in Layout Factory)
* @return void
*/
private function toDoUpgrade(Widget $widget)
{
$playlists = json_decode($widget->getOptionValue('subPlaylists', '[]'), true);
if (count($playlists) <= 0) {
// Try and load them the old way.
$this->getLogger()->debug('getAssignedPlaylists: playlists not found in subPlaylists option, loading the old way.');//@phpcs:ignore
$playlistIds = json_decode($widget->getOptionValue('subPlaylistIds', '[]'), true);
$subPlaylistOptions = json_decode($widget->getOptionValue('subPlaylistOptions', '[]'), true);
$i = 0;
foreach ($playlistIds as $playlistId) {
$i++;
$playlists[] = [
'rowNo' => $i,
'playlistId' => $playlistId,
'spotFill' => $subPlaylistOptions[$playlistId]['subPlaylistIdSpotFill'] ?? null,
'spotLength' => $subPlaylistOptions[$playlistId]['subPlaylistIdSpotLength'] ?? null,
'spots' => $subPlaylistOptions[$playlistId]['subPlaylistIdSpots'] ?? null,
];
}
} else {
$this->getLogger()->debug('getAssignedPlaylists: playlists found in subPlaylists option.');
}
// Tidy up any old options
if ($widget->getOptionValue('subPlaylistIds', null) !== null) {
$widget->setOptionValue('subPlaylistIds', 'attrib', null);
$widget->setOptionValue('subPlaylistOptions', 'attrib', null);
}
}
/**
* Handle a region being added
* @param RegionAddedEvent $event
* @return void
* @throws InvalidArgumentException
* @throws NotFoundException
* @throws \Xibo\Support\Exception\DuplicateEntityException
*/
public function onRegionAdded(RegionAddedEvent $event)
{
// We are a canvas region
if ($event->getRegion()->type === 'canvas') {
$this->getLogger()->debug('onRegionAdded: canvas region found, adding global widget');
// Add the global widget
$module = $this->moduleFactory->getById('core-canvas');
$widget = $this->widgetFactory->create(
$event->getRegion()->getOwnerId(),
$event->getRegion()->regionPlaylist->playlistId,
$module->type,
$module->defaultDuration,
$module->schemaVersion
);
$widget->calculateDuration($module);
$event->getRegion()->regionPlaylist->assignWidget($widget, 1);
$event->getRegion()->regionPlaylist->save(['notify' => false, 'validate' => false]);
}
}
}