Files

1577 lines
50 KiB
PHP
Raw Permalink Normal View History

2025-12-02 10:32:59 -05:00
<?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\Entity;
use Carbon\Carbon;
use Respect\Validation\Validator as v;
use Stash\Interfaces\PoolInterface;
use Xibo\Event\DisplayGroupLoadEvent;
use Xibo\Event\TriggerTaskEvent;
use Xibo\Factory\DisplayFactory;
use Xibo\Factory\DisplayGroupFactory;
use Xibo\Factory\DisplayProfileFactory;
use Xibo\Factory\FolderFactory;
use Xibo\Factory\LayoutFactory;
use Xibo\Helper\DateFormatHelper;
use Xibo\Service\ConfigServiceInterface;
use Xibo\Service\LogServiceInterface;
use Xibo\Storage\StorageServiceInterface;
use Xibo\Support\Exception\DeadlockException;
use Xibo\Support\Exception\GeneralException;
use Xibo\Support\Exception\InvalidArgumentException;
use Xibo\Support\Exception\NotFoundException;
/**
* Class Display
* @package Xibo\Entity
*
* @SWG\Definition()
*/
class Display implements \JsonSerializable
{
public static $STATUS_DONE = 1;
public static $STATUS_DOWNLOADING = 2;
public static $STATUS_PENDING = 3;
use EntityTrait;
use TagLinkTrait;
/**
* @SWG\Property(description="The ID of this Display")
* @var int
*/
public $displayId;
/**
* @SWG\Property(description="The Display Type ID of this Display")
* @var int
*/
public $displayTypeId;
/**
* @SWG\Property(description="The Venue ID of this Display")
* @var int
*/
public $venueId;
/**
* @SWG\Property(description="The Location Address of this Display")
* @var string
*/
public $address;
/**
* @SWG\Property(description="Is this Display mobile?")
* @var int
*/
public $isMobile;
/**
* @SWG\Property(description="The Languages supported in this display location")
* @var string
*/
public $languages;
/**
* @SWG\Property(description="The type of this Display")
* @var string
*/
public $displayType;
/**
* @SWG\Property(description="The screen size of this Display")
* @var int
*/
public $screenSize;
/**
* @SWG\Property(description="Is this Display Outdoor?")
* @var int
*/
public $isOutdoor;
/**
* @SWG\Property(description="The custom ID (an Id of any external system) of this Display")
* @var string
*/
public $customId;
/**
* @SWG\Property(description="The Cost Per Play of this Display")
* @var double
*/
public $costPerPlay;
/**
* @SWG\Property(description="The Impressions Per Play of this Display")
* @var double
*/
public $impressionsPerPlay;
/**
* @SWG\Property(description="Optional Reference 1")
* @var string
*/
public $ref1;
/**
* @SWG\Property(description="Optional Reference 2")
* @var string
*/
public $ref2;
/**
* @SWG\Property(description="Optional Reference 3")
* @var string
*/
public $ref3;
/**
* @SWG\Property(description="Optional Reference 4")
* @var string
*/
public $ref4;
/**
* @SWG\Property(description="Optional Reference 5")
* @var string
*/
public $ref5;
/**
* @SWG\Property(description="Flag indicating whether this Display is recording Auditing Information from XMDS")
* @var int
*/
public $auditingUntil;
/**
* @SWG\Property(description="The Name of this Display")
* @var string
*/
public $display;
/**
* @SWG\Property(description="The Description of this Display")
* @var string
*/
public $description;
/**
* @SWG\Property(description="The ID of the Default Layout")
* @var int
*/
public $defaultLayoutId = 4;
/**
* @SWG\Property(description="The Display Unique Identifier also called hardware key")
* @var string
*/
public $license;
/**
* @SWG\Property(description="A flag indicating whether this Display is licensed or not")
* @var int
*/
public $licensed;
private $currentlyLicensed;
/**
* @SWG\Property(description="A flag indicating whether this Display is currently logged in")
* @var int
*/
public $loggedIn;
/**
* @SWG\Property(description="A timestamp in CMS time for the last time the Display accessed XMDS")
* @var int
*/
public $lastAccessed;
/**
* @SWG\Property(description="A flag indicating whether the default layout is interleaved with the Schedule")
* @var int
*/
public $incSchedule;
/**
* @SWG\Property(description="A flag indicating whether the Display will send email alerts.")
* @var int
*/
public $emailAlert;
/**
* @SWG\Property(description="A timeout in seconds for the Display to send email alerts.")
* @var int
*/
public $alertTimeout;
/**
* @SWG\Property(description="The MAC Address of the Display")
* @var string
*/
public $clientAddress;
/**
* @SWG\Property(description="The media inventory status of the Display")
* @var int
*/
public $mediaInventoryStatus;
/**
* @SWG\Property(description="The current Mac Address of the Player")
* @var string
*/
public $macAddress;
/**
* @SWG\Property(description="A timestamp indicating the last time the Mac Address changed")
* @var int
*/
public $lastChanged;
/**
* @SWG\Property(description="A count of Mac Address changes")
* @var int
*/
public $numberOfMacAddressChanges;
/**
* @SWG\Property(description="A timestamp indicating the last time a WOL command was sent")
* @var int
*/
public $lastWakeOnLanCommandSent;
/**
* @SWG\Property(description="A flag indicating whether Wake On Lan is enabled")
* @var int
*/
public $wakeOnLanEnabled;
/**
* @SWG\Property(description="A h:i string indicating the time to send a WOL command")
* @var string
*/
public $wakeOnLanTime;
/**
* @SWG\Property(description="The broad cast address for this Display")
* @var string
*/
public $broadCastAddress;
/**
* @SWG\Property(description="The secureOn WOL settings for this display.")
* @var string
*/
public $secureOn;
/**
* @SWG\Property(description="The CIDR WOL settings for this display")
* @var string
*/
public $cidr;
/**
* @SWG\Property(description="The display Latitude")
* @var double
*/
public $latitude;
/**
* @SWG\Property(description="The display longitude")
* @var double
*/
public $longitude;
/**
* @SWG\Property(description="A string representing the player type")
* @var string
*/
public $clientType;
/**
* @SWG\Property(description="A string representing the player version")
* @var string
*/
public $clientVersion;
/**
* @SWG\Property(description="A number representing the Player version code")
* @var int
*/
public $clientCode;
/**
* @SWG\Property(description="The display settings profile ID for this Display")
* @var int
*/
public $displayProfileId;
/**
* @SWG\Property(description="The current layout ID reported via XMDS")
* @var int
*/
public $currentLayoutId;
/**
* @SWG\Property(description="A flag indicating that a screen shot should be taken by the Player")
* @var int
*/
public $screenShotRequested;
/**
* @SWG\Property(description="The number of bytes of storage available on the device.")
* @var int
*/
public $storageAvailableSpace;
/**
* @SWG\Property(description="The number of bytes of storage in total on the device")
* @var int
*/
public $storageTotalSpace;
/**
* @SWG\Property(description="The ID of the Display Group for this Device")
* @var int
*/
public $displayGroupId;
/**
* @SWG\Property(description="The current layout")
* @var string
*/
public $currentLayout;
/**
* @SWG\Property(description="The default layout")
* @var string
*/
public $defaultLayout;
/**
* @SWG\Property(description="The Display Groups this Display belongs to")
* @var DisplayGroup[]
*/
public $displayGroups = [];
/**
* @SWG\Property(description="The Player Subscription Channel")
* @var string
*/
public $xmrChannel;
/**
* @SWG\Property(description="The Player Public Key")
* @var string
*/
public $xmrPubKey;
/**
* @SWG\Property(description="The last command success, 0 = failure, 1 = success, 2 = unknown")
* @var int
*/
public $lastCommandSuccess = 0;
/**
* @SWG\Property(description="The Device Name for the device hardware associated with this Display")
* @var string
*/
public $deviceName;
/**
* @SWG\Property(description="The Display Timezone, or empty to use the CMS timezone")
* @var string
*/
public $timeZone;
/**
* @SWG\Property(description="Tags associated with this Display, array of TagLink objects")
* @var TagLink[]
*/
public $tags = [];
/**
* @SWG\Property(description="The configuration options that will overwrite Display Profile Config")
* @var string|array
*/
public $overrideConfig = [];
/**
* @SWG\Property(description="The display bandwidth limit")
* @var int
*/
public $bandwidthLimit;
/**
* @SWG\Property(description="The new CMS Address")
* @var string
*/
public $newCmsAddress;
/**
* @SWG\Property(description="The new CMS Key")
* @var string
*/
public $newCmsKey;
/**
* @SWG\Property(description="The orientation of the Display, either landscape or portrait")
* @var string
*/
public $orientation;
/**
* @SWG\Property(description="The resolution of the Display expressed as a string in the format WxH")
* @var string
*/
public $resolution;
/**
* @SWG\Property(description="Status of the commercial licence for this Display. 0 - Not licensed, 1 - licensed, 2 - trial licence, 3 - not applicable")
* @var int
*/
public $commercialLicence;
/**
* @SWG\Property(description="The TeamViewer serial number for this Display")
* @var string
*/
public $teamViewerSerial;
/**
* @SWG\Property(description="The Webkey serial number for this Display")
* @var string
*/
public $webkeySerial;
/**
* @SWG\Property(description="A comma separated list of groups/users with permissions to this Display")
* @var string
*/
public $groupsWithPermissions;
/**
* @SWG\Property(description="The datetime this entity was created")
* @var string
*/
public $createdDt;
/**
* @SWG\Property(description="The datetime this entity was last modified")
* @var string
*/
public $modifiedDt;
/**
* @SWG\Property(description="The id of the Folder this Display belongs to")
* @var int
*/
public $folderId;
/**
* @SWG\Property(description="The id of the Folder responsible for providing permissions for this Display")
* @var int
*/
public $permissionsFolderId;
/**
* @SWG\Property(description="The count of Player reported faults")
* @var int
*/
public $countFaults;
/**
* @SWG\Property(description="LAN IP Address, if available on the Player")
* @var string
*/
public $lanIpAddress;
/**
* @SWG\Property(description="The Display Group ID this Display is synced to")
* @var int
*/
public $syncGroupId;
/**
* @SWG\Property(description="The OS version of the Display")
* @var string
*/
public $osVersion;
/**
* @SWG\Property(description="The SDK version of the Display")
* @var string
*/
public $osSdk;
/**
* @SWG\Property(description="The manufacturer of the Display")
* @var string
*/
public $manufacturer;
/**
* @SWG\Property(description="The brand of the Display")
* @var string
*/
public $brand;
/**
* @SWG\Property(description="The model of the Display")
* @var string
*/
public $model;
/** @var array The configuration from the Display Profile */
private $profileConfig;
/** @var array Combined config */
private $combinedConfig;
/** @var \Xibo\Entity\DisplayProfile the resolved DisplayProfile for this Display */
private $_displayProfile;
private $datesToFormat = ['auditingUntil'];
/**
* Commands
* @var array[Command]
*/
private $commands = null;
public static $saveOptionsMinimum = ['validate' => false, 'audit' => false, 'setModifiedDt' => false];
/**
* @var ConfigServiceInterface
*/
private $config;
/**
* @var DisplayGroupFactory
*/
private $displayGroupFactory;
/**
* @var DisplayProfileFactory
*/
private $displayProfileFactory;
/**
* @var DisplayFactory
*/
private $displayFactory;
/**
* @var LayoutFactory
*/
private $layoutFactory;
/** @var FolderFactory */
private $folderFactory;
/**
* Entity constructor.
* @param StorageServiceInterface $store
* @param LogServiceInterface $log
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
* @param ConfigServiceInterface $config
* @param DisplayGroupFactory $displayGroupFactory
* @param DisplayProfileFactory $displayProfileFactory
* @param DisplayFactory $displayFactory
*/
public function __construct($store, $log, $dispatcher, $config, $displayGroupFactory, $displayProfileFactory, $displayFactory, $folderFactory)
{
$this->setCommonDependencies($store, $log, $dispatcher);
$this->excludeProperty('mediaInventoryXml');
$this->setPermissionsClass('Xibo\Entity\DisplayGroup');
$this->setCanChangeOwner(false);
$this->config = $config;
$this->displayGroupFactory = $displayGroupFactory;
$this->displayProfileFactory = $displayProfileFactory;
$this->displayFactory = $displayFactory;
$this->folderFactory = $folderFactory;
}
/**
* @return int
*/
public function getId()
{
return $this->displayGroupId;
}
public function getPermissionFolderId()
{
return $this->permissionsFolderId;
}
/**
* @return int
*/
public function getOwnerId()
{
// No owner
return 0;
}
/**
* Get the cache key
* @return string
*/
public static function getCachePrefix()
{
return 'display/';
}
/**
* Get the cache key
* @return string
*/
public function getCacheKey()
{
return self::getCachePrefix() . $this->displayId;
}
/**
* @return \Xibo\Entity\DisplayProfile
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function getDisplayProfile(): DisplayProfile
{
if ($this->_displayProfile === null) {
try {
if ($this->displayProfileId == 0) {
// Load the default profile
$displayProfile = $this->displayProfileFactory->getDefaultByType($this->clientType);
} else {
// Load the specified profile
$displayProfile = $this->displayProfileFactory->getById($this->displayProfileId);
}
} catch (NotFoundException) {
$this->getLog()->error('getDisplayProfile: Cannot get display profile, '
. $this->clientType . ' not found.');
$displayProfile = $this->displayProfileFactory->getUnknownProfile($this->clientType);
}
// Set our display profile
$this->_displayProfile = $displayProfile;
}
return $this->_displayProfile;
}
/**
* @return array
*/
public function getLanguages()
{
return empty($this->languages) ? [] : explode(',', $this->languages);
}
/**
* @return bool true is this display is a PWA
*/
public function isPwa(): bool
{
return $this->clientType === 'chromeOS';
}
/**
* @return bool true is this display supports WebSocket XMR
*/
public function isWebSocketXmrSupported(): bool
{
return $this->clientType === 'chromeOS'
|| ($this->clientType === 'windows' && $this->clientCode >= 406)
|| ($this->clientType === 'android' && $this->clientCode >= 408);
}
/**
* Is this display auditing?
* return bool
*/
public function isAuditing(): bool
{
$this->getLog()->debug(sprintf(
'Testing whether this display is auditing. %d vs %d.',
$this->auditingUntil,
Carbon::now()->format('U')
));
// Test $this->auditingUntil against the current date.
return (!empty($this->auditingUntil) && $this->auditingUntil >= Carbon::now()->format('U'));
}
/**
* Does this display has elevated log level?
* @return bool
* @throws NotFoundException
*/
public function isElevatedLogging(): bool
{
$elevatedUntil = $this->getSetting('elevateLogsUntil', 0);
$this->getLog()->debug(sprintf(
'Testing whether this display has elevated log level. %d vs %d.',
$elevatedUntil,
Carbon::now()->format('U')
));
return (!empty($elevatedUntil) && $elevatedUntil >= Carbon::now()->format('U'));
}
/**
* Get current log level for this Display
* @return string
* @throws NotFoundException
*/
public function getLogLevel(): string
{
$restingLogLevel = $this->getSetting('logLevel', 'error');
$isElevated = $this->isElevatedLogging();
return $isElevated ? 'audit' : $restingLogLevel;
}
/**
* Set the Media Status to Incomplete
*/
public function notify()
{
$this->getLog()->debug($this->display . ' requests notify');
$this->displayFactory->getDisplayNotifyService()->collectNow()->notifyByDisplayId($this->displayId);
}
/**
* Validate the Object as it stands
* @throws InvalidArgumentException
*/
public function validate()
{
if (!v::stringType()->notEmpty()->validate($this->display)) {
throw new InvalidArgumentException(__('Can not have a display without a name'), 'name');
}
if (!v::stringType()->notEmpty()->validate($this->license)) {
throw new InvalidArgumentException(__('Can not have a display without a hardware key'), 'license');
}
if ($this->wakeOnLanEnabled == 1 && $this->wakeOnLanTime == '') {
throw new InvalidArgumentException(
__('Wake on Lan is enabled, but you have not specified a time to wake the display'),
'wakeonlan'
);
}
// Broadcast Address
if ($this->broadCastAddress != '' && !v::ip()->validate($this->broadCastAddress)) {
throw new InvalidArgumentException(
__('BroadCast Address is not a valid IP Address'),
'broadCastAddress'
);
}
// CIDR
if (!empty($this->cidr) && !v::numeric()->between(0, 32)->validate($this->cidr)) {
throw new InvalidArgumentException(
__('CIDR subnet mask is not a number within the range of 0 to 32.'),
'cidr'
);
}
// secureOn
if ($this->secureOn != '') {
$this->secureOn = strtoupper($this->secureOn);
$this->secureOn = str_replace(':', '-', $this->secureOn);
if ((!preg_match('/([A-F0-9]{2}[-]){5}([0-9A-F]){2}/', $this->secureOn))
|| (strlen($this->secureOn) != 17)
) {
throw new InvalidArgumentException(
__('Pattern of secureOn-password is not "xx-xx-xx-xx-xx-xx" (x = digit or CAPITAL letter)'),
'secureOn'
);
}
}
// Mac Address Changes
if ($this->hasPropertyChanged('macAddress')) {
// Mac address change detected
$this->numberOfMacAddressChanges++;
$this->lastChanged = Carbon::now()->format('U');
}
// Lat/Long
if (!empty($this->longitude) && !v::longitude()->validate($this->longitude)) {
throw new InvalidArgumentException(__('The longitude entered is not valid.'), 'longitude');
}
if (!empty($this->latitude) && !v::latitude()->validate($this->latitude)) {
throw new InvalidArgumentException(__('The latitude entered is not valid.'), 'latitude');
}
if ($this->bandwidthLimit !== null && !v::intType()->min(0)->validate($this->bandwidthLimit)) {
throw new InvalidArgumentException(
__('Bandwidth limit must be a whole number greater than 0.'),
'bandwidthLimit'
);
}
// do we have default Layout set?
if (empty($this->defaultLayoutId)) {
// do we have global default Layout ?
$globalDefaultLayoutId = $this->config->getSetting('DEFAULT_LAYOUT');
if (!empty($globalDefaultLayoutId)) {
$this->getLog()->debug(
'No default Layout set on Display ID '
. $this->displayId
. ' falling back to global default Layout.'
);
$this->defaultLayoutId = $globalDefaultLayoutId;
$this->notify();
} else {
// we have no Default Layout and no global Default Layout
$this->getLog()->error(
'No global default Layout set and no default Layout set for Display ID ' . $this->displayId
);
throw new InvalidArgumentException(
__('Please set a Default Layout directly on this Display or in CMS Administrator Settings'),
'defaultLayoutId'
);
}
}
}
/**
* Check if there is display slot available, returns true when there are display slots available, return false if there are no display slots available
* @return boolean
*/
public function isDisplaySlotAvailable()
{
$maxDisplays = $this->config->GetSetting('MAX_LICENSED_DISPLAYS');
// Check the number of licensed displays
if ($maxDisplays > 0) {
$this->getLog()->debug(sprintf('Testing authorised displays against %d maximum. Currently authorised = %d, authorised = %d.', $maxDisplays, $this->currentlyLicensed, $this->licensed));
if ($this->currentlyLicensed != $this->licensed && $this->licensed == 1) {
$countLicensed = $this->getStore()->select('SELECT COUNT(DisplayID) AS CountLicensed FROM display WHERE licensed = 1', []);
$this->getLog()->debug(sprintf('There are %d authorised displays and we the maximum is %d', $countLicensed[0]['CountLicensed'], $maxDisplays));
if (intval($countLicensed[0]['CountLicensed']) + 1 > $maxDisplays) {
return false;
}
}
}
return true;
}
/**
* Load
* @throws NotFoundException
*/
public function load()
{
if ($this->loaded)
return;
// Load this displays group membership
$this->displayGroups = $this->displayGroupFactory->getByDisplayId($this->displayId);
$this->loaded = true;
}
/**
* Save the media inventory status
*/
public function saveMediaInventoryStatus()
{
try {
$this->getStore()->updateWithDeadlockLoop('UPDATE `display` SET mediaInventoryStatus = :mediaInventoryStatus WHERE displayId = :displayId', [
'mediaInventoryStatus' => $this->mediaInventoryStatus,
'displayId' => $this->displayId
]);
} catch (DeadlockException $deadlockException) {
$this->getLog()->error('Media Inventory Status save failed due to deadlock');
}
}
/**
* Save
* @param array $options
* @throws GeneralException
*/
public function save($options = [])
{
$options = array_merge([
'validate' => true,
'audit' => true,
'checkDisplaySlotAvailability' => true,
'setModifiedDt' => true,
], $options);
if ($options['validate']) {
$this->validate();
}
if ($options['checkDisplaySlotAvailability']) {
// Check if there are display slots available
$maxDisplays = $this->config->GetSetting('MAX_LICENSED_DISPLAYS');
if (!$this->isDisplaySlotAvailable()) {
throw new InvalidArgumentException(sprintf(
__('You have exceeded your maximum number of authorised displays. %d'),
$maxDisplays
), 'maxDisplays');
}
}
if ($this->displayId == null || $this->displayId == 0) {
$this->add();
} else {
$this->edit($options);
}
if ($options['audit'] && $this->getChangedProperties() != []) {
$this->getLog()->audit('Display', $this->displayId, 'Display Saved', $this->getChangedProperties());
}
// Trigger an update of all dynamic DisplayGroups?
if ($this->hasPropertyChanged('display') || $this->hasPropertyChanged('tags')) {
// Background update.
$this->getDispatcher()->dispatch(
new TriggerTaskEvent('\Xibo\XTR\MaintenanceRegularTask', 'DYNAMIC_DISPLAY_GROUP_ASSESSED'),
TriggerTaskEvent::$NAME
);
}
}
/**
* Delete
* @throws GeneralException
*/
public function delete()
{
$this->load();
// Delete references
$this->getStore()->update('DELETE FROM `display_media` WHERE displayId = :displayId', [
'displayId' => $this->displayId
]);
$this->getStore()->update('DELETE FROM `requiredfile` WHERE displayId = :displayId', [
'displayId' => $this->displayId
]);
$this->getStore()->update('DELETE FROM `player_faults` WHERE displayId = :displayId', [
'displayId' => $this->displayId
]);
$this->getStore()->update('DELETE FROM `schedule_sync` WHERE displayId = :displayId', [
'displayId' => $this->displayId
]);
// Remove our display from any groups it is assigned to
foreach ($this->displayGroups as $displayGroup) {
$this->getDispatcher()->dispatch(new DisplayGroupLoadEvent($displayGroup), DisplayGroupLoadEvent::$NAME);
$displayGroup->load();
$displayGroup->unassignDisplay($this);
$displayGroup->save(['validate' => false, 'manageDynamicDisplayLinks' => false, 'allowNotify' => false]);
}
// Delete our display specific group
$displayGroup = $this->displayGroupFactory->getById($this->displayGroupId);
$this->getDispatcher()->dispatch(new DisplayGroupLoadEvent($displayGroup), DisplayGroupLoadEvent::$NAME);
$displayGroup->delete();
// Delete the display
$this->getStore()->update('DELETE FROM `display` WHERE displayId = :displayId', [
'displayId' => $this->displayId
]);
$this->getLog()->audit('Display', $this->displayId, 'Display Deleted', [
'displayId' => $this->displayId,
'display' => $this->display,
]);
}
/**
* @throws GeneralException
* @throws NotFoundException
*/
private function add()
{
$this->displayId = $this->getStore()->insert('
INSERT INTO display (display, auditingUntil, defaultlayoutid, license, licensed, lastAccessed, inc_schedule, email_alert, alert_timeout, clientAddress, xmrChannel, xmrPubKey, lastCommandSuccess, macAddress, lastChanged, lastWakeOnLanCommandSent, client_type, client_version, client_code, overrideConfig, newCmsAddress, newCmsKey, commercialLicence, lanIpAddress, syncGroupId, osVersion, osSdk, manufacturer, brand, model)
VALUES (:display, :auditingUntil, :defaultlayoutid, :license, :licensed, :lastAccessed, :inc_schedule, :email_alert, :alert_timeout, :clientAddress, :xmrChannel, :xmrPubKey, :lastCommandSuccess, :macAddress, :lastChanged, :lastWakeOnLanCommandSent, :clientType, :clientVersion, :clientCode, :overrideConfig, :newCmsAddress, :newCmsKey, :commercialLicence, :lanIpAddress, :syncGroupId, :osVersion, :osSdk, :manufacturer, :brand, :model)
', [
'display' => $this->display,
'auditingUntil' => 0,
'defaultlayoutid' => $this->defaultLayoutId,
'license' => $this->license,
'licensed' => $this->licensed,
'lastAccessed' => $this->lastAccessed,
'inc_schedule' => 0,
'email_alert' => 0,
'alert_timeout' => 0,
'clientAddress' => $this->clientAddress,
'xmrChannel' => $this->xmrChannel,
'xmrPubKey' => ($this->xmrPubKey === null) ? '' : $this->xmrPubKey,
'lastCommandSuccess' => $this->lastCommandSuccess,
'macAddress' => $this->macAddress,
'lastChanged' => ($this->lastChanged === null) ? 0 : $this->lastChanged,
'lastWakeOnLanCommandSent' => ($this->lastWakeOnLanCommandSent === null) ? 0 : $this->lastWakeOnLanCommandSent,
'clientType' => $this->clientType,
'clientVersion' => $this->clientVersion,
'clientCode' => $this->clientCode,
'overrideConfig' => ($this->overrideConfig == '') ? null : json_encode($this->overrideConfig),
'newCmsAddress' => null,
'newCmsKey' => null,
'commercialLicence' => $this->commercialLicence,
'lanIpAddress' => empty($this->lanIpAddress) ? null : $this->lanIpAddress,
'syncGroupId' => empty($this->syncGroupId) ? null : $this->syncGroupId,
'osVersion' => $this->osVersion,
'osSdk' => $this->osSdk,
'manufacturer' => $this->manufacturer,
'brand' => $this->brand,
'model' => $this->model,
]);
$displayGroup = $this->displayGroupFactory->create();
$displayGroup->displayGroup = $this->display;
$displayGroup->tags = $this->tags;
// this is added from xmds, by default new displays will end up in root folder.
// Can be overridden per DISPLAY_DEFAULT_FOLDER setting
$folderId = $this->folderId ?? 1;
// If folderId is not set to Root Folder
// We need to check what permissionsFolderId should be set on the Display Group
if ($folderId !== 1) {
// just in case protect against no longer existing Folder.
try {
$folder = $this->folderFactory->getById($folderId, 0);
$displayGroup->folderId = $folder->getId();
$displayGroup->permissionsFolderId = $folder->getPermissionFolderIdOrThis();
} catch (NotFoundException $e) {
$this->getLog()->error('Display Default Folder no longer exists');
// if the Folder from settings no longer exists, default to Root Folder.
$displayGroup->folderId = 1;
$displayGroup->permissionsFolderId = 1;
}
} else {
$displayGroup->folderId = 1;
$displayGroup->permissionsFolderId = 1;
}
$displayGroup->setDisplaySpecificDisplay($this);
$this->getLog()->debug('Creating display specific group with userId ' . $displayGroup->userId);
$displayGroup->save();
}
/**
* @throws GeneralException
* @throws NotFoundException
*/
private function edit($options = [])
{
$this->getStore()->update('
UPDATE display
SET display = :display,
defaultlayoutid = :defaultLayoutId,
displayTypeId = :displayTypeId,
venueId = :venueId,
address = :address,
isMobile = :isMobile,
languages = :languages,
screenSize = :screenSize,
isOutdoor = :isOutdoor,
`customId` = :customId,
costPerPlay = :costPerPlay,
impressionsPerPlay = :impressionsPerPlay,
inc_schedule = :incSchedule,
license = :license,
licensed = :licensed,
auditingUntil = :auditingUntil,
email_alert = :emailAlert,
alert_timeout = :alertTimeout,
WakeOnLan = :wakeOnLanEnabled,
WakeOnLanTime = :wakeOnLanTime,
lastWakeOnLanCommandSent = :lastWakeOnLanCommandSent,
BroadCastAddress = :broadCastAddress,
SecureOn = :secureOn,
Cidr = :cidr,
GeoLocation = POINT(:latitude, :longitude),
displayprofileid = :displayProfileId,
lastaccessed = :lastAccessed,
loggedin = :loggedIn,
ClientAddress = :clientAddress,
MediaInventoryStatus = :mediaInventoryStatus,
client_type = :clientType,
client_version = :clientVersion,
client_code = :clientCode,
MacAddress = :macAddress,
LastChanged = :lastChanged,
NumberOfMacAddressChanges = :numberOfMacAddressChanges,
screenShotRequested = :screenShotRequested,
storageAvailableSpace = :storageAvailableSpace,
storageTotalSpace = :storageTotalSpace,
osVersion = :osVersion,
osSdk = :osSdk,
manufacturer = :manufacturer,
brand = :brand,
model = :model,
xmrChannel = :xmrChannel,
xmrPubKey = :xmrPubKey,
`lastCommandSuccess` = :lastCommandSuccess,
`deviceName` = :deviceName,
`timeZone` = :timeZone,
`overrideConfig` = :overrideConfig,
`newCmsAddress` = :newCmsAddress,
`newCmsKey` = :newCmsKey,
`orientation` = :orientation,
`resolution` = :resolution,
`commercialLicence` = :commercialLicence,
`teamViewerSerial` = :teamViewerSerial,
`webkeySerial` = :webkeySerial,
`lanIpAddress` = :lanIpAddress,
`syncGroupId` = :syncGroupId
WHERE displayid = :displayId
', [
'display' => $this->display,
'defaultLayoutId' => $this->defaultLayoutId,
'displayTypeId' => $this->displayTypeId === 0 ? null : $this->displayTypeId,
'venueId' => $this->venueId === 0 ? null : $this->venueId,
'address' => $this->address,
'isMobile' => $this->isMobile,
'languages' => $this->languages,
'screenSize' => $this->screenSize,
'isOutdoor' => $this->isOutdoor,
'customId' => $this->customId,
'costPerPlay' => $this->costPerPlay,
'impressionsPerPlay' => $this->impressionsPerPlay,
'incSchedule' => ($this->incSchedule == null) ? 0 : $this->incSchedule,
'license' => $this->license,
'licensed' => $this->licensed,
'auditingUntil' => ($this->auditingUntil == null) ? 0 : $this->auditingUntil,
'emailAlert' => $this->emailAlert,
'alertTimeout' => $this->alertTimeout,
'wakeOnLanEnabled' => $this->wakeOnLanEnabled,
'wakeOnLanTime' => $this->wakeOnLanTime,
'lastWakeOnLanCommandSent' => $this->lastWakeOnLanCommandSent,
'broadCastAddress' => $this->broadCastAddress,
'secureOn' => $this->secureOn,
'cidr' => $this->cidr,
'latitude' => $this->latitude,
'longitude' => $this->longitude,
'displayProfileId' => ($this->displayProfileId == null) ? null : $this->displayProfileId,
'lastAccessed' => $this->lastAccessed,
'loggedIn' => $this->loggedIn,
'clientAddress' => $this->clientAddress,
'mediaInventoryStatus' => $this->mediaInventoryStatus,
'clientType' => $this->clientType,
'clientVersion' => $this->clientVersion,
'clientCode' => $this->clientCode,
'macAddress' => $this->macAddress,
'lastChanged' => $this->lastChanged,
'numberOfMacAddressChanges' => $this->numberOfMacAddressChanges,
'screenShotRequested' => $this->screenShotRequested,
'storageAvailableSpace' => $this->storageAvailableSpace,
'storageTotalSpace' => $this->storageTotalSpace,
'xmrChannel' => $this->xmrChannel,
'xmrPubKey' => ($this->xmrPubKey === null) ? '' : $this->xmrPubKey,
'lastCommandSuccess' => $this->lastCommandSuccess,
'deviceName' => $this->deviceName,
'timeZone' => $this->timeZone,
'overrideConfig' => ($this->overrideConfig == '') ? null : json_encode($this->overrideConfig),
'newCmsAddress' => $this->newCmsAddress,
'newCmsKey' => $this->newCmsKey,
'orientation' => $this->orientation,
'resolution' => $this->resolution,
'commercialLicence' => $this->commercialLicence,
'teamViewerSerial' => empty($this->teamViewerSerial) ? null : $this->teamViewerSerial,
'webkeySerial' => empty($this->webkeySerial) ? null : $this->webkeySerial,
'lanIpAddress' => empty($this->lanIpAddress) ? null : $this->lanIpAddress,
'syncGroupId' => empty($this->syncGroupId) ? null : $this->syncGroupId,
'displayId' => $this->displayId,
'osVersion' => $this->osVersion,
'osSdk' => $this->osSdk,
'manufacturer' => $this->manufacturer,
'brand' => $this->brand,
'model' => $this->model,
]);
// Maintain the Display Group
if ($this->hasPropertyChanged('display')
|| $this->hasPropertyChanged('description')
|| $this->hasPropertyChanged('tags')
|| $this->hasPropertyChanged('bandwidthLimit')
|| $this->hasPropertyChanged('folderId')
|| $this->hasPropertyChanged('ref1')
|| $this->hasPropertyChanged('ref2')
|| $this->hasPropertyChanged('ref3')
|| $this->hasPropertyChanged('ref4')
|| $this->hasPropertyChanged('ref5')
) {
$this->getLog()->debug('Display specific DisplayGroup properties need updating');
$displayGroup = $this->displayGroupFactory->getById($this->displayGroupId);
$displayGroup->load();
$displayGroup->displayGroup = $this->display;
$displayGroup->description = $this->description;
$displayGroup->bandwidthLimit = $this->bandwidthLimit;
$displayGroup->ref1 = $this->ref1;
$displayGroup->ref2 = $this->ref2;
$displayGroup->ref3 = $this->ref3;
$displayGroup->ref4 = $this->ref4;
$displayGroup->ref5 = $this->ref5;
// Tags
$saveTags = false;
if ($this->hasPropertyChanged('tags')) {
$saveTags = true;
$displayGroup->updateTagLinks($this->tags);
}
// If the folderId has changed, we should check this user has permissions to the new folderId
// it shouldn't ever be null, but just in case.
$displayGroup->folderId = ($this->folderId == null) ? 1 : $this->folderId;
if ($this->hasPropertyChanged('folderId')) {
$folder = $this->folderFactory->getById($displayGroup->folderId, 0);
// We have permission, so assert the new folder's permission id
$displayGroup->permissionsFolderId = $folder->getPermissionFolderIdOrThis();
}
// manageDisplayLinks is false because we never update a display specific display group's display links
$displayGroup->save([
'validate' => false,
'saveGroup' => true,
'manageLinks' => false,
'manageDisplayLinks' => false,
'manageDynamicDisplayLinks' => false,
'allowNotify' => true,
'saveTags' => $saveTags,
'setModifiedDt' => $options['setModifiedDt'],
]);
} else if ($options['setModifiedDt']) {
// Bump the modified date.
$this->store->update('
UPDATE displaygroup
SET `modifiedDt` = :modifiedDt
WHERE displayGroupId = :displayGroupId
', [
'modifiedDt' => Carbon::now()->format(DateFormatHelper::getSystemFormat()),
'displayGroupId' => $this->displayGroupId
]);
}
}
/**
* Get the Settings Profile for this Display
* @param array $options
* @return array
* @throws GeneralException
*/
public function getSettings($options = [])
{
$options = array_merge([
'displayOverride' => false
], $options);
return $this->setConfig($options);
}
/**
* @return Command[]
*/
public function getCommands()
{
if ($this->commands == null) {
$displayProfile = $this->getDisplayProfile();
// Set any commands
$this->commands = $displayProfile->commands;
}
return $this->commands;
}
/**
* Get a particular setting
* @param string $key
* @param mixed $default
* @param array $options
* @return mixed
* @throws NotFoundException
*/
public function getSetting($key, $default = null, $options = [])
{
$options = array_merge([
'displayOverride' => true,
'displayOnly' => false
], $options);
$this->setConfig($options);
// Find
$return = $default;
if ($options['displayOnly']) {
// Only get an option if set from the override config on this display
foreach ($this->overrideConfig as $row) {
if ($row['name'] == $key || $row['name'] == ucfirst($key)) {
$return = array_key_exists('value', $row) ? $row['value'] : ((array_key_exists('default', $row)) ? $row['default'] : $default);
break;
}
}
} else if ($options['displayOverride']) {
// Get the option from the combined array of config
foreach ($this->combinedConfig as $row) {
if ($row['name'] == $key || $row['name'] == ucfirst($key)) {
$return = array_key_exists('value', $row) ? $row['value'] : ((array_key_exists('default', $row)) ? $row['default'] : $default);
break;
}
}
} else {
// Get the option from the profile only
foreach ($this->profileConfig as $row) {
if ($row['name'] == $key || $row['name'] == ucfirst($key)) {
$return = array_key_exists('value', $row) ? $row['value'] : ((array_key_exists('default', $row)) ? $row['default'] : $default);
break;
}
}
}
return $return;
}
/**
* Set the config array
* @param array $options
* @return array
* @throws NotFoundException
*/
private function setConfig(array $options = []): array
{
$options = array_merge([
'displayOverride' => false
], $options);
if ($this->profileConfig == null) {
$this->load();
// Get the display profile
try {
$displayProfile = $this->getDisplayProfile();
} catch (NotFoundException) {
$displayProfile = $this->displayProfileFactory->getUnknownProfile($this->clientType);
}
// Merge in any overrides we have on our display.
$this->profileConfig = $displayProfile->getProfileConfig();
$this->combinedConfig = $this->mergeConfigs($this->profileConfig, $this->overrideConfig);
}
return ($options['displayOverride']) ? $this->combinedConfig : $this->profileConfig;
}
/**
* Merge two configs
* @param $default
* @param $override
* @return array
*/
private function mergeConfigs($default, $override): array
{
// No overrides, then nothing to do.
if (empty($override) || !is_array($override)) {
return $default;
}
// Merge the settings together
foreach ($default as &$defaultItem) {
for ($i = 0; $i < count($override); $i++) {
if ($defaultItem['name'] == $override[$i]['name']) {
// For special json fields, we need to decode, merge, encode and save instead
if (in_array($defaultItem['name'], ['timers', 'pictureOptions', 'lockOptions'])
&& isset($defaultItem['value']) && isset($override[$i]['value'])
) {
// Decode values
$defaultItemValueDecoded = json_decode($defaultItem['value'], true);
$overrideValueDecoded = json_decode($override[$i]['value'], true);
// Merge values, encode and save
$defaultItem['value'] = json_encode(array_merge(
$defaultItemValueDecoded,
$overrideValueDecoded
));
} else {
// merge
$defaultItem = array_merge($defaultItem, $override[$i]);
}
break;
}
}
}
// Merge the remainder
return $default;
}
/**
* @param PoolInterface $pool
* @return int|null
*/
public function getCurrentLayoutId($pool, LayoutFactory $layoutFactory)
{
$item = $pool->getItem('/currentLayoutId/' . $this->displayId);
$data = $item->get();
if ($item->isHit()) {
$this->currentLayoutId = $data;
try {
$this->currentLayout = $layoutFactory->getById($this->currentLayoutId)->layout;
}
catch (NotFoundException $notFoundException) {
// This is ok
}
} else {
$this->getLog()->debug('Cache miss for setCurrentLayoutId on display ' . $this->display);
}
return $this->currentLayoutId;
}
/**
* @param PoolInterface $pool
* @param int $currentLayoutId
* @return $this
* @throws \Exception
*/
public function setCurrentLayoutId($pool, $currentLayoutId)
{
// Cache it
$this->getLog()->debug('Caching currentLayoutId with Pool');
$item = $pool->getItem('/currentLayoutId/' . $this->displayId);
$item->set($currentLayoutId);
$item->expiresAfter(new \DateInterval('P1W'));
$pool->saveDeferred($item);
return $this;
}
/**
* @param PoolInterface $pool
* @return int|null
*/
public function getCurrentScreenShotTime($pool)
{
$item = $pool->getItem('/screenShotTime/' . $this->displayId);
return $item->get();
}
/**
* @param PoolInterface $pool
* @param string $date
* @return $this
* @throws \Exception
*/
public function setCurrentScreenShotTime($pool, $date)
{
// Cache it
$this->getLog()->debug('Caching currentLayoutId with Pool');
$item = $pool->getItem('/screenShotTime/' . $this->displayId);
$item->set($date);
$item->expiresAfter(new \DateInterval('P1W'));
$pool->saveDeferred($item);
return $this;
}
/**
* @param PoolInterface $pool
* @return array
*/
public function getStatusWindow($pool)
{
$item = $pool->getItem('/statusWindow/' . $this->displayId);
if ($item->isMiss()) {
return [];
} else {
// special handling for Android
if ($this->clientType === 'android') {
return nl2br($item->get());
} else {
return $item->get();
}
}
}
/**
* @param PoolInterface $pool
* @param array $status
* @return $this
*/
public function setStatusWindow($pool, $status)
{
// Cache it
$this->getLog()->debug('Caching statusWindow with Pool');
$item = $pool->getItem('/statusWindow/' . $this->displayId);
$item->set($status);
$item->expiresAfter(new \DateInterval('P1D'));
$pool->saveDeferred($item);
return $this;
}
/**
* Check if this Display is set as Lead Display on any Sync Group
* @return bool
*/
public function isLead(): bool
{
$syncGroups = $this->getStore()->select(
'SELECT syncGroupId FROM `syncgroup` WHERE `syncgroup`.leadDisplayId = :displayId',
['displayId' => $this->displayId]
);
return count($syncGroups) > 0;
}
}