init commit
This commit is contained in:
174
lib/Service/BaseDependenciesService.php
Normal file
174
lib/Service/BaseDependenciesService.php
Normal file
@@ -0,0 +1,174 @@
|
||||
<?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\Service;
|
||||
|
||||
use Psr\Log\NullLogger;
|
||||
use Slim\Views\Twig;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Xibo\Entity\User;
|
||||
use Xibo\Helper\ApplicationState;
|
||||
use Xibo\Helper\NullSanitizer;
|
||||
use Xibo\Helper\NullView;
|
||||
use Xibo\Helper\SanitizerService;
|
||||
use Xibo\Storage\PdoStorageService;
|
||||
|
||||
class BaseDependenciesService
|
||||
{
|
||||
/**
|
||||
* @var LogServiceInterface
|
||||
*/
|
||||
private $log;
|
||||
|
||||
/**
|
||||
* @var SanitizerService
|
||||
*/
|
||||
private $sanitizerService;
|
||||
|
||||
/**
|
||||
* @var ApplicationState
|
||||
*/
|
||||
private $state;
|
||||
|
||||
/**
|
||||
* @var ConfigServiceInterface
|
||||
*/
|
||||
private $configService;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* @var Twig
|
||||
*/
|
||||
private $view;
|
||||
|
||||
/**
|
||||
* @var PdoStorageService
|
||||
*/
|
||||
private $storageService;
|
||||
|
||||
/** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface */
|
||||
private $dispatcher;
|
||||
|
||||
public function setLogger(LogServiceInterface $logService)
|
||||
{
|
||||
$this->log = $logService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LogServiceInterface
|
||||
*/
|
||||
public function getLogger()
|
||||
{
|
||||
if ($this->log === null) {
|
||||
$this->log = new NullLogService(new NullLogger());
|
||||
}
|
||||
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
public function setSanitizer(SanitizerService $sanitizerService)
|
||||
{
|
||||
$this->sanitizerService = $sanitizerService;
|
||||
}
|
||||
|
||||
public function getSanitizer(): SanitizerService
|
||||
{
|
||||
if ($this->sanitizerService === null) {
|
||||
$this->sanitizerService = new NullSanitizer();
|
||||
}
|
||||
|
||||
return $this->sanitizerService;
|
||||
}
|
||||
|
||||
public function setState(ApplicationState $applicationState)
|
||||
{
|
||||
$this->state = $applicationState;
|
||||
}
|
||||
|
||||
public function getState(): ApplicationState
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
public function setUser(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function getUser(): User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setConfig(ConfigServiceInterface $configService)
|
||||
{
|
||||
$this->configService = $configService;
|
||||
}
|
||||
|
||||
public function getConfig() : ConfigServiceInterface
|
||||
{
|
||||
return $this->configService;
|
||||
}
|
||||
|
||||
public function setView(Twig $view)
|
||||
{
|
||||
$this->view = $view;
|
||||
}
|
||||
|
||||
public function getView() : Twig
|
||||
{
|
||||
if ($this->view === null) {
|
||||
$this->view = new NullView();
|
||||
}
|
||||
return $this->view;
|
||||
}
|
||||
|
||||
public function setStore(PdoStorageService $storageService)
|
||||
{
|
||||
$this->storageService = $storageService;
|
||||
}
|
||||
|
||||
public function getStore()
|
||||
{
|
||||
return $this->storageService;
|
||||
}
|
||||
|
||||
public function setDispatcher(EventDispatcherInterface $dispatcher): BaseDependenciesService
|
||||
{
|
||||
$this->dispatcher = $dispatcher;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDispatcher(): EventDispatcherInterface
|
||||
{
|
||||
if ($this->dispatcher === null) {
|
||||
$this->getLogger()->error('getDispatcher: [base] No dispatcher found, returning an empty one');
|
||||
$this->dispatcher = new EventDispatcher();
|
||||
}
|
||||
return $this->dispatcher;
|
||||
}
|
||||
}
|
||||
817
lib/Service/ConfigService.php
Normal file
817
lib/Service/ConfigService.php
Normal file
@@ -0,0 +1,817 @@
|
||||
<?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\Service;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Xibo\Helper\Environment;
|
||||
use Xibo\Helper\NatoAlphabet;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Support\Exception\ConfigurationException;
|
||||
|
||||
/**
|
||||
* Class ConfigService
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
class ConfigService implements ConfigServiceInterface
|
||||
{
|
||||
/**
|
||||
* @var StorageServiceInterface
|
||||
*/
|
||||
public $store;
|
||||
|
||||
/**
|
||||
* @var PoolInterface
|
||||
*/
|
||||
public $pool;
|
||||
|
||||
/** @var string Setting Cache Key */
|
||||
private $settingCacheKey = 'settings';
|
||||
|
||||
/** @var bool Has the settings cache been dropped this request? */
|
||||
private $settingsCacheDropped = false;
|
||||
|
||||
/** @var array */
|
||||
private $settings = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $rootUri;
|
||||
|
||||
public $envTested = false;
|
||||
public $envFault = false;
|
||||
public $envWarning = false;
|
||||
|
||||
/**
|
||||
* Database Config
|
||||
* @var array
|
||||
*/
|
||||
public static $dbConfig = [];
|
||||
|
||||
//
|
||||
// Extra Settings
|
||||
//
|
||||
public $middleware = null;
|
||||
public $logHandlers = null;
|
||||
public $logProcessors = null;
|
||||
public $authentication = null;
|
||||
public $samlSettings = null;
|
||||
public $casSettings = null;
|
||||
public $cacheDrivers = null;
|
||||
public $timeSeriesStore = null;
|
||||
public $cacheNamespace = 'Xibo';
|
||||
private $apiKeyPaths = null;
|
||||
private $connectorSettings = null;
|
||||
|
||||
/**
|
||||
* Theme Specific Config
|
||||
* @var array
|
||||
*/
|
||||
public $themeConfig = [];
|
||||
|
||||
/** @var bool Has a theme been loaded? */
|
||||
private $themeLoaded = false;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setDependencies($store, $rootUri)
|
||||
{
|
||||
if ($store == null)
|
||||
throw new \RuntimeException('ConfigService setDependencies called with null store');
|
||||
|
||||
if ($rootUri == null)
|
||||
throw new \RuntimeException('ConfigService setDependencies called with null rootUri');
|
||||
|
||||
$this->store = $store;
|
||||
$this->rootUri = $rootUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setPool($pool)
|
||||
{
|
||||
$this->pool = $pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Cache Pool
|
||||
* @return \Stash\Interfaces\PoolInterface
|
||||
*/
|
||||
private function getPool()
|
||||
{
|
||||
return $this->pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Store
|
||||
* @return StorageServiceInterface
|
||||
*/
|
||||
protected function getStore()
|
||||
{
|
||||
if ($this->store == null)
|
||||
throw new \RuntimeException('Config Service called before setDependencies');
|
||||
|
||||
return $this->store;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getDatabaseConfig()
|
||||
{
|
||||
return self::$dbConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get App Root URI
|
||||
* @return string
|
||||
*/
|
||||
public function rootUri()
|
||||
{
|
||||
if ($this->rootUri == null)
|
||||
throw new \RuntimeException('Config Service called before setDependencies');
|
||||
|
||||
return $this->rootUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getCacheDrivers()
|
||||
{
|
||||
return $this->cacheDrivers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getTimeSeriesStore()
|
||||
{
|
||||
return $this->timeSeriesStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getCacheNamespace()
|
||||
{
|
||||
return $this->cacheNamespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getConnectorSettings(string $connector): array
|
||||
{
|
||||
if ($this->connectorSettings !== null && array_key_exists($connector, $this->connectorSettings)) {
|
||||
return $this->connectorSettings[$connector];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the settings from file.
|
||||
* DO NOT CALL ANY STORE() METHODS IN HERE
|
||||
* @param \Psr\Container\ContainerInterface $container DI container which may be used in settings.php
|
||||
* @param string $settings Settings Path
|
||||
* @return ConfigServiceInterface
|
||||
*/
|
||||
public static function Load($container, string $settings)
|
||||
{
|
||||
$config = new ConfigService();
|
||||
|
||||
// Include the provided settings file.
|
||||
require ($settings);
|
||||
|
||||
// Create a DB config
|
||||
self::$dbConfig = [
|
||||
'host' => $dbhost,
|
||||
'user' => $dbuser,
|
||||
'password' => $dbpass,
|
||||
'name' => $dbname,
|
||||
'ssl' => $dbssl ?? null,
|
||||
'sslVerify' => $dbsslverify ?? null
|
||||
];
|
||||
|
||||
// Pull in other settings
|
||||
|
||||
// Log handlers
|
||||
if (isset($logHandlers))
|
||||
$config->logHandlers = $logHandlers;
|
||||
|
||||
// Log Processors
|
||||
if (isset($logProcessors))
|
||||
$config->logProcessors = $logProcessors;
|
||||
|
||||
// Middleware
|
||||
if (isset($middleware))
|
||||
$config->middleware = $middleware;
|
||||
|
||||
// Authentication
|
||||
if (isset($authentication))
|
||||
$config->authentication = $authentication;
|
||||
|
||||
// Saml settings
|
||||
if (isset($samlSettings))
|
||||
$config->samlSettings = $samlSettings;
|
||||
|
||||
// CAS settings
|
||||
if (isset($casSettings))
|
||||
$config->casSettings = $casSettings;
|
||||
|
||||
// Cache drivers
|
||||
if (isset($cacheDrivers))
|
||||
$config->cacheDrivers = $cacheDrivers;
|
||||
|
||||
// Time series store settings
|
||||
if (isset($timeSeriesStore))
|
||||
$config->timeSeriesStore = $timeSeriesStore;
|
||||
|
||||
if (isset($cacheNamespace))
|
||||
$config->cacheNamespace = $cacheNamespace;
|
||||
|
||||
if (isset($apiKeyPaths))
|
||||
$config->apiKeyPaths = $apiKeyPaths;
|
||||
|
||||
// Connector settings
|
||||
if (isset($connectorSettings)) {
|
||||
$config->connectorSettings = $connectorSettings;
|
||||
}
|
||||
|
||||
// Set this as the global config
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the theme
|
||||
* @param string|null $themeName
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public function loadTheme($themeName = null): void
|
||||
{
|
||||
global $config;
|
||||
|
||||
// What is the currently selected theme?
|
||||
$globalTheme = ($themeName == null)
|
||||
? basename($this->getSetting('GLOBAL_THEME_NAME', 'default'))
|
||||
: $themeName;
|
||||
|
||||
// Is this theme valid?
|
||||
$systemTheme = (is_dir(PROJECT_ROOT . '/web/theme/' . $globalTheme)
|
||||
&& file_exists(PROJECT_ROOT . '/web/theme/' . $globalTheme . '/config.php'));
|
||||
$customTheme = (is_dir(PROJECT_ROOT . '/web/theme/custom/' . $globalTheme)
|
||||
&& file_exists(PROJECT_ROOT . '/web/theme/custom/' . $globalTheme . '/config.php'));
|
||||
|
||||
if ($systemTheme) {
|
||||
require(PROJECT_ROOT . '/web/theme/' . $globalTheme . '/config.php');
|
||||
$themeFolder = 'theme/' . $globalTheme . '/';
|
||||
} elseif ($customTheme) {
|
||||
require(PROJECT_ROOT . '/web/theme/custom/' . $globalTheme . '/config.php');
|
||||
$themeFolder = 'theme/custom/' . $globalTheme . '/';
|
||||
} else {
|
||||
throw new ConfigurationException(__('The theme "%s" does not exist', $globalTheme));
|
||||
}
|
||||
|
||||
$this->themeLoaded = true;
|
||||
$this->themeConfig = $config;
|
||||
$this->themeConfig['themeCode'] = $globalTheme;
|
||||
$this->themeConfig['themeFolder'] = $themeFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Theme Specific Settings
|
||||
* @param null $settingName
|
||||
* @param null $default
|
||||
* @return mixed|array|string
|
||||
*/
|
||||
public function getThemeConfig($settingName = null, $default = null)
|
||||
{
|
||||
if ($settingName == null)
|
||||
return $this->themeConfig;
|
||||
|
||||
if (isset($this->themeConfig[$settingName]))
|
||||
return $this->themeConfig[$settingName];
|
||||
else
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get theme URI
|
||||
* @param string $uri
|
||||
* @param bool $local
|
||||
* @return string
|
||||
*/
|
||||
public function uri($uri, $local = false)
|
||||
{
|
||||
$rootUri = ($local) ? PROJECT_ROOT . '/web/' : $this->rootUri();
|
||||
|
||||
if (!$this->themeLoaded)
|
||||
return $rootUri . 'theme/default/' . $uri;
|
||||
|
||||
// Serve the appropriate theme file
|
||||
if (is_dir(PROJECT_ROOT . '/web/' . $this->themeConfig['themeFolder'] . $uri)) {
|
||||
return $rootUri . $this->themeConfig['themeFolder'] . $uri;
|
||||
}
|
||||
else if (file_exists(PROJECT_ROOT . '/web/' . $this->themeConfig['themeFolder'] . $uri)) {
|
||||
return $rootUri . $this->themeConfig['themeFolder'] . $uri;
|
||||
}
|
||||
else {
|
||||
return $rootUri . 'theme/default/' . $uri;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a theme file exists
|
||||
* @param string $uri
|
||||
* @return string
|
||||
*/
|
||||
public function fileExists($uri)
|
||||
{
|
||||
// Serve the appropriate file
|
||||
return file_exists(PROJECT_ROOT . '/web/' . $uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a theme file exists
|
||||
* @param string $uri
|
||||
* @return string
|
||||
*/
|
||||
public function themeFileExists($uri)
|
||||
{
|
||||
if (!$this->themeLoaded)
|
||||
return file_exists(PROJECT_ROOT . '/web/theme/default/' . $uri);
|
||||
|
||||
// Serve the appropriate theme file
|
||||
if (file_exists(PROJECT_ROOT . '/web/' . $this->themeConfig['themeFolder'] . $uri)) {
|
||||
return true;
|
||||
} else {
|
||||
return file_exists(PROJECT_ROOT . '/web/theme/default/' . $uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
private function loadSettings()
|
||||
{
|
||||
$item = null;
|
||||
|
||||
if ($this->settings === null) {
|
||||
// We need to load in our settings
|
||||
if ($this->getPool() !== null) {
|
||||
// Try the cache
|
||||
$item = $this->getPool()->getItem($this->settingCacheKey);
|
||||
|
||||
$data = $item->get();
|
||||
|
||||
if ($item->isHit()) {
|
||||
$this->settings = $data;
|
||||
}
|
||||
}
|
||||
|
||||
// Are we still null?
|
||||
if ($this->settings === null) {
|
||||
// Load from the database
|
||||
$this->settings = $this->getStore()->select('SELECT `setting`, `value`, `userSee`, `userChange` FROM `setting`', []);
|
||||
}
|
||||
}
|
||||
|
||||
// We should have our settings by now, so cache them if we can/need to
|
||||
if ($item !== null && $item->isMiss()) {
|
||||
// See about caching these settings - dependent on whether we're logging or not
|
||||
$cacheExpiry = 60 * 5;
|
||||
foreach ($this->settings as $setting) {
|
||||
if ($setting['setting'] == 'ELEVATE_LOG_UNTIL' && intval($setting['value']) > Carbon::now()->format('U')) {
|
||||
$cacheExpiry = intval($setting['value']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$item->set($this->settings);
|
||||
$item->expiresAfter($cacheExpiry);
|
||||
$this->getPool()->saveDeferred($item);
|
||||
}
|
||||
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function getSettings()
|
||||
{
|
||||
$settings = $this->loadSettings();
|
||||
$parsed = [];
|
||||
|
||||
// Go through each setting and create a key/value pair
|
||||
foreach ($settings as $setting) {
|
||||
$parsed[$setting['setting']] = $setting['value'];
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function getSetting($setting, $default = NULL, $full = false)
|
||||
{
|
||||
$settings = $this->loadSettings();
|
||||
|
||||
if ($full) {
|
||||
foreach ($settings as $item) {
|
||||
if ($item['setting'] == $setting) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'setting' => $setting,
|
||||
'value' => $default,
|
||||
'userSee' => 1,
|
||||
'userChange' => 1
|
||||
];
|
||||
} else {
|
||||
$settings = $this->getSettings();
|
||||
return (isset($settings[$setting])) ? $settings[$setting] : $default;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function changeSetting($setting, $value, $userChange = 0)
|
||||
{
|
||||
$settings = $this->getSettings();
|
||||
|
||||
// Update in memory cache
|
||||
foreach ($this->settings as $item) {
|
||||
if ($item['setting'] == $setting) {
|
||||
$item['value'] = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($settings[$setting])) {
|
||||
// We've already got this setting recorded, update it for
|
||||
// Update in database
|
||||
$this->getStore()->update('UPDATE `setting` SET `value` = :value WHERE `setting` = :setting', [
|
||||
'setting' => $setting,
|
||||
'value' => ($value === null) ? '' : $value
|
||||
]);
|
||||
} else {
|
||||
// A new setting we've not seen before.
|
||||
// record it in the settings table.
|
||||
$this->getStore()->insert('
|
||||
INSERT INTO `setting` (`value`, setting, `userChange`) VALUES (:value, :setting, :userChange);', [
|
||||
'setting' => $setting,
|
||||
'value' => ($value === null) ? '' : $value,
|
||||
'userChange' => $userChange
|
||||
]);
|
||||
}
|
||||
|
||||
// Drop the cache if we've not already done so this time around
|
||||
if (!$this->settingsCacheDropped && $this->getPool() !== null) {
|
||||
$this->getPool()->deleteItem($this->settingCacheKey);
|
||||
$this->settingsCacheDropped = true;
|
||||
$this->settings = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the provided setting visible
|
||||
* @param string $setting
|
||||
* @return bool
|
||||
*/
|
||||
public function isSettingVisible($setting)
|
||||
{
|
||||
return $this->getSetting($setting, null, true)['userSee'] == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the provided setting editable
|
||||
* @param string $setting
|
||||
* @return bool
|
||||
*/
|
||||
public function isSettingEditable($setting)
|
||||
{
|
||||
$item = $this->getSetting($setting, null, true);
|
||||
return $item['userSee'] == 1 && $item['userChange'] == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the host be considered a proxy exception
|
||||
* @param $host
|
||||
* @return bool
|
||||
*/
|
||||
public function isProxyException($host)
|
||||
{
|
||||
$proxyExceptions = $this->getSetting('PROXY_EXCEPTIONS');
|
||||
|
||||
// If empty, cannot be an exception
|
||||
if (empty($proxyExceptions))
|
||||
return false;
|
||||
|
||||
// Simple test
|
||||
if (stripos($host, $proxyExceptions) !== false)
|
||||
return true;
|
||||
|
||||
// Host test
|
||||
$parsedHost = parse_url($host, PHP_URL_HOST);
|
||||
|
||||
// Kick out extremely malformed hosts
|
||||
if ($parsedHost === false)
|
||||
return false;
|
||||
|
||||
// Go through each exception and test against the host
|
||||
foreach (explode(',', $proxyExceptions) as $proxyException) {
|
||||
if (stripos($parsedHost, $proxyException) !== false)
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we've got here without returning, then we aren't an exception
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Proxy Configuration
|
||||
* @param array $httpOptions
|
||||
* @return array
|
||||
*/
|
||||
public function getGuzzleProxy($httpOptions = [])
|
||||
{
|
||||
// Proxy support
|
||||
if ($this->getSetting('PROXY_HOST') != '') {
|
||||
|
||||
$proxy = $this->getSetting('PROXY_HOST') . ':' . $this->getSetting('PROXY_PORT');
|
||||
|
||||
if ($this->getSetting('PROXY_AUTH') != '') {
|
||||
$scheme = explode('://', $proxy);
|
||||
|
||||
$proxy = $scheme[0] . '://' . $this->getSetting('PROXY_AUTH') . '@' . $scheme[1];
|
||||
}
|
||||
|
||||
$httpOptions['proxy'] = [
|
||||
'http' => $proxy,
|
||||
'https' => $proxy
|
||||
];
|
||||
|
||||
if ($this->getSetting('PROXY_EXCEPTIONS') != '') {
|
||||
$httpOptions['proxy']['no'] = explode(',', $this->getSetting('PROXY_EXCEPTIONS'));
|
||||
}
|
||||
}
|
||||
|
||||
// Global timeout
|
||||
// All outbound HTTP should have a timeout as they tie up a PHP process while the request completes (if
|
||||
// triggered from an incoming request)
|
||||
// https://github.com/xibosignage/xibo/issues/2631
|
||||
if (!array_key_exists('timeout', $httpOptions)) {
|
||||
$httpOptions['timeout'] = 20;
|
||||
}
|
||||
|
||||
if (!array_key_exists('connect_timeout', $httpOptions)) {
|
||||
$httpOptions['connect_timeout'] = 5;
|
||||
}
|
||||
|
||||
return $httpOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getApiKeyDetails()
|
||||
{
|
||||
if ($this->apiKeyPaths == null) {
|
||||
// We load the defaults
|
||||
$libraryLocation = $this->getSetting('LIBRARY_LOCATION');
|
||||
|
||||
// We use the defaults
|
||||
$this->apiKeyPaths = [
|
||||
'publicKeyPath' => $libraryLocation . 'certs/public.key',
|
||||
'privateKeyPath' => $libraryLocation . 'certs/private.key',
|
||||
'encryptionKey' => file_get_contents($libraryLocation . 'certs/encryption.key')
|
||||
];
|
||||
}
|
||||
|
||||
return $this->apiKeyPaths;
|
||||
}
|
||||
|
||||
private function testItem(&$results, $item, $result, $advice, $fault = true)
|
||||
{
|
||||
// 1=OK, 0=Failure, 2=Warning
|
||||
$status = ($result) ? 1 : (($fault) ? 0 : 2);
|
||||
|
||||
// Set fault flag
|
||||
if (!$result && $fault)
|
||||
$this->envFault = true;
|
||||
|
||||
// Set warning flag
|
||||
if (!$result && !$fault)
|
||||
$this->envWarning = true;
|
||||
|
||||
$results[] = [
|
||||
'item' => $item,
|
||||
'status' => $status,
|
||||
'advice' => $advice
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the Environment and Determines if it is suitable
|
||||
* @return array
|
||||
*/
|
||||
public function checkEnvironment()
|
||||
{
|
||||
$rows = array();
|
||||
|
||||
$this->testItem($rows, __('PHP Version'),
|
||||
Environment::checkPHP(),
|
||||
sprintf(__("PHP version %s or later required."), Environment::$VERSION_REQUIRED) . ' Detected ' . phpversion()
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('Cache File System Permissions'),
|
||||
Environment::checkCacheFileSystemPermissions(),
|
||||
__('Write permissions are required for cache/')
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('MySQL database (PDO MySql)'),
|
||||
Environment::checkPDO(),
|
||||
__('PDO support with MySQL drivers must be enabled in PHP.')
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('JSON Extension'),
|
||||
Environment::checkJson(),
|
||||
__('PHP JSON extension required to function.')
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('SOAP Extension'),
|
||||
Environment::checkSoap(),
|
||||
__('PHP SOAP extension required to function.')
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('GD Extension'),
|
||||
Environment::checkGd(),
|
||||
__('PHP GD extension required to function.')
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('Session'),
|
||||
Environment::checkGd(),
|
||||
__('PHP session support required to function.')
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('FileInfo'),
|
||||
Environment::checkFileInfo(),
|
||||
__('Requires PHP FileInfo support to function. If you are on Windows you need to enable the php_fileinfo.dll in your php.ini file.')
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('PCRE'),
|
||||
Environment::checkPCRE(),
|
||||
__('PHP PCRE support to function.')
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('Gettext'),
|
||||
Environment::checkPCRE(),
|
||||
__('PHP Gettext support to function.')
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('DOM Extension'),
|
||||
Environment::checkDom(),
|
||||
__('PHP DOM core functionality enabled.')
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('DOM XML Extension'),
|
||||
Environment::checkDomXml(),
|
||||
__('PHP DOM XML extension to function.')
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('Allow PHP to open external URLs'),
|
||||
(Environment::checkCurl() || Environment::checkAllowUrlFopen()),
|
||||
__('You must have the curl extension enabled or PHP configured with "allow_url_fopen = On" for the CMS to access external resources. We strongly recommend curl.'),
|
||||
false
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('DateTimeZone'),
|
||||
Environment::checkTimezoneIdentifiers(),
|
||||
__('This enables us to get a list of time zones supported by the hosting server.'),
|
||||
false
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('ZIP'),
|
||||
Environment::checkZip(),
|
||||
__('This enables import / export of layouts.')
|
||||
);
|
||||
|
||||
$advice = __('Support for uploading large files is recommended.');
|
||||
$advice .= __('We suggest setting your PHP post_max_size and upload_max_filesize to at least 128M, and also increasing your max_execution_time to at least 120 seconds.');
|
||||
|
||||
$this->testItem($rows, __('Large File Uploads'),
|
||||
Environment::checkPHPUploads(),
|
||||
$advice,
|
||||
false
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('cURL'),
|
||||
Environment::checkCurlInstalled(),
|
||||
__('cURL is used to fetch data from the Internet or Local Network')
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('OpenSSL'),
|
||||
Environment::checkOpenSsl(),
|
||||
__('OpenSSL is used to seal and verify messages sent to XMR'),
|
||||
false
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('SimpleXML'),
|
||||
Environment::checkSimpleXml(),
|
||||
__('SimpleXML is used to parse RSS feeds and other XML data sources')
|
||||
);
|
||||
|
||||
$this->testItem($rows, __('GNUPG'),
|
||||
Environment::checkGnu(),
|
||||
__('checkGnu is used to verify the integrity of Player Software versions uploaded to the CMS'),
|
||||
false
|
||||
);
|
||||
|
||||
$this->envTested = true;
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is there an environment fault
|
||||
* @return bool
|
||||
*/
|
||||
public function environmentFault()
|
||||
{
|
||||
if (!$this->envTested) {
|
||||
$this->checkEnvironment();
|
||||
}
|
||||
|
||||
return $this->envFault || !Environment::checkSettingsFileSystemPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is there an environment warning
|
||||
* @return bool
|
||||
*/
|
||||
public function environmentWarning()
|
||||
{
|
||||
if (!$this->envTested) {
|
||||
$this->checkEnvironment();
|
||||
}
|
||||
|
||||
return $this->envWarning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check binlog format
|
||||
* @return bool
|
||||
*/
|
||||
public function checkBinLogEnabled()
|
||||
{
|
||||
//TODO: move this into storage interface
|
||||
$results = $this->getStore()->select('show variables like \'log_bin\'', []);
|
||||
|
||||
if (count($results) <= 0)
|
||||
return false;
|
||||
|
||||
return ($results[0]['Value'] != 'OFF');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check binlog format
|
||||
* @return bool
|
||||
*/
|
||||
public function checkBinLogFormat()
|
||||
{
|
||||
//TODO: move this into storage interface
|
||||
$results = $this->getStore()->select('show variables like \'binlog_format\'', []);
|
||||
|
||||
if (count($results) <= 0)
|
||||
return false;
|
||||
|
||||
return ($results[0]['Value'] != 'STATEMENT');
|
||||
}
|
||||
|
||||
public function getPhoneticKey()
|
||||
{
|
||||
return NatoAlphabet::convertToNato($this->getSetting('SERVER_KEY'));
|
||||
}
|
||||
}
|
||||
185
lib/Service/ConfigServiceInterface.php
Normal file
185
lib/Service/ConfigServiceInterface.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?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\Service;
|
||||
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Support\Exception\ConfigurationException;
|
||||
|
||||
/**
|
||||
* Interface ConfigServiceInterface
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
interface ConfigServiceInterface
|
||||
{
|
||||
/**
|
||||
* Set Service Dependencies
|
||||
* @param StorageServiceInterface $store
|
||||
* @param string $rootUri
|
||||
*/
|
||||
public function setDependencies($store, $rootUri);
|
||||
|
||||
/**
|
||||
* Get Cache Pool
|
||||
* @param PoolInterface $pool
|
||||
* @return mixed
|
||||
*/
|
||||
public function setPool($pool);
|
||||
|
||||
/**
|
||||
* Get Database Config
|
||||
* @return array
|
||||
*/
|
||||
public function getDatabaseConfig();
|
||||
|
||||
/**
|
||||
* Get settings
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
public function getSettings();
|
||||
|
||||
/**
|
||||
* Gets the requested setting from the DB object given
|
||||
* @param $setting string
|
||||
* @param string[optional] $default
|
||||
* @param bool[optional] $full
|
||||
* @return string
|
||||
*/
|
||||
public function getSetting($setting, $default = NULL, $full = false);
|
||||
|
||||
/**
|
||||
* Change Setting
|
||||
* @param string $setting
|
||||
* @param mixed $value
|
||||
* @param int $userChange
|
||||
*/
|
||||
public function changeSetting($setting, $value, $userChange = 0);
|
||||
|
||||
/**
|
||||
* Is the provided setting visible
|
||||
* @param string $setting
|
||||
* @return bool
|
||||
*/
|
||||
public function isSettingVisible($setting);
|
||||
|
||||
/**
|
||||
* Is the provided setting editable
|
||||
* @param string $setting
|
||||
* @return bool
|
||||
*/
|
||||
public function isSettingEditable($setting);
|
||||
|
||||
/**
|
||||
* Should the host be considered a proxy exception
|
||||
* @param $host
|
||||
* @return bool
|
||||
*/
|
||||
public function isProxyException($host);
|
||||
|
||||
/**
|
||||
* Get Proxy Configuration
|
||||
* @param array $httpOptions
|
||||
* @return array
|
||||
*/
|
||||
public function getGuzzleProxy($httpOptions = []);
|
||||
|
||||
/**
|
||||
* Get API key details from Configuration
|
||||
* @return array
|
||||
*/
|
||||
public function getApiKeyDetails();
|
||||
|
||||
/**
|
||||
* Checks the Environment and Determines if it is suitable
|
||||
* @return string
|
||||
*/
|
||||
public function checkEnvironment();
|
||||
|
||||
/**
|
||||
* Loads the theme
|
||||
* @param string[Optional] $themeName
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public function loadTheme($themeName = null);
|
||||
|
||||
/**
|
||||
* Get Theme Specific Settings
|
||||
* @param null $settingName
|
||||
* @param null $default
|
||||
* @return null
|
||||
*/
|
||||
public function getThemeConfig($settingName = null, $default = null);
|
||||
|
||||
/**
|
||||
* Get theme URI
|
||||
* @param string $uri
|
||||
* @param bool $local
|
||||
* @return string
|
||||
*/
|
||||
public function uri($uri, $local = false);
|
||||
|
||||
/**
|
||||
* Check a theme file exists
|
||||
* @param string $uri
|
||||
* @return bool
|
||||
*/
|
||||
public function themeFileExists($uri);
|
||||
|
||||
/**
|
||||
* Check a web file exists
|
||||
* @param string $uri
|
||||
* @return bool
|
||||
*/
|
||||
public function fileExists($uri);
|
||||
|
||||
/**
|
||||
* Get App Root URI
|
||||
* @return mixed
|
||||
*/
|
||||
public function rootUri();
|
||||
|
||||
/**
|
||||
* Get cache drivers
|
||||
* @return array
|
||||
*/
|
||||
public function getCacheDrivers();
|
||||
|
||||
/**
|
||||
* Get time series store settings
|
||||
* @return array
|
||||
*/
|
||||
public function getTimeSeriesStore();
|
||||
|
||||
/**
|
||||
* Get the cache namespace
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheNamespace();
|
||||
|
||||
/**
|
||||
* Get Connector settings from the file based settings
|
||||
* this acts as an override for settings stored in the database
|
||||
* @param string $connector The connector to return settings for.
|
||||
* @return array
|
||||
*/
|
||||
public function getConnectorSettings(string $connector): array;
|
||||
}
|
||||
978
lib/Service/DisplayNotifyService.php
Normal file
978
lib/Service/DisplayNotifyService.php
Normal file
@@ -0,0 +1,978 @@
|
||||
<?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\Service;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Xibo\Entity\Display;
|
||||
use Xibo\Factory\ScheduleFactory;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Support\Exception\DeadlockException;
|
||||
use Xibo\XMR\CollectNowAction;
|
||||
use Xibo\XMR\DataUpdateAction;
|
||||
|
||||
/**
|
||||
* Class DisplayNotifyService
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
class DisplayNotifyService implements DisplayNotifyServiceInterface
|
||||
{
|
||||
/** @var ConfigServiceInterface */
|
||||
private $config;
|
||||
|
||||
/** @var LogServiceInterface */
|
||||
private $log;
|
||||
|
||||
/** @var StorageServiceInterface */
|
||||
private $store;
|
||||
|
||||
/** @var PoolInterface */
|
||||
private $pool;
|
||||
|
||||
/** @var PlayerActionServiceInterface */
|
||||
private $playerActionService;
|
||||
|
||||
/** @var ScheduleFactory */
|
||||
private $scheduleFactory;
|
||||
|
||||
/** @var bool */
|
||||
private $collectRequired = false;
|
||||
|
||||
/** @var int[] */
|
||||
private $displayIds = [];
|
||||
|
||||
/** @var int[] */
|
||||
private $displayIdsRequiringActions = [];
|
||||
|
||||
/** @var string[] */
|
||||
private $keysProcessed = [];
|
||||
|
||||
/** @inheritdoc */
|
||||
public function __construct($config, $log, $store, $pool, $playerActionService, $scheduleFactory)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->log = $log;
|
||||
$this->store = $store;
|
||||
$this->pool = $pool;
|
||||
$this->playerActionService = $playerActionService;
|
||||
$this->scheduleFactory = $scheduleFactory;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function init()
|
||||
{
|
||||
$this->collectRequired = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function collectNow()
|
||||
{
|
||||
$this->collectRequired = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function collectLater()
|
||||
{
|
||||
$this->collectRequired = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function processQueue()
|
||||
{
|
||||
if (count($this->displayIds) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->log->debug('Process queue of ' . count($this->displayIds) . ' display notifications');
|
||||
|
||||
// We want to do 3 things.
|
||||
// 1. Drop the Cache for each displayId
|
||||
// 2. Update the mediaInventoryStatus on each DisplayId to 3 (pending)
|
||||
// 3. Fire a PlayerAction if appropriate - what is appropriate?!
|
||||
|
||||
// Unique our displayIds
|
||||
$displayIds = array_values(array_unique($this->displayIds, SORT_NUMERIC));
|
||||
|
||||
// Make a list of them that we can use in the update statement
|
||||
$qmarks = str_repeat('?,', count($displayIds) - 1) . '?';
|
||||
|
||||
try {
|
||||
// This runs on the default connection which will already be committed and closed by the time we get
|
||||
// here. This doesn't run in a transaction.
|
||||
$this->store->updateWithDeadlockLoop(
|
||||
'UPDATE `display` SET mediaInventoryStatus = 3 WHERE displayId IN (' . $qmarks . ')',
|
||||
$displayIds,
|
||||
'default',
|
||||
false
|
||||
);
|
||||
} catch (DeadlockException $deadlockException) {
|
||||
$this->log->error('Failed to update media inventory status: ' . $deadlockException->getMessage());
|
||||
}
|
||||
|
||||
// Dump the cache
|
||||
foreach ($displayIds as $displayId) {
|
||||
$this->pool->deleteItem(Display::getCachePrefix() . $displayId);
|
||||
}
|
||||
|
||||
// Player actions
|
||||
$this->processPlayerActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Actions
|
||||
*/
|
||||
private function processPlayerActions()
|
||||
{
|
||||
if (count($this->displayIdsRequiringActions) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->log->debug('Process queue of ' . count($this->displayIdsRequiringActions) . ' display actions');
|
||||
|
||||
$displayIdsRequiringActions = array_values(array_unique($this->displayIdsRequiringActions, SORT_NUMERIC));
|
||||
$qmarks = str_repeat('?,', count($displayIdsRequiringActions) - 1) . '?';
|
||||
$displays = $this->store->select(
|
||||
'SELECT displayId, xmrChannel, xmrPubKey, display, client_type AS clientType, `client_code`
|
||||
FROM `display`
|
||||
WHERE displayId IN (' . $qmarks . ')',
|
||||
$displayIdsRequiringActions
|
||||
);
|
||||
|
||||
foreach ($displays as $row) {
|
||||
// TOOD: this should be improved
|
||||
$display = new Display(
|
||||
$this->store,
|
||||
$this->log,
|
||||
null,
|
||||
$this->config,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
$display->displayId = intval($row['displayId']);
|
||||
$display->xmrChannel = $row['xmrChannel'];
|
||||
$display->xmrPubKey = $row['xmrPubKey'];
|
||||
$display->display = $row['display'];
|
||||
$display->clientType = $row['clientType'];
|
||||
$display->clientCode = intval($row['client_code']);
|
||||
|
||||
try {
|
||||
$this->playerActionService->sendAction($display, new CollectNowAction());
|
||||
} catch (\Exception $e) {
|
||||
$this->log->notice(
|
||||
'DisplayId ' .
|
||||
$row['displayId'] .
|
||||
' Save would have triggered Player Action, but the action failed with message: ' . $e->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function notifyByDisplayId($displayId)
|
||||
{
|
||||
$this->log->debug('Notify by DisplayId ' . $displayId);
|
||||
|
||||
// Don't process if the displayId is already in the collection (there is little point in running the
|
||||
// extra query)
|
||||
if (in_array($displayId, $this->displayIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->displayIds[] = $displayId;
|
||||
|
||||
if ($this->collectRequired) {
|
||||
$this->displayIdsRequiringActions[] = $displayId;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function notifyByDisplayGroupId($displayGroupId)
|
||||
{
|
||||
$this->log->debug('Notify by DisplayGroupId ' . $displayGroupId);
|
||||
|
||||
if (in_array('displayGroup_' . $displayGroupId, $this->keysProcessed)) {
|
||||
$this->log->debug('Already processed ' . $displayGroupId . ' skipping this time.');
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = '
|
||||
SELECT DISTINCT `lkdisplaydg`.displayId
|
||||
FROM `lkdgdg`
|
||||
INNER JOIN `lkdisplaydg`
|
||||
ON `lkdisplaydg`.displayGroupID = `lkdgdg`.childId
|
||||
WHERE `lkdgdg`.parentId = :displayGroupId
|
||||
';
|
||||
|
||||
foreach ($this->store->select($sql, ['displayGroupId' => $displayGroupId]) as $row) {
|
||||
// Don't process if the displayId is already in the collection
|
||||
if (in_array($row['displayId'], $this->displayIds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->displayIds[] = $row['displayId'];
|
||||
|
||||
$this->log->debug(
|
||||
'DisplayGroup[' . $displayGroupId .'] change caused notify on displayId[' .
|
||||
$row['displayId'] . ']'
|
||||
);
|
||||
|
||||
if ($this->collectRequired) {
|
||||
$this->displayIdsRequiringActions[] = $row['displayId'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->keysProcessed[] = 'displayGroup_' . $displayGroupId;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function notifyByCampaignId($campaignId)
|
||||
{
|
||||
$this->log->debug('Notify by CampaignId ' . $campaignId);
|
||||
|
||||
if (in_array('campaign_' . $campaignId, $this->keysProcessed)) {
|
||||
$this->log->debug('Already processed ' . $campaignId . ' skipping this time.');
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = '
|
||||
SELECT DISTINCT display.displayId,
|
||||
schedule.eventId,
|
||||
schedule.fromDt,
|
||||
schedule.toDt,
|
||||
schedule.recurrence_type AS recurrenceType,
|
||||
schedule.recurrence_detail AS recurrenceDetail,
|
||||
schedule.recurrence_range AS recurrenceRange,
|
||||
schedule.recurrenceRepeatsOn,
|
||||
schedule.lastRecurrenceWatermark,
|
||||
schedule.dayPartId
|
||||
FROM `schedule`
|
||||
INNER JOIN `lkscheduledisplaygroup`
|
||||
ON `lkscheduledisplaygroup`.eventId = `schedule`.eventId
|
||||
INNER JOIN `lkdgdg`
|
||||
ON `lkdgdg`.parentId = `lkscheduledisplaygroup`.displayGroupId
|
||||
INNER JOIN `lkdisplaydg`
|
||||
ON lkdisplaydg.DisplayGroupID = `lkdgdg`.childId
|
||||
INNER JOIN `display`
|
||||
ON lkdisplaydg.DisplayID = display.displayID
|
||||
INNER JOIN (
|
||||
SELECT campaignId
|
||||
FROM campaign
|
||||
WHERE campaign.campaignId = :activeCampaignId
|
||||
UNION
|
||||
SELECT DISTINCT parent.campaignId
|
||||
FROM `lkcampaignlayout` child
|
||||
INNER JOIN `lkcampaignlayout` parent
|
||||
ON parent.layoutId = child.layoutId
|
||||
WHERE child.campaignId = :activeCampaignId
|
||||
|
||||
) campaigns
|
||||
ON campaigns.campaignId = `schedule`.campaignId
|
||||
WHERE (
|
||||
(`schedule`.FromDT < :toDt AND IFNULL(`schedule`.toDt, UNIX_TIMESTAMP()) > :fromDt)
|
||||
OR `schedule`.recurrence_range >= :fromDt
|
||||
OR (
|
||||
IFNULL(`schedule`.recurrence_range, 0) = 0 AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\'
|
||||
)
|
||||
)
|
||||
UNION
|
||||
SELECT DISTINCT display.DisplayID,
|
||||
0 AS eventId,
|
||||
0 AS fromDt,
|
||||
0 AS toDt,
|
||||
NULL AS recurrenceType,
|
||||
NULL AS recurrenceDetail,
|
||||
NULL AS recurrenceRange,
|
||||
NULL AS recurrenceRepeatsOn,
|
||||
NULL AS lastRecurrenceWatermark,
|
||||
NULL AS dayPartId
|
||||
FROM `display`
|
||||
INNER JOIN `lkcampaignlayout`
|
||||
ON `lkcampaignlayout`.LayoutID = `display`.DefaultLayoutID
|
||||
WHERE `lkcampaignlayout`.CampaignID = :activeCampaignId2
|
||||
UNION
|
||||
SELECT `lkdisplaydg`.displayId,
|
||||
0 AS eventId,
|
||||
0 AS fromDt,
|
||||
0 AS toDt,
|
||||
NULL AS recurrenceType,
|
||||
NULL AS recurrenceDetail,
|
||||
NULL AS recurrenceRange,
|
||||
NULL AS recurrenceRepeatsOn,
|
||||
NULL AS lastRecurrenceWatermark,
|
||||
NULL AS dayPartId
|
||||
FROM `lkdisplaydg`
|
||||
INNER JOIN `lklayoutdisplaygroup`
|
||||
ON `lklayoutdisplaygroup`.displayGroupId = `lkdisplaydg`.displayGroupId
|
||||
INNER JOIN `lkcampaignlayout`
|
||||
ON `lkcampaignlayout`.layoutId = `lklayoutdisplaygroup`.layoutId
|
||||
WHERE `lkcampaignlayout`.campaignId = :assignedCampaignId
|
||||
UNION
|
||||
SELECT `schedule_sync`.displayId,
|
||||
schedule.eventId,
|
||||
schedule.fromDt,
|
||||
schedule.toDt,
|
||||
schedule.recurrence_type AS recurrenceType,
|
||||
schedule.recurrence_detail AS recurrenceDetail,
|
||||
schedule.recurrence_range AS recurrenceRange,
|
||||
schedule.recurrenceRepeatsOn,
|
||||
schedule.lastRecurrenceWatermark,
|
||||
schedule.dayPartId
|
||||
FROM `schedule`
|
||||
INNER JOIN `schedule_sync`
|
||||
ON `schedule_sync`.eventId = `schedule`.eventId
|
||||
INNER JOIN `lkcampaignlayout`
|
||||
ON `lkcampaignlayout`.layoutId = `schedule_sync`.layoutId
|
||||
WHERE `lkcampaignlayout`.campaignId = :assignedCampaignId
|
||||
AND (
|
||||
(`schedule`.FromDT < :toDt AND IFNULL(`schedule`.toDt, `schedule`.fromDt) > :fromDt)
|
||||
OR `schedule`.recurrence_range >= :fromDt
|
||||
OR (
|
||||
IFNULL(`schedule`.recurrence_range, 0) = 0 AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\'
|
||||
)
|
||||
)
|
||||
';
|
||||
|
||||
$currentDate = Carbon::now();
|
||||
$rfLookAhead = $currentDate->copy()->addSeconds($this->config->getSetting('REQUIRED_FILES_LOOKAHEAD'));
|
||||
|
||||
$params = [
|
||||
'fromDt' => $currentDate->subHour()->format('U'),
|
||||
'toDt' => $rfLookAhead->format('U'),
|
||||
'activeCampaignId' => $campaignId,
|
||||
'activeCampaignId2' => $campaignId,
|
||||
'assignedCampaignId' => $campaignId
|
||||
];
|
||||
|
||||
foreach ($this->store->select($sql, $params) as $row) {
|
||||
// Don't process if the displayId is already in the collection (there is little point in running the
|
||||
// extra query)
|
||||
if (in_array($row['displayId'], $this->displayIds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is this schedule active?
|
||||
if ($row['eventId'] != 0) {
|
||||
$scheduleEvents = $this->scheduleFactory
|
||||
->createEmpty()
|
||||
->hydrate($row)
|
||||
->getEvents($currentDate, $rfLookAhead);
|
||||
|
||||
if (count($scheduleEvents) <= 0) {
|
||||
$this->log->debug(
|
||||
'Skipping eventId ' . $row['eventId'] .
|
||||
' because it doesnt have any active events in the window'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$this->log->debug(
|
||||
'Campaign[' . $campaignId .']
|
||||
change caused notify on displayId[' . $row['displayId'] . ']'
|
||||
);
|
||||
|
||||
$this->displayIds[] = $row['displayId'];
|
||||
|
||||
if ($this->collectRequired) {
|
||||
$this->displayIdsRequiringActions[] = $row['displayId'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->keysProcessed[] = 'campaign_' . $campaignId;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function notifyByDataSetId($dataSetId)
|
||||
{
|
||||
$this->log->debug('notifyByDataSetId: dataSetId: ' . $dataSetId);
|
||||
|
||||
if (in_array('dataSet_' . $dataSetId, $this->keysProcessed)) {
|
||||
$this->log->debug('notifyByDataSetId: already processed.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the Sync task to runNow
|
||||
$this->store->update('UPDATE `task` SET `runNow` = 1 WHERE `class` LIKE :taskClassLike', [
|
||||
'taskClassLike' => '%WidgetSyncTask%',
|
||||
]);
|
||||
|
||||
// Query the schedule for any data connectors.
|
||||
// This is a simple test to see if there are ever any schedules for this dataSetId
|
||||
// TODO: this could be improved.
|
||||
$sql = '
|
||||
SELECT DISTINCT display.displayId
|
||||
FROM `schedule`
|
||||
INNER JOIN `lkscheduledisplaygroup`
|
||||
ON `lkscheduledisplaygroup`.eventId = `schedule`.eventId
|
||||
INNER JOIN `lkdgdg`
|
||||
ON `lkdgdg`.parentId = `lkscheduledisplaygroup`.displayGroupId
|
||||
INNER JOIN `lkdisplaydg`
|
||||
ON `lkdisplaydg`.DisplayGroupID = `lkdgdg`.childId
|
||||
INNER JOIN `display`
|
||||
ON `lkdisplaydg`.DisplayID = `display`.displayID
|
||||
WHERE `schedule`.dataSetId = :dataSetId
|
||||
';
|
||||
|
||||
foreach ($this->store->select($sql, ['dataSetId' => $dataSetId]) as $row) {
|
||||
$this->displayIds[] = $row['displayId'];
|
||||
|
||||
if ($this->collectRequired) {
|
||||
$this->displayIdsRequiringActions[] = $row['displayId'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->keysProcessed[] = 'dataSet_' . $dataSetId;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function notifyByPlaylistId($playlistId)
|
||||
{
|
||||
$this->log->debug('Notify by PlaylistId ' . $playlistId);
|
||||
|
||||
if (in_array('playlist_' . $playlistId, $this->keysProcessed)) {
|
||||
$this->log->debug('Already processed ' . $playlistId . ' skipping this time.');
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = '
|
||||
SELECT DISTINCT display.displayId,
|
||||
schedule.eventId,
|
||||
schedule.fromDt,
|
||||
schedule.toDt,
|
||||
schedule.recurrence_type AS recurrenceType,
|
||||
schedule.recurrence_detail AS recurrenceDetail,
|
||||
schedule.recurrence_range AS recurrenceRange,
|
||||
schedule.recurrenceRepeatsOn,
|
||||
schedule.lastRecurrenceWatermark,
|
||||
schedule.dayPartId
|
||||
FROM `schedule`
|
||||
INNER JOIN `lkscheduledisplaygroup`
|
||||
ON `lkscheduledisplaygroup`.eventId = `schedule`.eventId
|
||||
INNER JOIN `lkdgdg`
|
||||
ON `lkdgdg`.parentId = `lkscheduledisplaygroup`.displayGroupId
|
||||
INNER JOIN `lkdisplaydg`
|
||||
ON lkdisplaydg.DisplayGroupID = `lkdgdg`.childId
|
||||
INNER JOIN `display`
|
||||
ON lkdisplaydg.DisplayID = display.displayID
|
||||
INNER JOIN `lkcampaignlayout`
|
||||
ON `lkcampaignlayout`.campaignId = `schedule`.campaignId
|
||||
INNER JOIN `region`
|
||||
ON `lkcampaignlayout`.layoutId = region.layoutId
|
||||
INNER JOIN `playlist`
|
||||
ON `playlist`.regionId = `region`.regionId
|
||||
WHERE `playlist`.playlistId = :playlistId
|
||||
AND (
|
||||
(schedule.FromDT < :toDt AND IFNULL(`schedule`.toDt, UNIX_TIMESTAMP()) > :fromDt)
|
||||
OR `schedule`.recurrence_range >= :fromDt
|
||||
OR (
|
||||
IFNULL(`schedule`.recurrence_range, 0) = 0 AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\'
|
||||
)
|
||||
)
|
||||
UNION
|
||||
SELECT DISTINCT display.DisplayID,
|
||||
0 AS eventId,
|
||||
0 AS fromDt,
|
||||
0 AS toDt,
|
||||
NULL AS recurrenceType,
|
||||
NULL AS recurrenceDetail,
|
||||
NULL AS recurrenceRange,
|
||||
NULL AS recurrenceRepeatsOn,
|
||||
NULL AS lastRecurrenceWatermark,
|
||||
NULL AS dayPartId
|
||||
FROM `display`
|
||||
INNER JOIN `lkcampaignlayout`
|
||||
ON `lkcampaignlayout`.LayoutID = `display`.DefaultLayoutID
|
||||
INNER JOIN `region`
|
||||
ON `lkcampaignlayout`.layoutId = region.layoutId
|
||||
INNER JOIN `playlist`
|
||||
ON `playlist`.regionId = `region`.regionId
|
||||
WHERE `playlist`.playlistId = :playlistId
|
||||
UNION
|
||||
SELECT `lkdisplaydg`.displayId,
|
||||
0 AS eventId,
|
||||
0 AS fromDt,
|
||||
0 AS toDt,
|
||||
NULL AS recurrenceType,
|
||||
NULL AS recurrenceDetail,
|
||||
NULL AS recurrenceRange,
|
||||
NULL AS recurrenceRepeatsOn,
|
||||
NULL AS lastRecurrenceWatermark,
|
||||
NULL AS dayPartId
|
||||
FROM `lkdisplaydg`
|
||||
INNER JOIN `lklayoutdisplaygroup`
|
||||
ON `lklayoutdisplaygroup`.displayGroupId = `lkdisplaydg`.displayGroupId
|
||||
INNER JOIN `lkcampaignlayout`
|
||||
ON `lkcampaignlayout`.layoutId = `lklayoutdisplaygroup`.layoutId
|
||||
INNER JOIN `region`
|
||||
ON `lkcampaignlayout`.layoutId = region.layoutId
|
||||
INNER JOIN `playlist`
|
||||
ON `playlist`.regionId = `region`.regionId
|
||||
WHERE `playlist`.playlistId = :playlistId
|
||||
';
|
||||
|
||||
$currentDate = Carbon::now();
|
||||
$rfLookAhead = $currentDate->copy()->addSeconds($this->config->getSetting('REQUIRED_FILES_LOOKAHEAD'));
|
||||
|
||||
$params = [
|
||||
'fromDt' => $currentDate->subHour()->format('U'),
|
||||
'toDt' => $rfLookAhead->format('U'),
|
||||
'playlistId' => $playlistId
|
||||
];
|
||||
|
||||
foreach ($this->store->select($sql, $params) as $row) {
|
||||
// Don't process if the displayId is already in the collection (there is little point in running the
|
||||
// extra query)
|
||||
if (in_array($row['displayId'], $this->displayIds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is this schedule active?
|
||||
if ($row['eventId'] != 0) {
|
||||
$scheduleEvents = $this->scheduleFactory
|
||||
->createEmpty()
|
||||
->hydrate($row)
|
||||
->getEvents($currentDate, $rfLookAhead);
|
||||
|
||||
if (count($scheduleEvents) <= 0) {
|
||||
$this->log->debug(
|
||||
'Skipping eventId ' . $row['eventId'] .
|
||||
' because it doesnt have any active events in the window'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$this->log->debug(
|
||||
'Playlist[' . $playlistId .'] change caused notify on displayId[' .
|
||||
$row['displayId'] . ']'
|
||||
);
|
||||
|
||||
$this->displayIds[] = $row['displayId'];
|
||||
|
||||
if ($this->collectRequired) {
|
||||
$this->displayIdsRequiringActions[] = $row['displayId'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->keysProcessed[] = 'playlist_' . $playlistId;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function notifyByLayoutCode($code)
|
||||
{
|
||||
if (in_array('layoutCode_' . $code, $this->keysProcessed)) {
|
||||
$this->log->debug('Already processed ' . $code . ' skipping this time.');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->log->debug('Notify by Layout Code: ' . $code);
|
||||
|
||||
// Get the Display Ids we need to notify
|
||||
$sql = '
|
||||
SELECT DISTINCT display.displayId,
|
||||
schedule.eventId,
|
||||
schedule.fromDt,
|
||||
schedule.toDt,
|
||||
schedule.recurrence_type AS recurrenceType,
|
||||
schedule.recurrence_detail AS recurrenceDetail,
|
||||
schedule.recurrence_range AS recurrenceRange,
|
||||
schedule.recurrenceRepeatsOn,
|
||||
schedule.lastRecurrenceWatermark,
|
||||
schedule.dayPartId
|
||||
FROM `schedule`
|
||||
INNER JOIN `lkscheduledisplaygroup`
|
||||
ON `lkscheduledisplaygroup`.eventId = `schedule`.eventId
|
||||
INNER JOIN `lkdgdg`
|
||||
ON `lkdgdg`.parentId = `lkscheduledisplaygroup`.displayGroupId
|
||||
INNER JOIN `lkdisplaydg`
|
||||
ON lkdisplaydg.DisplayGroupID = `lkdgdg`.childId
|
||||
INNER JOIN `display`
|
||||
ON lkdisplaydg.DisplayID = display.displayID
|
||||
INNER JOIN (
|
||||
SELECT DISTINCT campaignId
|
||||
FROM layout
|
||||
INNER JOIN lkcampaignlayout ON lkcampaignlayout.layoutId = layout.layoutId
|
||||
INNER JOIN action on layout.layoutId = action.sourceId
|
||||
WHERE action.layoutCode = :code AND layout.publishedStatusId = 1
|
||||
UNION
|
||||
SELECT DISTINCT campaignId
|
||||
FROM layout
|
||||
INNER JOIN lkcampaignlayout ON lkcampaignlayout.layoutId = layout.layoutId
|
||||
INNER JOIN region ON region.layoutId = layout.layoutId
|
||||
INNER JOIN action on region.regionId = action.sourceId
|
||||
WHERE action.layoutCode = :code AND layout.publishedStatusId = 1
|
||||
UNION
|
||||
SELECT DISTINCT campaignId
|
||||
FROM layout
|
||||
INNER JOIN lkcampaignlayout ON lkcampaignlayout.layoutId = layout.layoutId
|
||||
INNER JOIN region ON region.layoutId = layout.layoutId
|
||||
INNER JOIN playlist ON playlist.regionId = region.regionId
|
||||
INNER JOIN widget on playlist.playlistId = widget.playlistId
|
||||
INNER JOIN action on widget.widgetId = action.sourceId
|
||||
WHERE
|
||||
action.layoutCode = :code AND
|
||||
layout.publishedStatusId = 1
|
||||
) campaigns
|
||||
ON campaigns.campaignId = `schedule`.campaignId
|
||||
WHERE (
|
||||
(`schedule`.FromDT < :toDt AND IFNULL(`schedule`.toDt, UNIX_TIMESTAMP()) > :fromDt)
|
||||
OR `schedule`.recurrence_range >= :fromDt
|
||||
OR (
|
||||
IFNULL(`schedule`.recurrence_range, 0) = 0 AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\'
|
||||
)
|
||||
)
|
||||
UNION
|
||||
SELECT DISTINCT display.displayId,
|
||||
schedule.eventId,
|
||||
schedule.fromDt,
|
||||
schedule.toDt,
|
||||
schedule.recurrence_type AS recurrenceType,
|
||||
schedule.recurrence_detail AS recurrenceDetail,
|
||||
schedule.recurrence_range AS recurrenceRange,
|
||||
schedule.recurrenceRepeatsOn,
|
||||
schedule.lastRecurrenceWatermark,
|
||||
schedule.dayPartId
|
||||
FROM `schedule`
|
||||
INNER JOIN `lkscheduledisplaygroup`
|
||||
ON `lkscheduledisplaygroup`.eventId = `schedule`.eventId
|
||||
INNER JOIN `lkdgdg`
|
||||
ON `lkdgdg`.parentId = `lkscheduledisplaygroup`.displayGroupId
|
||||
INNER JOIN `lkdisplaydg`
|
||||
ON lkdisplaydg.DisplayGroupID = `lkdgdg`.childId
|
||||
INNER JOIN `display`
|
||||
ON lkdisplaydg.DisplayID = display.displayID
|
||||
WHERE schedule.actionLayoutCode = :code
|
||||
AND (
|
||||
(`schedule`.FromDT < :toDt AND IFNULL(`schedule`.toDt, UNIX_TIMESTAMP()) > :fromDt)
|
||||
OR `schedule`.recurrence_range >= :fromDt
|
||||
OR (
|
||||
IFNULL(`schedule`.recurrence_range, 0) = 0 AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\'
|
||||
)
|
||||
)
|
||||
UNION
|
||||
SELECT DISTINCT display.DisplayID,
|
||||
0 AS eventId,
|
||||
0 AS fromDt,
|
||||
0 AS toDt,
|
||||
NULL AS recurrenceType,
|
||||
NULL AS recurrenceDetail,
|
||||
NULL AS recurrenceRange,
|
||||
NULL AS recurrenceRepeatsOn,
|
||||
NULL AS lastRecurrenceWatermark,
|
||||
NULL AS dayPartId
|
||||
FROM `display`
|
||||
INNER JOIN `lkcampaignlayout`
|
||||
ON `lkcampaignlayout`.LayoutID = `display`.DefaultLayoutID
|
||||
WHERE `lkcampaignlayout`.CampaignID IN (
|
||||
SELECT DISTINCT campaignId
|
||||
FROM layout
|
||||
INNER JOIN lkcampaignlayout ON lkcampaignlayout.layoutId = layout.layoutId
|
||||
INNER JOIN action on layout.layoutId = action.sourceId
|
||||
WHERE action.layoutCode = :code AND layout.publishedStatusId = 1
|
||||
UNION
|
||||
SELECT DISTINCT campaignId
|
||||
FROM layout
|
||||
INNER JOIN lkcampaignlayout ON lkcampaignlayout.layoutId = layout.layoutId
|
||||
INNER JOIN region ON region.layoutId = layout.layoutId
|
||||
INNER JOIN action on region.regionId = action.sourceId
|
||||
WHERE action.layoutCode = :code AND layout.publishedStatusId = 1
|
||||
UNION
|
||||
SELECT DISTINCT campaignId
|
||||
FROM layout
|
||||
INNER JOIN lkcampaignlayout ON lkcampaignlayout.layoutId = layout.layoutId
|
||||
INNER JOIN region ON region.layoutId = layout.layoutId
|
||||
INNER JOIN playlist ON playlist.regionId = region.regionId
|
||||
INNER JOIN widget on playlist.playlistId = widget.playlistId
|
||||
INNER JOIN action on widget.widgetId = action.sourceId
|
||||
WHERE
|
||||
action.layoutCode = :code AND layout.publishedStatusId = 1
|
||||
)
|
||||
UNION
|
||||
SELECT `lkdisplaydg`.displayId,
|
||||
0 AS eventId,
|
||||
0 AS fromDt,
|
||||
0 AS toDt,
|
||||
NULL AS recurrenceType,
|
||||
NULL AS recurrenceDetail,
|
||||
NULL AS recurrenceRange,
|
||||
NULL AS recurrenceRepeatsOn,
|
||||
NULL AS lastRecurrenceWatermark,
|
||||
NULL AS dayPartId
|
||||
FROM `lkdisplaydg`
|
||||
INNER JOIN `lklayoutdisplaygroup`
|
||||
ON `lklayoutdisplaygroup`.displayGroupId = `lkdisplaydg`.displayGroupId
|
||||
INNER JOIN `lkcampaignlayout`
|
||||
ON `lkcampaignlayout`.layoutId = `lklayoutdisplaygroup`.layoutId
|
||||
WHERE `lkcampaignlayout`.campaignId IN (
|
||||
SELECT DISTINCT campaignId
|
||||
FROM layout
|
||||
INNER JOIN lkcampaignlayout ON lkcampaignlayout.layoutId = layout.layoutId
|
||||
INNER JOIN action on layout.layoutId = action.sourceId
|
||||
WHERE action.layoutCode = :code AND layout.publishedStatusId = 1
|
||||
UNION
|
||||
SELECT DISTINCT campaignId
|
||||
FROM layout
|
||||
INNER JOIN lkcampaignlayout ON lkcampaignlayout.layoutId = layout.layoutId
|
||||
INNER JOIN region ON region.layoutId = layout.layoutId
|
||||
INNER JOIN action on region.regionId = action.sourceId
|
||||
WHERE action.layoutCode = :code AND layout.publishedStatusId = 1
|
||||
UNION
|
||||
SELECT DISTINCT campaignId
|
||||
FROM layout
|
||||
INNER JOIN lkcampaignlayout ON lkcampaignlayout.layoutId = layout.layoutId
|
||||
INNER JOIN region ON region.layoutId = layout.layoutId
|
||||
INNER JOIN playlist ON playlist.regionId = region.regionId
|
||||
INNER JOIN widget on playlist.playlistId = widget.playlistId
|
||||
INNER JOIN action on widget.widgetId = action.sourceId
|
||||
WHERE
|
||||
action.layoutCode = :code AND layout.publishedStatusId = 1
|
||||
)
|
||||
';
|
||||
|
||||
$currentDate = Carbon::now();
|
||||
$rfLookAhead = $currentDate->copy()->addSeconds($this->config->getSetting('REQUIRED_FILES_LOOKAHEAD'));
|
||||
|
||||
$params = [
|
||||
'fromDt' => $currentDate->subHour()->format('U'),
|
||||
'toDt' => $rfLookAhead->format('U'),
|
||||
'code' => $code
|
||||
];
|
||||
|
||||
foreach ($this->store->select($sql, $params) as $row) {
|
||||
// Don't process if the displayId is already in the collection (there is little point in running the
|
||||
// extra query)
|
||||
if (in_array($row['displayId'], $this->displayIds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is this schedule active?
|
||||
if ($row['eventId'] != 0) {
|
||||
$scheduleEvents = $this->scheduleFactory
|
||||
->createEmpty()
|
||||
->hydrate($row)
|
||||
->getEvents($currentDate, $rfLookAhead);
|
||||
|
||||
if (count($scheduleEvents) <= 0) {
|
||||
$this->log->debug(
|
||||
'Skipping eventId ' . $row['eventId'] .
|
||||
' because it doesnt have any active events in the window'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$this->log->debug(sprintf(
|
||||
'Saving Layout with code %s, caused notify on
|
||||
displayId[' . $row['displayId'] . ']',
|
||||
$code
|
||||
));
|
||||
|
||||
$this->displayIds[] = $row['displayId'];
|
||||
|
||||
if ($this->collectRequired) {
|
||||
$this->displayIdsRequiringActions[] = $row['displayId'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->keysProcessed[] = 'layoutCode_' . $code;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function notifyByMenuBoardId($menuId)
|
||||
{
|
||||
$this->log->debug('Notify by MenuBoard ID ' . $menuId);
|
||||
|
||||
if (in_array('menuBoard_' . $menuId, $this->keysProcessed)) {
|
||||
$this->log->debug('Already processed ' . $menuId . ' skipping this time.');
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = '
|
||||
SELECT DISTINCT display.displayId,
|
||||
schedule.eventId,
|
||||
schedule.fromDt,
|
||||
schedule.toDt,
|
||||
schedule.recurrence_type AS recurrenceType,
|
||||
schedule.recurrence_detail AS recurrenceDetail,
|
||||
schedule.recurrence_range AS recurrenceRange,
|
||||
schedule.recurrenceRepeatsOn,
|
||||
schedule.lastRecurrenceWatermark,
|
||||
schedule.dayPartId
|
||||
FROM `schedule`
|
||||
INNER JOIN `lkscheduledisplaygroup`
|
||||
ON `lkscheduledisplaygroup`.eventId = `schedule`.eventId
|
||||
INNER JOIN `lkdgdg`
|
||||
ON `lkdgdg`.parentId = `lkscheduledisplaygroup`.displayGroupId
|
||||
INNER JOIN `lkdisplaydg`
|
||||
ON lkdisplaydg.DisplayGroupID = `lkdgdg`.childId
|
||||
INNER JOIN `display`
|
||||
ON lkdisplaydg.DisplayID = display.displayID
|
||||
INNER JOIN `lkcampaignlayout`
|
||||
ON `lkcampaignlayout`.campaignId = `schedule`.campaignId
|
||||
INNER JOIN `region`
|
||||
ON `region`.layoutId = `lkcampaignlayout`.layoutId
|
||||
INNER JOIN `playlist`
|
||||
ON `playlist`.regionId = `region`.regionId
|
||||
INNER JOIN `widget`
|
||||
ON `widget`.playlistId = `playlist`.playlistId
|
||||
INNER JOIN `widgetoption`
|
||||
ON `widgetoption`.widgetId = `widget`.widgetId
|
||||
AND `widgetoption`.type = \'attrib\'
|
||||
AND `widgetoption`.option = \'menuId\'
|
||||
AND `widgetoption`.value = :activeMenuId
|
||||
WHERE (
|
||||
(schedule.FromDT < :toDt AND IFNULL(`schedule`.toDt, `schedule`.fromDt) > :fromDt)
|
||||
OR `schedule`.recurrence_range >= :fromDt
|
||||
OR (
|
||||
IFNULL(`schedule`.recurrence_range, 0) = 0 AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\'
|
||||
)
|
||||
)
|
||||
UNION
|
||||
SELECT DISTINCT display.displayId,
|
||||
0 AS eventId,
|
||||
0 AS fromDt,
|
||||
0 AS toDt,
|
||||
NULL AS recurrenceType,
|
||||
NULL AS recurrenceDetail,
|
||||
NULL AS recurrenceRange,
|
||||
NULL AS recurrenceRepeatsOn,
|
||||
NULL AS lastRecurrenceWatermark,
|
||||
NULL AS dayPartId
|
||||
FROM `display`
|
||||
INNER JOIN `lkcampaignlayout`
|
||||
ON `lkcampaignlayout`.LayoutID = `display`.DefaultLayoutID
|
||||
INNER JOIN `region`
|
||||
ON `region`.layoutId = `lkcampaignlayout`.layoutId
|
||||
INNER JOIN `playlist`
|
||||
ON `playlist`.regionId = `region`.regionId
|
||||
INNER JOIN `widget`
|
||||
ON `widget`.playlistId = `playlist`.playlistId
|
||||
INNER JOIN `widgetoption`
|
||||
ON `widgetoption`.widgetId = `widget`.widgetId
|
||||
AND `widgetoption`.type = \'attrib\'
|
||||
AND `widgetoption`.option = \'menuId\'
|
||||
AND `widgetoption`.value = :activeMenuId2
|
||||
UNION
|
||||
SELECT DISTINCT `lkdisplaydg`.displayId,
|
||||
0 AS eventId,
|
||||
0 AS fromDt,
|
||||
0 AS toDt,
|
||||
NULL AS recurrenceType,
|
||||
NULL AS recurrenceDetail,
|
||||
NULL AS recurrenceRange,
|
||||
NULL AS recurrenceRepeatsOn,
|
||||
NULL AS lastRecurrenceWatermark,
|
||||
NULL AS dayPartId
|
||||
FROM `lklayoutdisplaygroup`
|
||||
INNER JOIN `lkdgdg`
|
||||
ON `lkdgdg`.parentId = `lklayoutdisplaygroup`.displayGroupId
|
||||
INNER JOIN `lkdisplaydg`
|
||||
ON lkdisplaydg.DisplayGroupID = `lkdgdg`.childId
|
||||
INNER JOIN `lkcampaignlayout`
|
||||
ON `lkcampaignlayout`.layoutId = `lklayoutdisplaygroup`.layoutId
|
||||
INNER JOIN `region`
|
||||
ON `region`.layoutId = `lkcampaignlayout`.layoutId
|
||||
INNER JOIN `playlist`
|
||||
ON `playlist`.regionId = `region`.regionId
|
||||
INNER JOIN `widget`
|
||||
ON `widget`.playlistId = `playlist`.playlistId
|
||||
INNER JOIN `widgetoption`
|
||||
ON `widgetoption`.widgetId = `widget`.widgetId
|
||||
AND `widgetoption`.type = \'attrib\'
|
||||
AND `widgetoption`.option = \'menuId\'
|
||||
AND `widgetoption`.value = :activeMenuId3
|
||||
';
|
||||
|
||||
$currentDate = Carbon::now();
|
||||
$rfLookAhead = $currentDate->copy()->addSeconds($this->config->getSetting('REQUIRED_FILES_LOOKAHEAD'));
|
||||
|
||||
$params = [
|
||||
'fromDt' => $currentDate->subHour()->format('U'),
|
||||
'toDt' => $rfLookAhead->format('U'),
|
||||
'activeMenuId' => $menuId,
|
||||
'activeMenuId2' => $menuId,
|
||||
'activeMenuId3' => $menuId
|
||||
];
|
||||
|
||||
foreach ($this->store->select($sql, $params) as $row) {
|
||||
// Don't process if the displayId is already in the collection (there is little point in running the
|
||||
// extra query)
|
||||
if (in_array($row['displayId'], $this->displayIds)) {
|
||||
$this->log->debug('displayId ' . $row['displayId'] . ' already in collection, skipping.');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is this schedule active?
|
||||
if ($row['eventId'] != 0) {
|
||||
$scheduleEvents = $this->scheduleFactory
|
||||
->createEmpty()
|
||||
->hydrate($row)
|
||||
->getEvents($currentDate, $rfLookAhead);
|
||||
|
||||
if (count($scheduleEvents) <= 0) {
|
||||
$this->log->debug(
|
||||
'Skipping eventId ' . $row['eventId'] .
|
||||
' because it doesnt have any active events in the window'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$this->log->debug('MenuBoard[' . $menuId .'] change caused notify on displayId[' . $row['displayId'] . ']');
|
||||
|
||||
$this->displayIds[] = $row['displayId'];
|
||||
|
||||
if ($this->collectRequired) {
|
||||
$this->displayIdsRequiringActions[] = $row['displayId'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->keysProcessed[] = 'menuBoard_' . $menuId;
|
||||
|
||||
$this->log->debug('Finished notify for Menu Board ID ' . $menuId);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function notifyDataUpdate(Display $display, int $widgetId): void
|
||||
{
|
||||
if (in_array('dataUpdate_' . $display->displayId . '_' . $widgetId, $this->keysProcessed)) {
|
||||
$this->log->debug('notifyDataUpdate: Already processed displayId: ' . $display->displayId
|
||||
. ', widgetId: ' . $widgetId . ', skipping this time.');
|
||||
return;
|
||||
}
|
||||
$this->log->debug('notifyDataUpdate: Process displayId: ' . $display->displayId . ', widgetId: ' . $widgetId);
|
||||
|
||||
try {
|
||||
$this->playerActionService->sendAction($display, new DataUpdateAction($widgetId));
|
||||
} catch (\Exception $e) {
|
||||
$this->log->notice('notifyDataUpdate: displayId: ' . $display->displayId
|
||||
. ', save would have triggered Player Action, but the action failed with message: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
119
lib/Service/DisplayNotifyServiceInterface.php
Normal file
119
lib/Service/DisplayNotifyServiceInterface.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?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\Service;
|
||||
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Xibo\Entity\Display;
|
||||
use Xibo\Factory\ScheduleFactory;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
|
||||
/**
|
||||
* Interface DisplayNotifyServiceInterface
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
interface DisplayNotifyServiceInterface
|
||||
{
|
||||
/**
|
||||
* DisplayNotifyServiceInterface constructor.
|
||||
* @param ConfigServiceInterface $config
|
||||
* @param StorageServiceInterface $store
|
||||
* @param LogServiceInterface $log
|
||||
* @param PoolInterface $pool
|
||||
* @param PlayerActionServiceInterface $playerActionService
|
||||
* @param ScheduleFactory $scheduleFactory
|
||||
*/
|
||||
public function __construct($config, $store, $log, $pool, $playerActionService, $scheduleFactory);
|
||||
|
||||
/**
|
||||
* Initialise
|
||||
* @return $this
|
||||
*/
|
||||
public function init();
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function collectNow();
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function collectLater();
|
||||
|
||||
/**
|
||||
* Process Queue of Display Notifications
|
||||
* @return $this
|
||||
*/
|
||||
public function processQueue();
|
||||
|
||||
/**
|
||||
* Notify by Display Id
|
||||
* @param $displayId
|
||||
*/
|
||||
public function notifyByDisplayId($displayId);
|
||||
|
||||
/**
|
||||
* Notify by Display Group Id
|
||||
* @param $displayGroupId
|
||||
*/
|
||||
public function notifyByDisplayGroupId($displayGroupId);
|
||||
|
||||
/**
|
||||
* Notify by CampaignId
|
||||
* @param $campaignId
|
||||
*/
|
||||
public function notifyByCampaignId($campaignId);
|
||||
|
||||
/**
|
||||
* Notify by DataSetId
|
||||
* @param $dataSetId
|
||||
*/
|
||||
public function notifyByDataSetId($dataSetId);
|
||||
|
||||
/**
|
||||
* Notify by PlaylistId
|
||||
* @param $playlistId
|
||||
*/
|
||||
public function notifyByPlaylistId($playlistId);
|
||||
|
||||
/**
|
||||
* Notify By Layout Code
|
||||
* @param $code
|
||||
*/
|
||||
public function notifyByLayoutCode($code);
|
||||
|
||||
/**
|
||||
* Notify by Menu Board ID
|
||||
* @param $menuId
|
||||
*/
|
||||
public function notifyByMenuBoardId($menuId);
|
||||
|
||||
/**
|
||||
* Notify that data has been updated for this display
|
||||
* @param \Xibo\Entity\Display $display
|
||||
* @param int $widgetId
|
||||
* @return void
|
||||
*/
|
||||
public function notifyDataUpdate(Display $display, int $widgetId): void;
|
||||
}
|
||||
80
lib/Service/DownloadService.php
Normal file
80
lib/Service/DownloadService.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Xibo\Service;
|
||||
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Xibo\Helper\HttpCacheProvider;
|
||||
|
||||
class DownloadService
|
||||
{
|
||||
/** @var string File path inside the library folder */
|
||||
private $filePath;
|
||||
|
||||
/** @var string Send file mode */
|
||||
private $sendFileMode;
|
||||
|
||||
/** @var LoggerInterface */
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @param string $filePath
|
||||
* @param string $sendFileMode
|
||||
*/
|
||||
public function __construct(
|
||||
string $filePath,
|
||||
string $sendFileMode
|
||||
) {
|
||||
$this->filePath = $filePath;
|
||||
$this->sendFileMode = $sendFileMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* @return $this
|
||||
*/
|
||||
public function useLogger(LoggerInterface $logger): DownloadService
|
||||
{
|
||||
$this->logger = $logger;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function returnFile($response, $attachmentName, $nginxRedirect)
|
||||
{
|
||||
// Issue some headers
|
||||
$response = HttpCacheProvider::withEtag($response, $this->filePath);
|
||||
$response = HttpCacheProvider::withExpires($response, '+1 week');
|
||||
// Set some headers
|
||||
$headers = [];
|
||||
$headers['Content-Length'] = filesize($this->filePath);
|
||||
$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'] = $this->filePath;
|
||||
} else if ($this->sendFileMode === 'Nginx') {
|
||||
// Send via Nginx X-Accel-Redirect?
|
||||
$headers['X-Accel-Redirect'] = $nginxRedirect;
|
||||
}
|
||||
|
||||
// Add the headers we've collected to our response
|
||||
foreach ($headers as $header => $value) {
|
||||
$response = $response->withHeader($header, $value);
|
||||
}
|
||||
|
||||
// Should we output the file via the application stack, or directly by reading the file.
|
||||
if ($this->sendFileMode == 'Off') {
|
||||
// Return the file with PHP
|
||||
$response = $response->withBody(new Stream(fopen($this->filePath, 'r')));
|
||||
|
||||
$this->logger->debug('Returning Stream with response body, sendfile off.');
|
||||
} else {
|
||||
$this->logger->debug('Using sendfile to return the file, only output headers.');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
97
lib/Service/HelpService.php
Normal file
97
lib/Service/HelpService.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\Service;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Xibo\Entity\HelpLink;
|
||||
|
||||
/**
|
||||
* Class HelpService
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
class HelpService implements HelpServiceInterface
|
||||
{
|
||||
/** @var string */
|
||||
private string $helpBase;
|
||||
|
||||
private ?array $links = null;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __construct($helpBase)
|
||||
{
|
||||
$this->helpBase = $helpBase;
|
||||
}
|
||||
|
||||
public function getLandingPage(): string
|
||||
{
|
||||
return $this->helpBase;
|
||||
}
|
||||
|
||||
public function getLinksForPage(string $pageName): array
|
||||
{
|
||||
if ($this->links === null) {
|
||||
$this->loadLinks();
|
||||
}
|
||||
return $this->links[$pageName] ?? [];
|
||||
}
|
||||
|
||||
private function loadLinks(): void
|
||||
{
|
||||
// Load links from file.
|
||||
try {
|
||||
if (file_exists(PROJECT_ROOT . '/custom/help-links.yaml')) {
|
||||
$links = (array)Yaml::parseFile(PROJECT_ROOT . '/custom/help-links.yaml');
|
||||
} else if (file_exists(PROJECT_ROOT . '/help-links.yaml')) {
|
||||
$links = (array)Yaml::parseFile(PROJECT_ROOT . '/help-links.yaml');
|
||||
} else {
|
||||
$this->links = [];
|
||||
return;
|
||||
}
|
||||
} catch (\Exception) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse links.
|
||||
$this->links = [];
|
||||
|
||||
foreach ($links as $pageName => $page) {
|
||||
// New page
|
||||
$this->links[$pageName] = [];
|
||||
|
||||
foreach ($page as $link) {
|
||||
$helpLink = new HelpLink($link);
|
||||
if (!Str::startsWith($helpLink->url, ['http://', 'https://'])) {
|
||||
$helpLink->url = $this->helpBase . $helpLink->url;
|
||||
}
|
||||
if (!empty($helpLink->summary)) {
|
||||
$helpLink->summary = \Parsedown::instance()->setSafeMode(true)->line($helpLink->summary);
|
||||
}
|
||||
|
||||
$this->links[$pageName][] = $helpLink;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
lib/Service/HelpServiceInterface.php
Normal file
45
lib/Service/HelpServiceInterface.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\Service;
|
||||
|
||||
use Xibo\Entity\HelpLink;
|
||||
|
||||
/**
|
||||
* Return help links for a page.
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
interface HelpServiceInterface
|
||||
{
|
||||
/**
|
||||
* Get the landing page
|
||||
* @return string
|
||||
*/
|
||||
public function getLandingPage(): string;
|
||||
|
||||
/**
|
||||
* Get links for page
|
||||
* @param string $pageName The page name to return links for
|
||||
* @return HelpLink[]
|
||||
*/
|
||||
public function getLinksForPage(string $pageName): array;
|
||||
}
|
||||
82
lib/Service/ImageProcessingService.php
Normal file
82
lib/Service/ImageProcessingService.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?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\Service;
|
||||
|
||||
use Xibo\Service\ImageProcessingServiceInterface;
|
||||
use Intervention\Image\Exception\NotReadableException;
|
||||
use Intervention\Image\ImageManagerStatic as Img;
|
||||
|
||||
/**
|
||||
* Class ImageProcessingService
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
class ImageProcessingService implements ImageProcessingServiceInterface
|
||||
{
|
||||
|
||||
/** @var LogServiceInterface */
|
||||
private $log;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setDependencies($log)
|
||||
{
|
||||
$this->log = $log;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function resizeImage($filePath, $width, $height)
|
||||
{
|
||||
try {
|
||||
Img::configure(array('driver' => 'gd'));
|
||||
$img = Img::make($filePath);
|
||||
$img->resize($width, $height, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
});
|
||||
|
||||
// Get the updated height and width
|
||||
$updatedHeight = $img->height();
|
||||
$updatedWidth = $img->width();
|
||||
|
||||
$img->save($filePath);
|
||||
$img->destroy();
|
||||
} catch (NotReadableException $notReadableException) {
|
||||
$this->log->error('Image not readable: ' . $notReadableException->getMessage());
|
||||
}
|
||||
|
||||
return [
|
||||
'filePath' => $filePath,
|
||||
'height' => $updatedHeight ?? $height,
|
||||
'width' => $updatedWidth ?? $width
|
||||
];
|
||||
}
|
||||
}
|
||||
51
lib/Service/ImageProcessingServiceInterface.php
Normal file
51
lib/Service/ImageProcessingServiceInterface.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2019 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\Service;
|
||||
|
||||
use Xibo\Service\LogServiceInterface;
|
||||
|
||||
/**
|
||||
* Interface ImageProcessingServiceInterface
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
interface ImageProcessingServiceInterface
|
||||
{
|
||||
/**
|
||||
* Image Processing constructor.
|
||||
*/
|
||||
public function __construct();
|
||||
|
||||
/**
|
||||
* Set Image Processing Dependencies
|
||||
* @param LogServiceInterface $logger
|
||||
*/
|
||||
public function setDependencies($logger);
|
||||
|
||||
/**
|
||||
* Resize Image
|
||||
* @param $filePath string
|
||||
* @param $width int
|
||||
* @param $height int
|
||||
*/
|
||||
public function resizeImage($filePath, $width, $height);
|
||||
}
|
||||
143
lib/Service/JwtService.php
Normal file
143
lib/Service/JwtService.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?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\Service;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Lcobucci\Clock\SystemClock;
|
||||
use Lcobucci\JWT\Configuration;
|
||||
use Lcobucci\JWT\Encoding\ChainedFormatter;
|
||||
use Lcobucci\JWT\Encoding\JoseEncoder;
|
||||
use Lcobucci\JWT\Signer\Key;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
||||
use Lcobucci\JWT\Token;
|
||||
use Lcobucci\JWT\Token\Builder;
|
||||
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
|
||||
use Lcobucci\JWT\Validation\Constraint\SignedWith;
|
||||
use Lcobucci\JWT\Validation\Constraint\ValidAt;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* A service to create and validate JWTs
|
||||
*/
|
||||
class JwtService implements JwtServiceInterface
|
||||
{
|
||||
/** @var \Psr\Log\LoggerInterface */
|
||||
private $logger;
|
||||
|
||||
/** @var array */
|
||||
private $keys;
|
||||
|
||||
/**
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* @return \Xibo\Service\JwtServiceInterface
|
||||
*/
|
||||
public function useLogger(LoggerInterface $logger): JwtServiceInterface
|
||||
{
|
||||
$this->logger = $logger;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Psr\Log\LoggerInterface|\Psr\Log\NullLogger
|
||||
*/
|
||||
private function getLogger(): LoggerInterface
|
||||
{
|
||||
if ($this->logger === null) {
|
||||
return new NullLogger();
|
||||
}
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $keys
|
||||
* @return \Xibo\Service\JwtServiceInterface
|
||||
*/
|
||||
public function useKeys($keys): JwtServiceInterface
|
||||
{
|
||||
$this->keys = $keys;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function generateJwt($issuedBy, $permittedFor, $identifiedBy, $relatedTo, $ttl): Token
|
||||
{
|
||||
$this->getLogger()->debug('generateJwt: Private key path is: ' . $this->getPrivateKeyPath()
|
||||
. ', identifiedBy: ' . $identifiedBy . ', relatedTo: ' . $relatedTo);
|
||||
|
||||
$tokenBuilder = (new Builder(new JoseEncoder(), ChainedFormatter::default()));
|
||||
$signingKey = Key\InMemory::file($this->getPrivateKeyPath());
|
||||
return $tokenBuilder
|
||||
->issuedBy($issuedBy)
|
||||
->permittedFor($permittedFor)
|
||||
->identifiedBy($identifiedBy)
|
||||
->issuedAt(Carbon::now()->toDateTimeImmutable())
|
||||
->canOnlyBeUsedAfter(Carbon::now()->toDateTimeImmutable())
|
||||
->expiresAt(Carbon::now()->addSeconds($ttl)->toDateTimeImmutable())
|
||||
->relatedTo($relatedTo)
|
||||
->getToken(new Sha256(), $signingKey);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function validateJwt($jwt): ?Token
|
||||
{
|
||||
$this->getLogger()->debug('validateJwt: ' . $jwt);
|
||||
|
||||
$signingKey = Key\InMemory::file($this->getPrivateKeyPath());
|
||||
$configuration = Configuration::forSymmetricSigner(new Sha256(), $signingKey);
|
||||
|
||||
$configuration->setValidationConstraints(
|
||||
new LooseValidAt(new SystemClock(new \DateTimeZone(\date_default_timezone_get()))),
|
||||
new SignedWith(new Sha256(), InMemory::plainText(file_get_contents($this->getPublicKeyPath())))
|
||||
);
|
||||
|
||||
// Parse the token
|
||||
$token = $configuration->parser()->parse($jwt);
|
||||
|
||||
$this->getLogger()->debug('validateJwt: token parsed');
|
||||
|
||||
// Test against constraints.
|
||||
$constraints = $configuration->validationConstraints();
|
||||
$configuration->validator()->assert($token, ...$constraints);
|
||||
|
||||
$this->getLogger()->debug('validateJwt: constraints valid');
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
private function getPublicKeyPath(): ?string
|
||||
{
|
||||
return $this->keys['publicKeyPath'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
private function getPrivateKeyPath(): ?string
|
||||
{
|
||||
return $this->keys['privateKeyPath'] ?? null;
|
||||
}
|
||||
}
|
||||
36
lib/Service/JwtServiceInterface.php
Normal file
36
lib/Service/JwtServiceInterface.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Service;
|
||||
|
||||
use Lcobucci\JWT\Token;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* A service to create and validate JWTs
|
||||
*/
|
||||
interface JwtServiceInterface
|
||||
{
|
||||
public function useLogger(LoggerInterface $logger): JwtServiceInterface;
|
||||
public function generateJwt($issuedBy, $permittedFor, $identifiedBy, $relatedTo, $ttl): Token;
|
||||
public function validateJwt($jwt): ?Token;
|
||||
}
|
||||
373
lib/Service/LogService.php
Normal file
373
lib/Service/LogService.php
Normal file
@@ -0,0 +1,373 @@
|
||||
<?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\Service;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Xibo\Helper\DatabaseLogHandler;
|
||||
use Xibo\Storage\PdoStorageService;
|
||||
|
||||
/**
|
||||
* Class LogService
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
class LogService implements LogServiceInterface
|
||||
{
|
||||
/**
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
private $log;
|
||||
|
||||
/**
|
||||
* The Log Mode
|
||||
* @var string
|
||||
*/
|
||||
private $mode;
|
||||
|
||||
/**
|
||||
* The user Id
|
||||
* @var int
|
||||
*/
|
||||
private $userId = 0;
|
||||
|
||||
/**
|
||||
* The User IP Address
|
||||
*/
|
||||
private $ipAddress;
|
||||
|
||||
/**
|
||||
* Audit Log Statement
|
||||
* @var \PDOStatement
|
||||
*/
|
||||
private $_auditLogStatement;
|
||||
|
||||
/**
|
||||
* The History session id.
|
||||
*/
|
||||
private $sessionHistoryId = 0;
|
||||
|
||||
/**
|
||||
* The API requestId.
|
||||
*/
|
||||
private $requestId = 0;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __construct($logger, $mode = 'production')
|
||||
{
|
||||
$this->log = $logger;
|
||||
$this->mode = $mode;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getLoggerInterface(): LoggerInterface
|
||||
{
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setIpAddress($ip)
|
||||
{
|
||||
$this->ipAddress = $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setUserId($userId)
|
||||
{
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setSessionHistoryId($sessionHistoryId)
|
||||
{
|
||||
$this->sessionHistoryId = $sessionHistoryId;
|
||||
}
|
||||
|
||||
public function setRequestId($requestId)
|
||||
{
|
||||
$this->requestId = $requestId;
|
||||
}
|
||||
|
||||
public function getUserId(): ?int
|
||||
{
|
||||
return $this->userId;
|
||||
}
|
||||
|
||||
public function getSessionHistoryId(): ?int
|
||||
{
|
||||
return $this->sessionHistoryId;
|
||||
}
|
||||
|
||||
public function getRequestId(): ?int
|
||||
{
|
||||
return $this->requestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setMode($mode)
|
||||
{
|
||||
$this->mode = $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function audit($entity, $entityId, $message, $object)
|
||||
{
|
||||
$this->debug(sprintf(
|
||||
'Audit Trail message recorded for %s with id %d. Message: %s from IP %s, session %d',
|
||||
$entity,
|
||||
$entityId,
|
||||
$message,
|
||||
$this->ipAddress,
|
||||
$this->sessionHistoryId
|
||||
));
|
||||
|
||||
if ($this->_auditLogStatement == null) {
|
||||
$this->prepareAuditLogStatement();
|
||||
}
|
||||
|
||||
// If we aren't a string then encode
|
||||
if (!is_string($object)) {
|
||||
$object = json_encode($object);
|
||||
}
|
||||
|
||||
$params = [
|
||||
'logDate' => Carbon::now()->format('U'),
|
||||
'userId' => $this->userId,
|
||||
'entity' => $entity,
|
||||
'message' => $message,
|
||||
'entityId' => $entityId,
|
||||
'ipAddress' => $this->ipAddress,
|
||||
'objectAfter' => $object,
|
||||
'sessionHistoryId' => $this->sessionHistoryId,
|
||||
'requestId' => $this->requestId
|
||||
];
|
||||
|
||||
try {
|
||||
$this->_auditLogStatement->execute($params);
|
||||
} catch (\PDOException $PDOException) {
|
||||
$errorCode = $PDOException->errorInfo[1] ?? $PDOException->getCode();
|
||||
|
||||
// Catch 2006 errors (mysql gone away)
|
||||
if ($errorCode != 2006) {
|
||||
throw $PDOException;
|
||||
} else {
|
||||
$this->prepareAuditLogStatement();
|
||||
$this->_auditLogStatement->execute($params);
|
||||
}
|
||||
}
|
||||
|
||||
// Although we use the default connection, track audit status separately.
|
||||
PdoStorageService::incrementStat('audit', 'insert');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to prepare a PDO statement for inserting into the Audit Log.
|
||||
* sets $_auditLogStatement
|
||||
* @return void
|
||||
*/
|
||||
private function prepareAuditLogStatement(): void
|
||||
{
|
||||
// Use the default connection
|
||||
// audit log should rollback on failure.
|
||||
$dbh = PdoStorageService::newConnection('default');
|
||||
$this->_auditLogStatement = $dbh->prepare('
|
||||
INSERT INTO `auditlog` (
|
||||
`logDate`,
|
||||
`userId`,
|
||||
`entity`,
|
||||
`message`,
|
||||
`entityId`,
|
||||
`objectAfter`,
|
||||
`ipAddress`,
|
||||
`sessionHistoryId`,
|
||||
`requestId`
|
||||
)
|
||||
VALUES (
|
||||
:logDate,
|
||||
:userId,
|
||||
:entity,
|
||||
:message,
|
||||
:entityId,
|
||||
:objectAfter,
|
||||
:ipAddress,
|
||||
:sessionHistoryId,
|
||||
:requestId
|
||||
)
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function sql($sql, $params, $logAsError = false)
|
||||
{
|
||||
if (strtolower($this->mode) == 'test' || $logAsError) {
|
||||
$paramSql = '';
|
||||
foreach ($params as $key => $param) {
|
||||
$paramSql .= 'SET @' . $key . '=\'' . $param . '\';' . PHP_EOL;
|
||||
}
|
||||
|
||||
($logAsError)
|
||||
? $this->log->error($paramSql . str_replace(':', '@', $sql))
|
||||
: $this->log->debug($paramSql . str_replace(':', '@', $sql));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function debug($object)
|
||||
{
|
||||
// Get the calling class / function
|
||||
$this->log->debug($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function notice($object)
|
||||
{
|
||||
$this->log->notice($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function info($object)
|
||||
{
|
||||
$this->log->info($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function warning($object)
|
||||
{
|
||||
$this->log->warning($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function error($object)
|
||||
{
|
||||
$this->log->error($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function critical($object)
|
||||
{
|
||||
$this->log->critical($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function alert($object)
|
||||
{
|
||||
$this->log->alert($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function emergency($object)
|
||||
{
|
||||
$this->log->emergency($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
private function prepare($object, $args)
|
||||
{
|
||||
if (is_string($object)) {
|
||||
array_shift($args);
|
||||
|
||||
if (count($args) > 0)
|
||||
$object = vsprintf($object, $args);
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public static function resolveLogLevel($level)
|
||||
{
|
||||
switch (strtolower($level)) {
|
||||
|
||||
case 'emergency':
|
||||
return Logger::EMERGENCY;
|
||||
|
||||
case 'alert':
|
||||
return Logger::ALERT;
|
||||
|
||||
case 'critical':
|
||||
return Logger::CRITICAL;
|
||||
|
||||
case 'warning':
|
||||
return Logger::WARNING;
|
||||
|
||||
case 'notice':
|
||||
return Logger::NOTICE;
|
||||
|
||||
case 'info':
|
||||
return Logger::INFO;
|
||||
|
||||
case 'debug':
|
||||
case 'audit' :
|
||||
return Logger::DEBUG;
|
||||
|
||||
case 'error':
|
||||
default:
|
||||
return Logger::ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function setLevel($level)
|
||||
{
|
||||
foreach ($this->log->getHandlers() as $handler) {
|
||||
if ($handler instanceof DatabaseLogHandler) {
|
||||
$handler->setLevel($level);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
162
lib/Service/LogServiceInterface.php
Normal file
162
lib/Service/LogServiceInterface.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?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\Service;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Interface LogServiceInterface
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
interface LogServiceInterface
|
||||
{
|
||||
/**
|
||||
* Log constructor.
|
||||
* @param LoggerInterface $logger
|
||||
* @param string $mode
|
||||
*/
|
||||
public function __construct($logger, $mode = 'production');
|
||||
|
||||
/**
|
||||
* Get the underlying logger interface
|
||||
* useful for custom code and modules which may not want to know about the full Xibo LogServiceInterface
|
||||
* @return \Psr\Log\LoggerInterface
|
||||
*/
|
||||
public function getLoggerInterface(): LoggerInterface;
|
||||
|
||||
public function getUserId(): ?int;
|
||||
public function getSessionHistoryId(): ?int;
|
||||
public function getRequestId(): ?int;
|
||||
|
||||
/**
|
||||
* Set the user Id
|
||||
* @param int $userId
|
||||
*/
|
||||
public function setUserId($userId);
|
||||
|
||||
/**
|
||||
* Set the User IP Address
|
||||
* @param $ip
|
||||
* @return mixed
|
||||
*/
|
||||
public function setIpAddress($ip);
|
||||
|
||||
/**
|
||||
* Set history session id
|
||||
* @param $sessionHistoryId
|
||||
* @return mixed
|
||||
*/
|
||||
public function setSessionHistoryId($sessionHistoryId);
|
||||
|
||||
/**
|
||||
* Set API requestId
|
||||
* @param $requestId
|
||||
* @return mixed
|
||||
*/
|
||||
public function setRequestId($requestId);
|
||||
|
||||
/**
|
||||
* @param $mode
|
||||
* @return mixed
|
||||
*/
|
||||
public function setMode($mode);
|
||||
|
||||
/**
|
||||
* Audit Log
|
||||
* @param string $entity
|
||||
* @param int $entityId
|
||||
* @param string $message
|
||||
* @param string|object|array $object
|
||||
*/
|
||||
public function audit($entity, $entityId, $message, $object);
|
||||
|
||||
/**
|
||||
* @param $sql
|
||||
* @param $params
|
||||
* @param bool $logAsError
|
||||
* @return mixed
|
||||
*/
|
||||
public function sql($sql, $params, $logAsError = false);
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @return mixed
|
||||
*/
|
||||
public function debug($object);
|
||||
|
||||
/**
|
||||
* @param ...$object
|
||||
* @return mixed
|
||||
*/
|
||||
public function notice($object);
|
||||
|
||||
/**
|
||||
* @param ...$object
|
||||
* @return mixed
|
||||
*/
|
||||
public function info($object);
|
||||
|
||||
/**
|
||||
* @param ...$object
|
||||
* @return mixed
|
||||
*/
|
||||
public function warning($object);
|
||||
|
||||
/**
|
||||
* @param ...$object
|
||||
* @return mixed
|
||||
*/
|
||||
public function error($object);
|
||||
|
||||
/**
|
||||
* @param ...$object
|
||||
* @return mixed
|
||||
*/
|
||||
public function critical($object);
|
||||
|
||||
/**
|
||||
* @param ...$object
|
||||
* @return mixed
|
||||
*/
|
||||
public function alert($object);
|
||||
|
||||
/**
|
||||
* @param ...$object
|
||||
* @return mixed
|
||||
*/
|
||||
public function emergency($object);
|
||||
|
||||
/**
|
||||
* Resolve the log level
|
||||
* @param string $level
|
||||
* @return int
|
||||
*/
|
||||
public static function resolveLogLevel($level);
|
||||
|
||||
/**
|
||||
* Set the log level on all handlers
|
||||
* @param $level
|
||||
*/
|
||||
public function setLevel($level);
|
||||
}
|
||||
386
lib/Service/MediaService.php
Normal file
386
lib/Service/MediaService.php
Normal file
@@ -0,0 +1,386 @@
|
||||
<?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\Service;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Mimey\MimeTypes;
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Xibo\Entity\User;
|
||||
use Xibo\Event\MediaDeleteEvent;
|
||||
use Xibo\Factory\FontFactory;
|
||||
use Xibo\Factory\MediaFactory;
|
||||
use Xibo\Helper\ByteFormatter;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Helper\Environment;
|
||||
use Xibo\Helper\SanitizerService;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Support\Exception\ConfigurationException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\LibraryFullException;
|
||||
|
||||
/**
|
||||
* MediaService
|
||||
*/
|
||||
class MediaService implements MediaServiceInterface
|
||||
{
|
||||
/** @var ConfigServiceInterface */
|
||||
private $configService;
|
||||
|
||||
/** @var LogServiceInterface */
|
||||
private $log;
|
||||
|
||||
/** @var StorageServiceInterface */
|
||||
private $store;
|
||||
|
||||
/** @var SanitizerService */
|
||||
private $sanitizerService;
|
||||
|
||||
/** @var PoolInterface */
|
||||
private $pool;
|
||||
|
||||
/** @var MediaFactory */
|
||||
private $mediaFactory;
|
||||
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
/** @var EventDispatcherInterface */
|
||||
private $dispatcher;
|
||||
/**
|
||||
* @var FontFactory
|
||||
*/
|
||||
private $fontFactory;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function __construct(
|
||||
ConfigServiceInterface $configService,
|
||||
LogServiceInterface $logService,
|
||||
StorageServiceInterface $store,
|
||||
SanitizerService $sanitizerService,
|
||||
PoolInterface $pool,
|
||||
MediaFactory $mediaFactory,
|
||||
FontFactory $fontFactory
|
||||
) {
|
||||
$this->configService = $configService;
|
||||
$this->log = $logService;
|
||||
$this->store = $store;
|
||||
$this->sanitizerService = $sanitizerService;
|
||||
$this->pool = $pool;
|
||||
$this->mediaFactory = $mediaFactory;
|
||||
$this->fontFactory = $fontFactory;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function setUser(User $user) : MediaServiceInterface
|
||||
{
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getUser() : User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function getPool() : PoolInterface
|
||||
{
|
||||
return $this->pool;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function setDispatcher(EventDispatcherInterface $dispatcher): MediaServiceInterface
|
||||
{
|
||||
$this->dispatcher = $dispatcher;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function libraryUsage(): int
|
||||
{
|
||||
$results = $this->store->select('SELECT IFNULL(SUM(FileSize), 0) AS SumSize FROM media', []);
|
||||
|
||||
return $this->sanitizerService->getSanitizer($results[0])->getInt('SumSize');
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function initLibrary(): MediaServiceInterface
|
||||
{
|
||||
MediaService::ensureLibraryExists($this->configService->getSetting('LIBRARY_LOCATION'));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function checkLibraryOrQuotaFull($isCheckUser = false): MediaServiceInterface
|
||||
{
|
||||
// Check that we have some space in our library
|
||||
$librarySizeLimit = $this->configService->getSetting('LIBRARY_SIZE_LIMIT_KB') * 1024;
|
||||
$librarySizeLimitMB = round(($librarySizeLimit / 1024) / 1024, 2);
|
||||
|
||||
if ($librarySizeLimit > 0 && $this->libraryUsage() > $librarySizeLimit) {
|
||||
throw new LibraryFullException(sprintf(__('Your library is full. Library Limit: %s MB'), $librarySizeLimitMB));
|
||||
}
|
||||
|
||||
if ($isCheckUser) {
|
||||
$this->getUser()->isQuotaFullByUser();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function checkMaxUploadSize($size): MediaServiceInterface
|
||||
{
|
||||
if (ByteFormatter::toBytes(Environment::getMaxUploadSize()) < $size) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(__('This file size exceeds your environment Max Upload Size %s'), Environment::getMaxUploadSize()),
|
||||
'size'
|
||||
);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getDownloadInfo($url): array
|
||||
{
|
||||
$downloadInfo = [];
|
||||
$guzzle = new Client($this->configService->getGuzzleProxy());
|
||||
|
||||
// first try to get the extension from pathinfo
|
||||
$info = pathinfo(parse_url($url, PHP_URL_PATH));
|
||||
$extension = $info['extension'] ?? '';
|
||||
$size = -1;
|
||||
|
||||
try {
|
||||
$head = $guzzle->head($url);
|
||||
|
||||
// First chance at getting the content length so that we can fail early.
|
||||
// Will fail for downloads with redirects.
|
||||
if ($head->hasHeader('Content-Length')) {
|
||||
$contentLength = $head->getHeader('Content-Length');
|
||||
|
||||
foreach ($contentLength as $value) {
|
||||
$size = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($extension)) {
|
||||
$contentType = $head->getHeaderLine('Content-Type');
|
||||
|
||||
$extension = $contentType;
|
||||
|
||||
if ($contentType === 'binary/octet-stream' && $head->hasHeader('x-amz-meta-filetype')) {
|
||||
$amazonContentType = $head->getHeaderLine('x-amz-meta-filetype');
|
||||
$extension = $amazonContentType;
|
||||
}
|
||||
|
||||
// get the extension corresponding to the mime type
|
||||
$mimeTypes = new MimeTypes();
|
||||
$extension = $mimeTypes->getExtension($extension);
|
||||
}
|
||||
} catch (RequestException $e) {
|
||||
$this->log->debug('Upload from url head request failed for URL ' . $url
|
||||
. ' with following message ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$downloadInfo['size'] = $size;
|
||||
$downloadInfo['extension'] = $extension;
|
||||
$downloadInfo['filename'] = $info['filename'];
|
||||
|
||||
return $downloadInfo;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function updateFontsCss()
|
||||
{
|
||||
// delete local cms fonts.css from cache
|
||||
$this->pool->deleteItem('localFontCss');
|
||||
|
||||
$this->log->debug('Regenerating player fonts.css file');
|
||||
|
||||
// Go through all installed fonts each time and regenerate.
|
||||
$fontTemplate = '@font-face {
|
||||
font-family: \'[family]\';
|
||||
src: url(\'[url]\');
|
||||
}';
|
||||
|
||||
// Save a fonts.css file to the library for use as a module
|
||||
$fonts = $this->fontFactory->query();
|
||||
|
||||
$css = '';
|
||||
|
||||
// Check the library exists
|
||||
$libraryLocation = $this->configService->getSetting('LIBRARY_LOCATION');
|
||||
MediaService::ensureLibraryExists($this->configService->getSetting('LIBRARY_LOCATION'));
|
||||
|
||||
// Build our font strings.
|
||||
foreach ($fonts as $font) {
|
||||
// Css for the player contains the actual stored as location of the font.
|
||||
$css .= str_replace('[url]', $font->fileName, str_replace('[family]', $font->familyName, $fontTemplate));
|
||||
}
|
||||
|
||||
// If we're a full regenerate, we want to also update the fonts.css file.
|
||||
$existingLibraryFontsCss = '';
|
||||
if (file_exists($libraryLocation . 'fonts/fonts.css')) {
|
||||
$existingLibraryFontsCss = file_get_contents($libraryLocation . 'fonts/fonts.css');
|
||||
}
|
||||
|
||||
$tempFontsCss = $libraryLocation . 'temp/fonts.css';
|
||||
file_put_contents($tempFontsCss, $css);
|
||||
// Check to see if the existing file is different from the new one
|
||||
if ($existingLibraryFontsCss == '' || md5($existingLibraryFontsCss) !== md5($tempFontsCss)) {
|
||||
$this->log->info('Detected change in fonts.css file, dropping the Display cache');
|
||||
rename($tempFontsCss, $libraryLocation . 'fonts/fonts.css');
|
||||
// Clear the display cache
|
||||
$this->pool->deleteItem('/display');
|
||||
} else {
|
||||
@unlink($tempFontsCss);
|
||||
$this->log->debug('Newly generated fonts.css is the same as the old file. Ignoring.');
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public static function ensureLibraryExists($libraryFolder)
|
||||
{
|
||||
// Check that this location exists - and if not create it..
|
||||
if (!file_exists($libraryFolder)) {
|
||||
mkdir($libraryFolder, 0777, true);
|
||||
}
|
||||
|
||||
if (!file_exists($libraryFolder . '/temp')) {
|
||||
mkdir($libraryFolder . '/temp', 0777, true);
|
||||
}
|
||||
if (!file_exists($libraryFolder . '/cache')) {
|
||||
mkdir($libraryFolder . '/cache', 0777, true);
|
||||
}
|
||||
|
||||
if (!file_exists($libraryFolder . '/screenshots')) {
|
||||
mkdir($libraryFolder . '/screenshots', 0777, true);
|
||||
}
|
||||
|
||||
if (!file_exists($libraryFolder . '/attachment')) {
|
||||
mkdir($libraryFolder . '/attachment', 0777, true);
|
||||
}
|
||||
|
||||
if (!file_exists($libraryFolder . '/thumbs')) {
|
||||
mkdir($libraryFolder . '/thumbs', 0777, true);
|
||||
}
|
||||
|
||||
if (!file_exists($libraryFolder . '/fonts')) {
|
||||
mkdir($libraryFolder . '/fonts', 0777, true);
|
||||
}
|
||||
|
||||
if (!file_exists($libraryFolder . '/playersoftware')) {
|
||||
mkdir($libraryFolder . '/playersoftware', 0777, true);
|
||||
}
|
||||
|
||||
if (!file_exists($libraryFolder . '/playersoftware/chromeos')) {
|
||||
mkdir($libraryFolder . '/playersoftware/chromeos', 0777, true);
|
||||
}
|
||||
|
||||
if (!file_exists($libraryFolder . '/savedreport')) {
|
||||
mkdir($libraryFolder . '/savedreport', 0777, true);
|
||||
}
|
||||
|
||||
if (!file_exists($libraryFolder . '/assets')) {
|
||||
mkdir($libraryFolder . '/assets', 0777, true);
|
||||
}
|
||||
|
||||
if (!file_exists($libraryFolder . '/data_connectors')) {
|
||||
mkdir($libraryFolder . '/data_connectors', 0777, true);
|
||||
}
|
||||
|
||||
// Check that we are now writable - if not then error
|
||||
if (!is_writable($libraryFolder)) {
|
||||
throw new ConfigurationException(__('Library not writable'));
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function removeTempFiles()
|
||||
{
|
||||
$libraryTemp = $this->configService->getSetting('LIBRARY_LOCATION') . 'temp';
|
||||
|
||||
if (!is_dir($libraryTemp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dump the files in the temp folder
|
||||
foreach (scandir($libraryTemp) as $item) {
|
||||
if ($item == '.' || $item == '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Path
|
||||
$filePath = $libraryTemp . DIRECTORY_SEPARATOR . $item;
|
||||
|
||||
if (is_dir($filePath)) {
|
||||
$this->log->debug('Skipping folder: ' . $item);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Has this file been written to recently?
|
||||
if (filemtime($filePath) > Carbon::now()->subSeconds(86400)->format('U')) {
|
||||
$this->log->debug('Skipping active file: ' . $item);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->log->debug('Deleting temp file: ' . $item);
|
||||
|
||||
unlink($filePath);
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function removeExpiredFiles()
|
||||
{
|
||||
// Get a list of all expired files and delete them
|
||||
foreach ($this->mediaFactory->query(
|
||||
null,
|
||||
[
|
||||
'expires' => Carbon::now()->format('U'),
|
||||
'allModules' => 1,
|
||||
'unlinkedOnly' => 1,
|
||||
'length' => 100,
|
||||
]
|
||||
) as $entry) {
|
||||
// If the media type is a module, then pretend it's a generic file
|
||||
$this->log->info(sprintf('Removing Expired File %s', $entry->name));
|
||||
$this->log->audit(
|
||||
'Media',
|
||||
$entry->mediaId,
|
||||
'Removing Expired',
|
||||
[
|
||||
'mediaId' => $entry->mediaId,
|
||||
'name' => $entry->name,
|
||||
'expired' => Carbon::createFromTimestamp($entry->expires)
|
||||
->format(DateFormatHelper::getSystemFormat())
|
||||
]
|
||||
);
|
||||
$this->dispatcher->dispatch(new MediaDeleteEvent($entry), MediaDeleteEvent::$NAME);
|
||||
$entry->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
144
lib/Service/MediaServiceInterface.php
Normal file
144
lib/Service/MediaServiceInterface.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2021 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Service;
|
||||
|
||||
use Slim\Routing\RouteParser;
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Xibo\Entity\User;
|
||||
use Xibo\Factory\FontFactory;
|
||||
use Xibo\Factory\MediaFactory;
|
||||
use Xibo\Helper\SanitizerService;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Support\Exception\ConfigurationException;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* MediaServiceInterface
|
||||
* Provides common functionality for library media
|
||||
*/
|
||||
interface MediaServiceInterface
|
||||
{
|
||||
/**
|
||||
* MediaService constructor.
|
||||
* @param ConfigServiceInterface $configService
|
||||
* @param LogServiceInterface $logService
|
||||
* @param StorageServiceInterface $store
|
||||
* @param SanitizerService $sanitizerService
|
||||
* @param PoolInterface $pool
|
||||
* @param MediaFactory $mediaFactory
|
||||
* @param FontFactory $fontFactory
|
||||
*/
|
||||
public function __construct(
|
||||
ConfigServiceInterface $configService,
|
||||
LogServiceInterface $logService,
|
||||
StorageServiceInterface $store,
|
||||
SanitizerService $sanitizerService,
|
||||
PoolInterface $pool,
|
||||
MediaFactory $mediaFactory,
|
||||
FontFactory $fontFactory
|
||||
);
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
public function setUser(User $user): MediaServiceInterface;
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function getUser(): User;
|
||||
|
||||
/**
|
||||
* @return PoolInterface
|
||||
*/
|
||||
public function getPool() : PoolInterface;
|
||||
|
||||
/**
|
||||
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
|
||||
* @return MediaServiceInterface
|
||||
*/
|
||||
public function setDispatcher(EventDispatcherInterface $dispatcher): MediaServiceInterface;
|
||||
|
||||
/**
|
||||
* Library Usage
|
||||
* @return int
|
||||
*/
|
||||
public function libraryUsage(): int;
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
* @throws \Xibo\Support\Exception\ConfigurationException
|
||||
*/
|
||||
public function initLibrary(): MediaServiceInterface;
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
* @throws \Xibo\Support\Exception\LibraryFullException
|
||||
*/
|
||||
public function checkLibraryOrQuotaFull($isCheckUser = false): MediaServiceInterface;
|
||||
|
||||
/**
|
||||
* @param $size
|
||||
* @return \Xibo\Service\MediaService
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function checkMaxUploadSize($size): MediaServiceInterface;
|
||||
|
||||
/**
|
||||
* Get download info for a URL
|
||||
* we're looking for the file size and the extension
|
||||
* @param $url
|
||||
* @return array
|
||||
*/
|
||||
public function getDownloadInfo($url): array;
|
||||
|
||||
/**
|
||||
* @return array|mixed
|
||||
* @throws ConfigurationException
|
||||
* @throws \Xibo\Support\Exception\DuplicateEntityException
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function updateFontsCss();
|
||||
|
||||
/**
|
||||
* @param $libraryFolder
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public static function ensureLibraryExists($libraryFolder);
|
||||
|
||||
/**
|
||||
* Remove temporary files
|
||||
*/
|
||||
public function removeTempFiles();
|
||||
|
||||
/**
|
||||
* Removes all expired media files
|
||||
* @throws NotFoundException
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function removeExpiredFiles();
|
||||
}
|
||||
240
lib/Service/NullLogService.php
Normal file
240
lib/Service/NullLogService.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?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\Service;
|
||||
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Class NullLogService
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
class NullLogService implements LogServiceInterface
|
||||
{
|
||||
/**
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
private $log;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __construct($logger, $mode = 'production')
|
||||
{
|
||||
$this->log = $logger;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getLoggerInterface(): LoggerInterface
|
||||
{
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setUserId($userId)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setIpAddress($ip)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setMode($mode)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function audit($entity, $entityId, $message, $object)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sql
|
||||
* @param $params
|
||||
* @param false $logAsError
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function sql($sql, $params, $logAsError = false)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function debug($object)
|
||||
{
|
||||
// Get the calling class / function
|
||||
$this->log->debug($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function notice($object)
|
||||
{
|
||||
$this->log->notice($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function info($object)
|
||||
{
|
||||
$this->log->info($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function warning($object)
|
||||
{
|
||||
$this->log->warning($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function error($object)
|
||||
{
|
||||
$this->log->error($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function critical($object)
|
||||
{
|
||||
$this->log->critical($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function alert($object)
|
||||
{
|
||||
$this->log->alert($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function emergency($object)
|
||||
{
|
||||
$this->log->emergency($this->prepare($object, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
private function prepare($object, $args)
|
||||
{
|
||||
if (is_string($object)) {
|
||||
array_shift($args);
|
||||
|
||||
if (count($args) > 0)
|
||||
$object = vsprintf($object, $args);
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public static function resolveLogLevel($level)
|
||||
{
|
||||
switch (strtolower($level)) {
|
||||
|
||||
case 'emergency':
|
||||
return Logger::EMERGENCY;
|
||||
|
||||
case 'alert':
|
||||
return Logger::ALERT;
|
||||
|
||||
case 'critical':
|
||||
return Logger::CRITICAL;
|
||||
|
||||
case 'warning':
|
||||
return Logger::WARNING;
|
||||
|
||||
case 'notice':
|
||||
return Logger::NOTICE;
|
||||
|
||||
case 'info':
|
||||
return Logger::INFO;
|
||||
|
||||
case 'debug':
|
||||
return Logger::DEBUG;
|
||||
|
||||
case 'error':
|
||||
default:
|
||||
return Logger::ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function setLevel($level)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function getUserId(): ?int
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getSessionHistoryId(): ?int
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRequestId(): ?int
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setSessionHistoryId($sessionHistoryId)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function setRequestId($requestId)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
196
lib/Service/PlayerActionService.php
Normal file
196
lib/Service/PlayerActionService.php
Normal file
@@ -0,0 +1,196 @@
|
||||
<?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\Service;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Xibo\Entity\Display;
|
||||
use Xibo\Support\Exception\ConfigurationException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\XMR\PlayerAction;
|
||||
use Xibo\XMR\PlayerActionException;
|
||||
|
||||
/**
|
||||
* Class PlayerActionService
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
class PlayerActionService implements PlayerActionServiceInterface
|
||||
{
|
||||
private ?string $xmrAddress;
|
||||
|
||||
/** @var PlayerAction[] */
|
||||
private array $actions = [];
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ConfigServiceInterface $config,
|
||||
private readonly LogServiceInterface $log,
|
||||
private readonly bool $triggerPlayerActions
|
||||
) {
|
||||
$this->xmrAddress = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Config
|
||||
* @return ConfigServiceInterface
|
||||
*/
|
||||
private function getConfig(): ConfigServiceInterface
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function sendAction($displays, $action): void
|
||||
{
|
||||
if (!$this->triggerPlayerActions) {
|
||||
return;
|
||||
}
|
||||
|
||||
// XMR network address
|
||||
if ($this->xmrAddress == null) {
|
||||
$this->xmrAddress = $this->getConfig()->getSetting('XMR_ADDRESS');
|
||||
}
|
||||
|
||||
if (empty($this->xmrAddress)) {
|
||||
throw new InvalidArgumentException(__('XMR address is not set'), 'xmrAddress');
|
||||
}
|
||||
|
||||
if (!is_array($displays)) {
|
||||
$displays = [$displays];
|
||||
}
|
||||
|
||||
// Send a message to all displays
|
||||
foreach ($displays as $display) {
|
||||
/* @var Display $display */
|
||||
$isEncrypt = false;
|
||||
|
||||
if ($display->xmrChannel == '') {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
__('%s is not configured or ready to receive push commands over XMR. Please contact your administrator.'),//phpcs:ignore
|
||||
$display->display
|
||||
),
|
||||
'xmrChannel'
|
||||
);
|
||||
}
|
||||
|
||||
// If we are using the old ZMQ XMR service, we also need to encrypt the message
|
||||
if (!$display->isWebSocketXmrSupported()) {
|
||||
// We also need a xmrPubKey
|
||||
$isEncrypt = true;
|
||||
|
||||
if ($display->xmrPubKey == '') {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
__('%s is not configured or ready to receive push commands over XMR. Please contact your administrator.'),//phpcs:ignore
|
||||
$display->display
|
||||
),
|
||||
'xmrPubKey'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Do not send anything if XMR is disabled.
|
||||
if (($isEncrypt && $this->getConfig()->getSetting('XMR_WS_ADDRESS') === 'DISABLED')
|
||||
|| (!$isEncrypt && $this->getConfig()->getSetting('XMR_PUB_ADDRESS') === 'DISABLED')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$displayAction = clone $action;
|
||||
|
||||
try {
|
||||
$displayAction->setIdentity($display->xmrChannel, $isEncrypt, $display->xmrPubKey ?? null);
|
||||
} catch (\Exception $exception) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
__('%s Invalid XMR registration'),
|
||||
$display->display
|
||||
),
|
||||
'xmrPubKey'
|
||||
);
|
||||
}
|
||||
|
||||
// Add to collection
|
||||
$this->actions[] = $displayAction;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getQueue(): array
|
||||
{
|
||||
return $this->actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function processQueue(): void
|
||||
{
|
||||
if (count($this->actions) > 0) {
|
||||
$this->log->debug('Player Action Service is looking to send %d actions', count($this->actions));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// XMR network address
|
||||
if ($this->xmrAddress == null) {
|
||||
$this->xmrAddress = $this->getConfig()->getSetting('XMR_ADDRESS');
|
||||
}
|
||||
|
||||
$client = new Client($this->config->getGuzzleProxy([
|
||||
'base_uri' => $this->getConfig()->getSetting('XMR_ADDRESS'),
|
||||
]));
|
||||
|
||||
$failures = 0;
|
||||
|
||||
// TODO: could I send them all in one request instead?
|
||||
foreach ($this->actions as $action) {
|
||||
/** @var PlayerAction $action */
|
||||
try {
|
||||
// Send each action
|
||||
$client->post('/', [
|
||||
'json' => $action->finaliseMessage(),
|
||||
]);
|
||||
} catch (GuzzleException | PlayerActionException $e) {
|
||||
$this->log->error('Player action connection failed. E = ' . $e->getMessage());
|
||||
$failures++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($failures > 0) {
|
||||
throw new ConfigurationException(
|
||||
sprintf(
|
||||
__('%d of %d player actions failed'),
|
||||
$failures,
|
||||
count($this->actions)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
lib/Service/PlayerActionServiceInterface.php
Normal file
56
lib/Service/PlayerActionServiceInterface.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\Service;
|
||||
|
||||
use Xibo\Entity\Display;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\XMR\PlayerAction;
|
||||
|
||||
/**
|
||||
* Interface PlayerActionServiceInterface
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
interface PlayerActionServiceInterface
|
||||
{
|
||||
/**
|
||||
* PlayerActionHelper constructor.
|
||||
*/
|
||||
public function __construct(ConfigServiceInterface $config, LogServiceInterface $log, bool $triggerPlayerActions);
|
||||
|
||||
/**
|
||||
* @param Display[]|Display $displays
|
||||
* @param PlayerAction $action
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function sendAction($displays, $action): void;
|
||||
|
||||
/**
|
||||
* Get the queue
|
||||
*/
|
||||
public function getQueue(): array;
|
||||
|
||||
/**
|
||||
* Process the Queue of Actions
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function processQueue(): void;
|
||||
}
|
||||
431
lib/Service/ReportService.php
Normal file
431
lib/Service/ReportService.php
Normal file
@@ -0,0 +1,431 @@
|
||||
<?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\Service;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Xibo\Entity\ReportResult;
|
||||
use Xibo\Event\ConnectorReportEvent;
|
||||
use Xibo\Factory\SavedReportFactory;
|
||||
use Xibo\Helper\SanitizerService;
|
||||
use Xibo\Report\ReportInterface;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Storage\TimeSeriesStoreInterface;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class ReportScheduleService
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
class ReportService implements ReportServiceInterface
|
||||
{
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
public $container;
|
||||
|
||||
/**
|
||||
* @var StorageServiceInterface
|
||||
*/
|
||||
private $store;
|
||||
|
||||
/**
|
||||
* @var TimeSeriesStoreInterface
|
||||
*/
|
||||
private $timeSeriesStore;
|
||||
|
||||
/**
|
||||
* @var LogServiceInterface
|
||||
*/
|
||||
private $log;
|
||||
|
||||
/**
|
||||
* @var ConfigServiceInterface
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var SanitizerService
|
||||
*/
|
||||
private $sanitizer;
|
||||
|
||||
/**
|
||||
* @var SavedReportFactory
|
||||
*/
|
||||
private $savedReportFactory;
|
||||
|
||||
/** @var EventDispatcherInterface */
|
||||
private $dispatcher;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __construct($container, $store, $timeSeriesStore, $log, $config, $sanitizer, $savedReportFactory)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->store = $store;
|
||||
$this->timeSeriesStore = $timeSeriesStore;
|
||||
$this->log = $log;
|
||||
$this->config = $config;
|
||||
$this->sanitizer = $sanitizer;
|
||||
$this->savedReportFactory = $savedReportFactory;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function setDispatcher(EventDispatcherInterface $dispatcher): ReportServiceInterface
|
||||
{
|
||||
$this->dispatcher = $dispatcher;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDispatcher(): EventDispatcherInterface
|
||||
{
|
||||
if ($this->dispatcher === null) {
|
||||
$this->dispatcher = new EventDispatcher();
|
||||
}
|
||||
return $this->dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function listReports()
|
||||
{
|
||||
$reports = [];
|
||||
|
||||
$files = array_merge(glob(PROJECT_ROOT . '/reports/*.report'), glob(PROJECT_ROOT . '/custom/*.report'));
|
||||
|
||||
foreach ($files as $file) {
|
||||
$config = json_decode(file_get_contents($file));
|
||||
$config->file = Str::replaceFirst(PROJECT_ROOT, '', $file);
|
||||
|
||||
// Compatibility check
|
||||
if (!isset($config->feature) || !isset($config->category)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if only allowed for admin
|
||||
if ($this->container->get('user')->userTypeId != 1) {
|
||||
if (isset($config->adminOnly) && !empty($config->adminOnly)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check Permissions
|
||||
if (!$this->container->get('user')->featureEnabled($config->feature)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$reports[$config->category][] = $config;
|
||||
}
|
||||
|
||||
$this->log->debug('Reports found in total: '.count($reports));
|
||||
|
||||
// Get reports that are allowed by connectors
|
||||
$event = new ConnectorReportEvent();
|
||||
$this->getDispatcher()->dispatch($event, ConnectorReportEvent::$NAME);
|
||||
$connectorReports = $event->getReports();
|
||||
|
||||
// Merge built in reports and connector reports
|
||||
if (count($connectorReports) > 0) {
|
||||
$reports = array_merge($reports, $connectorReports);
|
||||
}
|
||||
|
||||
foreach ($reports as $k => $report) {
|
||||
usort($report, function ($a, $b) {
|
||||
|
||||
if (empty($a->sort_order) || empty($b->sort_order)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $a->sort_order - $b->sort_order;
|
||||
});
|
||||
|
||||
$reports[$k] = $report;
|
||||
}
|
||||
|
||||
return $reports;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getReportByName($reportName)
|
||||
{
|
||||
foreach ($this->listReports() as $reports) {
|
||||
foreach ($reports as $report) {
|
||||
if ($report->name == $reportName) {
|
||||
$this->log->debug('Get report by name: '.json_encode($report, JSON_PRETTY_PRINT));
|
||||
|
||||
return $report;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//throw error
|
||||
throw new NotFoundException(__('Get Report By Name: No file to return'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getReportClass($reportName)
|
||||
{
|
||||
foreach ($this->listReports() as $reports) {
|
||||
foreach ($reports as $report) {
|
||||
if ($report->name == $reportName) {
|
||||
if ($report->class == '') {
|
||||
throw new NotFoundException(__('Report class not found'));
|
||||
}
|
||||
$this->log->debug('Get report class: '.$report->class);
|
||||
|
||||
return $report->class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// throw error
|
||||
throw new NotFoundException(__('Get report class: No file to return'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function createReportObject($className)
|
||||
{
|
||||
if (!\class_exists($className)) {
|
||||
throw new NotFoundException(__('Class %s not found', $className));
|
||||
}
|
||||
|
||||
/** @var ReportInterface $object */
|
||||
$object = new $className();
|
||||
$object
|
||||
->setCommonDependencies(
|
||||
$this->store,
|
||||
$this->timeSeriesStore
|
||||
)
|
||||
->useLogger($this->log)
|
||||
->setFactories($this->container);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getReportScheduleFormData($reportName, Request $request)
|
||||
{
|
||||
$this->log->debug('Populate form title and hidden fields');
|
||||
|
||||
$className = $this->getReportClass($reportName);
|
||||
|
||||
$object = $this->createReportObject($className);
|
||||
|
||||
// Populate form title and hidden fields
|
||||
return $object->getReportScheduleFormData($this->sanitizer->getSanitizer($request->getParams()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setReportScheduleFormData($reportName, Request $request)
|
||||
{
|
||||
$this->log->debug('Set Report Schedule form data');
|
||||
|
||||
$className = $this->getReportClass($reportName);
|
||||
|
||||
$object = $this->createReportObject($className);
|
||||
|
||||
// Set Report Schedule form data
|
||||
return $object->setReportScheduleFormData($this->sanitizer->getSanitizer($request->getParams()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function generateSavedReportName($reportName, $filterCriteria)
|
||||
{
|
||||
$this->log->debug('Generate Saved Report name');
|
||||
|
||||
$className = $this->getReportClass($reportName);
|
||||
|
||||
$object = $this->createReportObject($className);
|
||||
|
||||
$filterCriteria = json_decode($filterCriteria, true);
|
||||
|
||||
return $object->generateSavedReportName($this->sanitizer->getSanitizer($filterCriteria));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getSavedReportResults($savedreportId, $reportName)
|
||||
{
|
||||
$className = $this->getReportClass($reportName);
|
||||
|
||||
$object = $this->createReportObject($className);
|
||||
|
||||
$savedReport = $this->savedReportFactory->getById($savedreportId);
|
||||
|
||||
// Open a zipfile and read the json
|
||||
$zipFile = $this->config->getSetting('LIBRARY_LOCATION') .'savedreport/'. $savedReport->fileName;
|
||||
|
||||
// Do some pre-checks on the arguments we have been provided
|
||||
if (!file_exists($zipFile)) {
|
||||
throw new InvalidArgumentException(__('File does not exist'));
|
||||
}
|
||||
|
||||
// Open the Zip file
|
||||
$zip = new \ZipArchive();
|
||||
if (!$zip->open($zipFile)) {
|
||||
throw new InvalidArgumentException(__('Unable to open ZIP'));
|
||||
}
|
||||
|
||||
// Get the reportscheduledetails
|
||||
$json = json_decode($zip->getFromName('reportschedule.json'), true);
|
||||
|
||||
// Retrieve the saved report result array
|
||||
$results = $object->getSavedReportResults($json, $savedReport);
|
||||
|
||||
$this->log->debug('Saved Report results'. json_encode($results, JSON_PRETTY_PRINT));
|
||||
|
||||
// Return data to build chart
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function convertSavedReportResults($savedreportId, $reportName)
|
||||
{
|
||||
$className = $this->getReportClass($reportName);
|
||||
|
||||
$object = $this->createReportObject($className);
|
||||
|
||||
$savedReport = $this->savedReportFactory->getById($savedreportId);
|
||||
|
||||
// Open a zipfile and read the json
|
||||
$zipFile = $this->config->getSetting('LIBRARY_LOCATION') . $savedReport->storedAs;
|
||||
|
||||
// Do some pre-checks on the arguments we have been provided
|
||||
if (!file_exists($zipFile)) {
|
||||
throw new InvalidArgumentException(__('File does not exist'));
|
||||
}
|
||||
|
||||
// Open the Zip file
|
||||
$zip = new \ZipArchive();
|
||||
if (!$zip->open($zipFile)) {
|
||||
throw new InvalidArgumentException(__('Unable to open ZIP'));
|
||||
}
|
||||
|
||||
// Get the old json (saved report)
|
||||
$oldjson = json_decode($zip->getFromName('reportschedule.json'), true);
|
||||
|
||||
// Restructure the old json to new json
|
||||
$json = $object->restructureSavedReportOldJson($oldjson);
|
||||
|
||||
// Format the JSON as schemaVersion 2
|
||||
$fileName = tempnam($this->config->getSetting('LIBRARY_LOCATION') . '/temp/', 'reportschedule');
|
||||
$out = fopen($fileName, 'w');
|
||||
fwrite($out, json_encode($json));
|
||||
fclose($out);
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$result = $zip->open($zipFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
|
||||
if ($result !== true) {
|
||||
throw new InvalidArgumentException(__('Can\'t create ZIP. Error Code: %s', $result));
|
||||
}
|
||||
|
||||
$zip->addFile($fileName, 'reportschedule.json');
|
||||
$zip->close();
|
||||
|
||||
// Remove the JSON file
|
||||
unlink($fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function runReport($reportName, $filterCriteria, $user)
|
||||
{
|
||||
$this->log->debug('Run the report to get results');
|
||||
|
||||
$className = $this->getReportClass($reportName);
|
||||
|
||||
$object = $this->createReportObject($className);
|
||||
|
||||
// Set userId
|
||||
$object->setUser($user);
|
||||
|
||||
$filterCriteria = json_decode($filterCriteria, true);
|
||||
|
||||
// Retrieve the result array
|
||||
return $object->getResults($this->sanitizer->getSanitizer($filterCriteria));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getReportEmailTemplate($reportName)
|
||||
{
|
||||
$className = $this->getReportClass($reportName);
|
||||
|
||||
$object = $this->createReportObject($className);
|
||||
|
||||
// Set Report Schedule form data
|
||||
return $object->getReportEmailTemplate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getSavedReportTemplate($reportName)
|
||||
{
|
||||
$className = $this->getReportClass($reportName);
|
||||
|
||||
$object = $this->createReportObject($className);
|
||||
|
||||
// Set Report Schedule form data
|
||||
return $object->getSavedReportTemplate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getReportChartScript($savedreportId, $reportName)
|
||||
{
|
||||
/* @var ReportResult $results */
|
||||
$results = $this->getSavedReportResults($savedreportId, $reportName);
|
||||
|
||||
$className = $this->getReportClass($reportName);
|
||||
|
||||
$object = $this->createReportObject($className);
|
||||
|
||||
// Set Report Schedule form data
|
||||
return $object->getReportChartScript($results);
|
||||
}
|
||||
}
|
||||
166
lib/Service/ReportServiceInterface.php
Normal file
166
lib/Service/ReportServiceInterface.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2019 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\Service;
|
||||
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\SavedReportFactory;
|
||||
use Xibo\Helper\SanitizerService;
|
||||
use Xibo\Report\ReportInterface;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Storage\TimeSeriesStoreInterface;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
|
||||
/**
|
||||
* Interface ReportServiceInterface
|
||||
* @package Xibo\Service
|
||||
*/
|
||||
interface ReportServiceInterface
|
||||
{
|
||||
/**
|
||||
* ReportServiceInterface constructor.
|
||||
* @param \Psr\Container\ContainerInterface $app
|
||||
* @param StorageServiceInterface $store
|
||||
* @param TimeSeriesStoreInterface $timeSeriesStore
|
||||
* @param LogServiceInterface $log
|
||||
* @param ConfigServiceInterface $config
|
||||
* @param SanitizerService $sanitizer
|
||||
* @param SavedReportFactory $savedReportFactory
|
||||
*/
|
||||
public function __construct(
|
||||
$app,
|
||||
$store,
|
||||
$timeSeriesStore,
|
||||
$log,
|
||||
$config,
|
||||
$sanitizer,
|
||||
$savedReportFactory
|
||||
);
|
||||
|
||||
/**
|
||||
* List all reports that are available
|
||||
* @return array
|
||||
*/
|
||||
public function listReports();
|
||||
|
||||
/**
|
||||
* Get report by report name
|
||||
* @param string $reportName
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function getReportByName($reportName);
|
||||
|
||||
/**
|
||||
* Get report class by report name
|
||||
* @param string $reportName
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function getReportClass($reportName);
|
||||
|
||||
/**
|
||||
* Create the report object by report classname
|
||||
* @param string $className
|
||||
* @throws GeneralException
|
||||
* @return ReportInterface
|
||||
*/
|
||||
public function createReportObject($className);
|
||||
|
||||
/**
|
||||
* Populate form title and hidden fields
|
||||
* @param string $reportName
|
||||
* @param Request $request
|
||||
* @throws GeneralException
|
||||
* @return array
|
||||
*/
|
||||
public function getReportScheduleFormData($reportName, Request $request);
|
||||
|
||||
/**
|
||||
* Set Report Schedule form data
|
||||
* @param string $reportName
|
||||
* @param Request $request
|
||||
* @throws GeneralException
|
||||
* @return array
|
||||
*/
|
||||
public function setReportScheduleFormData($reportName, Request $request);
|
||||
|
||||
/**
|
||||
* Generate saved report name
|
||||
* @param string $reportName
|
||||
* @param string $filterCriteria
|
||||
* @throws GeneralException
|
||||
* @return string
|
||||
*/
|
||||
public function generateSavedReportName($reportName, $filterCriteria);
|
||||
|
||||
/**
|
||||
* Get saved report results
|
||||
* @param int $savedreportId
|
||||
* @param string $reportName
|
||||
* @throws GeneralException
|
||||
* @return array
|
||||
*/
|
||||
public function getSavedReportResults($savedreportId, $reportName);
|
||||
|
||||
/**
|
||||
* Convert saved report results from old schema 1 to schema version 2
|
||||
* @param int $savedreportId
|
||||
* @param string $reportName
|
||||
* @throws GeneralException
|
||||
* @return array
|
||||
*/
|
||||
public function convertSavedReportResults($savedreportId, $reportName);
|
||||
|
||||
/**
|
||||
* Run the report
|
||||
* @param string $reportName
|
||||
* @param string $filterCriteria
|
||||
* @param \Xibo\Entity\User $user
|
||||
* @throws GeneralException
|
||||
* @return array
|
||||
*/
|
||||
public function runReport($reportName, $filterCriteria, $user);
|
||||
|
||||
/**
|
||||
* Get report email template twig file name
|
||||
* @param string $reportName
|
||||
* @throws GeneralException
|
||||
* @return string
|
||||
*/
|
||||
public function getReportEmailTemplate($reportName);
|
||||
|
||||
/**
|
||||
* Get report email template twig file name
|
||||
* @param string $reportName
|
||||
* @throws GeneralException
|
||||
* @return string
|
||||
*/
|
||||
public function getSavedReportTemplate($reportName);
|
||||
|
||||
/**
|
||||
* Get chart script
|
||||
* @param int $savedreportId
|
||||
* @param string $reportName
|
||||
* @throws GeneralException
|
||||
* @return string
|
||||
*/
|
||||
public function getReportChartScript($savedreportId, $reportName);
|
||||
}
|
||||
59
lib/Service/UploadService.php
Normal file
59
lib/Service/UploadService.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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\Service;
|
||||
|
||||
use Xibo\Helper\ApplicationState;
|
||||
use Xibo\Helper\UploadHandler;
|
||||
|
||||
/**
|
||||
* Upload Service to scaffold an upload handler
|
||||
*/
|
||||
class UploadService
|
||||
{
|
||||
/**
|
||||
* UploadService constructor.
|
||||
* @param string $uploadDir
|
||||
* @param array $settings
|
||||
* @param LogServiceInterface $logger
|
||||
* @param ApplicationState $state
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $uploadDir,
|
||||
private readonly array $settings,
|
||||
private readonly LogServiceInterface $logger,
|
||||
private readonly ApplicationState $state
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new upload handler
|
||||
* @return UploadHandler
|
||||
*/
|
||||
public function createUploadHandler(): UploadHandler
|
||||
{
|
||||
// Blue imp requires an extra /
|
||||
$handler = new UploadHandler($this->uploadDir, $this->logger->getLoggerInterface(), $this->settings, false);
|
||||
|
||||
return $handler->setState($this->state);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user