Files
Cloud-CMS/lib/Listener/WidgetListener.php
Matt Batchelder 05ce0da296 Initial Upload
2025-12-02 10:32:59 -05:00

769 lines
31 KiB
PHP

<?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]);
}
}
}