Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various issues #1964

Merged
merged 14 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions lib/Controller/Library.php
Original file line number Diff line number Diff line change
Expand Up @@ -804,15 +804,32 @@ public function grid(Request $request, Response $response)
* @throws GeneralException
* @throws NotFoundException
*/
public function search(Request $request, Response $response)
public function search(Request $request, Response $response): Response
{
$parsedQueryParams = $this->getSanitizer($request->getQueryParams());
$provider = $parsedQueryParams->getString('provider', ['default' => 'both']);

$searchResults = new SearchResults();
if ($provider === 'both' || $provider === 'local') {
// Construct the SQL
$mediaList = $this->mediaFactory->query(['media.name'], $this->gridRenderFilter([
// Sorting options.
// only allow from a preset list
$sortCol = match ($parsedQueryParams->getString('sortCol')) {
'mediaId' => '`media`.`mediaId`',
'orientation' => '`media`.`orientation`',
'width' => '`media`.`width`',
'height' => '`media`.`height`',
'duration' => '`media`.`duration`',
'fileSize' => '`media`.`fileSize`',
'createdDt' => '`media`.`createdDt`',
'modifiedDt' => '`media`.`modifiedDt`',
default => '`media`.`name`',
};
$sortDir = match ($parsedQueryParams->getString('sortDir')) {
'DESC' => ' DESC',
default => ' ASC'
};

$mediaList = $this->mediaFactory->query([$sortCol . $sortDir], $this->gridRenderFilter([
'name' => $parsedQueryParams->getString('media'),
'useRegexForName' => $parsedQueryParams->getCheckbox('useRegexForName'),
'nameExact' => $parsedQueryParams->getString('nameExact'),
Expand Down
26 changes: 25 additions & 1 deletion lib/Controller/Widget.php
Original file line number Diff line number Diff line change
Expand Up @@ -1539,8 +1539,32 @@ public function saveElements(Request $request, Response $response, $id)
// Store the target regionId
$widget->load();

// Pull out elements directly from the request body
$elements = $request->getBody()->getContents();
$elementJson = json_decode($elements, true);
if ($elementJson === null) {
throw new InvalidArgumentException(__('Invalid element JSON'), 'body');
}

// Parse the element JSON to see if we need to set `itemsPerPage`
$slots = [];
$uniqueSlots = 0;
foreach ($elementJson as $widgetElement) {
foreach ($widgetElement['elements'] ?? [] as $element) {
$slotNo = 'slot_' . ($element['slot'] ?? 0);
if (!in_array($slotNo, $slots)) {
$slots[] = $slotNo;
$uniqueSlots++;
}
}
}

if ($uniqueSlots > 0) {
$widget->setOptionValue('itemsPerPage', 'attrib', $uniqueSlots);
}

// Save elements
$widget->setOptionValue('elements', 'raw', $request->getBody()->getContents());
$widget->setOptionValue('elements', 'raw', $elements);

// Save
$widget->save([
Expand Down
3 changes: 1 addition & 2 deletions lib/Entity/DataSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -840,9 +840,8 @@ public function save($options = [])
// Columns
if ($options['saveColumns']) {
foreach ($this->columns as $column) {
/* @var \Xibo\Entity\DataSetColumn $column */
$column->dataSetId = $this->dataSetId;
$column->save();
$column->save($options);
}
}

Expand Down
66 changes: 48 additions & 18 deletions lib/Entity/DataSetColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,13 @@ public function listContentArray()
* Validate
* @throws InvalidArgumentException
*/
public function validate()
public function validate($options = [])
{
$options = array_merge([
'testFormulas' => true,
'allowSpacesInHeading' => false,
], $options);

if ($this->dataSetId == 0 || $this->dataSetId == '')
throw new InvalidArgumentException(__('Missing dataSetId'), 'dataSetId');

Expand All @@ -208,8 +213,16 @@ public function validate()
if ($this->heading == '')
throw new InvalidArgumentException(__('Please provide a column heading.'), 'heading');

if (!v::stringType()->alnum()->validate($this->heading) || strtolower($this->heading) == 'id')
throw new InvalidArgumentException(__('Please provide an alternative column heading %s can not be used.', $this->heading), 'heading');
// We allow spaces here for backwards compatibility, but only on import and edit.
$additionalCharacters = $options['allowSpacesInHeading'] ? ' ' : '';
if (!v::stringType()->alnum($additionalCharacters)->validate($this->heading)
|| strtolower($this->heading) == 'id'
) {
throw new InvalidArgumentException(sprintf(
__('Please provide an alternative column heading %s can not be used.'),
$this->heading
), 'heading');
}

if ($this->dataSetColumnTypeId == 2 && $this->formula == '') {
throw new InvalidArgumentException(__('Please enter a valid formula'), 'formula');
Expand Down Expand Up @@ -273,33 +286,50 @@ public function validate()
throw new InvalidArgumentException(__('New list content value is invalid as it does not include values for existing data'), 'listcontent');
}

// if formula dataSetType is set and formula is not empty, try to execute the SQL to validate it - we're ignoring client side formulas here.
if ($this->dataSetColumnTypeId == 2 && $this->formula != '' && substr($this->formula, 0, 1) !== '$') {
try {
$formula = str_replace('[DisplayId]', 0, $this->formula);
$this->getStore()->select('SELECT * FROM (SELECT `id`, ' . $formula . ' AS `' . $this->heading . '` FROM `dataset_' . $this->dataSetId . '`) dataset WHERE 1 = 1 ', []);
} catch (\Exception $e) {
$this->getLog()->debug('Formula validation failed with following message ' . $e->getMessage());
throw new InvalidArgumentException(__('Provided formula is invalid'), 'formula');
}
// if formula dataSetType is set and formula is not empty, try to execute the SQL to validate it - we're
// ignoring client side formulas here.
if ($options['testFormulas']
&& $this->dataSetColumnTypeId == 2
&& $this->formula != ''
&& !str_starts_with($this->formula, '$')
) {
try {
$formula = str_replace('[DisplayId]', 0, $this->formula);
$this->getStore()->select('
SELECT *
FROM (
SELECT `id`, ' . $formula . ' AS `' . $this->heading . '`
FROM `dataset_' . $this->dataSetId . '`
) dataset
', []);
} catch (\Exception $e) {
$this->getLog()->debug('Formula validation failed with following message ' . $e->getMessage());
throw new InvalidArgumentException(__('Provided formula is invalid'), 'formula');
}
}
}

/**
* Save
* @param array[Optional] $options
* @param array $options
* @throws InvalidArgumentException
*/
public function save($options = [])
{
$options = array_merge(['validate' => true, 'rebuilding' => false], $options);
$options = array_merge([
'validate' => true,
'rebuilding' => false,
], $options);

if ($options['validate'] && !$options['rebuilding'])
$this->validate();
if ($this->dataSetColumnId == 0)
if ($options['validate'] && !$options['rebuilding']) {
$this->validate($options);
}

if ($this->dataSetColumnId == 0) {
$this->add();
else
} else {
$this->edit($options);
}
}

/**
Expand Down
47 changes: 43 additions & 4 deletions lib/Entity/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -354,13 +354,52 @@ public function createDataProvider(Widget $widget): DataProvider
*/
public function fetchDurationOrDefaultFromFile(string $file): int
{
if ($this->widgetProvider === null) {
return $this->defaultDuration;
$this->getLog()->debug('fetchDurationOrDefaultFromFile: fetchDuration with file: ' . $file);

// If we don't have a file name, then we use the default duration of 0 (end-detect)
if (empty($file)) {
return 0;
} else {
$info = new \getID3();
$file = $info->analyze($file);
return intval($file['playtime_seconds'] ?? 0);
}
$durationProvider = $this->moduleFactory->createDurationProvider($file, null);
}

/**
* Calculate the duration of this Widget.
* @param Widget $widget
* @return int|null
*/
public function calculateDuration(Widget $widget): ?int
{
if ($this->widgetProvider === null && $this->regionSpecific === 1) {
// Take some default action to cover the majourity of region specific widgets
// Duration can depend on the number of items per page for some widgets
// this is a legacy way of working, and our preference is to use elements
$numItems = $widget->getOptionValue('numItems', 15);

if ($widget->getOptionValue('durationIsPerItem', 0) == 1 && $numItems > 1) {
// If we have paging involved then work out the page count.
$itemsPerPage = $widget->getOptionValue('itemsPerPage', 0);
if ($itemsPerPage > 0) {
$numItems = ceil($numItems / $itemsPerPage);
}

return $widget->calculatedDuration * $numItems;
} else {
return null;
}
} else if ($this->widgetProvider === null) {
return null;
}

$this->getLog()->debug('calculateDuration: using widget provider');

$durationProvider = $this->moduleFactory->createDurationProvider($this, $widget);
$this->widgetProvider->fetchDuration($durationProvider);

return $durationProvider->getDuration();
return $durationProvider->isDurationSet() ? $durationProvider->getDuration() : null;
}

/**
Expand Down
43 changes: 16 additions & 27 deletions lib/Entity/Widget.php
Original file line number Diff line number Diff line change
Expand Up @@ -765,40 +765,29 @@ public function calculateDuration(
$event = new SubPlaylistDurationEvent($this);
$this->getDispatcher()->dispatch($event, SubPlaylistDurationEvent::$NAME);
$this->calculatedDuration = $event->getDuration();
} else if (($module->type === 'video' || $module->type === 'audio') && $this->useDuration === 0) {
// Video/Audio needs handling for the default duration being 0.
$this->getLog()->debug('calculateDuration: ' . $module->type . ' without specified duration');

try {
$mediaId = $this->getPrimaryMediaId();
$this->calculatedDuration = $this->widgetMediaFactory->getDurationForMediaId($mediaId);
} catch (NotFoundException $notFoundException) {
$this->getLog()->error('calculateDuration: video/audio without primaryMediaId. widgetId: '
. $this->widgetId);
}
} else if ($module->regionSpecific === 1) {
// Non-file based module
$this->getLog()->debug('calculateDuration: ' . $module->type . ', non-file based module.');

// Duration can depend on the number of items per page for some widgets
// this is a legacy way of working, and our preference is to use elements
$numItems = $this->getOptionValue('numItems', 15);

if ($this->getOptionValue('durationIsPerItem', 0) == 1 && $numItems > 1) {
// If we have paging involved then work out the page count.
$itemsPerPage = $this->getOptionValue('itemsPerPage', 0);
if ($itemsPerPage > 0) {
$numItems = ceil($numItems / $itemsPerPage);
}

$this->calculatedDuration = $this->calculatedDuration * $numItems;
} else {
// Our module will calculate the duration for us.
$duration = $module->calculateDuration($this);
if ($duration !== null) {
$this->calculatedDuration = $duration;
} else {
$this->getLog()->debug('calculateDuration: Duration not set by module');
}
}

$this->getLog()->debug('calculateDuration: set to ' . $this->calculatedDuration);
return $this;
}

/**
* @return int
* @throws NotFoundException
*/
public function getDurationForMedia(): int
{
return $this->widgetMediaFactory->getDurationForMediaId($this->getPrimaryMediaId());
}

/**
* Load the Widget
* @param bool $loadActions
Expand Down
60 changes: 60 additions & 0 deletions lib/Event/MenuBoardModifiedDtRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?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\Event;

use Carbon\Carbon;

/**
* Menu Board Product Request.
*/
class MenuBoardModifiedDtRequest extends Event
{
public static $NAME = 'menuboard.modifiedDt.request.event';

/** @var int */
private $menuId;

/** @var Carbon */
private $modifiedDt;

public function __construct(int $menuId)
{
$this->menuId = $menuId;
}

public function getDataSetId(): int
{
return $this->menuId;
}

public function setModifiedDt(Carbon $modifiedDt): MenuBoardModifiedDtRequest
{
$this->modifiedDt = $modifiedDt;
return $this;
}

public function getModifiedDt(): ?Carbon
{
return $this->modifiedDt;
}
}
Loading
Loading