Skip to content

Commit c4f6f84

Browse files
authored
Merge pull request #2965 from xibosignage/develop
Release 4.2.2
2 parents 18d6365 + 1de01c2 commit c4f6f84

File tree

102 files changed

+2460
-2031
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+2460
-2031
lines changed

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default [...compat.extends('google'), {
3434
'valid-jsdoc': 'off',
3535
'require-jsdoc': 'off',
3636
'new-cap': 'off',
37+
'no-const-assign': 'error',
3738
},
3839
}, {
3940
files: ['**/*.js'],

lib/Controller/Campaign.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ public function grid(Request $request, Response $response)
253253
'layoutId' => $parsedParams->getInt('layoutId'),
254254
'logicalOperator' => $parsedParams->getString('logicalOperator'),
255255
'logicalOperatorName' => $parsedParams->getString('logicalOperatorName'),
256+
'excludeMedia' => $parsedParams->getInt('excludeMedia'),
256257
];
257258

258259
$embed = ($parsedParams->getString('embed') !== null) ? explode(',', $parsedParams->getString('embed')) : [];

lib/Controller/DisplayProfileConfigFields.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22
/*
3-
* Copyright (C) 2024 Xibo Signage Ltd
3+
* Copyright (C) 2025 Xibo Signage Ltd
44
*
55
* Xibo - Digital Signage - https://xibosignage.com
66
*
@@ -907,6 +907,24 @@ public function editConfigFields($displayProfile, $sanitizedParams, $config = nu
907907
// Encode option and save it as a string to the lock setting
908908
$displayProfile->setSetting('lockOptions', json_encode($lockOptions), $ownConfig, $config);
909909

910+
// Multiple video decoders
911+
if ($sanitizedParams->hasParam('isUseMultipleVideoDecoders')) {
912+
$this->handleChangedSettings(
913+
'isUseMultipleVideoDecoders',
914+
($ownConfig)
915+
? $displayProfile->getSetting('isUseMultipleVideoDecoders')
916+
: $display->getSetting('isUseMultipleVideoDecoders'),
917+
$sanitizedParams->getString('isUseMultipleVideoDecoders'),
918+
$changedSettings
919+
);
920+
$displayProfile->setSetting(
921+
'isUseMultipleVideoDecoders',
922+
$sanitizedParams->getString('isUseMultipleVideoDecoders'),
923+
$ownConfig,
924+
$config
925+
);
926+
}
927+
910928
break;
911929

912930
case 'chromeOS':
@@ -962,6 +980,23 @@ public function editConfigFields($displayProfile, $sanitizedParams, $config = nu
962980
);
963981
}
964982

983+
if ($sanitizedParams->hasParam('sendCurrentLayoutAsStatusUpdate')) {
984+
$this->handleChangedSettings(
985+
'sendCurrentLayoutAsStatusUpdate',
986+
($ownConfig)
987+
? $displayProfile->getSetting('sendCurrentLayoutAsStatusUpdate')
988+
: $display->getSetting('sendCurrentLayoutAsStatusUpdate'),
989+
$sanitizedParams->getCheckbox('sendCurrentLayoutAsStatusUpdate'),
990+
$changedSettings
991+
);
992+
$displayProfile->setSetting(
993+
'sendCurrentLayoutAsStatusUpdate',
994+
$sanitizedParams->getCheckbox('sendCurrentLayoutAsStatusUpdate'),
995+
$ownConfig,
996+
$config
997+
);
998+
}
999+
9651000
if ($sanitizedParams->hasParam('playerVersionId')) {
9661001
$this->handleChangedSettings('playerVersionId', ($ownConfig) ? $displayProfile->getSetting('playerVersionId') : $display->getSetting('playerVersionId'), $sanitizedParams->getInt('playerVersionId'), $changedSettings);
9671002
$displayProfile->setSetting('playerVersionId', $sanitizedParams->getInt('playerVersionId'), $ownConfig, $config);

lib/Controller/Folder.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,26 @@ public function delete(Request $request, Response $response, $folderId)
405405
);
406406
}
407407

408+
// Check if the folder is in use
409+
$this->folderFactory->decorateWithUsage($folder);
410+
$usage = $folder->getUnmatchedProperty('usage');
411+
412+
// Prevent deletion if the folder has any usage
413+
if (!empty($usage)) {
414+
$usageDetails = [];
415+
416+
// Loop through usage data and construct the formatted message
417+
foreach ($usage as $item) {
418+
$usageDetails[] = $item['type'] . ' (' . $item['count'] . ')';
419+
}
420+
421+
throw new InvalidArgumentException(
422+
__('Cannot remove Folder with content: ' . implode(', ', $usageDetails)),
423+
'folderId',
424+
__('Reassign objects from this Folder before deleting.')
425+
);
426+
}
427+
408428
try {
409429
$folder->delete();
410430
} catch (\Exception $exception) {

lib/Controller/Layout.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3539,7 +3539,7 @@ public function createFullScreenLayout(Request $request, Response $response): Re
35393539
$layout->schemaVersion = Environment::$XLF_VERSION;
35403540
$layout->folderId = ($type === 'media') ? $media->folderId : $playlist->folderId;
35413541

3542-
$layout->save();
3542+
$layout->save(['type' => $type]);
35433543

35443544
$draft = $this->layoutFactory->checkoutLayout($layout);
35453545

@@ -3590,6 +3590,12 @@ public function createFullScreenLayout(Request $request, Response $response): Re
35903590
// Calculate the duration
35913591
$widget->calculateDuration($module);
35923592

3593+
// Set loop for media items with custom duration
3594+
if ($type === 'media' && $media->mediaType === 'video' && $itemDuration > $media->duration) {
3595+
$widget->setOptionValue('loop', 'attrib', 1);
3596+
$widget->save();
3597+
}
3598+
35933599
// Assign the widget to the playlist
35943600
$region->getPlaylist()->assignWidget($widget);
35953601
// Save the playlist

lib/Controller/MenuBoardCategory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ public function add(Request $request, Response $response, $id): Response
316316

317317
$menuBoardCategory = $this->menuBoardCategoryFactory->create($id, $name, $mediaId, $code, $description);
318318
$menuBoardCategory->save();
319+
$menuBoard->save(['audit' => false]);
319320

320321
// Return
321322
$this->getState()->hydrate([
@@ -430,6 +431,7 @@ public function edit(Request $request, Response $response, $id): Response
430431
$menuBoardCategory->code = $sanitizedParams->getString('code');
431432
$menuBoardCategory->description = $sanitizedParams->getString('description');
432433
$menuBoardCategory->save();
434+
$menuBoard->save();
433435

434436
// Success
435437
$this->getState()->hydrate([

lib/Controller/Tag.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ public function editMultiple(Request $request, Response $response)
738738
$entity->assignTag($tag);
739739
}
740740

741-
$entity->save();
741+
$entity->save(['isTagEdit' => true]);
742742
}
743743

744744
// Once we're done, and if we're a Display entity, we need to calculate the dynamic display groups

lib/Controller/Widget.php

Lines changed: 92 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,14 +1694,14 @@ public function widgetSetRegion(Request $request, Response $response, $id)
16941694
* @param Request $request
16951695
* @param Response $response
16961696
* @param $id
1697-
* @return \Psr\Http\Message\ResponseInterface|Response
1698-
* @throws AccessDeniedException
1699-
* @throws GeneralException
1700-
* @throws InvalidArgumentException
1701-
* @throws NotFoundException
1697+
* @return \Slim\Http\Response
1698+
* @throws \Xibo\Support\Exception\AccessDeniedException
17021699
* @throws \Xibo\Support\Exception\ControllerNotImplemented
1700+
* @throws \Xibo\Support\Exception\GeneralException
1701+
* @throws \Xibo\Support\Exception\InvalidArgumentException
1702+
* @throws \Xibo\Support\Exception\NotFoundException
17031703
*/
1704-
public function saveElements(Request $request, Response $response, $id)
1704+
public function saveElements(Request $request, Response $response, $id): Response
17051705
{
17061706
$widget = $this->widgetFactory->getById($id);
17071707
if (!$this->getUser()->checkEditable($widget)) {
@@ -1747,20 +1747,39 @@ public function saveElements(Request $request, Response $response, $id)
17471747
);
17481748
}
17491749

1750-
// Parse the element JSON to see if we need to set `itemsPerPage`
1750+
// Parse the element JSON
17511751
$slots = [];
17521752
$uniqueSlots = 0;
17531753
$isMediaOnlyWidget = true;
17541754
$maxDuration = 1;
17551755

1756-
foreach ($elementJson as $widgetElement) {
1757-
foreach ($widgetElement['elements'] ?? [] as $element) {
1756+
foreach ($elementJson as $widgetIndex => $widgetElement) {
1757+
foreach ($widgetElement['elements'] ?? [] as $elementIndex => $element) {
1758+
$this->getLog()->debug('saveElements: processing widget index ' . $widgetIndex
1759+
. ', element index ' . $elementIndex . ' with id ' . $element['id']);
1760+
17581761
$slotNo = 'slot_' . ($element['slot'] ?? 0);
17591762
if (!in_array($slotNo, $slots)) {
17601763
$slots[] = $slotNo;
17611764
$uniqueSlots++;
17621765
}
17631766

1767+
// Handle some of the common properties.
1768+
$elementParams = $this->getSanitizer([
1769+
'elementName' => $element['elementName'],
1770+
]);
1771+
$groupParams = $this->getSanitizer($element['groupProperties'] ?? []);
1772+
1773+
// Reassert safely
1774+
// TODO: we should parse out all of the fields available in the JSON provided.
1775+
$elementJson[$widgetIndex]['elements'][$elementIndex]['elementName']
1776+
= $elementParams->getString('elementName');
1777+
$elementGroupName = $groupParams->getString('elementGroupName');
1778+
if (!empty($elementGroupName)) {
1779+
$elementJson[$widgetIndex]['elements'][$elementIndex]['groupProperties']['elementGroupName']
1780+
= $elementGroupName;
1781+
}
1782+
17641783
// Handle elements with the mediaId property so that media is linked and unlinked correctly.
17651784
if (!empty($element['mediaId'])) {
17661785
$mediaId = intval($element['mediaId']);
@@ -1776,14 +1795,69 @@ public function saveElements(Request $request, Response $response, $id)
17761795
$isMediaOnlyWidget = false;
17771796
}
17781797

1779-
// Temporary workaround to process properties from the mediaSelector component when used by elements
1780-
foreach ($element['properties'] ?? [] as $property) {
1781-
if (!empty($property['mediaId'])) {
1782-
$mediaId = intval($property['mediaId']);
1783-
$widget->assignMedia($mediaId);
1784-
$newMediaIds[] = $mediaId;
1798+
// Get this elements template
1799+
$elementTemplate = $this->moduleTemplateFactory->getByTypeAndId('element', $element['id']);
1800+
1801+
// Does this template extend another? if so combine their properties
1802+
if ($elementTemplate->extends !== null) {
1803+
$extendedTemplate = $this->moduleTemplateFactory->getByTypeAndId(
1804+
'element',
1805+
$elementTemplate->extends->template,
1806+
);
1807+
1808+
$elementTemplate->properties = array_merge(
1809+
$elementTemplate->properties,
1810+
$extendedTemplate->properties
1811+
);
1812+
}
1813+
1814+
// Process element properties.
1815+
// Switch from {id:'',value:''} to key/value
1816+
$elementPropertyParams = [];
1817+
foreach (($element['properties'] ?? []) as $elementProperty) {
1818+
$elementPropertyParams[$elementProperty['id']] = $elementProperty['value'] ?? null;
1819+
}
1820+
1821+
$this->getLog()->debug('saveElements: parsed ' . count($elementPropertyParams)
1822+
. ' properties from request, there are ' . count($elementTemplate->properties)
1823+
. ' properties defined in the template');
1824+
1825+
// Load into a sanitizer
1826+
$elementProperties = $this->getSanitizer($elementPropertyParams);
1827+
1828+
// Process each property against its definition
1829+
foreach ($elementTemplate->properties as $property) {
1830+
if ($property->type === 'message') {
1831+
continue;
1832+
}
1833+
$property->setValueByType($elementProperties);
1834+
1835+
// Process properties from the mediaSelector component
1836+
if ($property->type === 'mediaSelector') {
1837+
if (!empty($property->value) && is_numeric($property->value)) {
1838+
$mediaId = intval($property->value);
1839+
$widget->assignMedia($mediaId);
1840+
$newMediaIds[] = $mediaId;
1841+
}
17851842
}
17861843
}
1844+
1845+
$elementTemplate->validateProperties('save');
1846+
1847+
// Convert these back into objects
1848+
// and reassert new properties.
1849+
$elementJson[$widgetIndex]['elements'][$elementIndex]['properties'] = [];
1850+
foreach ($elementTemplate->getPropertyValues(
1851+
false,
1852+
null,
1853+
false,
1854+
true
1855+
) as $propertyKey => $propertyValue) {
1856+
$elementJson[$widgetIndex]['elements'][$elementIndex]['properties'][] = [
1857+
'id' => $propertyKey,
1858+
'value' => $propertyValue,
1859+
];
1860+
}
17871861
}
17881862
}
17891863

@@ -1803,7 +1877,7 @@ public function saveElements(Request $request, Response $response, $id)
18031877
}
18041878

18051879
// Save elements
1806-
$widget->setOptionValue('elements', 'raw', $elements);
1880+
$widget->setOptionValue('elements', 'raw', json_encode($elementJson));
18071881

18081882
// Unassign any mediaIds from elements which are no longer used.
18091883
foreach ($existingMediaIds as $existingMediaId) {
@@ -1841,7 +1915,8 @@ public function saveElements(Request $request, Response $response, $id)
18411915
// Successful
18421916
$this->getState()->hydrate([
18431917
'httpStatus' => 204,
1844-
'message' => __('Saved elements')
1918+
'message' => __('Saved elements'),
1919+
'data' => $elementJson,
18451920
]);
18461921

18471922
return $this->render($request, $response);

lib/Entity/Campaign.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,11 +525,17 @@ public function save($options = [])
525525
'validate' => true,
526526
'notify' => true,
527527
'collectNow' => true,
528-
'saveTags' => true
528+
'saveTags' => true,
529+
'isTagEdit' => false
529530
], $options);
530531

531532
$this->getLog()->debug('Saving ' . $this);
532533

534+
// Manually load display group IDs when editing only campaign tags.
535+
if ($options['isTagEdit']) {
536+
$this->displayGroupIds = $this->loadDisplayGroupIds();
537+
}
538+
533539
if ($options['validate']) {
534540
$this->validate();
535541
}

lib/Entity/Layout.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,8 @@ public function save($options = [])
891891
'import' => false,
892892
'appendCountOnDuplicate' => false,
893893
'setModifiedDt' => true,
894-
'auditMessage' => null,
894+
'auditMessage' => 'Saved',
895+
'type' => null
895896
], $options);
896897

897898
if ($options['validate']) {
@@ -1099,7 +1100,9 @@ public function validate($options)
10991100
}
11001101

11011102
// Validation
1102-
if (empty($this->layout) || strlen($this->layout) > 50 || strlen($this->layout) < 1) {
1103+
// Layout created from media follows the media character limit
1104+
if ((empty($this->layout) || strlen($this->layout) > 50 || strlen($this->layout) < 1)
1105+
&& $options['type'] !== 'media') {
11031106
throw new InvalidArgumentException(
11041107
__('Layout Name must be between 1 and 50 characters'),
11051108
'name'

0 commit comments

Comments
 (0)