init commit
This commit is contained in:
68
lib/Widget/AudioProvider.php
Normal file
68
lib/Widget/AudioProvider.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?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\Widget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
use Xibo\Widget\Provider\DataProviderInterface;
|
||||
use Xibo\Widget\Provider\DurationProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderTrait;
|
||||
|
||||
/**
|
||||
* Handles setting the correct audio duration.
|
||||
*/
|
||||
class AudioProvider implements WidgetProviderInterface
|
||||
{
|
||||
use WidgetProviderTrait;
|
||||
|
||||
public function fetchData(DataProviderInterface $dataProvider): WidgetProviderInterface
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fetchDuration(DurationProviderInterface $durationProvider): WidgetProviderInterface
|
||||
{
|
||||
// If we have not been provided a specific duration, we should use the duration stored in the library
|
||||
try {
|
||||
if ($durationProvider->getWidget()->useDuration === 0) {
|
||||
$durationProvider->setDuration($durationProvider->getWidget()->getDurationForMedia());
|
||||
}
|
||||
} catch (NotFoundException) {
|
||||
$this->getLog()->error('fetchDuration: video/audio without primaryMediaId. widgetId: '
|
||||
. $durationProvider->getWidget()->getId());
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDataCacheKey(DataProviderInterface $dataProvider): ?string
|
||||
{
|
||||
// No special cache key requirements.
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDataModifiedDt(DataProviderInterface $dataProvider): ?Carbon
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
76
lib/Widget/Compatibility/CalendarWidgetCompatibility.php
Normal file
76
lib/Widget/Compatibility/CalendarWidgetCompatibility.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?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\Widget\Compatibility;
|
||||
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityInterface;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityTrait;
|
||||
|
||||
/**
|
||||
* Convert a v3 calendar or calendaradvanced widget to its v4 counterpart.
|
||||
*/
|
||||
class CalendarWidgetCompatibility implements WidgetCompatibilityInterface
|
||||
{
|
||||
use WidgetCompatibilityTrait;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function upgradeWidget(Widget $widget, int $fromSchema, int $toSchema): bool
|
||||
{
|
||||
$this->getLog()->debug('upgradeWidget: ' . $widget->getId() . ' from: ' . $fromSchema . ' to: ' . $toSchema);
|
||||
|
||||
// Track if we've been upgraded.
|
||||
$upgraded = false;
|
||||
|
||||
// Did we originally come from an agenda (the old calendar widget)
|
||||
if ($widget->getOriginalValue('type') === 'calendar') {
|
||||
$newTemplateId = 'event_custom_html';
|
||||
|
||||
// New options names.
|
||||
$widget->changeOption('template', 'text');
|
||||
} else {
|
||||
// We are a calendaradvanced
|
||||
// Calendar type is either 1=schedule, 2=daily, 3=weekly or 4=monthly.
|
||||
$newTemplateId = match ($widget->getOptionValue('calendarType', 1)) {
|
||||
2 => 'daily',
|
||||
3 => 'weekly',
|
||||
4 => 'monthly',
|
||||
default => 'schedule',
|
||||
};
|
||||
|
||||
// Apply the theme
|
||||
$newTemplateId .= '_' . $widget->getOptionValue('templateTheme', 'light');
|
||||
}
|
||||
|
||||
if (!empty($newTemplateId)) {
|
||||
$widget->setOptionValue('templateId', 'attrib', $newTemplateId);
|
||||
$upgraded = true;
|
||||
}
|
||||
|
||||
return $upgraded;
|
||||
}
|
||||
|
||||
public function saveTemplate(string $template, string $fileName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
66
lib/Widget/Compatibility/ClockWidgetCompatibility.php
Normal file
66
lib/Widget/Compatibility/ClockWidgetCompatibility.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?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\Widget\Compatibility;
|
||||
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityInterface;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityTrait;
|
||||
|
||||
/**
|
||||
* Convert widget from an old schema to a new schema
|
||||
*/
|
||||
class ClockWidgetCompatibility implements WidgetCompatibilityInterface
|
||||
{
|
||||
use WidgetCompatibilityTrait;
|
||||
|
||||
/** @inheritdoc
|
||||
*/
|
||||
public function upgradeWidget(Widget $widget, int $fromSchema, int $toSchema): bool
|
||||
{
|
||||
$this->getLog()->debug('upgradeWidget: ' . $widget->getId() . ' from: ' . $fromSchema . ' to: ' . $toSchema);
|
||||
|
||||
// The old clock widget had a `clockTypeId` option which determines the template
|
||||
// we must make a choice here.
|
||||
$widget->type = match ($widget->getOptionValue('clockTypeId', 1)) {
|
||||
2 => 'clock-digital',
|
||||
3 => 'clock-flip',
|
||||
default => 'clock-analogue',
|
||||
};
|
||||
|
||||
// in v3 this option used to ba called theme, now it is themeId
|
||||
if ($widget->type === 'clock-analogue') {
|
||||
$widget->setOptionValue('themeId', 'attrib', $widget->getOptionValue('theme', 1));
|
||||
}
|
||||
|
||||
// We don't need the old options anymore
|
||||
$widget->removeOption('clockTypeId');
|
||||
$widget->removeOption('theme');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function saveTemplate(string $template, string $fileName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
71
lib/Widget/Compatibility/CountDownWidgetCompatibility.php
Normal file
71
lib/Widget/Compatibility/CountDownWidgetCompatibility.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?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\Widget\Compatibility;
|
||||
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityInterface;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityTrait;
|
||||
|
||||
/**
|
||||
* Convert widget from an old schema to a new schema
|
||||
*/
|
||||
class CountDownWidgetCompatibility implements WidgetCompatibilityInterface
|
||||
{
|
||||
use WidgetCompatibilityTrait;
|
||||
|
||||
/** @inheritdoc
|
||||
*/
|
||||
public function upgradeWidget(Widget $widget, int $fromSchema, int $toSchema): bool
|
||||
{
|
||||
$this->getLog()->debug('upgradeWidget: ' . $widget->getId() . ' from: ' . $fromSchema . ' to: ' . $toSchema);
|
||||
|
||||
$countdownType = $widget->getOptionValue('countdownType', 1);
|
||||
$overrideTemplate = $widget->getOptionValue('overrideTemplate', 0);
|
||||
|
||||
// Old countdown had countdownType.
|
||||
if ($overrideTemplate == 1) {
|
||||
$widget->type = 'countdown-custom';
|
||||
} else {
|
||||
$widget->type = match ($countdownType) {
|
||||
2 => 'countdown-clock',
|
||||
3 => 'countdown-table',
|
||||
4 => 'countdown-days',
|
||||
default => 'countdown-text',
|
||||
};
|
||||
}
|
||||
|
||||
// If overriden, we need to tranlate the legacy options to the new values
|
||||
if ($overrideTemplate == 1) {
|
||||
$widget->changeOption('widgetOriginalWidth', 'widgetDesignWidth');
|
||||
$widget->changeOption('widgetOriginalHeight', 'widgetDesignHeight');
|
||||
$widget->removeOption('templateId');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function saveTemplate(string $template, string $fileName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
66
lib/Widget/Compatibility/CurrenciesWidgetCompatibility.php
Normal file
66
lib/Widget/Compatibility/CurrenciesWidgetCompatibility.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?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\Widget\Compatibility;
|
||||
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityInterface;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityTrait;
|
||||
|
||||
/**
|
||||
* Convert widget from an old schema to a new schema
|
||||
*/
|
||||
class CurrenciesWidgetCompatibility implements WidgetCompatibilityInterface
|
||||
{
|
||||
use WidgetCompatibilityTrait;
|
||||
|
||||
/** @inheritdoc
|
||||
*/
|
||||
public function upgradeWidget(Widget $widget, int $fromSchema, int $toSchema): bool
|
||||
{
|
||||
$this->getLog()->debug('upgradeWidget: ' . $widget->getId() . ' from: ' . $fromSchema . ' to: ' . $toSchema);
|
||||
|
||||
$upgraded = false;
|
||||
$overrideTemplate = $widget->getOptionValue('overrideTemplate', 0);
|
||||
|
||||
// If the widget has override template
|
||||
if ($overrideTemplate == 1) {
|
||||
$widget->setOptionValue('templateId', 'attrib', 'currencies_custom_html');
|
||||
$upgraded = true;
|
||||
|
||||
// We need to tranlate the legacy options to the new values
|
||||
$widget->changeOption('widgetOriginalWidth', 'widgetDesignWidth');
|
||||
$widget->changeOption('widgetOriginalHeight', 'widgetDesignHeight');
|
||||
}
|
||||
|
||||
// We need to change duration per page to duration per item
|
||||
$widget->changeOption('durationIsPerPage', 'durationIsPerItem');
|
||||
|
||||
return $upgraded;
|
||||
}
|
||||
|
||||
public function saveTemplate(string $template, string $fileName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
78
lib/Widget/Compatibility/DatasetWidgetCompatibility.php
Normal file
78
lib/Widget/Compatibility/DatasetWidgetCompatibility.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?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\Widget\Compatibility;
|
||||
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityInterface;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityTrait;
|
||||
|
||||
/**
|
||||
* Convert widget from an old schema to a new schema
|
||||
*/
|
||||
class DatasetWidgetCompatibility implements WidgetCompatibilityInterface
|
||||
{
|
||||
use WidgetCompatibilityTrait;
|
||||
|
||||
/** @inheritdoc
|
||||
*/
|
||||
public function upgradeWidget(Widget $widget, int $fromSchema, int $toSchema): bool
|
||||
{
|
||||
$this->getLog()->debug('upgradeWidget: ' . $widget->getId() . ' from: ' . $fromSchema . ' to: ' . $toSchema);
|
||||
|
||||
// Did we originally come from a dataset ticker?
|
||||
if ($widget->getOriginalValue('type') === 'datasetticker') {
|
||||
$newTemplateId = 'dataset_custom_html';
|
||||
$widget->changeOption('css', 'styleSheet');
|
||||
} else {
|
||||
if ($widget->getOptionValue('overrideTemplate', 0) == 0) {
|
||||
$newTemplateId = match ($widget->getOptionValue('templateId', '')) {
|
||||
'light-green' => 'dataset_table_2',
|
||||
'simple-round' => 'dataset_table_3',
|
||||
'transparent-blue' => 'dataset_table_4',
|
||||
'orange-grey-striped' => 'dataset_table_5',
|
||||
'split-rows' => 'dataset_table_6',
|
||||
'dark-round' => 'dataset_table_7',
|
||||
'pill-colored' => 'dataset_table_8',
|
||||
default => 'dataset_table_1',
|
||||
};
|
||||
} else {
|
||||
$newTemplateId = 'dataset_table_custom_html';
|
||||
}
|
||||
|
||||
// We have changed the format of columns to be an array in v4.
|
||||
$columns = $widget->getOptionValue('columns', '');
|
||||
if (!empty($columns)) {
|
||||
$widget->setOptionValue('columns', 'attrib', '[' . $columns . ']');
|
||||
}
|
||||
}
|
||||
|
||||
$widget->setOptionValue('templateId', 'attrib', $newTemplateId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function saveTemplate(string $template, string $fileName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
52
lib/Widget/Compatibility/NotificationViewCompatibility.php
Normal file
52
lib/Widget/Compatibility/NotificationViewCompatibility.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?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\Widget\Compatibility;
|
||||
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityInterface;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityTrait;
|
||||
|
||||
class NotificationViewCompatibility implements WidgetCompatibilityInterface
|
||||
{
|
||||
use WidgetCompatibilityTrait;
|
||||
|
||||
public function upgradeWidget(Widget $widget, int $fromSchema, int $toSchema): bool
|
||||
{
|
||||
$this->getLog()->debug('upgradeWidget: ' . $widget->getId() . ' from: ' . $fromSchema . ' to: ' . $toSchema);
|
||||
|
||||
$upgraded = false;
|
||||
|
||||
if ($fromSchema <= 1) {
|
||||
// Add a templateId.
|
||||
$widget->setOptionValue('templateId', 'attrib', 'message_custom_html');
|
||||
$upgraded = true;
|
||||
}
|
||||
|
||||
return $upgraded;
|
||||
}
|
||||
|
||||
public function saveTemplate(string $template, string $fileName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
157
lib/Widget/Compatibility/RssWidgetCompatibility.php
Normal file
157
lib/Widget/Compatibility/RssWidgetCompatibility.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?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\Widget\Compatibility;
|
||||
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityInterface;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityTrait;
|
||||
|
||||
/**
|
||||
* Convert RSS old kebab-case properties to camelCase
|
||||
*/
|
||||
class RssWidgetCompatibility implements WidgetCompatibilityInterface
|
||||
{
|
||||
use WidgetCompatibilityTrait;
|
||||
|
||||
/** @inheritdoc
|
||||
*/
|
||||
public function upgradeWidget(Widget $widget, int $fromSchema, int $toSchema): bool
|
||||
{
|
||||
$this->getLog()->debug('upgradeWidget: ' . $widget->getId() . ' from: ' . $fromSchema . ' to: ' . $toSchema);
|
||||
|
||||
// Decode URL (always make sure we save URLs decoded)
|
||||
$widget->setOptionValue('uri', 'attrib', urldecode($widget->getOptionValue('uri', '')));
|
||||
|
||||
// Swap to new template names.
|
||||
$overrideTemplate = $widget->getOptionValue('overrideTemplate', 0);
|
||||
|
||||
if ($overrideTemplate) {
|
||||
$newTemplateId = 'article_custom_html';
|
||||
} else {
|
||||
$newTemplateId = match ($widget->getOptionValue('templateId', '')) {
|
||||
'media-rss-image-only' => 'article_image_only',
|
||||
'media-rss-with-left-hand-text' => 'article_with_left_hand_text',
|
||||
'media-rss-with-title' => 'article_with_title',
|
||||
'prominent-title-with-desc-and-name-separator' => 'article_with_desc_and_name_separator',
|
||||
default => 'article_title_only',
|
||||
};
|
||||
|
||||
// If template id is "article_with_desc_and_name_separator"
|
||||
// set showSideBySide to 1 to replicate behaviour in v3 for marquee
|
||||
$effect = $widget->getOptionValue('effect', null);
|
||||
if (
|
||||
$newTemplateId === 'article_with_desc_and_name_separator' &&
|
||||
$effect === 'marqueeLeft' ||
|
||||
$effect === 'marqueeRight' ||
|
||||
$effect === 'marqueeUp' ||
|
||||
$effect === 'marqueeDown'
|
||||
) {
|
||||
$widget->setOptionValue('showSideBySide', 'attrib', 1);
|
||||
}
|
||||
}
|
||||
$widget->setOptionValue('templateId', 'attrib', $newTemplateId);
|
||||
|
||||
// If the new templateId is custom, we need to parse the old template for image enclosures
|
||||
if ($newTemplateId === 'article_custom_html') {
|
||||
$template = $widget->getOptionValue('template', null);
|
||||
if (!empty($template)) {
|
||||
$modified = false;
|
||||
$matches = [];
|
||||
preg_match_all('/\[(.*?)\]/', $template, $matches);
|
||||
|
||||
for ($i = 0; $i < count($matches[1]); $i++) {
|
||||
// We have a [Link] or a [xxx|image] tag
|
||||
$match = $matches[1][$i];
|
||||
if ($match === 'Link' || $match === 'Link|image') {
|
||||
// This is a straight-up enclosure (which is the default).
|
||||
$template = str_replace($matches[0][$i], '<img src="[image]" alt="Image" />', $template);
|
||||
$modified = true;
|
||||
} else if (str_contains($match, '|image')) {
|
||||
// [tag|image|attribute]
|
||||
// Set the necessary options depending on how our tag is made up
|
||||
$parts = explode('|', $match);
|
||||
$tag = $parts[0];
|
||||
$attribute = $parts[2] ?? null;
|
||||
|
||||
$widget->setOptionValue('imageSource', 'attrib', 'custom');
|
||||
$widget->setOptionValue('imageSourceTag', 'attrib', $tag);
|
||||
if (!empty($attribute)) {
|
||||
$widget->setOptionValue('imageSourceAttribute', 'attrib', $attribute);
|
||||
}
|
||||
|
||||
$template = str_replace($matches[0][$i], '<img src="[image]" alt="Image"/>', $template);
|
||||
$modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($modified) {
|
||||
$widget->setOptionValue('template', 'cdata', $template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Change some other options if they have been set.
|
||||
foreach ($widget->widgetOptions as $option) {
|
||||
$widgetChangeOption = null;
|
||||
switch ($option->option) {
|
||||
case 'background-color':
|
||||
$widgetChangeOption = 'itemBackgroundColor';
|
||||
break;
|
||||
|
||||
case 'title-color':
|
||||
$widgetChangeOption = 'itemTitleColor';
|
||||
break;
|
||||
|
||||
case 'name-color':
|
||||
$widgetChangeOption = 'itemNameColor';
|
||||
break;
|
||||
|
||||
case 'description-color':
|
||||
$widgetChangeOption = 'itemDescriptionColor';
|
||||
break;
|
||||
|
||||
case 'font-size':
|
||||
$widgetChangeOption = 'itemFontSize';
|
||||
break;
|
||||
|
||||
case 'image-fit':
|
||||
$widgetChangeOption = 'itemImageFit';
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!empty($widgetChangeOption)) {
|
||||
$widget->changeOption($option->option, $widgetChangeOption);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function saveTemplate(string $template, string $fileName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
77
lib/Widget/Compatibility/SocialMediaWidgetCompatibility.php
Normal file
77
lib/Widget/Compatibility/SocialMediaWidgetCompatibility.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?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\Widget\Compatibility;
|
||||
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityInterface;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityTrait;
|
||||
|
||||
/**
|
||||
* Convert widget from an old schema to a new schema
|
||||
*/
|
||||
class SocialMediaWidgetCompatibility implements WidgetCompatibilityInterface
|
||||
{
|
||||
use WidgetCompatibilityTrait;
|
||||
|
||||
/** @inheritdoc
|
||||
*/
|
||||
public function upgradeWidget(Widget $widget, int $fromSchema, int $toSchema): bool
|
||||
{
|
||||
$this->getLog()->debug('upgradeWidget: ' . $widget->getId() . ' from: ' . $fromSchema . ' to: ' . $toSchema);
|
||||
|
||||
$overrideTemplate = $widget->getOptionValue('overrideTemplate', 0);
|
||||
if ($overrideTemplate == 1) {
|
||||
$newTemplateId = 'social_media_custom_html';
|
||||
} else {
|
||||
$newTemplateId = match ($widget->getOptionValue('templateId', '')) {
|
||||
'full-timeline-np' => 'social_media_static_1',
|
||||
'full-timeline' => 'social_media_static_2',
|
||||
'tweet-with-profileimage-left' => 'social_media_static_4',
|
||||
'tweet-with-profileimage-right' => 'social_media_static_5',
|
||||
'tweet-1' => 'social_media_static_6',
|
||||
'tweet-2' => 'social_media_static_7',
|
||||
'tweet-4' => 'social_media_static_8',
|
||||
'tweet-6NP' => 'social_media_static_9',
|
||||
'tweet-6PL' => 'social_media_static_10',
|
||||
'tweet-7' => 'social_media_static_11',
|
||||
'tweet-8' => 'social_media_static_12',
|
||||
default => 'social_media_static_3',
|
||||
};
|
||||
}
|
||||
$widget->setOptionValue('templateId', 'attrib', $newTemplateId);
|
||||
|
||||
// If overriden, we need to tranlate the legacy options to the new values
|
||||
if ($overrideTemplate == 1) {
|
||||
$widget->changeOption('widgetOriginalWidth', 'widgetDesignWidth');
|
||||
$widget->changeOption('widgetOriginalHeight', 'widgetDesignHeight');
|
||||
$widget->changeOption('widgetOriginalPadding', 'widgetDesignGap');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function saveTemplate(string $template, string $fileName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
66
lib/Widget/Compatibility/StocksWidgetCompatibility.php
Normal file
66
lib/Widget/Compatibility/StocksWidgetCompatibility.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?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\Widget\Compatibility;
|
||||
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityInterface;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityTrait;
|
||||
|
||||
/**
|
||||
* Convert widget from an old schema to a new schema
|
||||
*/
|
||||
class StocksWidgetCompatibility implements WidgetCompatibilityInterface
|
||||
{
|
||||
use WidgetCompatibilityTrait;
|
||||
|
||||
/** @inheritdoc
|
||||
*/
|
||||
public function upgradeWidget(Widget $widget, int $fromSchema, int $toSchema): bool
|
||||
{
|
||||
$this->getLog()->debug('upgradeWidget: ' . $widget->getId() . ' from: ' . $fromSchema . ' to: ' . $toSchema);
|
||||
|
||||
$upgraded = false;
|
||||
$overrideTemplate = $widget->getOptionValue('overrideTemplate', 0);
|
||||
|
||||
// If the widget has override template
|
||||
if ($overrideTemplate == 1) {
|
||||
$widget->setOptionValue('templateId', 'attrib', 'stocks_custom_html');
|
||||
$upgraded = true;
|
||||
|
||||
// We need to tranlate the legacy options to the new values
|
||||
$widget->changeOption('widgetOriginalWidth', 'widgetDesignWidth');
|
||||
$widget->changeOption('widgetOriginalHeight', 'widgetDesignHeight');
|
||||
}
|
||||
|
||||
// We need to change duration per page to duration per item
|
||||
$widget->changeOption('durationIsPerPage', 'durationIsPerItem');
|
||||
|
||||
return $upgraded;
|
||||
}
|
||||
|
||||
public function saveTemplate(string $template, string $fileName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
109
lib/Widget/Compatibility/SubPlaylistWidgetCompatibility.php
Normal file
109
lib/Widget/Compatibility/SubPlaylistWidgetCompatibility.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?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\Widget\Compatibility;
|
||||
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityInterface;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityTrait;
|
||||
use Xibo\Widget\SubPlaylistItem;
|
||||
|
||||
/**
|
||||
* Convert widget from an old schema to a new schema
|
||||
*/
|
||||
class SubPlaylistWidgetCompatibility implements WidgetCompatibilityInterface
|
||||
{
|
||||
use WidgetCompatibilityTrait;
|
||||
|
||||
/** @inheritdoc
|
||||
*/
|
||||
public function upgradeWidget(Widget $widget, int $fromSchema, int $toSchema): bool
|
||||
{
|
||||
$this->getLog()->debug('upgradeWidget: ' . $widget->getId() . ' from: ' . $fromSchema . ' to: ' . $toSchema);
|
||||
|
||||
$upgraded = false;
|
||||
$playlists = [];
|
||||
$playlistIds = [];
|
||||
|
||||
// subPlaylistOptions and subPlaylistIds are no longer in use from 2.3
|
||||
// we need to capture these options to support Layout with sub-playlist import from older CMS
|
||||
foreach ($widget->widgetOptions as $option) {
|
||||
if ($option->option === 'subPlaylists') {
|
||||
$playlists = json_decode($widget->getOptionValue('subPlaylists', '[]'), true);
|
||||
}
|
||||
|
||||
if ($option->option === 'subPlaylistIds') {
|
||||
$playlistIds = json_decode($widget->getOptionValue('subPlaylistIds', '[]'), true);
|
||||
}
|
||||
|
||||
if ($option->option === 'subPlaylistOptions') {
|
||||
$subPlaylistOptions = json_decode($widget->getOptionValue('subPlaylistOptions', '[]'), true);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($playlists) <= 0) {
|
||||
$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,
|
||||
];
|
||||
}
|
||||
|
||||
$playlistItems = [];
|
||||
foreach ($playlists as $playlist) {
|
||||
$item = new SubPlaylistItem();
|
||||
$item->rowNo = intval($playlist['rowNo']);
|
||||
$item->playlistId = $playlist['playlistId'];
|
||||
$item->spotFill = $playlist['spotFill'] ?? '';
|
||||
$item->spotLength = $playlist['spotLength'] !== '' ? intval($playlist['spotLength']) : '';
|
||||
$item->spots = $playlist['spots'] !== '' ? intval($playlist['spots']) : '';
|
||||
|
||||
$playlistItems[] = $item;
|
||||
}
|
||||
|
||||
if (count($playlistItems) > 0) {
|
||||
$widget->setOptionValue('subPlaylists', 'attrib', json_encode($playlistItems));
|
||||
$widget->removeOption('subPlaylistIds');
|
||||
$widget->removeOption('subPlaylistOptions');
|
||||
$upgraded = true;
|
||||
}
|
||||
} else {
|
||||
$this->getLog()->debug(
|
||||
'upgradeWidget : subplaylist ' . $widget->widgetId .
|
||||
' with already updated widget options, save to update schema version'
|
||||
);
|
||||
$upgraded = true;
|
||||
}
|
||||
|
||||
return $upgraded;
|
||||
}
|
||||
|
||||
public function saveTemplate(string $template, string $fileName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
100
lib/Widget/Compatibility/WeatherWidgetCompatibility.php
Normal file
100
lib/Widget/Compatibility/WeatherWidgetCompatibility.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?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\Widget\Compatibility;
|
||||
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityInterface;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityTrait;
|
||||
|
||||
/**
|
||||
* Convert widget from an old schema to a new schema
|
||||
*/
|
||||
class WeatherWidgetCompatibility implements WidgetCompatibilityInterface
|
||||
{
|
||||
use WidgetCompatibilityTrait;
|
||||
|
||||
/** @inheritdoc
|
||||
*/
|
||||
public function upgradeWidget(Widget $widget, int $fromSchema, int $toSchema): bool
|
||||
{
|
||||
$this->getLog()->debug('upgradeWidget: ' . $widget->getId() . ' from: ' . $fromSchema . ' to: ' . $toSchema);
|
||||
|
||||
$overrideTemplate = $widget->getOptionValue('overrideTemplate', 0);
|
||||
if ($overrideTemplate == 1) {
|
||||
$newTemplateId = 'weather_custom_html';
|
||||
} else {
|
||||
$newTemplateId = match ($widget->getOptionValue('templateId', '')) {
|
||||
'weather-module0-singleday' => 'weather_2',
|
||||
'weather-module0-singleday2' => 'weather_3',
|
||||
'weather-module1l' => 'weather_4',
|
||||
'weather-module1p' => 'weather_5',
|
||||
'weather-module2l' => 'weather_6',
|
||||
'weather-module2p' => 'weather_7',
|
||||
'weather-module3l' => 'weather_8',
|
||||
'weather-module3p' => 'weather_9',
|
||||
'weather-module4l' => 'weather_10',
|
||||
'weather-module4p' => 'weather_11',
|
||||
'weather-module5l' => 'weather_12',
|
||||
'weather-module6h' => 'weather_13',
|
||||
'weather-module6v' => 'weather_14',
|
||||
'weather-module-7s' => 'weather_15',
|
||||
'weather-module-8s' => 'weather_16',
|
||||
'weather-module-9' => 'weather_17',
|
||||
'weather-module-10l' => 'weather_18',
|
||||
default => 'weather_1',
|
||||
};
|
||||
}
|
||||
$widget->setOptionValue('templateId', 'attrib', $newTemplateId);
|
||||
|
||||
// If overriden, we need to tranlate the legacy options to the new values
|
||||
if ($overrideTemplate == 1) {
|
||||
$widget->changeOption('widgetOriginalWidth', 'widgetDesignWidth');
|
||||
$widget->changeOption('widgetOriginalHeight', 'widgetDesignHeight');
|
||||
}
|
||||
|
||||
// Process the background image properties so that they are removed if empty
|
||||
$this->removeOptionIfEquals($widget, 'cloudy-image');
|
||||
$this->removeOptionIfEquals($widget, 'day-cloudy-image');
|
||||
$this->removeOptionIfEquals($widget, 'day-sunny-image');
|
||||
$this->removeOptionIfEquals($widget, 'fog-image');
|
||||
$this->removeOptionIfEquals($widget, 'hail-image');
|
||||
$this->removeOptionIfEquals($widget, 'night-clear-image');
|
||||
$this->removeOptionIfEquals($widget, 'night-partly-cloudy-image');
|
||||
$this->removeOptionIfEquals($widget, 'rain-image');
|
||||
$this->removeOptionIfEquals($widget, 'snow-image');
|
||||
$this->removeOptionIfEquals($widget, 'windy-image');
|
||||
return true;
|
||||
}
|
||||
|
||||
private function removeOptionIfEquals(Widget $widget, string $option): void
|
||||
{
|
||||
if ($widget->getOptionValue($option, null) === $option) {
|
||||
$widget->removeOption($option);
|
||||
}
|
||||
}
|
||||
|
||||
public function saveTemplate(string $template, string $fileName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
78
lib/Widget/Compatibility/WorldClockWidgetCompatibility.php
Normal file
78
lib/Widget/Compatibility/WorldClockWidgetCompatibility.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?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\Widget\Compatibility;
|
||||
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityInterface;
|
||||
use Xibo\Widget\Provider\WidgetCompatibilityTrait;
|
||||
|
||||
/**
|
||||
* Convert widget from an old schema to a new schema
|
||||
*/
|
||||
class WorldClockWidgetCompatibility implements WidgetCompatibilityInterface
|
||||
{
|
||||
use WidgetCompatibilityTrait;
|
||||
|
||||
/** @inheritdoc
|
||||
*/
|
||||
public function upgradeWidget(Widget $widget, int $fromSchema, int $toSchema): bool
|
||||
{
|
||||
$this->getLog()->debug('upgradeWidget: ' . $widget->getId() . ' from: ' . $fromSchema . ' to: ' . $toSchema);
|
||||
|
||||
$overrideTemplate = $widget->getOptionValue('overrideTemplate', 0);
|
||||
if ($overrideTemplate) {
|
||||
$widget->type = 'worldclock-digital-custom';
|
||||
} else {
|
||||
$widget->type = match ($widget->getOptionValue('clockType', 1)) {
|
||||
2 => 'worldclock-analogue',
|
||||
default => match ($widget->getOptionValue('templateId', '')) {
|
||||
'worldclock1' => 'worldclock-digital-text',
|
||||
'worldclock2' => 'worldclock-digital-date',
|
||||
default => 'worldclock-digital-custom',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// We need to tranlate the legacy options to the new values
|
||||
$widget->changeOption('clockCols', 'numCols');
|
||||
$widget->changeOption('clockRows', 'numRows');
|
||||
|
||||
if ($overrideTemplate == 1) {
|
||||
$widget->changeOption('mainTemplate', 'template_html');
|
||||
$widget->changeOption('styleSheet', 'template_style');
|
||||
$widget->changeOption('widgetOriginalWidth', 'widgetDesignWidth');
|
||||
$widget->changeOption('widgetOriginalHeight', 'widgetDesignHeight');
|
||||
}
|
||||
|
||||
// Always remove template id / clockType from world clock
|
||||
$widget->removeOption('templateId');
|
||||
$widget->removeOption('clockType');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function saveTemplate(string $template, string $fileName): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
96
lib/Widget/CurrenciesAndStocksProvider.php
Normal file
96
lib/Widget/CurrenciesAndStocksProvider.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?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\Widget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Xibo\Widget\Provider\DataProviderInterface;
|
||||
use Xibo\Widget\Provider\DurationProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderTrait;
|
||||
|
||||
/**
|
||||
* A widget provider for stocks and currencies, only used to correctly set the numItems
|
||||
*/
|
||||
class CurrenciesAndStocksProvider implements WidgetProviderInterface
|
||||
{
|
||||
use WidgetProviderTrait;
|
||||
|
||||
/**
|
||||
* We want to pass this out to the event mechanism for 3rd party sources.
|
||||
* @param \Xibo\Widget\Provider\DataProviderInterface $dataProvider
|
||||
* @return \Xibo\Widget\Provider\WidgetProviderInterface
|
||||
*/
|
||||
public function fetchData(DataProviderInterface $dataProvider): WidgetProviderInterface
|
||||
{
|
||||
$dataProvider->setIsUseEvent();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Special handling for currencies and stocks where the number of data items is based on the quantity of
|
||||
* items input in the `items` property.
|
||||
* @param \Xibo\Widget\Provider\DurationProviderInterface $durationProvider
|
||||
* @return \Xibo\Widget\Provider\WidgetProviderInterface
|
||||
*/
|
||||
public function fetchDuration(DurationProviderInterface $durationProvider): WidgetProviderInterface
|
||||
{
|
||||
$this->getLog()->debug('fetchDuration: CurrenciesAndStocksProvider');
|
||||
|
||||
// Currencies and stocks are based on the number of items set in the respective fields.
|
||||
$items = $durationProvider->getWidget()->getOptionValue('items', null);
|
||||
if ($items === null) {
|
||||
$this->getLog()->debug('fetchDuration: CurrenciesAndStocksProvider: no items set');
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($durationProvider->getWidget()->getOptionValue('durationIsPerItem', 0) == 0) {
|
||||
$this->getLog()->debug('fetchDuration: CurrenciesAndStocksProvider: duration per item not set');
|
||||
return $this;
|
||||
}
|
||||
|
||||
$numItems = count(explode(',', $items));
|
||||
|
||||
$this->getLog()->debug('fetchDuration: CurrenciesAndStocksProvider: number of items: ' . $numItems);
|
||||
|
||||
if ($numItems > 1) {
|
||||
// If we have paging involved then work out the page count.
|
||||
$itemsPerPage = $durationProvider->getWidget()->getOptionValue('itemsPerPage', 0);
|
||||
if ($itemsPerPage > 0) {
|
||||
$numItems = ceil($numItems / $itemsPerPage);
|
||||
}
|
||||
|
||||
$durationProvider->setDuration($durationProvider->getWidget()->calculatedDuration * $numItems);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDataCacheKey(DataProviderInterface $dataProvider): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDataModifiedDt(DataProviderInterface $dataProvider): ?Carbon
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
60
lib/Widget/DashboardProvider.php
Normal file
60
lib/Widget/DashboardProvider.php
Normal file
@@ -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\Widget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Xibo\Event\DashboardDataRequestEvent;
|
||||
use Xibo\Widget\Provider\DataProviderInterface;
|
||||
use Xibo\Widget\Provider\DurationProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderTrait;
|
||||
|
||||
class DashboardProvider implements WidgetProviderInterface
|
||||
{
|
||||
use WidgetProviderTrait;
|
||||
|
||||
public function fetchData(DataProviderInterface $dataProvider): WidgetProviderInterface
|
||||
{
|
||||
$this->getLog()->debug('fetchData: DashboardProvider passing to event');
|
||||
$this->getDispatcher()->dispatch(
|
||||
new DashboardDataRequestEvent($dataProvider),
|
||||
DashboardDataRequestEvent::$NAME
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fetchDuration(DurationProviderInterface $durationProvider): WidgetProviderInterface
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDataCacheKey(DataProviderInterface $dataProvider): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDataModifiedDt(DataProviderInterface $dataProvider): ?Carbon
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
97
lib/Widget/DataSetProvider.php
Normal file
97
lib/Widget/DataSetProvider.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?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\Widget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Xibo\Event\DataSetDataRequestEvent;
|
||||
use Xibo\Event\DataSetModifiedDtRequestEvent;
|
||||
use Xibo\Widget\Provider\DataProviderInterface;
|
||||
use Xibo\Widget\Provider\DurationProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderTrait;
|
||||
|
||||
/**
|
||||
* Provides data from DataSets.
|
||||
*/
|
||||
class DataSetProvider implements WidgetProviderInterface
|
||||
{
|
||||
use WidgetProviderTrait;
|
||||
|
||||
public function fetchData(DataProviderInterface $dataProvider): WidgetProviderInterface
|
||||
{
|
||||
$this->getLog()->debug('fetchData: DataSetProvider passing to event');
|
||||
$this->getDispatcher()->dispatch(
|
||||
new DataSetDataRequestEvent($dataProvider),
|
||||
DataSetDataRequestEvent::$NAME
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fetchDuration(DurationProviderInterface $durationProvider): WidgetProviderInterface
|
||||
{
|
||||
if ($durationProvider->getWidget()->getOptionValue('durationIsPerItem', 0) == 1) {
|
||||
// Count of rows
|
||||
$numItems = $durationProvider->getWidget()->getOptionValue('numItems', 0);
|
||||
|
||||
// Workaround: dataset static (from v3 dataset view) has rowsPerPage instead.
|
||||
$rowsPerPage = $durationProvider->getWidget()->getOptionValue('rowsPerPage', 0);
|
||||
$itemsPerPage = $durationProvider->getWidget()->getOptionValue('itemsPerPage', 0);
|
||||
|
||||
// If we have paging involved then work out the page count.
|
||||
$itemsPerPage = max($itemsPerPage, $rowsPerPage);
|
||||
if ($itemsPerPage > 0) {
|
||||
$numItems = ceil($numItems / $itemsPerPage);
|
||||
}
|
||||
|
||||
$durationProvider->setDuration($durationProvider->getWidget()->calculatedDuration * $numItems);
|
||||
|
||||
$this->getLog()->debug(sprintf(
|
||||
'fetchDuration: duration is per item, numItems: %s, rowsPerPage: %s, itemsPerPage: %s',
|
||||
$numItems,
|
||||
$rowsPerPage,
|
||||
$itemsPerPage
|
||||
));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDataCacheKey(DataProviderInterface $dataProvider): ?string
|
||||
{
|
||||
// No special cache key requirements.
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDataModifiedDt(DataProviderInterface $dataProvider): ?Carbon
|
||||
{
|
||||
$this->getLog()->debug('fetchData: DataSetProvider passing to modifiedDt request event');
|
||||
$dataSetId = $dataProvider->getProperty('dataSetId');
|
||||
if ($dataSetId !== null) {
|
||||
// Raise an event to get the modifiedDt of this dataSet
|
||||
$event = new DataSetModifiedDtRequestEvent($dataSetId);
|
||||
$this->getDispatcher()->dispatch($event, DataSetModifiedDtRequestEvent::$NAME);
|
||||
return max($event->getModifiedDt(), $dataProvider->getWidgetModifiedDt());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
80
lib/Widget/DataType/Article.php
Normal file
80
lib/Widget/DataType/Article.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?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\Widget\DataType;
|
||||
|
||||
use Xibo\Widget\Definition\DataType;
|
||||
|
||||
/**
|
||||
* An article, usually from a blog or news feed.
|
||||
*/
|
||||
class Article implements \JsonSerializable, DataTypeInterface
|
||||
{
|
||||
public static $NAME = 'article';
|
||||
public $title;
|
||||
public $summary;
|
||||
public $content;
|
||||
public $author;
|
||||
public $permalink;
|
||||
public $link;
|
||||
public $image;
|
||||
|
||||
/** @var \Carbon\Carbon */
|
||||
public $date;
|
||||
|
||||
/** @var \Carbon\Carbon */
|
||||
public $publishedDate;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'title' => $this->title,
|
||||
'summary' => $this->summary,
|
||||
'content' => $this->content,
|
||||
'author' => $this->author,
|
||||
'permalink' => $this->permalink,
|
||||
'link' => $this->link,
|
||||
'date' => $this->date->format('c'),
|
||||
'publishedDate' => $this->publishedDate->format('c'),
|
||||
'image' => $this->image,
|
||||
];
|
||||
}
|
||||
|
||||
public function getDefinition(): DataType
|
||||
{
|
||||
$dataType = new DataType();
|
||||
$dataType->id = self::$NAME;
|
||||
$dataType->name = __('Article');
|
||||
$dataType
|
||||
->addField('title', __('Title'), 'text')
|
||||
->addField('summary', __('Summary'), 'text')
|
||||
->addField('content', __('Content'), 'text')
|
||||
->addField('author', __('Author'), 'text')
|
||||
->addField('permalink', __('Permalink'), 'text')
|
||||
->addField('link', __('Link'), 'text')
|
||||
->addField('date', __('Created Date'), 'datetime')
|
||||
->addField('publishedDate', __('Published Date'), 'datetime')
|
||||
->addField('image', __('Image'), 'image');
|
||||
return $dataType;
|
||||
}
|
||||
}
|
||||
37
lib/Widget/DataType/DataTypeInterface.php
Normal file
37
lib/Widget/DataType/DataTypeInterface.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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\Widget\DataType;
|
||||
|
||||
use Xibo\Widget\Definition\DataType;
|
||||
|
||||
/**
|
||||
* A class representation of a data type.
|
||||
*/
|
||||
interface DataTypeInterface
|
||||
{
|
||||
/**
|
||||
* Return the definition
|
||||
* @return \Xibo\Widget\Definition\DataType
|
||||
*/
|
||||
public function getDefinition(): DataType;
|
||||
}
|
||||
73
lib/Widget/DataType/Event.php
Normal file
73
lib/Widget/DataType/Event.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?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\Widget\DataType;
|
||||
|
||||
use Xibo\Widget\Definition\DataType;
|
||||
|
||||
/**
|
||||
* Event data type
|
||||
*/
|
||||
class Event implements \JsonSerializable, DataTypeInterface
|
||||
{
|
||||
public static $NAME = 'event';
|
||||
public $summary;
|
||||
public $description;
|
||||
public $location;
|
||||
|
||||
/** @var \Carbon\Carbon */
|
||||
public $startDate;
|
||||
|
||||
/** @var \Carbon\Carbon */
|
||||
public $endDate;
|
||||
|
||||
/** @var bool */
|
||||
public $isAllDay = false;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'summary' => $this->summary,
|
||||
'description' => $this->description,
|
||||
'location' => $this->location,
|
||||
'startDate' => $this->startDate->format('c'),
|
||||
'endDate' => $this->endDate->format('c'),
|
||||
'isAllDay' => $this->isAllDay,
|
||||
];
|
||||
}
|
||||
|
||||
public function getDefinition(): DataType
|
||||
{
|
||||
$dataType = new DataType();
|
||||
$dataType->id = self::$NAME;
|
||||
$dataType->name = __('Event');
|
||||
$dataType
|
||||
->addField('summary', __('Summary'), 'text')
|
||||
->addField('description', __('Description'), 'text')
|
||||
->addField('location', __('Location'), 'text')
|
||||
->addField('startDate', __('Start Date'), 'datetime')
|
||||
->addField('endDate', __('End Date'), 'datetime')
|
||||
->addField('isAllDay', __('All Day Event'), 'boolean');
|
||||
return $dataType;
|
||||
}
|
||||
}
|
||||
161
lib/Widget/DataType/Forecast.php
Normal file
161
lib/Widget/DataType/Forecast.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?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\Widget\DataType;
|
||||
|
||||
use Xibo\Widget\Definition\DataType;
|
||||
|
||||
/**
|
||||
* Forecast DataType
|
||||
*/
|
||||
class Forecast implements \JsonSerializable, DataTypeInterface
|
||||
{
|
||||
public static $NAME = 'forecast';
|
||||
public $time;
|
||||
public $sunSet;
|
||||
public $sunRise;
|
||||
public $summary;
|
||||
public $icon;
|
||||
public $wicon;
|
||||
public $temperature;
|
||||
public $temperatureRound;
|
||||
public $temperatureNight;
|
||||
public $temperatureNightRound;
|
||||
public $temperatureMorning;
|
||||
public $temperatureMorningRound;
|
||||
public $temperatureEvening;
|
||||
public $temperatureEveningRound;
|
||||
public $temperatureHigh;
|
||||
public $temperatureMaxRound;
|
||||
public $temperatureLow;
|
||||
public $temperatureMinRound;
|
||||
public $temperatureMean;
|
||||
public $temperatureMeanRound;
|
||||
public $apparentTemperature;
|
||||
public $apparentTemperatureRound;
|
||||
public $dewPoint;
|
||||
public $humidity;
|
||||
public $humidityPercent;
|
||||
public $pressure;
|
||||
public $windSpeed;
|
||||
public $windBearing;
|
||||
public $windDirection;
|
||||
public $cloudCover;
|
||||
public $uvIndex;
|
||||
public $visibility;
|
||||
public $ozone;
|
||||
public $location;
|
||||
|
||||
public $temperatureUnit;
|
||||
public $windSpeedUnit;
|
||||
public $visibilityDistanceUnit;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'time' => $this->time,
|
||||
'sunSet' => $this->sunSet,
|
||||
'sunRise' => $this->sunRise,
|
||||
'summary' => $this->summary,
|
||||
'icon' => $this->icon,
|
||||
'wicon' => $this->wicon,
|
||||
'temperature' => $this->temperature,
|
||||
'temperatureRound' => $this->temperatureRound,
|
||||
'temperatureNight' => $this->temperatureNight,
|
||||
'temperatureNightRound' => $this->temperatureNightRound,
|
||||
'temperatureMorning' => $this->temperatureMorning,
|
||||
'temperatureMorningRound' => $this->temperatureMorningRound,
|
||||
'temperatureEvening' => $this->temperatureEvening,
|
||||
'temperatureEveningRound' => $this->temperatureEveningRound,
|
||||
'temperatureHigh' => $this->temperatureHigh,
|
||||
'temperatureMaxRound' => $this->temperatureMaxRound,
|
||||
'temperatureLow' => $this->temperatureLow,
|
||||
'temperatureMinRound' => $this->temperatureMinRound,
|
||||
'temperatureMean' => $this->temperatureMean,
|
||||
'temperatureMeanRound' => $this->temperatureMeanRound,
|
||||
'apparentTemperature' => $this->apparentTemperature,
|
||||
'apparentTemperatureRound' => $this->apparentTemperatureRound,
|
||||
'dewPoint' => $this->dewPoint,
|
||||
'humidity' => $this->humidity,
|
||||
'humidityPercent' => $this->humidityPercent,
|
||||
'pressure' => $this->pressure,
|
||||
'windSpeed' => $this->windSpeed,
|
||||
'windBearing' => $this->windBearing,
|
||||
'windDirection' => $this->windDirection,
|
||||
'cloudCover' => $this->cloudCover,
|
||||
'uvIndex' => $this->uvIndex,
|
||||
'visibility' => $this->visibility,
|
||||
'ozone' => $this->ozone,
|
||||
'location' => $this->location,
|
||||
'temperatureUnit' => $this->temperatureUnit,
|
||||
'windSpeedUnit' => $this->windSpeedUnit,
|
||||
'visibilityDistanceUnit' => $this->visibilityDistanceUnit
|
||||
];
|
||||
}
|
||||
|
||||
public function getDefinition(): DataType
|
||||
{
|
||||
$dataType = new DataType();
|
||||
$dataType->id = self::$NAME;
|
||||
$dataType->name = __('Forecast');
|
||||
$dataType
|
||||
->addField('time', 'Time', 'datetime')
|
||||
->addField('sunSet', 'Sun Set', 'datetime')
|
||||
->addField('sunRise', 'Sun Rise', 'datetime')
|
||||
->addField('summary', 'Summary', 'text')
|
||||
->addField('icon', 'Icon', 'text')
|
||||
->addField('wicon', 'Weather Icon', 'text')
|
||||
->addField('temperature', 'Temperature', 'number')
|
||||
->addField('temperatureRound', 'Temperature Round', 'number')
|
||||
->addField('temperatureNight', 'Temperature Night', 'number')
|
||||
->addField('temperatureNightRound', 'Temperature Night Round', 'number')
|
||||
->addField('temperatureMorning', 'Temperature Morning', 'number')
|
||||
->addField('temperatureMorningRound', 'Temperature Morning Round', 'number')
|
||||
->addField('temperatureEvening', 'Temperature Evening', 'number')
|
||||
->addField('temperatureEveningRound', 'Temperature Evening Round', 'number')
|
||||
->addField('temperatureHigh', 'Temperature High', 'number')
|
||||
->addField('temperatureMaxRound', 'Temperature Max Round', 'number')
|
||||
->addField('temperatureLow', 'Temperature Low', 'number')
|
||||
->addField('temperatureMinRound', 'Temperature Min Round', 'number')
|
||||
->addField('temperatureMean', 'Temperature Mean', 'number')
|
||||
->addField('temperatureMeanRound', 'Temperature Mean Round', 'number')
|
||||
->addField('apparentTemperature', 'Apparent Temperature', 'number')
|
||||
->addField('apparentTemperatureRound', 'Apparent Temperature Round', 'number')
|
||||
->addField('dewPoint', 'Dew Point', 'number')
|
||||
->addField('humidity', 'Humidity', 'number')
|
||||
->addField('humidityPercent', 'Humidity Percent', 'number')
|
||||
->addField('pressure', 'Pressure', 'number')
|
||||
->addField('windSpeed', 'Wind Speed', 'number')
|
||||
->addField('windBearing', 'Wind Bearing', 'number')
|
||||
->addField('windDirection', 'Wind Direction', 'text')
|
||||
->addField('cloudCover', 'Cloud Cover', 'number')
|
||||
->addField('uvIndex', 'Uv Index', 'number')
|
||||
->addField('visibility', 'Visibility', 'number')
|
||||
->addField('ozone', 'Ozone', 'number')
|
||||
->addField('location', 'Location', 'text')
|
||||
->addField('temperatureUnit', 'Temperature Unit', 'text')
|
||||
->addField('windSpeedUnit', 'WindSpeed Unit', 'text')
|
||||
->addField('visibilityDistanceUnit', 'VisibilityDistance Unit', 'text');
|
||||
return $dataType;
|
||||
}
|
||||
}
|
||||
70
lib/Widget/DataType/Product.php
Normal file
70
lib/Widget/DataType/Product.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?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\Widget\DataType;
|
||||
|
||||
use Xibo\Widget\Definition\DataType;
|
||||
|
||||
/**
|
||||
* Product DataType (primarily used for the Menu Board component)
|
||||
*/
|
||||
class Product implements \JsonSerializable, DataTypeInterface
|
||||
{
|
||||
public $name;
|
||||
public $price;
|
||||
public $description;
|
||||
public $availability;
|
||||
public $allergyInfo;
|
||||
public $calories;
|
||||
public $image;
|
||||
public $productOptions;
|
||||
|
||||
public function getDefinition(): DataType
|
||||
{
|
||||
$dataType = new DataType();
|
||||
$dataType->id = 'product';
|
||||
$dataType->name = __('Product');
|
||||
$dataType->addField('name', __('Name'), 'string');
|
||||
$dataType->addField('price', __('Price'), 'decimal');
|
||||
$dataType->addField('description', __('Description'), 'string');
|
||||
$dataType->addField('availability', __('Availability'), 'int');
|
||||
$dataType->addField('allergyInfo', __('Allergy Information'), 'string');
|
||||
$dataType->addField('calories', __('Calories'), 'string');
|
||||
$dataType->addField('image', __('Image'), 'int');
|
||||
$dataType->addField('productOptions', __('Product Options'), 'array');
|
||||
return $dataType;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'price' => $this->price,
|
||||
'description' => $this->description,
|
||||
'availability' => $this->availability,
|
||||
'calories' => $this->calories,
|
||||
'allergyInfo' => $this->allergyInfo,
|
||||
'image' => $this->image,
|
||||
'productOptions' => $this->productOptions,
|
||||
];
|
||||
}
|
||||
}
|
||||
55
lib/Widget/DataType/ProductCategory.php
Normal file
55
lib/Widget/DataType/ProductCategory.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?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\Widget\DataType;
|
||||
|
||||
use Xibo\Widget\Definition\DataType;
|
||||
|
||||
/**
|
||||
* Product Category (primarily used for the Menu Board component)
|
||||
*/
|
||||
class ProductCategory implements \JsonSerializable, DataTypeInterface
|
||||
{
|
||||
public $name;
|
||||
public $description;
|
||||
public $image;
|
||||
|
||||
public function getDefinition(): DataType
|
||||
{
|
||||
$dataType = new DataType();
|
||||
$dataType->id = 'product-category';
|
||||
$dataType->name = __('Product Category');
|
||||
$dataType->addField('name', __('Name'), 'string');
|
||||
$dataType->addField('description', __('Description'), 'string');
|
||||
$dataType->addField('image', __('Image'), 'int');
|
||||
return $dataType;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
'image' => $this->image,
|
||||
];
|
||||
}
|
||||
}
|
||||
76
lib/Widget/DataType/SocialMedia.php
Normal file
76
lib/Widget/DataType/SocialMedia.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?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\Widget\DataType;
|
||||
|
||||
use Xibo\Widget\Definition\DataType;
|
||||
|
||||
/**
|
||||
* Social Media DataType
|
||||
*/
|
||||
class SocialMedia implements \JsonSerializable, DataTypeInterface
|
||||
{
|
||||
public static $NAME = 'social-media';
|
||||
public $text;
|
||||
public $user;
|
||||
public $userProfileImage;
|
||||
public $userProfileImageMini;
|
||||
public $userProfileImageBigger;
|
||||
public $location;
|
||||
public $screenName;
|
||||
public $date;
|
||||
public $photo;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'text' => $this->text,
|
||||
'user' => $this->user,
|
||||
'userProfileImage' => $this->userProfileImage,
|
||||
'userProfileImageMini' => $this->userProfileImageMini,
|
||||
'userProfileImageBigger' => $this->userProfileImageBigger,
|
||||
'location' => $this->location,
|
||||
'screenName' => $this->screenName,
|
||||
'date' => $this->date,
|
||||
'photo' => $this->photo,
|
||||
];
|
||||
}
|
||||
|
||||
public function getDefinition(): DataType
|
||||
{
|
||||
$dataType = new DataType();
|
||||
$dataType->id = self::$NAME;
|
||||
$dataType->name = __('Social Media');
|
||||
$dataType
|
||||
->addField('text', __('Text'), 'text', true)
|
||||
->addField('user', __('User'), 'text')
|
||||
->addField('userProfileImage', __('Profile Image'), 'image')
|
||||
->addField('userProfileImageMini', __('Mini Profile Image'), 'image')
|
||||
->addField('userProfileImageBigger', __('Bigger Profile Image'), 'image')
|
||||
->addField('location', __('Location'), 'text')
|
||||
->addField('screenName', __('Screen Name'), 'text')
|
||||
->addField('date', __('Date'), 'datetime')
|
||||
->addField('photo', __('Photo'), 'image');
|
||||
return $dataType;
|
||||
}
|
||||
}
|
||||
196
lib/Widget/Definition/Asset.php
Normal file
196
lib/Widget/Definition/Asset.php
Normal file
@@ -0,0 +1,196 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use Illuminate\Support\Str;
|
||||
use Intervention\Image\ImageManagerStatic as Img;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Slim\Http\Response;
|
||||
use Slim\Http\ServerRequest;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
use Xibo\Xmds\Entity\Dependency;
|
||||
|
||||
/**
|
||||
* An asset
|
||||
*/
|
||||
class Asset implements \JsonSerializable
|
||||
{
|
||||
public $id;
|
||||
public $type;
|
||||
public $alias;
|
||||
public $path;
|
||||
public $mimeType;
|
||||
|
||||
/** @var bool */
|
||||
public $autoInclude;
|
||||
|
||||
/** @var bool */
|
||||
public $cmsOnly;
|
||||
|
||||
public $assetNo;
|
||||
|
||||
private $fileSize;
|
||||
private $md5;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'alias' => $this->alias,
|
||||
'type' => $this->type,
|
||||
'path' => $this->path,
|
||||
'mimeType' => $this->mimeType,
|
||||
'cmsOnly' => $this->cmsOnly,
|
||||
'autoInclude' => $this->autoInclude,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this asset be sent to the player?
|
||||
* @return bool
|
||||
*/
|
||||
public function isSendToPlayer(): bool
|
||||
{
|
||||
return !($this->cmsOnly ?? false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this asset be auto included in the HTML sent to the player
|
||||
* @return bool
|
||||
*/
|
||||
public function isAutoInclude(): bool
|
||||
{
|
||||
return $this->autoInclude && $this->isSendToPlayer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $libraryLocation
|
||||
* @param bool $forceUpdate
|
||||
* @return $this
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function updateAssetCache(string $libraryLocation, bool $forceUpdate = false): Asset
|
||||
{
|
||||
// Verify the asset is cached and update its path.
|
||||
$assetPath = $libraryLocation . 'assets/' . $this->getFilename();
|
||||
if (!file_exists($assetPath) || $forceUpdate) {
|
||||
$result = @copy(PROJECT_ROOT . $this->path, $assetPath);
|
||||
if (!$result) {
|
||||
throw new GeneralException('Unable to copy asset');
|
||||
}
|
||||
$forceUpdate = true;
|
||||
}
|
||||
|
||||
// Get the bundle MD5
|
||||
$assetMd5CachePath = $assetPath . '.md5';
|
||||
if (!file_exists($assetMd5CachePath) || $forceUpdate) {
|
||||
$assetMd5 = md5_file($assetPath);
|
||||
file_put_contents($assetMd5CachePath, $assetMd5);
|
||||
} else {
|
||||
$assetMd5 = file_get_contents($assetPath . '.md5');
|
||||
}
|
||||
|
||||
$this->path = $assetPath;
|
||||
$this->md5 = $assetMd5;
|
||||
$this->fileSize = filesize($assetPath);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this asset as a dependency.
|
||||
* @return \Xibo\Xmds\Entity\Dependency
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function getDependency(): Dependency
|
||||
{
|
||||
// Check that this asset is valid.
|
||||
if (!file_exists($this->path)) {
|
||||
throw new NotFoundException(sprintf(__('Asset %s not found'), $this->path));
|
||||
}
|
||||
|
||||
// Return a dependency
|
||||
return new Dependency(
|
||||
'asset',
|
||||
$this->id,
|
||||
$this->getLegacyId(),
|
||||
$this->path,
|
||||
$this->fileSize,
|
||||
$this->md5,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file name for this asset
|
||||
* @return string
|
||||
*/
|
||||
public function getFilename(): string
|
||||
{
|
||||
return basename($this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a PSR response for this asset.
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function psrResponse(ServerRequest $request, Response $response, string $sendFileMode): ResponseInterface
|
||||
{
|
||||
// Make sure this asset exists
|
||||
if (!file_exists($this->path)) {
|
||||
throw new NotFoundException(__('Asset file does not exist'));
|
||||
}
|
||||
|
||||
$response = $response->withHeader('Content-Length', $this->fileSize);
|
||||
$response = $response->withHeader('Content-Type', $this->mimeType);
|
||||
|
||||
// Output the file
|
||||
if ($sendFileMode === 'Apache') {
|
||||
// Send via Apache X-Sendfile header?
|
||||
$response = $response->withHeader('X-Sendfile', $this->path);
|
||||
} else if ($sendFileMode === 'Nginx') {
|
||||
// Send via Nginx X-Accel-Redirect?
|
||||
$response = $response->withHeader('X-Accel-Redirect', '/download/assets/' . $this->getFilename());
|
||||
} else if (Str::startsWith('image', $this->mimeType)) {
|
||||
$response = Img::make('/' . $this->path)->psrResponse();
|
||||
} else {
|
||||
// Set the right content type.
|
||||
$response = $response->withBody(new Stream(fopen($this->path, 'r')));
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Legacy ID for this asset on older players
|
||||
* there is a risk that this ID will change as modules/templates with assets are added/removed in the system
|
||||
* however, we have mitigated by ensuring that only one instance of any required file is added to rf return
|
||||
* @return int
|
||||
*/
|
||||
private function getLegacyId(): int
|
||||
{
|
||||
return (Dependency::LEGACY_ID_OFFSET_ASSET + $this->assetNo) * -1;
|
||||
}
|
||||
}
|
||||
43
lib/Widget/Definition/Condition.php
Normal file
43
lib/Widget/Definition/Condition.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
/**
|
||||
* Represents a condition for a test
|
||||
*/
|
||||
class Condition implements \JsonSerializable
|
||||
{
|
||||
public $field;
|
||||
public $type;
|
||||
public $value;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'field' => $this->field,
|
||||
'type' => $this->type,
|
||||
'value' => $this->value
|
||||
];
|
||||
}
|
||||
}
|
||||
56
lib/Widget/Definition/DataType.php
Normal file
56
lib/Widget/Definition/DataType.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
/**
|
||||
* A module data type
|
||||
*/
|
||||
class DataType implements \JsonSerializable
|
||||
{
|
||||
public $id;
|
||||
public $name;
|
||||
|
||||
/** @var \Xibo\Widget\Definition\Field[] */
|
||||
public $fields = [];
|
||||
|
||||
public function addField(string $id, string $title, string $type, bool $isRequired = false): DataType
|
||||
{
|
||||
$field = new Field();
|
||||
$field->id = $id;
|
||||
$field->type = $type;
|
||||
$field->title = $title;
|
||||
$field->isRequired = $isRequired;
|
||||
$this->fields[] = $field;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'fields' => $this->fields,
|
||||
];
|
||||
}
|
||||
}
|
||||
56
lib/Widget/Definition/Element.php
Normal file
56
lib/Widget/Definition/Element.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
/**
|
||||
* @SWG\Definition()
|
||||
* A class representing an instance of an element template
|
||||
*/
|
||||
class Element implements \JsonSerializable
|
||||
{
|
||||
public $id;
|
||||
public $top;
|
||||
public $left;
|
||||
public $width;
|
||||
public $height;
|
||||
public $rotation;
|
||||
public $layer;
|
||||
public $elementGroupId;
|
||||
public $properties = [];
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'top' => $this->top,
|
||||
'left' => $this->left,
|
||||
'width' => $this->width,
|
||||
'height' => $this->height,
|
||||
'rotation' => $this->rotation,
|
||||
'layer' => $this->layer,
|
||||
'elementGroupId' => $this->elementGroupId,
|
||||
'properties' => $this->properties
|
||||
];
|
||||
}
|
||||
}
|
||||
56
lib/Widget/Definition/ElementGroup.php
Normal file
56
lib/Widget/Definition/ElementGroup.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
/**
|
||||
* A class representing an instance of a group of elements
|
||||
* @SWG\Definition()
|
||||
*/
|
||||
class ElementGroup implements \JsonSerializable
|
||||
{
|
||||
public $id;
|
||||
public $top;
|
||||
public $left;
|
||||
public $width;
|
||||
public $height;
|
||||
public $layer;
|
||||
public $title;
|
||||
public $slot;
|
||||
public $pinSlot;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'top' => $this->top,
|
||||
'left' => $this->left,
|
||||
'width' => $this->width,
|
||||
'height' => $this->height,
|
||||
'layer' => $this->layer,
|
||||
'title' => $this->title,
|
||||
'slot' => $this->slot,
|
||||
'pinSlot' => $this->pinSlot
|
||||
];
|
||||
}
|
||||
}
|
||||
46
lib/Widget/Definition/Extend.php
Normal file
46
lib/Widget/Definition/Extend.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
/**
|
||||
* @SWG\Definition()
|
||||
* A class representing one template extending another
|
||||
*/
|
||||
class Extend implements \JsonSerializable
|
||||
{
|
||||
public $template;
|
||||
public $override;
|
||||
public $with;
|
||||
public $escapeHtml;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'template' => $this->template,
|
||||
'override' => $this->override,
|
||||
'with' => $this->with,
|
||||
'escapeHtml' => $this->escapeHtml,
|
||||
];
|
||||
}
|
||||
}
|
||||
45
lib/Widget/Definition/Field.php
Normal file
45
lib/Widget/Definition/Field.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
/**
|
||||
* Class representing a data type field
|
||||
*/
|
||||
class Field implements \JsonSerializable
|
||||
{
|
||||
public $id;
|
||||
public $type;
|
||||
public $title;
|
||||
public $isRequired;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'type' => $this->type,
|
||||
'title' => $this->title,
|
||||
'isRequired' => $this->isRequired,
|
||||
];
|
||||
}
|
||||
}
|
||||
42
lib/Widget/Definition/LegacyType.php
Normal file
42
lib/Widget/Definition/LegacyType.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
/**
|
||||
* A Legacy Type
|
||||
* @SWG\Definition()
|
||||
*/
|
||||
class LegacyType implements \JsonSerializable
|
||||
{
|
||||
public $name;
|
||||
public $condition;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'condition' => $this->condition,
|
||||
];
|
||||
}
|
||||
}
|
||||
67
lib/Widget/Definition/Option.php
Normal file
67
lib/Widget/Definition/Option.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
/**
|
||||
* Option: typically used when paired with a dropdown
|
||||
* @SWG\Definition()
|
||||
*/
|
||||
class Option implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @SWG\Property(description="Name")
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @SWG\Property(description="Image: optional image asset")
|
||||
* @var string
|
||||
*/
|
||||
public $image;
|
||||
|
||||
/**
|
||||
* @SWG\Property(description="Set")
|
||||
* @var string[]
|
||||
*/
|
||||
public $set = [];
|
||||
|
||||
/**
|
||||
* * @SWG\Property(description="Title: shown in the dropdown/select")
|
||||
* @var string
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'image' => $this->image,
|
||||
'set' => $this->set,
|
||||
'title' => $this->title
|
||||
];
|
||||
}
|
||||
}
|
||||
53
lib/Widget/Definition/PlayerCompatibility.php
Normal file
53
lib/Widget/Definition/PlayerCompatibility.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
/**
|
||||
* Player compatibility
|
||||
*/
|
||||
class PlayerCompatibility implements \JsonSerializable
|
||||
{
|
||||
public $windows;
|
||||
public $linux;
|
||||
public $android;
|
||||
public $webos;
|
||||
public $tizen;
|
||||
public $chromeos;
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'windows' => $this->windows,
|
||||
'linux' => $this->linux,
|
||||
'android' => $this->android,
|
||||
'webos' => $this->webos,
|
||||
'tizen' => $this->tizen,
|
||||
'chromeos' => $this->chromeos,
|
||||
'message' => $this->message,
|
||||
];
|
||||
}
|
||||
}
|
||||
554
lib/Widget/Definition/Property.php
Normal file
554
lib/Widget/Definition/Property.php
Normal file
@@ -0,0 +1,554 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterval;
|
||||
use Illuminate\Support\Str;
|
||||
use Respect\Validation\Validator as v;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\ValueTooLargeException;
|
||||
use Xibo\Support\Sanitizer\SanitizerInterface;
|
||||
|
||||
/**
|
||||
* A Property
|
||||
* @SWG\Definition()
|
||||
*/
|
||||
class Property implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @SWG\Property(description="ID, saved as a widget option")
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @SWG\Property(description="Type, determines the field type")
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @SWG\Property(description="Title: shown in the property panel")
|
||||
* @var string
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* @SWG\Property(description="Help Text: shown in the property panel")
|
||||
* @var string
|
||||
*/
|
||||
public $helpText;
|
||||
|
||||
/** @var \Xibo\Widget\Definition\Rule */
|
||||
public $validation;
|
||||
|
||||
/**
|
||||
* @SWG\Property()
|
||||
* @var string An optional default value
|
||||
*/
|
||||
public $default;
|
||||
|
||||
/** @var \Xibo\Widget\Definition\Option[] */
|
||||
public $options;
|
||||
|
||||
/** @var \Xibo\Widget\Definition\Test[] */
|
||||
public $visibility = [];
|
||||
|
||||
/** @var string The element variant */
|
||||
public $variant;
|
||||
|
||||
/** @var string The data format */
|
||||
public $format;
|
||||
|
||||
/** @var bool Should library refs be permitted in the value? */
|
||||
public $allowLibraryRefs = false;
|
||||
|
||||
/** @var bool Should asset refs be permitted in the value? */
|
||||
public $allowAssetRefs = false;
|
||||
|
||||
/** @var bool Should translations be parsed in the value? */
|
||||
public $parseTranslations = false;
|
||||
|
||||
/** @var bool Should the property be included in the XLF? */
|
||||
public $includeInXlf = false;
|
||||
|
||||
/** @var bool Should the property be sent into Elements */
|
||||
public $sendToElements = false;
|
||||
|
||||
/** @var bool Should the default value be written out to widget options */
|
||||
public $saveDefault = false;
|
||||
|
||||
/** @var \Xibo\Widget\Definition\PlayerCompatibility */
|
||||
public $playerCompatibility;
|
||||
|
||||
/** @var string HTML to populate a custom popover to be shown next to the input */
|
||||
public $customPopOver;
|
||||
|
||||
/** @var string HTML selector of the element that this property depends on */
|
||||
public $dependsOn;
|
||||
|
||||
/** @var string ID of the target element */
|
||||
public $target;
|
||||
|
||||
/** @var string The mode of the property */
|
||||
public $mode;
|
||||
|
||||
/** @var string The group ID of the property */
|
||||
public $propertyGroupId;
|
||||
|
||||
/** @var mixed The value assigned to this property. This is set from widget options, or settings, never via XML */
|
||||
public $value;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'value' => $this->value,
|
||||
'type' => $this->type,
|
||||
'variant' => $this->variant,
|
||||
'format' => $this->format,
|
||||
'title' => $this->title,
|
||||
'mode' => $this->mode,
|
||||
'target' => $this->target,
|
||||
'propertyGroupId' => $this->propertyGroupId,
|
||||
'helpText' => $this->helpText,
|
||||
'validation' => $this->validation,
|
||||
'default' => $this->default,
|
||||
'options' => $this->options,
|
||||
'customPopOver' => $this->customPopOver,
|
||||
'playerCompatibility' => $this->playerCompatibility,
|
||||
'visibility' => $this->visibility,
|
||||
'allowLibraryRefs' => $this->allowLibraryRefs,
|
||||
'allowAssetRefs' => $this->allowAssetRefs,
|
||||
'parseTranslations' => $this->parseTranslations,
|
||||
'saveDefault' => $this->saveDefault,
|
||||
'dependsOn' => $this->dependsOn,
|
||||
'sendToElements' => $this->sendToElements,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an option
|
||||
* @param string $name
|
||||
* @param string $image
|
||||
* @param array $set
|
||||
* @param string $title
|
||||
* @return $this
|
||||
*/
|
||||
public function addOption(string $name, string $image, array $set, string $title): Property
|
||||
{
|
||||
$option = new Option();
|
||||
$option->name = $name;
|
||||
$option->image = $image;
|
||||
$option->set = $set;
|
||||
$option->title = __($title);
|
||||
$this->options[] = $option;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a visibility test
|
||||
* @param string $type
|
||||
* @param string|null $message
|
||||
* @param array $conditions
|
||||
* @return $this
|
||||
*/
|
||||
public function addVisibilityTest(string $type, ?string $message, array $conditions): Property
|
||||
{
|
||||
$this->visibility[] = $this->parseTest($type, $message, $conditions);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Xibo\Support\Sanitizer\SanitizerInterface $params
|
||||
* @param string|null $key
|
||||
* @return \Xibo\Widget\Definition\Property
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function setDefaultByType(SanitizerInterface $params, ?string $key = null): Property
|
||||
{
|
||||
$this->default = $this->getByType($params, $key);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SanitizerInterface $params
|
||||
* @param string|null $key
|
||||
* @param bool $ignoreDefault
|
||||
* @return Property
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setValueByType(
|
||||
SanitizerInterface $params,
|
||||
?string $key = null,
|
||||
bool $ignoreDefault = false
|
||||
): Property {
|
||||
$value = $this->getByType($params, $key);
|
||||
if ($value !== $this->default || $ignoreDefault || $this->saveDefault) {
|
||||
$this->value = $value;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $properties A key/value array of all properties for this entity (be it module or template)
|
||||
* @param string $stage What stage are we at?
|
||||
* @return Property
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ValueTooLargeException
|
||||
*/
|
||||
public function validate(array $properties, string $stage): Property
|
||||
{
|
||||
if (!empty($this->value) && strlen($this->value) > 67108864) {
|
||||
throw new ValueTooLargeException(sprintf(__('Value too large for %s'), $this->title), $this->id);
|
||||
}
|
||||
|
||||
// Skip if no validation.
|
||||
if ($this->validation === null
|
||||
|| ($stage === 'save' && !$this->validation->onSave)
|
||||
|| ($stage === 'status' && !$this->validation->onStatus)
|
||||
) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
foreach ($this->validation->tests as $test) {
|
||||
// We have a test, evaulate its conditions.
|
||||
$exceptions = [];
|
||||
|
||||
foreach ($test->conditions as $condition) {
|
||||
try {
|
||||
// Assume we're testing the field we belong to, and if that's empty use the default value
|
||||
$testValue = $this->value ?? $this->default;
|
||||
|
||||
// What value are we testing against (only used by certain types)
|
||||
if (empty($condition->field)) {
|
||||
$valueToTestAgainst = $condition->value;
|
||||
} else {
|
||||
// If a field and a condition value is provided, test against those, ignoring my own field value
|
||||
if (!empty($condition->value)) {
|
||||
$testValue = $condition->value;
|
||||
}
|
||||
|
||||
$valueToTestAgainst = $properties[$condition->field] ?? null;
|
||||
}
|
||||
|
||||
// Do we have a message
|
||||
$message = empty($test->message) ? null : __($test->message);
|
||||
|
||||
switch ($condition->type) {
|
||||
case 'required':
|
||||
// We will accept the default value here
|
||||
if (empty($testValue) && empty($this->default)) {
|
||||
throw new InvalidArgumentException(
|
||||
$message ?? sprintf(__('Missing required property %s'), $this->title),
|
||||
$this->id
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'uri':
|
||||
if (!empty($testValue)
|
||||
&& !v::url()->validate($testValue)
|
||||
) {
|
||||
throw new InvalidArgumentException(
|
||||
$message ?? sprintf(__('%s must be a valid URI'), $this->title),
|
||||
$this->id
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'windowsPath':
|
||||
// Ensure the path is a valid Windows file path ending in a file, not a directory
|
||||
$windowsPathRegex = '/^(?P<Root>[A-Za-z]:)(?P<Relative>(?:\\\\[^<>:"\/\\\\|?*\r\n]+)+)(?P<File>\\\\[^<>:"\/\\\\|?*\r\n]+)$/';
|
||||
|
||||
// Check if the test value is not empty and does not match the regular expression
|
||||
if (!empty($testValue)
|
||||
&& !preg_match($windowsPathRegex, $testValue)
|
||||
) {
|
||||
// Throw an InvalidArgumentException if the test value is not a valid Windows path
|
||||
throw new InvalidArgumentException(
|
||||
$message ?? sprintf(__('%s must be a valid Windows path'), $this->title),
|
||||
$this->id
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'interval':
|
||||
if (!empty($testValue)) {
|
||||
// Try to create a date interval from it
|
||||
$dateInterval = CarbonInterval::createFromDateString($testValue);
|
||||
if ($dateInterval === false) {
|
||||
throw new InvalidArgumentException(
|
||||
// phpcs:ignore Generic.Files.LineLength
|
||||
__('That is not a valid date interval, please use natural language such as 1 week'),
|
||||
'customInterval'
|
||||
);
|
||||
}
|
||||
|
||||
// Use now and add the date interval to it
|
||||
$now = Carbon::now();
|
||||
$check = $now->copy()->add($dateInterval);
|
||||
|
||||
if ($now->equalTo($check)) {
|
||||
throw new InvalidArgumentException(
|
||||
// phpcs:ignore Generic.Files.LineLength
|
||||
$message ?? __('That is not a valid date interval, please use natural language such as 1 week'),
|
||||
$this->id
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'eq':
|
||||
if ($testValue != $valueToTestAgainst) {
|
||||
throw new InvalidArgumentException(
|
||||
$message ?? sprintf(__('%s must equal %s'), $this->title, $valueToTestAgainst),
|
||||
$this->id,
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'neq':
|
||||
if ($testValue == $valueToTestAgainst) {
|
||||
throw new InvalidArgumentException(
|
||||
$message ?? sprintf(__('%s must not equal %s'), $this->title, $valueToTestAgainst),
|
||||
$this->id,
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'contains':
|
||||
if (!empty($testValue) && !Str::contains($testValue, $valueToTestAgainst)) {
|
||||
throw new InvalidArgumentException(
|
||||
$message ?? sprintf(__('%s must contain %s'), $this->title, $valueToTestAgainst),
|
||||
$this->id,
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ncontains':
|
||||
if (!empty($testValue) && Str::contains($testValue, $valueToTestAgainst)) {
|
||||
throw new InvalidArgumentException(
|
||||
// phpcs:ignore Generic.Files.LineLength
|
||||
$message ?? sprintf(__('%s must not contain %s'), $this->title, $valueToTestAgainst),
|
||||
$this->id,
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'lt':
|
||||
// Value must be < to the condition value, or field value
|
||||
if (!($testValue < $valueToTestAgainst)) {
|
||||
throw new InvalidArgumentException(
|
||||
// phpcs:ignore Generic.Files.LineLength
|
||||
$message ?? sprintf(__('%s must be less than %s'), $this->title, $valueToTestAgainst),
|
||||
$this->id
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'lte':
|
||||
// Value must be <= to the condition value, or field value
|
||||
if (!($testValue <= $valueToTestAgainst)) {
|
||||
throw new InvalidArgumentException(
|
||||
// phpcs:ignore Generic.Files.LineLength
|
||||
$message ?? sprintf(__('%s must be less than or equal to %s'), $this->title, $valueToTestAgainst),
|
||||
$this->id
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'gte':
|
||||
// Value must be >= to the condition value, or field value
|
||||
if (!($testValue >= $valueToTestAgainst)) {
|
||||
throw new InvalidArgumentException(
|
||||
// phpcs:ignore Generic.Files.LineLength
|
||||
$message ?? sprintf(__('%s must be greater than or equal to %s'), $this->title, $valueToTestAgainst),
|
||||
$this->id
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'gt':
|
||||
// Value must be > to the condition value, or field value
|
||||
if (!($testValue > $valueToTestAgainst)) {
|
||||
throw new InvalidArgumentException(
|
||||
// phpcs:ignore Generic.Files.LineLength
|
||||
$message ?? sprintf(__('%s must be greater than %s'), $this->title, $valueToTestAgainst),
|
||||
$this->id
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Nothing to validate
|
||||
}
|
||||
} catch (InvalidArgumentException $invalidArgumentException) {
|
||||
// If we are an AND test, all conditions must pass, so we know already to exception here.
|
||||
if ($test->type === 'and') {
|
||||
throw $invalidArgumentException;
|
||||
}
|
||||
|
||||
// We're an OR
|
||||
$exceptions[] = $invalidArgumentException;
|
||||
}
|
||||
}
|
||||
|
||||
// If we are an OR then make sure all conditions have failed.
|
||||
$countOfFailures = count($exceptions);
|
||||
if ($test->type === 'or' && $countOfFailures === count($test->conditions)) {
|
||||
throw $exceptions[0];
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Xibo\Support\Sanitizer\SanitizerInterface $params
|
||||
* @param string|null $key
|
||||
* @return bool|float|int|string|null
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
private function getByType(SanitizerInterface $params, ?string $key = null)
|
||||
{
|
||||
$key = $key ?: $this->id;
|
||||
|
||||
if (!$params->hasParam($key) && $this->type !== 'checkbox') {
|
||||
// Clear the stored value and therefore use the default
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse according to the type of field we're expecting
|
||||
switch ($this->type) {
|
||||
case 'checkbox':
|
||||
return $params->getCheckbox($key);
|
||||
|
||||
case 'integer':
|
||||
return $params->getInt($key);
|
||||
|
||||
case 'number':
|
||||
return $params->getDouble($key);
|
||||
|
||||
case 'dropdown':
|
||||
$value = $params->getString($key);
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$found = false;
|
||||
foreach ($this->options as $option) {
|
||||
if ($option->name === $value) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($found) {
|
||||
return $value;
|
||||
} else {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(__('%s is not a valid option'), $value),
|
||||
$key
|
||||
);
|
||||
}
|
||||
|
||||
case 'code':
|
||||
case 'richText':
|
||||
return $params->getParam($key);
|
||||
case 'text':
|
||||
if ($this->variant === 'sql') {
|
||||
// Handle raw SQL clauses
|
||||
return str_ireplace(Sql::DISALLOWED_KEYWORDS, '', $params->getParam($key));
|
||||
} else {
|
||||
return $params->getString($key);
|
||||
}
|
||||
default:
|
||||
return $params->getString($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply any filters on the data.
|
||||
* @return void
|
||||
*/
|
||||
public function applyFilters(): void
|
||||
{
|
||||
if ($this->variant === 'uri' || $this->type === 'commandBuilder') {
|
||||
$this->value = urlencode($this->value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse filters
|
||||
* @return void
|
||||
*/
|
||||
public function reverseFilters(): void
|
||||
{
|
||||
$this->value = $this->reverseFiltersOnValue($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function reverseFiltersOnValue(mixed $value): mixed
|
||||
{
|
||||
if (($this->variant === 'uri' || $this->type === 'commandBuilder') && !empty($value)) {
|
||||
$value = urldecode($value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this property be represented with CData
|
||||
* @return bool
|
||||
*/
|
||||
public function isCData(): bool
|
||||
{
|
||||
return $this->type === 'code' || $this->type === 'richText';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param string $message
|
||||
* @param array $conditions
|
||||
* @return Test
|
||||
*/
|
||||
public function parseTest(string $type, string $message, array $conditions): Test
|
||||
{
|
||||
$test = new Test();
|
||||
$test->type = $type ?: 'and';
|
||||
$test->message = $message;
|
||||
|
||||
foreach ($conditions as $item) {
|
||||
$condition = new Condition();
|
||||
$condition->type = $item['type'];
|
||||
$condition->field = $item['field'];
|
||||
$condition->value = $item['value'];
|
||||
$test->conditions[] = $condition;
|
||||
}
|
||||
return $test;
|
||||
}
|
||||
}
|
||||
46
lib/Widget/Definition/PropertyGroup.php
Normal file
46
lib/Widget/Definition/PropertyGroup.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
/**
|
||||
* A class representing an instance of a group property to put a property in assigned Tab
|
||||
* @SWG\Definition()
|
||||
*/
|
||||
class PropertyGroup implements \JsonSerializable
|
||||
{
|
||||
public $id;
|
||||
public $expanded;
|
||||
public $title;
|
||||
public $helpText;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'expanded' => $this->expanded,
|
||||
'title' => $this->title,
|
||||
'helpText' => $this->helpText
|
||||
];
|
||||
}
|
||||
}
|
||||
52
lib/Widget/Definition/Rule.php
Normal file
52
lib/Widget/Definition/Rule.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
/**
|
||||
* A rule to apply to a property
|
||||
* @SWG\Definition()
|
||||
*/
|
||||
class Rule implements \JsonSerializable
|
||||
{
|
||||
public $onSave = true;
|
||||
|
||||
public $onStatus = true;
|
||||
|
||||
/** @var Test[] */
|
||||
public $tests;
|
||||
|
||||
public function addRuleTest(Test $test): Rule
|
||||
{
|
||||
$this->tests[] = $test;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'onSave' => $this->onSave,
|
||||
'onStatus' => $this->onStatus,
|
||||
'tests' => $this->tests,
|
||||
];
|
||||
}
|
||||
}
|
||||
46
lib/Widget/Definition/Sql.php
Normal file
46
lib/Widget/Definition/Sql.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
/**
|
||||
* SQL definitions
|
||||
*/
|
||||
class Sql
|
||||
{
|
||||
const DISALLOWED_KEYWORDS = [
|
||||
';',
|
||||
'INSERT',
|
||||
'UPDATE',
|
||||
'SELECT',
|
||||
'FROM',
|
||||
'WHERE',
|
||||
'DELETE',
|
||||
'TRUNCATE',
|
||||
'TABLE',
|
||||
'ALTER',
|
||||
'GRANT',
|
||||
'REVOKE',
|
||||
'CREATE',
|
||||
'DROP',
|
||||
];
|
||||
}
|
||||
80
lib/Widget/Definition/Stencil.php
Normal file
80
lib/Widget/Definition/Stencil.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?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\Widget\Definition;
|
||||
|
||||
/**
|
||||
* @SWG\Definition()
|
||||
* A Stencil is a template which is rendered in the server and/or client
|
||||
* it can optionally have properties and/or elements
|
||||
*/
|
||||
class Stencil implements \JsonSerializable
|
||||
{
|
||||
/** @var \Xibo\Widget\Definition\Element[] */
|
||||
public $elements = [];
|
||||
|
||||
/** @var string|null */
|
||||
public $twig;
|
||||
|
||||
/** @var string|null */
|
||||
public $hbs;
|
||||
|
||||
/** @var string|null */
|
||||
public $head;
|
||||
|
||||
/** @var string|null */
|
||||
public $style;
|
||||
|
||||
/** @var string|null */
|
||||
public $hbsId;
|
||||
|
||||
/** @var double Optional positional information if contained as part of an element group */
|
||||
public $width;
|
||||
|
||||
/** @var double Optional positional information if contained as part of an element group */
|
||||
public $height;
|
||||
|
||||
/** @var double Optional positional information if contained as part of an element group */
|
||||
public $gapBetweenHbs;
|
||||
|
||||
/**
|
||||
* @SWG\Property(description="An array of element groups")
|
||||
* @var \Xibo\Widget\Definition\ElementGroup[]
|
||||
*/
|
||||
public $elementGroups = [];
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'hbsId' => $this->hbsId,
|
||||
'hbs' => $this->hbs,
|
||||
'head' => $this->head,
|
||||
'style' => $this->style,
|
||||
'width' => $this->width,
|
||||
'height' => $this->height,
|
||||
'gapBetweenHbs' => $this->gapBetweenHbs,
|
||||
'elements' => $this->elements,
|
||||
'elementGroups' => $this->elementGroups
|
||||
];
|
||||
}
|
||||
}
|
||||
49
lib/Widget/Definition/Test.php
Normal file
49
lib/Widget/Definition/Test.php
Normal 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\Widget\Definition;
|
||||
|
||||
/**
|
||||
* Represents a test/group of conditions
|
||||
* @SWG\Definition()
|
||||
*/
|
||||
class Test implements \JsonSerializable
|
||||
{
|
||||
/** @var string */
|
||||
public $type;
|
||||
|
||||
/** @var Condition[] */
|
||||
public $conditions;
|
||||
|
||||
/** @var string|null */
|
||||
public $message;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->type,
|
||||
'message' => $this->message,
|
||||
'conditions' => $this->conditions,
|
||||
];
|
||||
}
|
||||
}
|
||||
265
lib/Widget/IcsProvider.php
Normal file
265
lib/Widget/IcsProvider.php
Normal file
@@ -0,0 +1,265 @@
|
||||
<?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\Widget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use ICal\ICal;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Support\Exception\ConfigurationException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Widget\DataType\Event;
|
||||
use Xibo\Widget\Provider\DataProviderInterface;
|
||||
use Xibo\Widget\Provider\DurationProviderNumItemsTrait;
|
||||
use Xibo\Widget\Provider\WidgetProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderTrait;
|
||||
|
||||
/**
|
||||
* Download and parse an ISC feed
|
||||
*/
|
||||
class IcsProvider implements WidgetProviderInterface
|
||||
{
|
||||
use WidgetProviderTrait;
|
||||
use DurationProviderNumItemsTrait;
|
||||
|
||||
/**
|
||||
* Fetch the ISC feed and load its data.
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function fetchData(DataProviderInterface $dataProvider): WidgetProviderInterface
|
||||
{
|
||||
// Do we have a feed configured?
|
||||
$uri = $dataProvider->getProperty('uri');
|
||||
if (empty($uri)) {
|
||||
throw new InvalidArgumentException(__('Please enter the URI to a valid ICS feed.'), 'uri');
|
||||
}
|
||||
|
||||
// Create an ICal helper and pass it the contents of the file.
|
||||
$iCalConfig = [
|
||||
'replaceWindowsTimeZoneIds' => ($dataProvider->getProperty('replaceWindowsTimeZoneIds', 0) == 1),
|
||||
'defaultSpan' => 1,
|
||||
];
|
||||
|
||||
// What event range are we interested in?
|
||||
// Decide on the Range we're interested in
|
||||
// $iCal->eventsFromInterval only works for future events
|
||||
$excludeAllDay = $dataProvider->getProperty('excludeAllDay', 0) == 1;
|
||||
|
||||
$excludePastEvents = $dataProvider->getProperty('excludePast', 0) == 1;
|
||||
|
||||
$startOfDay = match ($dataProvider->getProperty('startIntervalFrom')) {
|
||||
'month' => Carbon::now()->startOfMonth(),
|
||||
'week' => Carbon::now()->startOfWeek(),
|
||||
default => Carbon::now()->startOfDay(),
|
||||
};
|
||||
|
||||
// Force timezone of each event?
|
||||
$useEventTimezone = $dataProvider->getProperty('useEventTimezone', 1);
|
||||
|
||||
// do we use interval or provided date range?
|
||||
if ($dataProvider->getProperty('useDateRange')) {
|
||||
$rangeStart = $dataProvider->getProperty('rangeStart');
|
||||
$rangeStart = empty($rangeStart)
|
||||
? Carbon::now()->startOfMonth()
|
||||
: Carbon::createFromFormat(DateFormatHelper::getSystemFormat(), $rangeStart);
|
||||
|
||||
$rangeEnd = $dataProvider->getProperty('rangeEnd');
|
||||
$rangeEnd = empty($rangeEnd)
|
||||
? Carbon::now()->endOfMonth()
|
||||
: Carbon::createFromFormat(DateFormatHelper::getSystemFormat(), $rangeEnd);
|
||||
} else {
|
||||
$interval = $dataProvider->getProperty('customInterval');
|
||||
$rangeStart = $startOfDay->copy();
|
||||
$rangeEnd = $rangeStart->copy()->add(
|
||||
\DateInterval::createFromDateString(empty($interval) ? '1 week' : $interval)
|
||||
);
|
||||
}
|
||||
|
||||
$this->getLog()->debug('fetchData: final range, start=' . $rangeStart->toAtomString()
|
||||
. ', end=' . $rangeEnd->toAtomString());
|
||||
|
||||
// Set up fuzzy filtering supported by the ICal library. This is included for performance.
|
||||
// https://github.com/u01jmg3/ics-parser?tab=readme-ov-file#variables
|
||||
$iCalConfig['filterDaysBefore'] = $rangeStart->diffInDays(Carbon::now(), false) + 2;
|
||||
$iCalConfig['filterDaysAfter'] = $rangeEnd->diffInDays(Carbon::now()) + 2;
|
||||
|
||||
$this->getLog()->debug('Range start: ' . $rangeStart->toDateTimeString()
|
||||
. ', range end: ' . $rangeEnd->toDateTimeString()
|
||||
. ', config: ' . var_export($iCalConfig, true));
|
||||
|
||||
try {
|
||||
$iCal = new ICal(false, $iCalConfig);
|
||||
$iCal->initString($this->downloadIcs($uri, $dataProvider));
|
||||
|
||||
$this->getLog()->debug('Feed initialised');
|
||||
|
||||
// Before we parse anything - should we use the calendar timezone as a base for our calculations?
|
||||
if ($dataProvider->getProperty('useCalendarTimezone') == 1) {
|
||||
$iCal->defaultTimeZone = $iCal->calendarTimeZone();
|
||||
}
|
||||
|
||||
$this->getLog()->debug('Calendar timezone set to: ' . $iCal->defaultTimeZone);
|
||||
|
||||
// Get an array of events
|
||||
/** @var \ICal\Event[] $events */
|
||||
$events = $iCal->eventsFromRange($rangeStart, $rangeEnd);
|
||||
|
||||
// Go through each event returned
|
||||
foreach ($events as $event) {
|
||||
try {
|
||||
// Parse the ICal Event into our own data type object.
|
||||
$entry = new Event();
|
||||
$entry->summary = $event->summary;
|
||||
$entry->description = $event->description;
|
||||
$entry->location = $event->location;
|
||||
|
||||
// Parse out the start/end dates.
|
||||
if ($useEventTimezone === 1) {
|
||||
// Use the timezone from the event.
|
||||
$entry->startDate = Carbon::instance($iCal->iCalDateToDateTime($event->dtstart_array[3]));
|
||||
$entry->endDate = Carbon::instance($iCal->iCalDateToDateTime($event->dtend_array[3]));
|
||||
} else {
|
||||
// Use the parser calculated timezone shift
|
||||
$entry->startDate = Carbon::instance($iCal->iCalDateToDateTime($event->dtstart_tz));
|
||||
$entry->endDate = Carbon::instance($iCal->iCalDateToDateTime($event->dtend_tz));
|
||||
}
|
||||
|
||||
$this->getLog()->debug('Event: ' . $event->summary . ' with '
|
||||
. $entry->startDate->format('c') . ' / ' . $entry->endDate->format('c'));
|
||||
|
||||
// Detect all day event
|
||||
$isAllDay = false;
|
||||
|
||||
// If dtstart has value DATE
|
||||
// (following RFC recommendations in https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.4 )
|
||||
if (isset($event->dtstart_array[0])) {
|
||||
// If it's a string
|
||||
if (is_string($event->dtstart_array[0]) && strtoupper($event->dtstart_array[0]) === 'DATE') {
|
||||
$isAllDay = true;
|
||||
}
|
||||
|
||||
// If it's an array
|
||||
if (is_array($event->dtstart_array[0]) &&
|
||||
isset($event->dtstart_array[0]['VALUE']) &&
|
||||
strtoupper($event->dtstart_array[0]['VALUE']) === 'DATE') {
|
||||
$isAllDay = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If MS extension flags it as all day
|
||||
if (isset($event->x_microsoft_cdo_alldayevent) &&
|
||||
is_string($event->x_microsoft_cdo_alldayevent) &&
|
||||
strtoupper($event->x_microsoft_cdo_alldayevent) === 'TRUE') {
|
||||
$isAllDay = true;
|
||||
}
|
||||
|
||||
// Fallback: If both times are midnight and event is more than one day
|
||||
if (!$isAllDay) {
|
||||
$startAtMidnight = $entry->startDate->isStartOfDay();
|
||||
$endsAtMidnight = $entry->endDate->isStartOfDay();
|
||||
$diffDays = $entry->endDate->copy()->startOfDay()->diffInDays(
|
||||
$entry->startDate->copy()->startOfDay()
|
||||
);
|
||||
|
||||
$isAllDay = $startAtMidnight && $endsAtMidnight && $diffDays >= 1;
|
||||
}
|
||||
|
||||
$entry->isAllDay = $isAllDay;
|
||||
|
||||
if ($excludeAllDay && $isAllDay) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($excludePastEvents && $entry->endDate->isPast()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dataProvider->addItem($entry);
|
||||
} catch (\Exception $exception) {
|
||||
$this->getLog()->error('Unable to parse event. ' . var_export($event, true));
|
||||
}
|
||||
}
|
||||
|
||||
$dataProvider->setCacheTtl($dataProvider->getProperty('updateInterval', 60) * 60);
|
||||
$dataProvider->setIsHandled();
|
||||
} catch (\Exception $exception) {
|
||||
$this->getLog()->error('iscProvider: fetchData: ' . $exception->getMessage());
|
||||
$this->getLog()->debug($exception->getTraceAsString());
|
||||
|
||||
$dataProvider->addError(__('The iCal provided is not valid, please choose a valid feed'));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDataCacheKey(DataProviderInterface $dataProvider): ?string
|
||||
{
|
||||
// No special cache key requirements.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
private function downloadIcs(string $uri, DataProviderInterface $dataProvider): string
|
||||
{
|
||||
// See if we have this ICS cached already.
|
||||
$cache = $dataProvider->getPool()->getItem('/widget/' . $dataProvider->getDataType() . '/' . md5($uri));
|
||||
$ics = $cache->get();
|
||||
|
||||
if ($cache->isMiss() || $ics === null) {
|
||||
// Make a new request.
|
||||
$this->getLog()->debug('downloadIcs: cache miss');
|
||||
|
||||
try {
|
||||
// Create a Guzzle Client to get the Feed XML
|
||||
$response = $dataProvider
|
||||
->getGuzzleClient([
|
||||
'timeout' => 20, // wait no more than 20 seconds
|
||||
])
|
||||
->get($uri);
|
||||
|
||||
$ics = $response->getBody()->getContents();
|
||||
|
||||
// Save the resonse to cache
|
||||
$cache->set($ics);
|
||||
$cache->expiresAfter($dataProvider->getSetting('cachePeriod', 1440) * 60);
|
||||
$dataProvider->getPool()->saveDeferred($cache);
|
||||
} catch (RequestException $requestException) {
|
||||
// Log and return empty?
|
||||
$this->getLog()->error('downloadIcs: Unable to get feed: ' . $requestException->getMessage());
|
||||
$this->getLog()->debug($requestException->getTraceAsString());
|
||||
|
||||
throw new ConfigurationException(__('Unable to download feed'));
|
||||
}
|
||||
} else {
|
||||
$this->getLog()->debug('downloadIcs: cache hit');
|
||||
}
|
||||
|
||||
return $ics;
|
||||
}
|
||||
|
||||
public function getDataModifiedDt(DataProviderInterface $dataProvider): ?Carbon
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
202
lib/Widget/MastodonProvider.php
Normal file
202
lib/Widget/MastodonProvider.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?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\Widget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Xibo\Widget\DataType\SocialMedia;
|
||||
use Xibo\Widget\Provider\DataProviderInterface;
|
||||
use Xibo\Widget\Provider\DurationProviderNumItemsTrait;
|
||||
use Xibo\Widget\Provider\WidgetProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderTrait;
|
||||
|
||||
/**
|
||||
* Downloads a Mastodon feed and returns SocialMedia data types
|
||||
*/
|
||||
class MastodonProvider implements WidgetProviderInterface
|
||||
{
|
||||
use WidgetProviderTrait;
|
||||
use DurationProviderNumItemsTrait;
|
||||
|
||||
public function fetchData(DataProviderInterface $dataProvider): WidgetProviderInterface
|
||||
{
|
||||
$uri = $dataProvider->getSetting('defaultServerUrl', 'https://mastodon.social');
|
||||
|
||||
try {
|
||||
$httpOptions = [
|
||||
'timeout' => 20, // wait no more than 20 seconds
|
||||
];
|
||||
|
||||
$queryOptions = [
|
||||
'limit' => $dataProvider->getProperty('numItems', 15)
|
||||
];
|
||||
|
||||
if ($dataProvider->getProperty('searchOn', 'all') === 'local') {
|
||||
$queryOptions['local'] = true;
|
||||
} elseif ($dataProvider->getProperty('searchOn', 'all') === 'remote') {
|
||||
$queryOptions['remote'] = true;
|
||||
}
|
||||
|
||||
// Media Only
|
||||
if ($dataProvider->getProperty('onlyMedia', 0)) {
|
||||
$queryOptions['only_media'] = true;
|
||||
}
|
||||
|
||||
if (!empty($dataProvider->getProperty('serverUrl', ''))) {
|
||||
$uri = $dataProvider->getProperty('serverUrl');
|
||||
}
|
||||
|
||||
// Hashtag
|
||||
$hashtag = trim($dataProvider->getProperty('hashtag', ''));
|
||||
|
||||
// when username is provided do not search in public timeline
|
||||
if (!empty($dataProvider->getProperty('userName', ''))) {
|
||||
// username search: get account ID, always returns one record
|
||||
$accountId = $this->getAccountId($uri, $dataProvider->getProperty('userName'), $dataProvider);
|
||||
$queryOptions['tagged'] = trim($hashtag, '#');
|
||||
$queryOptions['exclude_replies'] = true; // exclude replies to other users
|
||||
$queryOptions['exclude_reblogs'] = true; // exclude reposts/boosts
|
||||
$uri = rtrim($uri, '/') . '/api/v1/accounts/' . $accountId . '/statuses?';
|
||||
} else {
|
||||
// Hashtag: When empty we should do a public search, when filled we should do a hashtag search
|
||||
if (!empty($hashtag)) {
|
||||
$uri = rtrim($uri, '/') . '/api/v1/timelines/tag/' . trim($hashtag, '#');
|
||||
} else {
|
||||
$uri = rtrim($uri, '/') . '/api/v1/timelines/public';
|
||||
}
|
||||
}
|
||||
|
||||
$response = $dataProvider
|
||||
->getGuzzleClient($httpOptions)
|
||||
->get($uri, [
|
||||
'query' => $queryOptions
|
||||
]);
|
||||
|
||||
$result = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
$this->getLog()->debug('Mastodon: uri: ' . $uri . ' httpOptions: ' . json_encode($httpOptions));
|
||||
|
||||
$this->getLog()->debug('Mastodon: count: ' . count($result));
|
||||
|
||||
// Expiry time for any media that is downloaded
|
||||
$expires = Carbon::now()->addHours($dataProvider->getSetting('cachePeriodImages', 24))->format('U');
|
||||
|
||||
foreach ($result as $item) {
|
||||
// Parse the mastodon
|
||||
$mastodon = new SocialMedia();
|
||||
|
||||
$mastodon->text = strip_tags($item['content']);
|
||||
$mastodon->user = $item['account']['acct'];
|
||||
$mastodon->screenName = $item['account']['display_name'];
|
||||
$mastodon->date = $item['created_at'];
|
||||
|
||||
// Original Default Image
|
||||
$mastodon->userProfileImage = $dataProvider->addImage(
|
||||
'mastodon_' . $item['account']['id'],
|
||||
$item['account']['avatar'],
|
||||
$expires
|
||||
);
|
||||
|
||||
// Mini image
|
||||
$mastodon->userProfileImageMini = $mastodon->userProfileImage;
|
||||
|
||||
// Bigger image
|
||||
$mastodon->userProfileImageBigger = $mastodon->userProfileImage;
|
||||
|
||||
|
||||
// Photo
|
||||
// See if there are any photos associated with this status.
|
||||
if ((isset($item['media_attachments']) && count($item['media_attachments']) > 0)) {
|
||||
// only take the first one
|
||||
$mediaObject = $item['media_attachments'][0];
|
||||
|
||||
$photoUrl = $mediaObject['preview_url'];
|
||||
if (!empty($photoUrl)) {
|
||||
$mastodon->photo = $dataProvider->addImage(
|
||||
'mastodon_' . $mediaObject['id'],
|
||||
$photoUrl,
|
||||
$expires
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the mastodon topic.
|
||||
$dataProvider->addItem($mastodon);
|
||||
}
|
||||
|
||||
// If we've got data, then set our cache period.
|
||||
$dataProvider->setCacheTtl($dataProvider->getSetting('cachePeriod', 3600));
|
||||
$dataProvider->setIsHandled();
|
||||
} catch (RequestException $requestException) {
|
||||
// Log and return empty?
|
||||
$this->getLog()->error('Mastodon: Unable to get posts: ' . $uri
|
||||
. ', e: ' . $requestException->getMessage());
|
||||
$dataProvider->addError(__('Unable to download posts'));
|
||||
} catch (\Exception $exception) {
|
||||
// Log and return empty?
|
||||
$this->getLog()->error('Mastodon: ' . $exception->getMessage());
|
||||
$this->getLog()->debug($exception->getTraceAsString());
|
||||
$dataProvider->addError(__('Unknown issue getting posts'));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDataCacheKey(DataProviderInterface $dataProvider): ?string
|
||||
{
|
||||
// No special cache key requirements.
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDataModifiedDt(DataProviderInterface $dataProvider): ?Carbon
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Mastodon Account Id from username
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
private function getAccountId(string $uri, string $username, DataProviderInterface $dataProvider)
|
||||
{
|
||||
$uri = rtrim($uri, '/').'/api/v1/accounts/lookup?';
|
||||
|
||||
$httpOptions = [
|
||||
'timeout' => 20, // wait no more than 20 seconds
|
||||
'query' => [
|
||||
'acct' => $username
|
||||
],
|
||||
];
|
||||
$response = $dataProvider
|
||||
->getGuzzleClient($httpOptions)
|
||||
->get($uri);
|
||||
|
||||
$result = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
$this->getLog()->debug('Mastodon: getAccountId: ID ' . $result['id']);
|
||||
|
||||
return $result['id'];
|
||||
}
|
||||
}
|
||||
73
lib/Widget/MenuBoardCategoryProvider.php
Normal file
73
lib/Widget/MenuBoardCategoryProvider.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?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\Widget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Xibo\Event\MenuBoardCategoryRequest;
|
||||
use Xibo\Event\MenuBoardModifiedDtRequest;
|
||||
use Xibo\Widget\Provider\DataProviderInterface;
|
||||
use Xibo\Widget\Provider\DurationProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderTrait;
|
||||
|
||||
/**
|
||||
* Menu Board Category Provider
|
||||
*/
|
||||
class MenuBoardCategoryProvider implements WidgetProviderInterface
|
||||
{
|
||||
use WidgetProviderTrait;
|
||||
|
||||
public function fetchData(DataProviderInterface $dataProvider): WidgetProviderInterface
|
||||
{
|
||||
$this->getLog()->debug('fetchData: MenuBoardCategoryRequest passing to event');
|
||||
$this->getDispatcher()->dispatch(new MenuBoardCategoryRequest($dataProvider), MenuBoardCategoryRequest::$NAME);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fetchDuration(DurationProviderInterface $durationProvider): WidgetProviderInterface
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDataCacheKey(DataProviderInterface $dataProvider): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDataModifiedDt(DataProviderInterface $dataProvider): ?Carbon
|
||||
{
|
||||
$this->getLog()->debug('fetchData: MenuBoardCategoryProvider passing to modifiedDt request event');
|
||||
|
||||
$menuId = $dataProvider->getProperty('menuId');
|
||||
|
||||
if ($menuId !== null) {
|
||||
// Raise an event to get the modifiedDt of this dataSet
|
||||
$event = new MenuBoardModifiedDtRequest($menuId);
|
||||
$this->getDispatcher()->dispatch($event, MenuBoardModifiedDtRequest::$NAME);
|
||||
|
||||
return max($event->getModifiedDt(), $dataProvider->getWidgetModifiedDt());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
84
lib/Widget/MenuBoardProductProvider.php
Normal file
84
lib/Widget/MenuBoardProductProvider.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?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\Widget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Xibo\Event\MenuBoardModifiedDtRequest;
|
||||
use Xibo\Event\MenuBoardProductRequest;
|
||||
use Xibo\Widget\Provider\DataProviderInterface;
|
||||
use Xibo\Widget\Provider\DurationProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderTrait;
|
||||
|
||||
/**
|
||||
* Menu Board Product Provider
|
||||
*/
|
||||
class MenuBoardProductProvider implements WidgetProviderInterface
|
||||
{
|
||||
use WidgetProviderTrait;
|
||||
|
||||
public function fetchData(DataProviderInterface $dataProvider): WidgetProviderInterface
|
||||
{
|
||||
$this->getLog()->debug('fetchData: MenuBoardProductProvider passing to event');
|
||||
$this->getDispatcher()->dispatch(new MenuBoardProductRequest($dataProvider), MenuBoardProductRequest::$NAME);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fetchDuration(DurationProviderInterface $durationProvider): WidgetProviderInterface
|
||||
{
|
||||
if ($durationProvider->getWidget()->getOptionValue('durationIsPerItem', 0) == 1) {
|
||||
$this->getLog()->debug('fetchDuration: duration is per item');
|
||||
|
||||
$lowerLimit = $durationProvider->getWidget()->getOptionValue('lowerLimit', 0);
|
||||
$upperLimit = $durationProvider->getWidget()->getOptionValue('upperLimit', 15);
|
||||
$numItems = $upperLimit - $lowerLimit;
|
||||
|
||||
$itemsPerPage = $durationProvider->getWidget()->getOptionValue('itemsPerPage', 0);
|
||||
if ($itemsPerPage > 0) {
|
||||
$numItems = ceil($numItems / $itemsPerPage);
|
||||
}
|
||||
|
||||
$durationProvider->setDuration($durationProvider->getWidget()->calculatedDuration * $numItems);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDataCacheKey(DataProviderInterface $dataProvider): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDataModifiedDt(DataProviderInterface $dataProvider): ?Carbon
|
||||
{
|
||||
$this->getLog()->debug('fetchData: MenuBoardProductProvider passing to modifiedDt request event');
|
||||
$menuId = $dataProvider->getProperty('menuId');
|
||||
if ($menuId !== null) {
|
||||
// Raise an event to get the modifiedDt of this dataSet
|
||||
$event = new MenuBoardModifiedDtRequest($menuId);
|
||||
$this->getDispatcher()->dispatch($event, MenuBoardModifiedDtRequest::$NAME);
|
||||
return max($event->getModifiedDt(), $dataProvider->getWidgetModifiedDt());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
lib/Widget/NotificationProvider.php
Normal file
59
lib/Widget/NotificationProvider.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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\Widget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Xibo\Event\NotificationDataRequestEvent;
|
||||
use Xibo\Event\NotificationModifiedDtRequestEvent;
|
||||
use Xibo\Widget\Provider\DataProviderInterface;
|
||||
use Xibo\Widget\Provider\DurationProviderNumItemsTrait;
|
||||
use Xibo\Widget\Provider\WidgetProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderTrait;
|
||||
|
||||
class NotificationProvider implements WidgetProviderInterface
|
||||
{
|
||||
use WidgetProviderTrait;
|
||||
use DurationProviderNumItemsTrait;
|
||||
|
||||
public function fetchData(DataProviderInterface $dataProvider): WidgetProviderInterface
|
||||
{
|
||||
$this->getLog()->debug('fetchData: NotificationProvider passing to event');
|
||||
$this->getDispatcher()->dispatch(
|
||||
new NotificationDataRequestEvent($dataProvider),
|
||||
NotificationDataRequestEvent::$NAME
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDataCacheKey(DataProviderInterface $dataProvider): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDataModifiedDt(DataProviderInterface $dataProvider): ?Carbon
|
||||
{
|
||||
$event = new NotificationModifiedDtRequestEvent($dataProvider->getDisplayId());
|
||||
$this->getDispatcher()->dispatch($event, NotificationModifiedDtRequestEvent::$NAME);
|
||||
return $event->getModifiedDt();
|
||||
}
|
||||
}
|
||||
85
lib/Widget/PdfProvider.php
Normal file
85
lib/Widget/PdfProvider.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?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\Widget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Mpdf\Mpdf;
|
||||
use Xibo\Widget\Provider\DataProviderInterface;
|
||||
use Xibo\Widget\Provider\DurationProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderTrait;
|
||||
|
||||
/**
|
||||
* PDF provider to calculate the duration if durationIsPerItem is selected.
|
||||
*/
|
||||
class PdfProvider implements WidgetProviderInterface
|
||||
{
|
||||
use WidgetProviderTrait;
|
||||
|
||||
public function fetchData(DataProviderInterface $dataProvider): WidgetProviderInterface
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fetchDuration(DurationProviderInterface $durationProvider): WidgetProviderInterface
|
||||
{
|
||||
$widget = $durationProvider->getWidget();
|
||||
if ($widget->getOptionValue('durationIsPerItem', 0) == 1) {
|
||||
// Do we already have an option stored for the number of pages?
|
||||
$pageCount = 1;
|
||||
$cachedPageCount = $widget->getOptionValue('pageCount', null);
|
||||
if ($cachedPageCount === null) {
|
||||
try {
|
||||
$sourceFile = $widget->getPrimaryMediaPath();
|
||||
|
||||
$this->getLog()->debug('fetchDuration: loading PDF file to get the number of pages, file: '
|
||||
. $sourceFile);
|
||||
|
||||
$mPdf = new Mpdf([
|
||||
'tempDir' => $widget->getLibraryTempPath(),
|
||||
]);
|
||||
$pageCount = $mPdf->setSourceFile($sourceFile);
|
||||
|
||||
$widget->setOptionValue('pageCount', 'attrib', $pageCount);
|
||||
} catch (\Exception $e) {
|
||||
$this->getLog()->error('fetchDuration: unable to get PDF page count, e: ' . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
$pageCount = $cachedPageCount;
|
||||
}
|
||||
|
||||
$durationProvider->setDuration($durationProvider->getWidget()->calculatedDuration * $pageCount);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDataCacheKey(DataProviderInterface $dataProvider): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDataModifiedDt(DataProviderInterface $dataProvider): ?Carbon
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
449
lib/Widget/Provider/DataProvider.php
Normal file
449
lib/Widget/Provider/DataProvider.php
Normal file
@@ -0,0 +1,449 @@
|
||||
<?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\Widget\Provider;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use GuzzleHttp\Client;
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Xibo\Entity\Module;
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Factory\MediaFactory;
|
||||
use Xibo\Helper\SanitizerService;
|
||||
use Xibo\Support\Sanitizer\SanitizerInterface;
|
||||
|
||||
/**
|
||||
* Xibo default implementation of a Widget Data Provider
|
||||
*/
|
||||
class DataProvider implements DataProviderInterface
|
||||
{
|
||||
/** @var \Xibo\Factory\MediaFactory */
|
||||
private $mediaFactory;
|
||||
|
||||
/** @var boolean should we use the event? */
|
||||
private $isUseEvent = false;
|
||||
|
||||
/** @var bool Is this data provider handled? */
|
||||
private $isHandled = false;
|
||||
|
||||
/** @var array errors */
|
||||
private $errors = [];
|
||||
|
||||
/** @var array the data */
|
||||
private $data = [];
|
||||
|
||||
/** @var array the metadata */
|
||||
private $meta = [];
|
||||
|
||||
/** @var \Xibo\Entity\Media[] */
|
||||
private $media = [];
|
||||
|
||||
/** @var int the cache ttl in seconds - default to 7 days */
|
||||
private $cacheTtl = 86400 * 7;
|
||||
|
||||
/** @var int the displayId */
|
||||
private $displayId = 0;
|
||||
|
||||
/** @var float the display latitude */
|
||||
private $latitude;
|
||||
|
||||
/** @var float the display longitude */
|
||||
private $longitude;
|
||||
|
||||
/** @var bool Is this data provider in preview mode? */
|
||||
private $isPreview = false;
|
||||
|
||||
/** @var \GuzzleHttp\Client */
|
||||
private $client;
|
||||
|
||||
/** @var null cached property values. */
|
||||
private $properties = null;
|
||||
|
||||
/** @var null cached setting values. */
|
||||
private $settings = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param Module $module
|
||||
* @param Widget $widget
|
||||
* @param array $guzzleProxy
|
||||
* @param SanitizerService $sanitizer
|
||||
* @param PoolInterface $pool
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly Module $module,
|
||||
private readonly Widget $widget,
|
||||
private readonly array $guzzleProxy,
|
||||
private readonly SanitizerService $sanitizer,
|
||||
private readonly PoolInterface $pool
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the latitude and longitude for this data provider.
|
||||
* This is primary used if a widget is display specific
|
||||
* @param $latitude
|
||||
* @param $longitude
|
||||
* @param int $displayId
|
||||
* @return \Xibo\Widget\Provider\DataProviderInterface
|
||||
*/
|
||||
public function setDisplayProperties($latitude, $longitude, int $displayId = 0): DataProviderInterface
|
||||
{
|
||||
$this->latitude = $latitude;
|
||||
$this->longitude = $longitude;
|
||||
$this->displayId = $displayId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Xibo\Factory\MediaFactory $mediaFactory
|
||||
* @return \Xibo\Widget\Provider\DataProviderInterface
|
||||
*/
|
||||
public function setMediaFactory(MediaFactory $mediaFactory): DataProviderInterface
|
||||
{
|
||||
$this->mediaFactory = $mediaFactory;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this data provider is in preview mode
|
||||
* @param bool $isPreview
|
||||
* @return DataProviderInterface
|
||||
*/
|
||||
public function setIsPreview(bool $isPreview): DataProviderInterface
|
||||
{
|
||||
$this->isPreview = $isPreview;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getDataSource(): string
|
||||
{
|
||||
return $this->module->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getDataType(): string
|
||||
{
|
||||
return $this->module->dataType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getDisplayId(): int
|
||||
{
|
||||
return $this->displayId ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getDisplayLatitude(): ?float
|
||||
{
|
||||
return $this->latitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getDisplayLongitude(): ?float
|
||||
{
|
||||
return $this->longitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isPreview(): bool
|
||||
{
|
||||
return $this->isPreview;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getWidgetId(): int
|
||||
{
|
||||
return $this->widget->widgetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getProperty(string $property, $default = null)
|
||||
{
|
||||
if ($this->properties === null) {
|
||||
$this->properties = $this->module->getPropertyValues(false);
|
||||
}
|
||||
|
||||
$value = $this->properties[$property] ?? $default;
|
||||
if (is_integer($default)) {
|
||||
return intval($value);
|
||||
} else if (is_numeric($value)) {
|
||||
return doubleval($value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getSetting(string $setting, $default = null)
|
||||
{
|
||||
if ($this->settings === null) {
|
||||
foreach ($this->module->settings as $item) {
|
||||
$this->settings[$item->id] = $item->value ?: $item->default;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->settings[$setting] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this data provider handled?
|
||||
* @return bool
|
||||
*/
|
||||
public function isHandled(): bool
|
||||
{
|
||||
return $this->isHandled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setIsUseEvent(): DataProviderInterface
|
||||
{
|
||||
$this->isUseEvent = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setIsHandled(): DataProviderInterface
|
||||
{
|
||||
$this->isHandled = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getMeta(): array
|
||||
{
|
||||
return $this->meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any errors recorded on this provider
|
||||
* @return array
|
||||
*/
|
||||
public function getErrors(): array
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getWidgetModifiedDt(): ?Carbon
|
||||
{
|
||||
return Carbon::createFromTimestamp($this->widget->modifiedDt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addError(string $errorMessage): DataProviderInterface
|
||||
{
|
||||
$this->errors[] = $errorMessage;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addItem($item): DataProviderInterface
|
||||
{
|
||||
if (!is_array($item) && !is_object($item)) {
|
||||
throw new \RuntimeException('Item must be an array or an object');
|
||||
}
|
||||
|
||||
if (is_object($item) && !($item instanceof \JsonSerializable)) {
|
||||
throw new \RuntimeException('Item must be JSON serilizable');
|
||||
}
|
||||
|
||||
$this->data[] = $item;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addItems(array $items): DataProviderInterface
|
||||
{
|
||||
foreach ($items as $item) {
|
||||
$this->addItem($item);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addOrUpdateMeta(string $key, $item): DataProviderInterface
|
||||
{
|
||||
if (!is_array($item) && (is_object($item) && !$item instanceof \JsonSerializable)) {
|
||||
throw new \RuntimeException('Item must be an array or a JSON serializable object');
|
||||
}
|
||||
|
||||
$this->meta[$key] = $item;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addImage(string $id, string $url, int $expiresAt): string
|
||||
{
|
||||
$media = $this->mediaFactory->queueDownload($id, $url, $expiresAt);
|
||||
$this->media[] = $media;
|
||||
|
||||
return '[[mediaId=' . $media->mediaId . ']]';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addLibraryFile(int $mediaId): string
|
||||
{
|
||||
$media = $this->mediaFactory->getById($mediaId);
|
||||
$this->media[] = $media;
|
||||
|
||||
return '[[mediaId=' . $media->mediaId . ']]';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Xibo\Entity\Media[]
|
||||
*/
|
||||
public function getImages(): array
|
||||
{
|
||||
return $this->media;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function getImageIds(): array
|
||||
{
|
||||
$mediaIds = [];
|
||||
foreach ($this->getImages() as $media) {
|
||||
$mediaIds[] = $media->mediaId;
|
||||
}
|
||||
return $mediaIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function clearData(): DataProviderInterface
|
||||
{
|
||||
$this->media = [];
|
||||
$this->data = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function clearMeta(): DataProviderInterface
|
||||
{
|
||||
$this->meta = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isUseEvent(): bool
|
||||
{
|
||||
return $this->isUseEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setCacheTtl(int $ttlSeconds): DataProviderInterface
|
||||
{
|
||||
$this->cacheTtl = $ttlSeconds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getCacheTtl(): int
|
||||
{
|
||||
return $this->cacheTtl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getGuzzleClient(array $requestOptions = []): Client
|
||||
{
|
||||
if ($this->client === null) {
|
||||
$this->client = new Client(array_merge($this->guzzleProxy, $requestOptions));
|
||||
}
|
||||
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getPool(): PoolInterface
|
||||
{
|
||||
return $this->pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getSanitizer(array $params): SanitizerInterface
|
||||
{
|
||||
return $this->sanitizer->getSanitizer($params);
|
||||
}
|
||||
}
|
||||
205
lib/Widget/Provider/DataProviderInterface.php
Normal file
205
lib/Widget/Provider/DataProviderInterface.php
Normal file
@@ -0,0 +1,205 @@
|
||||
<?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\Widget\Provider;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use GuzzleHttp\Client;
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Xibo\Support\Sanitizer\SanitizerInterface;
|
||||
|
||||
/**
|
||||
* Data Provider
|
||||
* -------------
|
||||
* A data provider is passed to a Widget which specifies a class in its configuration file
|
||||
* It should return data for the widget in the formated expected by the widgets datatype
|
||||
*
|
||||
* The widget might provid a class for other reasons and wish to use the widget.request.data event
|
||||
* to supply its data. In which case it should set is `setIsUseEvent()`.
|
||||
*
|
||||
* void methods on the data provider are chainable.
|
||||
*/
|
||||
interface DataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Get the data source expected by this provider
|
||||
* This will be the Module type that requested the provider
|
||||
* @return string
|
||||
*/
|
||||
public function getDataSource(): string;
|
||||
|
||||
/**
|
||||
* Get the datatype expected by this provider
|
||||
* @return string
|
||||
*/
|
||||
public function getDataType(): string;
|
||||
|
||||
/**
|
||||
* Get the ID for this display
|
||||
* @return int
|
||||
*/
|
||||
public function getDisplayId(): int;
|
||||
|
||||
/**
|
||||
* Get the latitude for this display
|
||||
* @return float|null
|
||||
*/
|
||||
public function getDisplayLatitude(): ?float;
|
||||
|
||||
/**
|
||||
* Get the longitude for this display
|
||||
* @return float|null
|
||||
*/
|
||||
public function getDisplayLongitude(): ?float;
|
||||
|
||||
/**
|
||||
* Get the preview flag
|
||||
* @return bool
|
||||
*/
|
||||
public function isPreview(): bool;
|
||||
|
||||
/**
|
||||
* Get the ID for this Widget
|
||||
* @return int
|
||||
*/
|
||||
public function getWidgetId(): int;
|
||||
|
||||
/**
|
||||
* Get a configured Guzzle client
|
||||
* this will have its proxy configuration set and be ready to use.
|
||||
* @param array $requestOptions An optional array of additional request options.
|
||||
* @return Client
|
||||
*/
|
||||
public function getGuzzleClient(array $requestOptions = []): Client;
|
||||
|
||||
/**
|
||||
* Get a cache pool interface
|
||||
* this will be a cache pool configured using the CMS settings.
|
||||
* @return PoolInterface
|
||||
*/
|
||||
public function getPool(): PoolInterface;
|
||||
|
||||
/**
|
||||
* Get property
|
||||
* Properties are set on Widgets and can be things like "feedUrl"
|
||||
* the property must exist in module properties for this type of widget
|
||||
* @param string $property The property name
|
||||
* @param mixed $default An optional default value. The return will be cast to the datatype of this default value.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getProperty(string $property, $default = null);
|
||||
|
||||
/**
|
||||
* Get setting
|
||||
* Settings are set on Modules and can be things like "apiKey"
|
||||
* the setting must exist in module settings for this type of widget
|
||||
* @param string $setting The setting name
|
||||
* @param mixed $default An optional default value. The return will be cast to the datatype of this default value.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSetting(string $setting, $default = null);
|
||||
|
||||
/**
|
||||
* Get a Santiziter
|
||||
* @param array $params key/value array of variable to sanitize
|
||||
* @return SanitizerInterface
|
||||
*/
|
||||
public function getSanitizer(array $params): SanitizerInterface;
|
||||
|
||||
/**
|
||||
* Get the widget modifiedDt
|
||||
* @return \Carbon\Carbon|null
|
||||
*/
|
||||
public function getWidgetModifiedDt(): ?Carbon;
|
||||
|
||||
/**
|
||||
* Indicate that we should use the event mechanism to handle this event.
|
||||
* @return \Xibo\Widget\Provider\DataProviderInterface
|
||||
*/
|
||||
public function setIsUseEvent(): DataProviderInterface;
|
||||
|
||||
/**
|
||||
* Indicate that this data provider has been handled.
|
||||
* @return DataProviderInterface
|
||||
*/
|
||||
public function setIsHandled(): DataProviderInterface;
|
||||
|
||||
/**
|
||||
* Add an error to this data provider, if no other data providers handle this request, the error will be
|
||||
* thrown as a configuration error.
|
||||
* @param string $errorMessage
|
||||
* @return DataProviderInterface
|
||||
*/
|
||||
public function addError(string $errorMessage): DataProviderInterface;
|
||||
|
||||
/**
|
||||
* Add an item to the provider
|
||||
* You should ensure that you provide all properties required by the datatype you are returning
|
||||
* example data types would be: article, social, event, menu, tabular
|
||||
* @param array|object $item An array containing the item to render in any templates used by this data provider
|
||||
* @return \Xibo\Widget\Provider\DataProviderInterface
|
||||
*/
|
||||
public function addItem($item): DataProviderInterface;
|
||||
|
||||
/**
|
||||
* Add items to the provider
|
||||
* You should ensure that you provide all properties required by the datatype you are returning
|
||||
* example data types would be: article, social, event, menu, tabular
|
||||
* @param array $items An array containing the item to render in any templates used by this data provider
|
||||
* @return \Xibo\Widget\Provider\DataProviderInterface
|
||||
*/
|
||||
public function addItems(array $items): DataProviderInterface;
|
||||
|
||||
/**
|
||||
* Add metadata to the provider
|
||||
* This is a key/value array of metadata which should be delivered alongside the data
|
||||
* @param string $key
|
||||
* @param mixed $item An array/object containing the metadata, which must be JSON serializable
|
||||
* @return \Xibo\Widget\Provider\DataProviderInterface
|
||||
*/
|
||||
public function addOrUpdateMeta(string $key, $item): DataProviderInterface;
|
||||
|
||||
/**
|
||||
* Add an image to the data provider and return the URL for that image
|
||||
* @param string $id A unique ID for this image, we recommend adding a module/connector specific prefix
|
||||
* @param string $url The URL on which this image should be downloaded
|
||||
* @param int $expiresAt A unix timestamp for when this image should be removed - should be longer than cache ttl
|
||||
* @return string
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function addImage(string $id, string $url, int $expiresAt): string;
|
||||
|
||||
/**
|
||||
* Add a library file
|
||||
* @param int $mediaId The mediaId for this file.
|
||||
* @return string
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function addLibraryFile(int $mediaId): string;
|
||||
|
||||
/**
|
||||
* Set the cache TTL
|
||||
* @param int $ttlSeconds The time to live in seconds
|
||||
* @return \Xibo\Widget\Provider\DataProviderInterface
|
||||
*/
|
||||
public function setCacheTtl(int $ttlSeconds): DataProviderInterface;
|
||||
}
|
||||
97
lib/Widget/Provider/DurationProvider.php
Normal file
97
lib/Widget/Provider/DurationProvider.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?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\Widget\Provider;
|
||||
|
||||
use Xibo\Entity\Module;
|
||||
use Xibo\Entity\Widget;
|
||||
|
||||
/**
|
||||
* Xibo's default implementation of the Duration Provider
|
||||
*/
|
||||
class DurationProvider implements DurationProviderInterface
|
||||
{
|
||||
/** @var Module */
|
||||
private $module;
|
||||
|
||||
/** @var Widget */
|
||||
private $widget;
|
||||
|
||||
/** @var int Duration in seconds */
|
||||
private $duration;
|
||||
|
||||
/** @var bool Has the duration been set? */
|
||||
private $isDurationSet = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param Module $module
|
||||
* @param Widget $widget
|
||||
*/
|
||||
public function __construct(Module $module, Widget $widget)
|
||||
{
|
||||
$this->module = $module;
|
||||
$this->widget = $widget;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setDuration(int $seconds): DurationProviderInterface
|
||||
{
|
||||
$this->isDurationSet = true;
|
||||
$this->duration = $seconds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getDuration(): int
|
||||
{
|
||||
return $this->duration ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isDurationSet(): bool
|
||||
{
|
||||
return $this->isDurationSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getModule(): Module
|
||||
{
|
||||
return $this->module;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getWidget(): Widget
|
||||
{
|
||||
return $this->widget;
|
||||
}
|
||||
}
|
||||
62
lib/Widget/Provider/DurationProviderInterface.php
Normal file
62
lib/Widget/Provider/DurationProviderInterface.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?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\Widget\Provider;
|
||||
|
||||
use Xibo\Entity\Module;
|
||||
use Xibo\Entity\Widget;
|
||||
|
||||
/**
|
||||
* A duration provider is used to return the duration for a Widget which has a media file
|
||||
*/
|
||||
interface DurationProviderInterface
|
||||
{
|
||||
/**
|
||||
* Get the Module
|
||||
* @return Module
|
||||
*/
|
||||
public function getModule(): Module;
|
||||
|
||||
/**
|
||||
* Get the Widget
|
||||
* @return Widget
|
||||
*/
|
||||
public function getWidget(): Widget;
|
||||
|
||||
/**
|
||||
* Get the duration
|
||||
* @return int the duration in seconds
|
||||
*/
|
||||
public function getDuration(): int;
|
||||
|
||||
/**
|
||||
* Set the duration in seconds
|
||||
* @param int $seconds the duration in seconds
|
||||
* @return \Xibo\Widget\Provider\DurationProviderInterface
|
||||
*/
|
||||
public function setDuration(int $seconds): DurationProviderInterface;
|
||||
|
||||
/**
|
||||
* @return bool true if the duration has been set
|
||||
*/
|
||||
public function isDurationSet(): bool;
|
||||
}
|
||||
50
lib/Widget/Provider/DurationProviderNumItemsTrait.php
Normal file
50
lib/Widget/Provider/DurationProviderNumItemsTrait.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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\Widget\Provider;
|
||||
|
||||
/**
|
||||
* A trait providing the duration for widgets using numItems, durationIsPerItem and itemsPerPage
|
||||
*/
|
||||
trait DurationProviderNumItemsTrait
|
||||
{
|
||||
public function fetchDuration(DurationProviderInterface $durationProvider): WidgetProviderInterface
|
||||
{
|
||||
$this->getLog()->debug('fetchDuration: DurationProviderNumItemsTrait');
|
||||
|
||||
// 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 = $durationProvider->getWidget()->getOptionValue('numItems', 15);
|
||||
|
||||
if ($durationProvider->getWidget()->getOptionValue('durationIsPerItem', 0) == 1 && $numItems > 1) {
|
||||
// If we have paging involved then work out the page count.
|
||||
$itemsPerPage = $durationProvider->getWidget()->getOptionValue('itemsPerPage', 0);
|
||||
if ($itemsPerPage > 0) {
|
||||
$numItems = ceil($numItems / $itemsPerPage);
|
||||
}
|
||||
|
||||
$durationProvider->setDuration($durationProvider->getWidget()->calculatedDuration * $numItems);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
61
lib/Widget/Provider/WidgetCompatibilityInterface.php
Normal file
61
lib/Widget/Provider/WidgetCompatibilityInterface.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?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\Widget\Provider;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Xibo\Entity\Widget;
|
||||
|
||||
/**
|
||||
* Widget Compatibility Interface should be implemented by custom widget upgrade classes.
|
||||
* Takes necessary actions to make the existing widgets from v3 compatible with v4.
|
||||
*
|
||||
* The schema from and the schema to (currently set to 1 and 2, respectively).
|
||||
* It also provides a method to save a template to the library in a sub-folder named templates/. This method
|
||||
* is called whenever a widget is loaded with a different schema version.
|
||||
*
|
||||
*/
|
||||
interface WidgetCompatibilityInterface
|
||||
{
|
||||
public function getLog(): LoggerInterface;
|
||||
|
||||
public function setLog(LoggerInterface $logger): WidgetCompatibilityInterface;
|
||||
|
||||
/**
|
||||
* Upgrade the given widget to be compatible with the specified schema version.
|
||||
*
|
||||
* @param Widget $widget The widget model to upgrade.
|
||||
* @param int $fromSchema The version of the schema the widget is currently using.
|
||||
* @param int $toSchema The version of the schema to upgrade the widget to.
|
||||
* @return bool Whether the upgrade was successful
|
||||
*/
|
||||
public function upgradeWidget(Widget $widget, int $fromSchema, int $toSchema): bool;
|
||||
|
||||
/**
|
||||
* Save the given widget template to the templates/ subfolder.
|
||||
*
|
||||
* @param string $template The widget template to save.
|
||||
* @param string $fileName The file name to save the template as.
|
||||
* @return bool Returns true if the template was saved successfully, false otherwise.
|
||||
*/
|
||||
public function saveTemplate(string $template, string $fileName): bool;
|
||||
}
|
||||
48
lib/Widget/Provider/WidgetCompatibilityTrait.php
Normal file
48
lib/Widget/Provider/WidgetCompatibilityTrait.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?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\Widget\Provider;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* A trait to set common objects on a Widget Compatibility Interface
|
||||
*/
|
||||
trait WidgetCompatibilityTrait
|
||||
{
|
||||
private $log;
|
||||
|
||||
public function getLog(): LoggerInterface
|
||||
{
|
||||
if ($this->log === null) {
|
||||
$this->log = new NullLogger();
|
||||
}
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
public function setLog(LoggerInterface $logger): WidgetCompatibilityInterface
|
||||
{
|
||||
$this->log = $logger;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
97
lib/Widget/Provider/WidgetProviderInterface.php
Normal file
97
lib/Widget/Provider/WidgetProviderInterface.php
Normal 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\Widget\Provider;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* The Widget Provider Interface should be implemented by any Widget which specifies a `class` in its Module
|
||||
* configuration.
|
||||
*
|
||||
* The provider should be modified accordingly before returning $this
|
||||
*
|
||||
* If the widget does not need to fetch Data or fetch Duration, then it can return without
|
||||
* modifying the provider.
|
||||
*/
|
||||
interface WidgetProviderInterface
|
||||
{
|
||||
public function getLog(): LoggerInterface;
|
||||
public function setLog(LoggerInterface $logger): WidgetProviderInterface;
|
||||
|
||||
/**
|
||||
* Get the event dispatcher
|
||||
* @return \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
public function getDispatcher(): EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Set the event dispatcher
|
||||
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $logger
|
||||
* @return \Xibo\Widget\Provider\WidgetProviderInterface
|
||||
*/
|
||||
public function setDispatcher(EventDispatcherInterface $logger): WidgetProviderInterface;
|
||||
|
||||
/**
|
||||
* Fetch data
|
||||
* The widget provider must either addItems to the data provider, or indicate that data is provided by
|
||||
* an event instead by setting isUseEvent()
|
||||
* If data is to be provided by an event, core will raise the `widget.request.data` event with parameters
|
||||
* indicating this widget's datatype, name, settings and currently configured options
|
||||
* @param \Xibo\Widget\Provider\DataProviderInterface $dataProvider
|
||||
* @return \Xibo\Widget\Provider\WidgetProviderInterface
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function fetchData(DataProviderInterface $dataProvider): WidgetProviderInterface;
|
||||
|
||||
/**
|
||||
* Fetch duration
|
||||
* This is typically only relevant to widgets which have a media file associated, for example video or audio
|
||||
* in cases where this is not appropriate, return without modifying to use the module default duration from
|
||||
* module configuration.
|
||||
* @param \Xibo\Widget\Provider\DurationProviderInterface $durationProvider
|
||||
* @return \Xibo\Widget\Provider\WidgetProviderInterface
|
||||
*/
|
||||
public function fetchDuration(DurationProviderInterface $durationProvider): WidgetProviderInterface;
|
||||
|
||||
/**
|
||||
* Get data cache key
|
||||
* Use this method to return a cache key for this widget. This is typically only relevant when the data cache
|
||||
* should be different based on the value of a setting. For example, if the tweetDistance is set on a Twitter
|
||||
* widget, then the cache should be by displayId. If the cache is always by displayId, then you should supply
|
||||
* the `dataCacheKey` via module config XML instead.
|
||||
* @param \Xibo\Widget\Provider\DataProviderInterface $dataProvider
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDataCacheKey(DataProviderInterface $dataProvider): ?string;
|
||||
|
||||
/**
|
||||
* Get data modified date
|
||||
* Use this method to invalidate cache ahead of its expiry date/time by returning the date/time that the underlying
|
||||
* data is expected to or has been modified
|
||||
* @param \Xibo\Widget\Provider\DataProviderInterface $dataProvider
|
||||
* @return \Carbon\Carbon|null
|
||||
*/
|
||||
public function getDataModifiedDt(DataProviderInterface $dataProvider): ?Carbon;
|
||||
}
|
||||
67
lib/Widget/Provider/WidgetProviderTrait.php
Normal file
67
lib/Widget/Provider/WidgetProviderTrait.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?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\Widget\Provider;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* A trait to set common objects on a Widget Provider Interface
|
||||
*/
|
||||
trait WidgetProviderTrait
|
||||
{
|
||||
private $log;
|
||||
private $dispatcher;
|
||||
|
||||
public function getLog(): LoggerInterface
|
||||
{
|
||||
if ($this->log === null) {
|
||||
$this->log = new NullLogger();
|
||||
}
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
public function setLog(LoggerInterface $logger): WidgetProviderInterface
|
||||
{
|
||||
$this->log = $logger;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getDispatcher(): EventDispatcherInterface
|
||||
{
|
||||
if ($this->dispatcher === null) {
|
||||
$this->dispatcher = new EventDispatcher();
|
||||
}
|
||||
return $this->dispatcher;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function setDispatcher(EventDispatcherInterface $dispatcher): WidgetProviderInterface
|
||||
{
|
||||
$this->dispatcher = $dispatcher;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
48
lib/Widget/Provider/WidgetValidatorInterface.php
Normal file
48
lib/Widget/Provider/WidgetValidatorInterface.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?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\Widget\Provider;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Xibo\Entity\Module;
|
||||
use Xibo\Entity\Widget;
|
||||
|
||||
/**
|
||||
* Widget Validator Interface
|
||||
* --------------------------
|
||||
* Used to validate the properties of a module after it all of its individual properties and those of its
|
||||
* template have been validated via their property rules.
|
||||
*/
|
||||
interface WidgetValidatorInterface
|
||||
{
|
||||
public function getLog(): LoggerInterface;
|
||||
|
||||
public function setLog(LoggerInterface $logger): WidgetValidatorInterface;
|
||||
|
||||
/**
|
||||
* Validate the widget provided
|
||||
* @param Module $module The Module
|
||||
* @param Widget $widget The Widget - this is read only
|
||||
* @param string $stage Which stage are we validating, either `save` or `status`
|
||||
*/
|
||||
public function validate(Module $module, Widget $widget, string $stage): void;
|
||||
}
|
||||
48
lib/Widget/Provider/WidgetValidatorTrait.php
Normal file
48
lib/Widget/Provider/WidgetValidatorTrait.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?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\Widget\Provider;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* A trait to set common objects on a Widget Compatibility Interface
|
||||
*/
|
||||
trait WidgetValidatorTrait
|
||||
{
|
||||
private $log;
|
||||
|
||||
public function getLog(): LoggerInterface
|
||||
{
|
||||
if ($this->log === null) {
|
||||
$this->log = new NullLogger();
|
||||
}
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
public function setLog(LoggerInterface $logger): WidgetValidatorInterface
|
||||
{
|
||||
$this->log = $logger;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
517
lib/Widget/Render/WidgetDataProviderCache.php
Normal file
517
lib/Widget/Render/WidgetDataProviderCache.php
Normal file
@@ -0,0 +1,517 @@
|
||||
<?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\Widget\Render;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Stash\Invalidation;
|
||||
use Stash\Item;
|
||||
use Xibo\Entity\Display;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Helper\LinkSigner;
|
||||
use Xibo\Helper\ObjectVars;
|
||||
use Xibo\Service\ConfigServiceInterface;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Widget\Provider\DataProvider;
|
||||
use Xibo\Widget\Provider\DataProviderInterface;
|
||||
use Xibo\Xmds\Wsdl;
|
||||
|
||||
/**
|
||||
* Acts as a cache for the Widget data cache.
|
||||
*/
|
||||
class WidgetDataProviderCache
|
||||
{
|
||||
/** @var LoggerInterface */
|
||||
private $logger;
|
||||
|
||||
/** @var \Stash\Interfaces\PoolInterface */
|
||||
private $pool;
|
||||
|
||||
/** @var Item */
|
||||
private $lock;
|
||||
|
||||
/** @var Item */
|
||||
private $cache;
|
||||
|
||||
/** @var string The cache key */
|
||||
private $key;
|
||||
|
||||
/** @var bool Is the cache a miss or old */
|
||||
private $isMissOrOld = true;
|
||||
|
||||
private $cachedMediaIds;
|
||||
|
||||
/**
|
||||
* @param \Stash\Interfaces\PoolInterface $pool
|
||||
*/
|
||||
public function __construct(PoolInterface $pool)
|
||||
{
|
||||
$this->pool = $pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* @return $this
|
||||
*/
|
||||
public function useLogger(LoggerInterface $logger): WidgetDataProviderCache
|
||||
{
|
||||
$this->logger = $logger;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Psr\Log\LoggerInterface
|
||||
*/
|
||||
private function getLog(): LoggerInterface
|
||||
{
|
||||
if ($this->logger === null) {
|
||||
$this->logger = new NullLogger();
|
||||
}
|
||||
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate this data provider with cache
|
||||
* @param DataProvider $dataProvider
|
||||
* @param string $cacheKey
|
||||
* @param Carbon|null $dataModifiedDt The date any associated data was modified.
|
||||
* @param bool $isLockIfMiss Should the cache be locked if it's a miss? Defaults to true.
|
||||
* @return bool
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function decorateWithCache(
|
||||
DataProvider $dataProvider,
|
||||
string $cacheKey,
|
||||
?Carbon $dataModifiedDt,
|
||||
bool $isLockIfMiss = true,
|
||||
): bool {
|
||||
// Construct a key
|
||||
$this->key = '/widget/'
|
||||
. ($dataProvider->getDataType() ?: $dataProvider->getDataSource())
|
||||
. '/' . md5($cacheKey);
|
||||
|
||||
$this->getLog()->debug('decorateWithCache: key is ' . $this->key);
|
||||
|
||||
// Get the cache
|
||||
$this->cache = $this->pool->getItem($this->key);
|
||||
|
||||
// Invalidation method old means that if this cache key is being regenerated concurrently to this request
|
||||
// we return the old data we have stored already.
|
||||
$this->cache->setInvalidationMethod(Invalidation::OLD);
|
||||
|
||||
// Get the data (this might be OLD data)
|
||||
$data = $this->cache->get();
|
||||
$cacheCreationDt = $this->cache->getCreation();
|
||||
|
||||
// Does the cache have data?
|
||||
// we keep data 50% longer than we need to, so that it has a chance to be regenerated out of band
|
||||
if ($data === null) {
|
||||
$this->getLog()->debug('decorateWithCache: miss, no data');
|
||||
$hasData = false;
|
||||
} else {
|
||||
$hasData = true;
|
||||
|
||||
// Clear the data provider and add the cached items back to it.
|
||||
$dataProvider->clearData();
|
||||
$dataProvider->clearMeta();
|
||||
$dataProvider->addItems($data->data ?? []);
|
||||
|
||||
// Record any cached mediaIds
|
||||
$this->cachedMediaIds = $data->media ?? [];
|
||||
|
||||
// Update any meta
|
||||
foreach (($data->meta ?? []) as $key => $item) {
|
||||
$dataProvider->addOrUpdateMeta($key, $item);
|
||||
}
|
||||
|
||||
// Determine whether this cache is a miss (i.e. expired and being regenerated, expired, out of date)
|
||||
// We use our own expireDt here because Stash will only return expired data with invalidation method OLD
|
||||
// if the data is currently being regenerated and another process has called lock() on it
|
||||
$expireDt = $dataProvider->getMeta()['expireDt'] ?? null;
|
||||
if ($expireDt !== null) {
|
||||
$expireDt = Carbon::createFromFormat('c', $expireDt);
|
||||
} else {
|
||||
$expireDt = $this->cache->getExpiration();
|
||||
}
|
||||
|
||||
// Determine if the cache returned is a miss or older than the modified/expired dates
|
||||
$this->isMissOrOld = $this->cache->isMiss()
|
||||
|| ($dataModifiedDt !== null && $cacheCreationDt !== false && $dataModifiedDt->isAfter($cacheCreationDt)
|
||||
|| ($expireDt->isBefore(Carbon::now()))
|
||||
);
|
||||
|
||||
$this->getLog()->debug('decorateWithCache: cache has data, is miss or old: '
|
||||
. var_export($this->isMissOrOld, true));
|
||||
}
|
||||
|
||||
// If we do not have data/we're old/missed cache, and we have requested a lock, then we will be refreshing
|
||||
// the cache, so lock the record
|
||||
if ($isLockIfMiss && (!$hasData || $this->isMissOrOld)) {
|
||||
$this->concurrentRequestLock();
|
||||
}
|
||||
|
||||
return $hasData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the cache a miss, or old data.
|
||||
* @return bool
|
||||
*/
|
||||
public function isCacheMissOrOld(): bool
|
||||
{
|
||||
return $this->isMissOrOld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache date for this data provider and key
|
||||
* @param DataProvider $dataProvider
|
||||
* @param string $cacheKey
|
||||
* @return Carbon|null
|
||||
*/
|
||||
public function getCacheDate(DataProvider $dataProvider, string $cacheKey): ?Carbon
|
||||
{
|
||||
// Construct a key
|
||||
$this->key = '/widget/'
|
||||
. ($dataProvider->getDataType() ?: $dataProvider->getDataSource())
|
||||
. '/' . md5($cacheKey);
|
||||
|
||||
$this->getLog()->debug('getCacheDate: key is ' . $this->key);
|
||||
|
||||
// Get the cache
|
||||
$this->cache = $this->pool->getItem($this->key);
|
||||
$cacheCreationDt = $this->cache->getCreation();
|
||||
|
||||
return $cacheCreationDt ? Carbon::instance($cacheCreationDt) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataProviderInterface $dataProvider
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function saveToCache(DataProviderInterface $dataProvider): void
|
||||
{
|
||||
if ($this->cache === null) {
|
||||
throw new GeneralException('No cache to save');
|
||||
}
|
||||
|
||||
// Set some cache dates so that we can track when this data provider was cached and when it should expire.
|
||||
$dataProvider->addOrUpdateMeta('cacheDt', Carbon::now()->format('c'));
|
||||
$dataProvider->addOrUpdateMeta(
|
||||
'expireDt',
|
||||
Carbon::now()->addSeconds($dataProvider->getCacheTtl())->format('c')
|
||||
);
|
||||
|
||||
// Set our cache from the data provider.
|
||||
$object = new \stdClass();
|
||||
$object->data = $dataProvider->getData();
|
||||
$object->meta = $dataProvider->getMeta();
|
||||
$object->media = $dataProvider->getImageIds();
|
||||
$cached = $this->cache->set($object);
|
||||
|
||||
if (!$cached) {
|
||||
throw new GeneralException('Cache failure');
|
||||
}
|
||||
|
||||
// Keep the cache 50% longer than necessary
|
||||
// The expireDt must always be 15 minutes to allow plenty of time for the WidgetSyncTask to regenerate.
|
||||
$this->cache->expiresAfter(ceil(max($dataProvider->getCacheTtl() * 1.5, 900)));
|
||||
|
||||
// Save to the pool
|
||||
$this->pool->save($this->cache);
|
||||
|
||||
$this->getLog()->debug('saveToCache: cached ' . $this->key
|
||||
. ' for ' . $dataProvider->getCacheTtl() . ' seconds');
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalise the cache process
|
||||
*/
|
||||
public function finaliseCache(): void
|
||||
{
|
||||
$this->concurrentRequestRelease();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any cached mediaIds
|
||||
* @return array
|
||||
*/
|
||||
public function getCachedMediaIds(): array
|
||||
{
|
||||
return $this->cachedMediaIds ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate for a preview
|
||||
* @param array $data The data
|
||||
* @param callable $urlFor
|
||||
* @return array
|
||||
*/
|
||||
public function decorateForPreview(array $data, callable $urlFor): array
|
||||
{
|
||||
foreach ($data as $row => $item) {
|
||||
// This is either an object or an array
|
||||
if (is_array($item)) {
|
||||
foreach ($item as $key => $value) {
|
||||
if (is_string($value)) {
|
||||
$data[$row][$key] = $this->decorateMediaForPreview($urlFor, $value);
|
||||
}
|
||||
}
|
||||
} else if (is_object($item)) {
|
||||
foreach (ObjectVars::getObjectVars($item) as $key => $value) {
|
||||
if (is_string($value)) {
|
||||
$item->{$key} = $this->decorateMediaForPreview($urlFor, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $urlFor
|
||||
* @param string|null $data
|
||||
* @return string|null
|
||||
*/
|
||||
private function decorateMediaForPreview(callable $urlFor, ?string $data): ?string
|
||||
{
|
||||
if ($data === null) {
|
||||
return null;
|
||||
}
|
||||
$matches = [];
|
||||
preg_match_all('/\[\[(.*?)\]\]/', $data, $matches);
|
||||
foreach ($matches[1] as $match) {
|
||||
if (Str::startsWith($match, 'mediaId')) {
|
||||
$value = explode('=', $match);
|
||||
$data = str_replace(
|
||||
'[[' . $match . ']]',
|
||||
$urlFor('library.download', ['id' => $value[1], 'type' => 'image']),
|
||||
$data
|
||||
);
|
||||
} else if (Str::startsWith($match, 'connector')) {
|
||||
$value = explode('=', $match);
|
||||
$data = str_replace(
|
||||
'[[' . $match . ']]',
|
||||
$urlFor('layout.preview.connector', [], ['token' => $value[1], 'isDebug' => 1]),
|
||||
$data
|
||||
);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate for a player
|
||||
* @param \Xibo\Service\ConfigServiceInterface $configService
|
||||
* @param \Xibo\Entity\Display $display
|
||||
* @param string $encryptionKey
|
||||
* @param array $data The data
|
||||
* @param array $storedAs A keyed array of module files this widget has access to
|
||||
* @return array
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function decorateForPlayer(
|
||||
ConfigServiceInterface $configService,
|
||||
Display $display,
|
||||
string $encryptionKey,
|
||||
array $data,
|
||||
array $storedAs,
|
||||
): array {
|
||||
$this->getLog()->debug('decorateForPlayer');
|
||||
|
||||
$cdnUrl = $configService->getSetting('CDN_URL');
|
||||
|
||||
foreach ($data as $row => $item) {
|
||||
// Each data item can be an array or an object
|
||||
if (is_array($item)) {
|
||||
foreach ($item as $key => $value) {
|
||||
if (is_string($value)) {
|
||||
$data[$row][$key] = $this->decorateMediaForPlayer(
|
||||
$cdnUrl,
|
||||
$display,
|
||||
$encryptionKey,
|
||||
$storedAs,
|
||||
$value,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (is_object($item)) {
|
||||
foreach (ObjectVars::getObjectVars($item) as $key => $value) {
|
||||
if (is_string($value)) {
|
||||
$item->{$key} = $this->decorateMediaForPlayer(
|
||||
$cdnUrl,
|
||||
$display,
|
||||
$encryptionKey,
|
||||
$storedAs,
|
||||
$value
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $cdnUrl
|
||||
* @param \Xibo\Entity\Display $display
|
||||
* @param string $encryptionKey
|
||||
* @param array $storedAs
|
||||
* @param string|null $data
|
||||
* @return string|null
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
private function decorateMediaForPlayer(
|
||||
?string $cdnUrl,
|
||||
Display $display,
|
||||
string $encryptionKey,
|
||||
array $storedAs,
|
||||
?string $data,
|
||||
): ?string {
|
||||
if ($data === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Do we need to add a URL prefix to the requests?
|
||||
$prefix = $display->isPwa() ? '/pwa/' : '';
|
||||
|
||||
// Media substitutes
|
||||
$matches = [];
|
||||
preg_match_all('/\[\[(.*?)\]\]/', $data, $matches);
|
||||
foreach ($matches[1] as $match) {
|
||||
if (Str::startsWith($match, 'mediaId')) {
|
||||
$value = explode('=', $match);
|
||||
if (array_key_exists($value[1], $storedAs)) {
|
||||
if ($display->isPwa()) {
|
||||
$url = LinkSigner::generateSignedLink(
|
||||
$display,
|
||||
$encryptionKey,
|
||||
$cdnUrl,
|
||||
'M',
|
||||
$value[1],
|
||||
$storedAs[$value[1]],
|
||||
null,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
$url = $storedAs[$value[1]];
|
||||
}
|
||||
$data = str_replace('[[' . $match . ']]', $prefix . $url, $data);
|
||||
} else {
|
||||
$data = str_replace('[[' . $match . ']]', '', $data);
|
||||
}
|
||||
} else if (Str::startsWith($match, 'connector')) {
|
||||
// We have WSDL here because this is only called from XMDS.
|
||||
$value = explode('=', $match);
|
||||
$data = str_replace(
|
||||
'[[' . $match . ']]',
|
||||
Wsdl::getRoot() . '?connector=true&token=' . $value[1],
|
||||
$data
|
||||
);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
// <editor-fold desc="Request locking">
|
||||
|
||||
/**
|
||||
* Hold a lock on concurrent requests
|
||||
* blocks if the request is locked
|
||||
* @param int $ttl seconds
|
||||
* @param int $wait seconds
|
||||
* @param int $tries
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
private function concurrentRequestLock(int $ttl = 300, int $wait = 2, int $tries = 5)
|
||||
{
|
||||
if ($this->cache === null) {
|
||||
throw new GeneralException('No cache to lock');
|
||||
}
|
||||
|
||||
$this->lock = $this->pool->getItem('locks/concurrency/' . $this->cache->getKey());
|
||||
|
||||
// Set the invalidation method to simply return the value (not that we use it, but it gets us a miss on expiry)
|
||||
// isMiss() returns false if the item is missing or expired, no exceptions.
|
||||
$this->lock->setInvalidationMethod(Invalidation::NONE);
|
||||
|
||||
// Get the lock
|
||||
// other requests will wait here until we're done, or we've timed out
|
||||
$locked = $this->lock->get();
|
||||
|
||||
// Did we get a lock?
|
||||
// if we're a miss, then we're not already locked
|
||||
if ($this->lock->isMiss() || $locked === false) {
|
||||
$this->getLog()->debug('Lock miss or false. Locking for ' . $ttl
|
||||
. ' seconds. $locked is '. var_export($locked, true)
|
||||
. ', key = ' . $this->cache->getKey());
|
||||
|
||||
// so lock now
|
||||
$this->lock->set(true);
|
||||
$this->lock->expiresAfter($ttl);
|
||||
$this->lock->save();
|
||||
} else {
|
||||
// We are a hit - we must be locked
|
||||
$this->getLog()->debug('LOCK hit for ' . $this->cache->getKey() . ' expires '
|
||||
. $this->lock->getExpiration()->format(DateFormatHelper::getSystemFormat())
|
||||
. ', created ' . $this->lock->getCreation()->format(DateFormatHelper::getSystemFormat()));
|
||||
|
||||
// Try again?
|
||||
$tries--;
|
||||
|
||||
if ($tries <= 0) {
|
||||
// We've waited long enough
|
||||
throw new GeneralException('Concurrent record locked, time out.');
|
||||
} else {
|
||||
$this->getLog()->debug('Unable to get a lock, trying again. Remaining retries: ' . $tries);
|
||||
|
||||
// Hang about waiting for the lock to be released.
|
||||
sleep($wait);
|
||||
|
||||
// Recursive request (we've decremented the number of tries)
|
||||
$this->concurrentRequestLock($ttl, $wait, $tries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a lock on concurrent requests
|
||||
*/
|
||||
private function concurrentRequestRelease()
|
||||
{
|
||||
if ($this->lock !== null) {
|
||||
$this->getLog()->debug('Releasing lock ' . $this->lock->getKey());
|
||||
|
||||
// Release lock
|
||||
$this->lock->set(false);
|
||||
$this->lock->expiresAfter(10); // Expire straight away (but give time to save)
|
||||
|
||||
$this->pool->save($this->lock);
|
||||
}
|
||||
}
|
||||
|
||||
// </editor-fold>
|
||||
}
|
||||
340
lib/Widget/Render/WidgetDownloader.php
Normal file
340
lib/Widget/Render/WidgetDownloader.php
Normal file
@@ -0,0 +1,340 @@
|
||||
<?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\Widget\Render;
|
||||
|
||||
use GuzzleHttp\Psr7\LimitStream;
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use Intervention\Image\ImageManagerStatic as Img;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Entity\Media;
|
||||
use Xibo\Helper\HttpCacheProvider;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
use Xibo\Support\Sanitizer\SanitizerInterface;
|
||||
|
||||
/**
|
||||
* A helper class to download widgets from the library (as media files)
|
||||
*/
|
||||
class WidgetDownloader
|
||||
{
|
||||
/** @var LoggerInterface */
|
||||
private LoggerInterface $logger;
|
||||
|
||||
/**
|
||||
* @param string $libraryLocation Library location
|
||||
* @param string $sendFileMode Send file mode
|
||||
* @param int $resizeLimit CMS resize limit
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $libraryLocation,
|
||||
private readonly string $sendFileMode,
|
||||
private readonly int $resizeLimit
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* @return $this
|
||||
*/
|
||||
public function useLogger(LoggerInterface $logger): WidgetDownloader
|
||||
{
|
||||
$this->logger = $logger;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return File
|
||||
* @param \Xibo\Entity\Media $media
|
||||
* @param \Slim\Http\ServerRequest $request
|
||||
* @param \Slim\Http\Response $response
|
||||
* @param string|null $contentType An optional content type, if provided the attachment is ignored
|
||||
* @param string|null $attachment An optional attachment, defaults to the stored file name (storedAs)
|
||||
* @return \Slim\Http\Response
|
||||
*/
|
||||
public function download(
|
||||
Media $media,
|
||||
Request $request,
|
||||
Response $response,
|
||||
?string $contentType = null,
|
||||
?string $attachment = null
|
||||
): Response {
|
||||
$this->logger->debug('widgetDownloader::download: Download for mediaId ' . $media->mediaId);
|
||||
|
||||
// The file path
|
||||
$libraryPath = $this->libraryLocation . $media->storedAs;
|
||||
|
||||
$this->logger->debug('widgetDownloader::download: ' . $libraryPath . ', ' . $contentType);
|
||||
|
||||
// Set some headers
|
||||
$headers = [];
|
||||
$fileSize = filesize($libraryPath);
|
||||
$headers['Content-Length'] = $fileSize;
|
||||
|
||||
// If we have been given a content type, then serve that to the browser.
|
||||
if ($contentType !== null) {
|
||||
$headers['Content-Type'] = $contentType;
|
||||
} else {
|
||||
// This widget is expected to output a file - usually this is for file based media
|
||||
// Get the name with library
|
||||
$attachmentName = empty($attachment) ? $media->storedAs : $attachment;
|
||||
|
||||
// Issue some headers
|
||||
$response = HttpCacheProvider::withEtag($response, $media->md5);
|
||||
$response = HttpCacheProvider::withExpires($response, '+1 week');
|
||||
|
||||
$headers['Content-Type'] = 'application/octet-stream';
|
||||
$headers['Content-Transfer-Encoding'] = 'Binary';
|
||||
$headers['Content-disposition'] = 'attachment; filename="' . $attachmentName . '"';
|
||||
}
|
||||
|
||||
// Output the file
|
||||
if ($this->sendFileMode === 'Apache') {
|
||||
// Send via Apache X-Sendfile header?
|
||||
$headers['X-Sendfile'] = $libraryPath;
|
||||
} else if ($this->sendFileMode === 'Nginx') {
|
||||
// Send via Nginx X-Accel-Redirect?
|
||||
$headers['X-Accel-Redirect'] = '/download/' . $media->storedAs;
|
||||
}
|
||||
|
||||
// Should we output the file via the application stack, or directly by reading the file.
|
||||
if ($this->sendFileMode == 'Off') {
|
||||
// Return the file with PHP
|
||||
$this->logger->debug('download: Returning Stream with response body, sendfile off.');
|
||||
|
||||
$stream = new Stream(fopen($libraryPath, 'r'));
|
||||
$start = 0;
|
||||
$end = $fileSize - 1;
|
||||
|
||||
$rangeHeader = $request->getHeaderLine('Range');
|
||||
if ($rangeHeader !== '') {
|
||||
$this->logger->debug('download: Handling Range request, header: ' . $rangeHeader);
|
||||
|
||||
if (preg_match('/bytes=(\d+)-(\d*)/', $rangeHeader, $matches)) {
|
||||
$start = (int) $matches[1];
|
||||
$end = $matches[2] !== '' ? (int) $matches[2] : $end;
|
||||
if ($start > $end || $end >= $fileSize) {
|
||||
return $response
|
||||
->withStatus(416)
|
||||
->withHeader('Content-Range', 'bytes */' . $fileSize);
|
||||
}
|
||||
}
|
||||
$headers['Content-Range'] = 'bytes ' . $start . '-' . $end . '/' . $fileSize;
|
||||
$headers['Content-Length'] = $end - $start + 1;
|
||||
$response = $response
|
||||
->withBody(new LimitStream($stream, $end - $start + 1, $start))
|
||||
->withStatus(206);
|
||||
} else {
|
||||
$response = $response->withBody($stream);
|
||||
}
|
||||
} else {
|
||||
$this->logger->debug('Using sendfile to return the file, only output headers.');
|
||||
}
|
||||
|
||||
// Add the headers we've collected to our response
|
||||
foreach ($headers as $header => $value) {
|
||||
$response = $response->withHeader($header, $value);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a thumbnail for the given media
|
||||
* @param \Xibo\Entity\Media $media
|
||||
* @param \Slim\Http\Response $response
|
||||
* @param string|null $errorThumb
|
||||
* @return \Slim\Http\Response
|
||||
*/
|
||||
public function thumbnail(
|
||||
Media $media,
|
||||
Response $response,
|
||||
?string $errorThumb = null
|
||||
): Response {
|
||||
// Our convention is to upload media covers in {mediaId}_{mediaType}cover.png
|
||||
// and then thumbnails in tn_{mediaId}_{mediaType}cover.png
|
||||
// unless we are an image module, which is its own image, and would then have a thumbnail in
|
||||
// tn_{mediaId}_{mediaType}cover.png
|
||||
try {
|
||||
$width = 120;
|
||||
$height = 120;
|
||||
|
||||
if ($media->mediaType === 'image') {
|
||||
$filePath = $this->libraryLocation . $media->storedAs;
|
||||
$thumbnailFilePath = $this->libraryLocation . 'tn_' . $media->storedAs;
|
||||
} else {
|
||||
$filePath = $this->libraryLocation . $media->mediaId . '_'
|
||||
. $media->mediaType . 'cover.png';
|
||||
$thumbnailFilePath = $this->libraryLocation . 'tn_' . $media->mediaId . '_'
|
||||
. $media->mediaType . 'cover.png';
|
||||
|
||||
// A video cover might not exist
|
||||
if (!file_exists($filePath)) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
// Does the thumbnail exist already?
|
||||
Img::configure(['driver' => 'gd']);
|
||||
$img = null;
|
||||
$regenerate = true;
|
||||
if (file_exists($thumbnailFilePath)) {
|
||||
$img = Img::make($thumbnailFilePath);
|
||||
if ($img->width() === $width || $img->height() === $height) {
|
||||
// Correct cache
|
||||
$regenerate = false;
|
||||
}
|
||||
$response = $response->withHeader('Content-Type', $img->mime());
|
||||
}
|
||||
|
||||
if ($regenerate) {
|
||||
// Check that our source image is not too large
|
||||
$imageInfo = getimagesize($filePath);
|
||||
|
||||
// Make sure none of the sides are greater than allowed
|
||||
if ($this->resizeLimit > 0
|
||||
&& ($imageInfo[0] > $this->resizeLimit || $imageInfo[1] > $this->resizeLimit)
|
||||
) {
|
||||
throw new InvalidArgumentException(__('Image too large'));
|
||||
}
|
||||
|
||||
// Get the full image and make a thumbnail
|
||||
$img = Img::make($filePath);
|
||||
$img->resize($width, $height, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
});
|
||||
$img->save($thumbnailFilePath);
|
||||
$response = $response->withHeader('Content-Type', $img->mime());
|
||||
}
|
||||
|
||||
// Output Etag
|
||||
$response = HttpCacheProvider::withEtag($response, md5_file($thumbnailFilePath));
|
||||
|
||||
$response->write($img->encode());
|
||||
} catch (\Exception) {
|
||||
$this->logger->debug('thumbnail: exception raised.');
|
||||
|
||||
if ($errorThumb !== null) {
|
||||
$img = Img::make($errorThumb);
|
||||
$response->write($img->encode());
|
||||
|
||||
// Output the mime type
|
||||
$response = $response->withHeader('Content-Type', $img->mime());
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output an image preview
|
||||
* @param \Xibo\Support\Sanitizer\SanitizerInterface $params
|
||||
* @param string $filePath
|
||||
* @param \Slim\Http\Response $response
|
||||
* @param string|null $errorThumb
|
||||
* @return \Slim\Http\Response
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function imagePreview(
|
||||
SanitizerInterface $params,
|
||||
string $filePath,
|
||||
Response $response,
|
||||
?string $errorThumb = null
|
||||
): Response {
|
||||
// Image previews call for dynamically generated images as various sizes
|
||||
// for example a background image will stretch to the entire region
|
||||
// an image widget may be aspect, fit or scale
|
||||
try {
|
||||
$filePath = $this->libraryLocation . $filePath;
|
||||
|
||||
// Does it exist?
|
||||
if (!file_exists($filePath)) {
|
||||
throw new NotFoundException(__('File not found'));
|
||||
}
|
||||
|
||||
// Check that our source image is not too large
|
||||
$imageInfo = getimagesize($filePath);
|
||||
|
||||
// Make sure none of the sides are greater than allowed
|
||||
if ($this->resizeLimit > 0
|
||||
&& ($imageInfo[0] > $this->resizeLimit || $imageInfo[1] > $this->resizeLimit)
|
||||
) {
|
||||
throw new InvalidArgumentException(__('Image too large'));
|
||||
}
|
||||
|
||||
// Continue to output at the desired size
|
||||
$width = intval($params->getDouble('width'));
|
||||
$height = intval($params->getDouble('height'));
|
||||
$proportional = !$params->hasParam('proportional')
|
||||
|| $params->getCheckbox('proportional') == 1;
|
||||
|
||||
$fit = $proportional && $params->getCheckbox('fit') === 1;
|
||||
|
||||
// only use upsize constraint, if we the requested dimensions are larger than resize limit.
|
||||
$useUpsizeConstraint = max($width, $height) > $this->resizeLimit;
|
||||
|
||||
$this->logger->debug('Whole file: ' . $filePath
|
||||
. ' requested with Width and Height ' . $width . ' x ' . $height
|
||||
. ', proportional: ' . var_export($proportional, true)
|
||||
. ', fit: ' . var_export($fit, true)
|
||||
. ', upsizeConstraint ' . var_export($useUpsizeConstraint, true));
|
||||
|
||||
// Does the thumbnail exist already?
|
||||
Img::configure(['driver' => 'gd']);
|
||||
$img = Img::make($filePath);
|
||||
|
||||
// Output a specific width/height
|
||||
if ($width > 0 && $height > 0) {
|
||||
if ($fit) {
|
||||
$img->fit($width, $height);
|
||||
} else {
|
||||
$img->resize($width, $height, function ($constraint) use ($proportional, $useUpsizeConstraint) {
|
||||
if ($proportional) {
|
||||
$constraint->aspectRatio();
|
||||
}
|
||||
if ($useUpsizeConstraint) {
|
||||
$constraint->upsize();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$response->write($img->encode());
|
||||
$response = HttpCacheProvider::withExpires($response, '+1 week');
|
||||
|
||||
$response = $response->withHeader('Content-Type', $img->mime());
|
||||
} catch (\Exception $e) {
|
||||
if ($errorThumb !== null) {
|
||||
$img = Img::make($errorThumb);
|
||||
$response->write($img->encode());
|
||||
$response = $response->withHeader('Content-Type', $img->mime());
|
||||
} else {
|
||||
$this->logger->error('Cannot parse image: ' . $e->getMessage());
|
||||
throw new InvalidArgumentException(__('Cannot parse image.'), 'storedAs');
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
1057
lib/Widget/Render/WidgetHtmlRenderer.php
Normal file
1057
lib/Widget/Render/WidgetHtmlRenderer.php
Normal file
File diff suppressed because it is too large
Load Diff
281
lib/Widget/RssProvider.php
Normal file
281
lib/Widget/RssProvider.php
Normal file
@@ -0,0 +1,281 @@
|
||||
<?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\Widget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use PicoFeed\Config\Config;
|
||||
use PicoFeed\Logging\Logger;
|
||||
use PicoFeed\Parser\Item;
|
||||
use PicoFeed\PicoFeedException;
|
||||
use PicoFeed\Reader\Reader;
|
||||
use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig;
|
||||
use Xibo\Helper\Environment;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Widget\DataType\Article;
|
||||
use Xibo\Widget\Provider\DataProviderInterface;
|
||||
use Xibo\Widget\Provider\DurationProviderNumItemsTrait;
|
||||
use Xibo\Widget\Provider\WidgetProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderTrait;
|
||||
|
||||
/**
|
||||
* Downloads an RSS feed and returns Article data types
|
||||
*/
|
||||
class RssProvider implements WidgetProviderInterface
|
||||
{
|
||||
use WidgetProviderTrait;
|
||||
use DurationProviderNumItemsTrait;
|
||||
|
||||
public function fetchData(DataProviderInterface $dataProvider): WidgetProviderInterface
|
||||
{
|
||||
$uri = $dataProvider->getProperty('uri');
|
||||
if (empty($uri)) {
|
||||
throw new InvalidArgumentException(__('Please enter the URI to a valid RSS feed.'), 'uri');
|
||||
}
|
||||
|
||||
$picoFeedLoggingEnabled = Environment::isDevMode();
|
||||
|
||||
// Image expiry
|
||||
$expiresImage = Carbon::now()
|
||||
->addMinutes($dataProvider->getProperty('updateIntervalImages', 1440))
|
||||
->format('U');
|
||||
|
||||
try {
|
||||
// Get the feed
|
||||
$response = $this->getFeed($dataProvider, $uri);
|
||||
|
||||
// Pull out the content type
|
||||
$contentType = $response['contentType'];
|
||||
|
||||
$this->getLog()->debug('Feed returned content-type ' . $contentType);
|
||||
|
||||
// https://github.com/xibosignage/xibo/issues/1401
|
||||
if (stripos($contentType, 'rss') === false
|
||||
&& stripos($contentType, 'xml') === false
|
||||
&& stripos($contentType, 'text') === false
|
||||
&& stripos($contentType, 'html') === false
|
||||
) {
|
||||
// The content type isn't compatible
|
||||
$this->getLog()->error('Incompatible content type: ' . $contentType);
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Get the body, etc
|
||||
$result = explode('charset=', $contentType);
|
||||
$document['encoding'] = $result[1] ?? '';
|
||||
$document['xml'] = $response['body'];
|
||||
|
||||
$this->getLog()->debug('Feed downloaded.');
|
||||
|
||||
// Load the feed XML document into a feed parser
|
||||
// Enable logging if we need to
|
||||
if ($picoFeedLoggingEnabled) {
|
||||
$this->getLog()->debug('Setting Picofeed Logger to Enabled.');
|
||||
Logger::enable();
|
||||
}
|
||||
|
||||
// Client config
|
||||
$clientConfig = new Config();
|
||||
|
||||
// Get the feed parser
|
||||
$reader = new Reader($clientConfig);
|
||||
$parser = $reader->getParser($uri, $document['xml'], $document['encoding']);
|
||||
|
||||
// Get a feed object
|
||||
$feed = $parser->execute();
|
||||
|
||||
// Get all items
|
||||
$feedItems = $feed->getItems();
|
||||
|
||||
// Disable date sorting?
|
||||
if ($dataProvider->getProperty('disableDateSort') == 0
|
||||
&& $dataProvider->getProperty('randomiseItems', 0) == 0
|
||||
) {
|
||||
// Sort the items array by date
|
||||
usort($feedItems, function ($a, $b) {
|
||||
/* @var Item $a */
|
||||
/* @var Item $b */
|
||||
return $b->getDate()->getTimestamp() - $a->getDate()->getTimestamp();
|
||||
});
|
||||
}
|
||||
|
||||
$sanitizer = null;
|
||||
if ($dataProvider->getProperty('stripTags') != '') {
|
||||
$sanitizer = (new HtmlSanitizerConfig())->allowSafeElements();
|
||||
|
||||
// Add the tags to strip
|
||||
foreach (explode(',', $dataProvider->getProperty('stripTags')) as $forbidden) {
|
||||
$this->getLog()->debug('fetchData: blocking element ' . $forbidden);
|
||||
$sanitizer = $sanitizer->blockElement($forbidden);
|
||||
}
|
||||
}
|
||||
|
||||
// Where should we get images?
|
||||
$imageSource = $dataProvider->getProperty('imageSource', 'enclosure');
|
||||
$imageTag = match ($imageSource) {
|
||||
'mediaContent' => 'media:content',
|
||||
'image' => 'image',
|
||||
'custom' => $dataProvider->getProperty('imageSourceTag', 'image'),
|
||||
default => 'enclosure'
|
||||
};
|
||||
$imageSourceAttribute = null;
|
||||
if ($imageSource === 'mediaContent') {
|
||||
$imageSourceAttribute = 'url';
|
||||
} else if ($imageSource === 'custom') {
|
||||
$imageSourceAttribute = $dataProvider->getProperty('imageSourceAttribute', null);
|
||||
}
|
||||
|
||||
// Parse each item into an article
|
||||
foreach ($feedItems as $item) {
|
||||
/* @var Item $item */
|
||||
$article = new Article();
|
||||
$article->title = $item->getTitle();
|
||||
$article->author = $item->getAuthor();
|
||||
$article->link = $item->getUrl();
|
||||
$article->date = Carbon::instance($item->getDate());
|
||||
$article->publishedDate = Carbon::instance($item->getPublishedDate());
|
||||
|
||||
// Body safe HTML
|
||||
$article->content = $dataProvider->getSanitizer(['content' => $item->getContent()])
|
||||
->getHtml('content', [
|
||||
'htmlSanitizerConfig' => $sanitizer
|
||||
]);
|
||||
|
||||
// RSS doesn't support a summary/excerpt tag.
|
||||
$descriptionTag = $item->getTag('description');
|
||||
$article->summary = trim($descriptionTag ? strip_tags($descriptionTag[0]) : $article->content);
|
||||
|
||||
// Do we have an image included?
|
||||
$link = null;
|
||||
if ($imageTag === 'enclosure') {
|
||||
if (stripos($item->getEnclosureType(), 'image') > -1) {
|
||||
$link = $item->getEnclosureUrl();
|
||||
}
|
||||
} else {
|
||||
$link = $item->getTag($imageTag, $imageSourceAttribute)[0] ?? null;
|
||||
}
|
||||
|
||||
if (!(empty($link))) {
|
||||
$article->image = $dataProvider->addImage('ticker_' . md5($link), $link, $expiresImage);
|
||||
} else {
|
||||
$this->getLog()->debug('fetchData: no image found for image tag using ' . $imageTag);
|
||||
}
|
||||
|
||||
if ($dataProvider->getProperty('decodeHtml') == 1) {
|
||||
$article->content = htmlspecialchars_decode($article->content);
|
||||
}
|
||||
|
||||
// Add the article.
|
||||
$dataProvider->addItem($article);
|
||||
}
|
||||
|
||||
$dataProvider->setCacheTtl($dataProvider->getProperty('updateInterval', 60) * 60);
|
||||
$dataProvider->setIsHandled();
|
||||
} catch (GuzzleException $requestException) {
|
||||
// Log and return empty?
|
||||
$this->getLog()->error('Unable to get feed: ' . $uri
|
||||
. ', e: ' . $requestException->getMessage());
|
||||
$dataProvider->addError(__('Unable to download feed'));
|
||||
} catch (PicoFeedException $picoFeedException) {
|
||||
// Output any PicoFeed logs
|
||||
if ($picoFeedLoggingEnabled) {
|
||||
$this->getLog()->debug('Outputting Picofeed Logs.');
|
||||
foreach (Logger::getMessages() as $message) {
|
||||
$this->getLog()->debug($message);
|
||||
}
|
||||
}
|
||||
|
||||
// Log and return empty?
|
||||
$this->getLog()->error('Unable to parse feed: ' . $picoFeedException->getMessage());
|
||||
$this->getLog()->debug($picoFeedException->getTraceAsString());
|
||||
$dataProvider->addError(__('Unable to parse feed'));
|
||||
}
|
||||
|
||||
// Output any PicoFeed logs
|
||||
if ($picoFeedLoggingEnabled) {
|
||||
foreach (Logger::getMessages() as $message) {
|
||||
$this->getLog()->debug($message);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDataCacheKey(DataProviderInterface $dataProvider): ?string
|
||||
{
|
||||
// No special cache key requirements.
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDataModifiedDt(DataProviderInterface $dataProvider): ?Carbon
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataProviderInterface $dataProvider
|
||||
* @param string $uri
|
||||
* @return array body, contentType
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
private function getFeed(DataProviderInterface $dataProvider, string $uri): array
|
||||
{
|
||||
// See if we have this feed cached already.
|
||||
$cache = $dataProvider->getPool()->getItem('/widget/' . $dataProvider->getDataType() . '/' . md5($uri));
|
||||
$body = $cache->get();
|
||||
|
||||
if ($cache->isMiss() || $body === null || !is_array($body)) {
|
||||
// Make a new request.
|
||||
$this->getLog()->debug('getFeed: cache miss');
|
||||
$body = [];
|
||||
|
||||
$httpOptions = [
|
||||
'headers' => [
|
||||
'Accept' => 'application/rss+xml, application/rdf+xml;q=0.8, application/atom+xml;q=0.6,'
|
||||
. 'application/xml;q=0.4, text/xml;q=0.4, text/html;q=0.2, text/*;q=0.1'
|
||||
],
|
||||
'timeout' => 20, // wait no more than 20 seconds
|
||||
];
|
||||
|
||||
if (!empty($dataProvider->getProperty('userAgent'))) {
|
||||
$httpOptions['headers']['User-Agent'] = trim($dataProvider->getProperty('userAgent'));
|
||||
}
|
||||
|
||||
$response = $dataProvider
|
||||
->getGuzzleClient($httpOptions)
|
||||
->get($uri);
|
||||
|
||||
$body['body'] = $response->getBody()->getContents();
|
||||
$body['contentType'] = $response->getHeaderLine('Content-Type');
|
||||
|
||||
// Save the resonse to cache
|
||||
$cache->set($body);
|
||||
$cache->expiresAfter($dataProvider->getSetting('cachePeriod', 1440) * 60);
|
||||
$dataProvider->getPool()->saveDeferred($cache);
|
||||
} else {
|
||||
$this->getLog()->debug('getFeed: cache hit');
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
53
lib/Widget/SubPlaylistItem.php
Normal file
53
lib/Widget/SubPlaylistItem.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?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\Widget;
|
||||
|
||||
class SubPlaylistItem implements \JsonSerializable
|
||||
{
|
||||
/** @var int */
|
||||
public $rowNo;
|
||||
|
||||
/** @var int */
|
||||
public $playlistId;
|
||||
|
||||
/** @var string */
|
||||
public $spotFill;
|
||||
|
||||
/** @var int */
|
||||
public $spotLength;
|
||||
|
||||
/** @var ?int */
|
||||
public $spots;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'rowNo' => $this->rowNo,
|
||||
'playlistId' => $this->playlistId,
|
||||
'spotFill' => $this->spotFill,
|
||||
'spotLength' => $this->spotLength,
|
||||
'spots' => $this->spots,
|
||||
];
|
||||
}
|
||||
}
|
||||
64
lib/Widget/Validator/DisplayOrGeoValidator.php
Normal file
64
lib/Widget/Validator/DisplayOrGeoValidator.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?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\Widget\Validator;
|
||||
|
||||
use Respect\Validation\Validator as v;
|
||||
use Xibo\Entity\Module;
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Widget\Provider\WidgetValidatorInterface;
|
||||
use Xibo\Widget\Provider\WidgetValidatorTrait;
|
||||
|
||||
/**
|
||||
* Validate that we either use display location or a lat/lng have been set
|
||||
*/
|
||||
class DisplayOrGeoValidator implements WidgetValidatorInterface
|
||||
{
|
||||
use WidgetValidatorTrait;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function validate(Module $module, Widget $widget, string $stage): void
|
||||
{
|
||||
$useDisplayLocation = $widget->getOptionValue('useDisplayLocation', null);
|
||||
if ($useDisplayLocation === null) {
|
||||
foreach ($module->properties as $property) {
|
||||
if ($property->id === 'useDisplayLocation') {
|
||||
$useDisplayLocation = $property->default;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($useDisplayLocation === 0) {
|
||||
// Validate lat/long
|
||||
// only if they have been provided (our default is the CMS lat/long).
|
||||
$lat = $widget->getOptionValue('latitude', null);
|
||||
if (!empty($lat) && !v::latitude()->validate($lat)) {
|
||||
throw new InvalidArgumentException(__('The latitude entered is not valid.'), 'latitude');
|
||||
}
|
||||
|
||||
$lng = $widget->getOptionValue('longitude', null);
|
||||
if (!empty($lng) && !v::longitude()->validate($lng)) {
|
||||
throw new InvalidArgumentException(__('The longitude entered is not valid.'), 'longitude');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
64
lib/Widget/Validator/RemoteUrlsZeroDurationValidator.php
Normal file
64
lib/Widget/Validator/RemoteUrlsZeroDurationValidator.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?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\Widget\Validator;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Xibo\Entity\Module;
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Widget\Provider\WidgetValidatorInterface;
|
||||
use Xibo\Widget\Provider\WidgetValidatorTrait;
|
||||
|
||||
/**
|
||||
* Validate that we have a duration greater than 0
|
||||
*/
|
||||
class RemoteUrlsZeroDurationValidator implements WidgetValidatorInterface
|
||||
{
|
||||
use WidgetValidatorTrait;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function validate(Module $module, Widget $widget, string $stage): void
|
||||
{
|
||||
$url = urldecode($widget->getOptionValue('uri', ''));
|
||||
if ($widget->useDuration === 1
|
||||
&& $widget->duration <= 0
|
||||
&& !Str::startsWith($url, 'file://')
|
||||
&& Str::contains($url, '://')
|
||||
) {
|
||||
// This is not a locally stored file, and so we should have a duration
|
||||
throw new InvalidArgumentException(
|
||||
__('The duration needs to be greater than 0 for remote URLs'),
|
||||
'duration'
|
||||
);
|
||||
} else if ($widget->useDuration === 1 && $widget->duration <= 0) {
|
||||
// Locally stored file, still needs a positive duration.
|
||||
throw new InvalidArgumentException(
|
||||
__('The duration needs to be above 0 for a locally stored file '),
|
||||
'duration'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
lib/Widget/Validator/ShellCommandValidator.php
Normal file
52
lib/Widget/Validator/ShellCommandValidator.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?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\Widget\Validator;
|
||||
|
||||
use Xibo\Entity\Module;
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Widget\Provider\WidgetValidatorInterface;
|
||||
use Xibo\Widget\Provider\WidgetValidatorTrait;
|
||||
|
||||
/**
|
||||
* Ensure a command has been entered somewhere in the widget
|
||||
*/
|
||||
class ShellCommandValidator implements WidgetValidatorInterface
|
||||
{
|
||||
use WidgetValidatorTrait;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function validate(Module $module, Widget $widget, string $stage): void
|
||||
{
|
||||
if ($widget->getOptionValue('globalCommand', '') == ''
|
||||
&& $widget->getOptionValue('androidCommand', '') == ''
|
||||
&& $widget->getOptionValue('windowsCommand', '') == ''
|
||||
&& $widget->getOptionValue('linuxCommand', '') == ''
|
||||
&& $widget->getOptionValue('commandCode', '') == ''
|
||||
&& $widget->getOptionValue('webosCommand', '') == ''
|
||||
&& $widget->getOptionValue('tizenCommand', '') == ''
|
||||
) {
|
||||
throw new InvalidArgumentException(__('You must enter a command'), 'command');
|
||||
}
|
||||
}
|
||||
}
|
||||
52
lib/Widget/Validator/ZeroDurationValidator.php
Normal file
52
lib/Widget/Validator/ZeroDurationValidator.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?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\Widget\Validator;
|
||||
|
||||
use Xibo\Entity\Module;
|
||||
use Xibo\Entity\Widget;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Widget\Provider\WidgetValidatorInterface;
|
||||
use Xibo\Widget\Provider\WidgetValidatorTrait;
|
||||
|
||||
/**
|
||||
* Validate that we have a duration greater than 0
|
||||
*/
|
||||
class ZeroDurationValidator implements WidgetValidatorInterface
|
||||
{
|
||||
use WidgetValidatorTrait;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function validate(Module $module, Widget $widget, string $stage): void
|
||||
{
|
||||
// Videos can have 0 durations (but not if useDuration is selected)
|
||||
if ($widget->useDuration === 1 && $widget->duration <= 0) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(__('Duration needs to be above 0 for %s'), $module->name),
|
||||
'duration'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
68
lib/Widget/VideoProvider.php
Normal file
68
lib/Widget/VideoProvider.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?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\Widget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
use Xibo\Widget\Provider\DataProviderInterface;
|
||||
use Xibo\Widget\Provider\DurationProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderInterface;
|
||||
use Xibo\Widget\Provider\WidgetProviderTrait;
|
||||
|
||||
/**
|
||||
* Handles setting the correct video duration.
|
||||
*/
|
||||
class VideoProvider implements WidgetProviderInterface
|
||||
{
|
||||
use WidgetProviderTrait;
|
||||
|
||||
public function fetchData(DataProviderInterface $dataProvider): WidgetProviderInterface
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fetchDuration(DurationProviderInterface $durationProvider): WidgetProviderInterface
|
||||
{
|
||||
// If we have not been provided a specific duration, we should use the duration stored in the library
|
||||
try {
|
||||
if ($durationProvider->getWidget()->useDuration === 0) {
|
||||
$durationProvider->setDuration($durationProvider->getWidget()->getDurationForMedia());
|
||||
}
|
||||
} catch (NotFoundException) {
|
||||
$this->getLog()->error('fetchDuration: video/audio without primaryMediaId. widgetId: '
|
||||
. $durationProvider->getWidget()->getId());
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDataCacheKey(DataProviderInterface $dataProvider): ?string
|
||||
{
|
||||
// No special cache key requirements.
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDataModifiedDt(DataProviderInterface $dataProvider): ?Carbon
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user