829 lines
33 KiB
PHP
829 lines
33 KiB
PHP
|
|
<?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\Xmds;
|
||
|
|
|
||
|
|
use Carbon\Carbon;
|
||
|
|
use Intervention\Image\ImageManagerStatic as Img;
|
||
|
|
use Xibo\Entity\Bandwidth;
|
||
|
|
use Xibo\Entity\Display;
|
||
|
|
use Xibo\Event\XmdsDependencyRequestEvent;
|
||
|
|
use Xibo\Helper\DateFormatHelper;
|
||
|
|
use Xibo\Helper\Random;
|
||
|
|
use Xibo\Support\Exception\GeneralException;
|
||
|
|
use Xibo\Support\Exception\NotFoundException;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Class Soap4
|
||
|
|
* @package Xibo\Xmds
|
||
|
|
* @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||
|
|
*/
|
||
|
|
class Soap4 extends Soap
|
||
|
|
{
|
||
|
|
/**
|
||
|
|
* Registers a new display
|
||
|
|
* @param string $serverKey
|
||
|
|
* @param string $hardwareKey
|
||
|
|
* @param string $displayName
|
||
|
|
* @param string $clientType
|
||
|
|
* @param string $clientVersion
|
||
|
|
* @param int $clientCode
|
||
|
|
* @param string $operatingSystem
|
||
|
|
* @param string $macAddress
|
||
|
|
* @param null $xmrChannel
|
||
|
|
* @param null $xmrPubKey
|
||
|
|
* @return string
|
||
|
|
* @throws GeneralException
|
||
|
|
* @throws NotFoundException
|
||
|
|
* @throws \SoapFault
|
||
|
|
*/
|
||
|
|
public function RegisterDisplay($serverKey, $hardwareKey, $displayName, $clientType, $clientVersion, $clientCode, $operatingSystem, $macAddress, $xmrChannel = null, $xmrPubKey = null)
|
||
|
|
{
|
||
|
|
$this->logProcessor->setRoute('RegisterDisplay');
|
||
|
|
|
||
|
|
$sanitized = $this->getSanitizer([
|
||
|
|
'serverKey' => $serverKey,
|
||
|
|
'hardwareKey' => $hardwareKey,
|
||
|
|
'displayName' => $displayName,
|
||
|
|
'clientType' => $clientType,
|
||
|
|
'clientVersion' => $clientVersion,
|
||
|
|
'clientCode' => $clientCode,
|
||
|
|
'operatingSystem' => $operatingSystem,
|
||
|
|
'macAddress' => $macAddress,
|
||
|
|
'xmrChannel' => $xmrChannel,
|
||
|
|
'xmrPubKey' => $xmrPubKey,
|
||
|
|
]);
|
||
|
|
|
||
|
|
// Sanitize
|
||
|
|
$serverKey = $sanitized->getString('serverKey');
|
||
|
|
$hardwareKey = $sanitized->getString('hardwareKey');
|
||
|
|
$displayName = $sanitized->getString('displayName');
|
||
|
|
$clientType = $sanitized->getString('clientType');
|
||
|
|
$clientVersion = $sanitized->getString('clientVersion');
|
||
|
|
$clientCode = $sanitized->getInt('clientCode');
|
||
|
|
$macAddress = $sanitized->getString('macAddress');
|
||
|
|
$clientAddress = $this->getIp();
|
||
|
|
$operatingSystem = $sanitized->getString('operatingSystem');
|
||
|
|
|
||
|
|
// Check the serverKey matches
|
||
|
|
if ($serverKey != $this->getConfig()->getSetting('SERVER_KEY')) {
|
||
|
|
throw new \SoapFault('Sender', 'The Server key you entered does not match with the server key at this address');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check the Length of the hardwareKey
|
||
|
|
if (strlen($hardwareKey) > 40) {
|
||
|
|
throw new \SoapFault('Sender', 'The Hardware Key you sent was too long. Only 40 characters are allowed (SHA1).');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Return an XML formatted string
|
||
|
|
$return = new \DOMDocument('1.0');
|
||
|
|
$displayElement = $return->createElement('display');
|
||
|
|
$return->appendChild($displayElement);
|
||
|
|
|
||
|
|
// Check in the database for this hardwareKey
|
||
|
|
try {
|
||
|
|
$display = $this->displayFactory->getByLicence($hardwareKey);
|
||
|
|
$this->display = $display;
|
||
|
|
|
||
|
|
$this->logProcessor->setDisplay($display->displayId, $display->isAuditing());
|
||
|
|
|
||
|
|
// Audit in
|
||
|
|
$this->getLog()->debug(
|
||
|
|
'serverKey: ' . $serverKey . ', hardwareKey: ' . $hardwareKey .
|
||
|
|
', displayName: ' . $displayName . ', macAddress: ' . $macAddress
|
||
|
|
);
|
||
|
|
|
||
|
|
// Now
|
||
|
|
$dateNow = Carbon::now();
|
||
|
|
|
||
|
|
// Append the time
|
||
|
|
$displayElement->setAttribute('date', $dateNow->format(DateFormatHelper::getSystemFormat()));
|
||
|
|
$displayElement->setAttribute('timezone', $this->getConfig()->getSetting('defaultTimezone'));
|
||
|
|
|
||
|
|
// Determine if we are licensed or not
|
||
|
|
if ($display->licensed == 0) {
|
||
|
|
// It is not authorised
|
||
|
|
$displayElement->setAttribute('status', 2);
|
||
|
|
$displayElement->setAttribute('code', 'WAITING');
|
||
|
|
$displayElement->setAttribute('message', 'Display is awaiting licensing approval from an Administrator.');
|
||
|
|
} else {
|
||
|
|
// It is licensed
|
||
|
|
$displayElement->setAttribute('status', 0);
|
||
|
|
$displayElement->setAttribute('code', 'READY');
|
||
|
|
$displayElement->setAttribute('message', 'Display is active and ready to start.');
|
||
|
|
|
||
|
|
// Display Settings
|
||
|
|
$settings = $this->display->getSettings(['displayOverride' => true]);
|
||
|
|
|
||
|
|
// Create the XML nodes
|
||
|
|
foreach ($settings as $arrayItem) {
|
||
|
|
// Upper case the setting name for windows
|
||
|
|
$settingName = ($clientType == 'windows') ? ucfirst($arrayItem['name']) : $arrayItem['name'];
|
||
|
|
|
||
|
|
// Patch download and update windows to make sure they are unix time stamps
|
||
|
|
// XMDS schema 4 sent down unix time
|
||
|
|
// https://github.com/xibosignage/xibo/issues/1791
|
||
|
|
if (strtolower($arrayItem['name']) == 'downloadstartwindow'
|
||
|
|
|| strtolower($arrayItem['name']) == 'downloadendwindow'
|
||
|
|
|| strtolower($arrayItem['name']) == 'updatestartwindow'
|
||
|
|
|| strtolower($arrayItem['name']) == 'updateendwindow'
|
||
|
|
) {
|
||
|
|
// Split by :
|
||
|
|
$timeParts = explode(':', $arrayItem['value']);
|
||
|
|
if ($timeParts[0] == '00' && $timeParts[1] == '00') {
|
||
|
|
$arrayItem['value'] = 0;
|
||
|
|
} else {
|
||
|
|
$arrayItem['value'] = Carbon::now()->setTime(intval($timeParts[0]), intval($timeParts[1]));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Apply an offset to the collectInterval
|
||
|
|
// https://github.com/xibosignage/xibo/issues/3530
|
||
|
|
if (strtolower($arrayItem['name']) == 'collectinterval') {
|
||
|
|
$arrayItem['value'] = $this->collectionIntervalWithOffset($arrayItem['value']);
|
||
|
|
}
|
||
|
|
|
||
|
|
$node = $return->createElement($arrayItem['name'], $arrayItem['value'] ?? $arrayItem['default']);
|
||
|
|
$node->setAttribute('type', $arrayItem['type']);
|
||
|
|
$displayElement->appendChild($node);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Player upgrades
|
||
|
|
$version = '';
|
||
|
|
try {
|
||
|
|
$versionId = $this->display->getSetting('versionMediaId', null, ['displayOverride' => true]);
|
||
|
|
|
||
|
|
if ($clientType != 'windows' && $versionId != null) {
|
||
|
|
$version = $this->playerVersionFactory->getById($versionId);
|
||
|
|
|
||
|
|
if ($clientType == 'android') {
|
||
|
|
$version = json_encode([
|
||
|
|
'id' => $versionId,
|
||
|
|
'file' => $version->fileName,
|
||
|
|
'code' => $version->code
|
||
|
|
]);
|
||
|
|
} elseif ($clientType == 'lg') {
|
||
|
|
$version = json_encode([
|
||
|
|
'id' => $versionId,
|
||
|
|
'file' => $version->fileName,
|
||
|
|
'code' => $version->code
|
||
|
|
]);
|
||
|
|
} elseif ($clientType == 'sssp') {
|
||
|
|
// Create a nonce and store it in the cache for this display.
|
||
|
|
$nonce = Random::generateString();
|
||
|
|
$cache = $this->getPool()->getItem('/playerVersion/' . $nonce);
|
||
|
|
$cache->set($this->display->displayId);
|
||
|
|
$cache->expiresAfter(86400);
|
||
|
|
$this->getPool()->saveDeferred($cache);
|
||
|
|
|
||
|
|
$version = json_encode([
|
||
|
|
'id' => $versionId,
|
||
|
|
'file' => $version->fileName,
|
||
|
|
'code' => $version->code,
|
||
|
|
'url' => str_replace('/xmds.php', '', Wsdl::getRoot()) . '/playersoftware/' . $nonce
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (NotFoundException $notFoundException) {
|
||
|
|
$this->getLog()->error('Non-existing version set on displayId ' . $this->display->displayId);
|
||
|
|
}
|
||
|
|
|
||
|
|
$displayElement->setAttribute('version_instructions', $version);
|
||
|
|
|
||
|
|
// Add some special settings
|
||
|
|
$nodeName = ($clientType == 'windows') ? 'DisplayName' : 'displayName';
|
||
|
|
$node = $return->createElement($nodeName);
|
||
|
|
$node->appendChild($return->createTextNode($display->display));
|
||
|
|
$node->setAttribute('type', 'string');
|
||
|
|
$displayElement->appendChild($node);
|
||
|
|
|
||
|
|
$nodeName = ($clientType == 'windows') ? 'ScreenShotRequested' : 'screenShotRequested';
|
||
|
|
$node = $return->createElement($nodeName, $display->screenShotRequested);
|
||
|
|
$node->setAttribute('type', 'checkbox');
|
||
|
|
$displayElement->appendChild($node);
|
||
|
|
|
||
|
|
$nodeName = ($clientType == 'windows') ? 'DisplayTimeZone' : 'displayTimeZone';
|
||
|
|
$node = $return->createElement($nodeName, (!empty($display->timeZone)) ? $display->timeZone : '');
|
||
|
|
$node->setAttribute('type', 'string');
|
||
|
|
$displayElement->appendChild($node);
|
||
|
|
|
||
|
|
if (!empty($display->timeZone)) {
|
||
|
|
// Calculate local time
|
||
|
|
$dateNow->timezone($display->timeZone);
|
||
|
|
|
||
|
|
// Append Local Time
|
||
|
|
$displayElement->setAttribute('localDate', $dateNow->format(DateFormatHelper::getSystemFormat()));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (NotFoundException $e) {
|
||
|
|
// Add a new display
|
||
|
|
try {
|
||
|
|
$display = $this->displayFactory->createEmpty();
|
||
|
|
$this->display = $display;
|
||
|
|
$display->display = $displayName;
|
||
|
|
$display->auditingUntil = 0;
|
||
|
|
$display->defaultLayoutId = $this->getConfig()->getSetting('DEFAULT_LAYOUT');
|
||
|
|
$display->license = $hardwareKey;
|
||
|
|
$display->licensed = $this->getConfig()->getSetting('DISPLAY_AUTO_AUTH', 0);
|
||
|
|
$display->incSchedule = 0;
|
||
|
|
$display->clientAddress = $this->getIp();
|
||
|
|
|
||
|
|
if (!$display->isDisplaySlotAvailable()) {
|
||
|
|
$display->licensed = 0;
|
||
|
|
}
|
||
|
|
} catch (\InvalidArgumentException $e) {
|
||
|
|
throw new \SoapFault('Sender', $e->getMessage());
|
||
|
|
}
|
||
|
|
|
||
|
|
$displayElement->setAttribute('status', 1);
|
||
|
|
$displayElement->setAttribute('code', 'ADDED');
|
||
|
|
if ($display->licensed == 0) {
|
||
|
|
$displayElement->setAttribute('message', 'Display added and is awaiting licensing approval from an Administrator.');
|
||
|
|
} else {
|
||
|
|
$displayElement->setAttribute('message', 'Display is active and ready to start.');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Send Notification if required
|
||
|
|
$this->alertDisplayUp();
|
||
|
|
|
||
|
|
$display->lastAccessed = Carbon::now()->format('U');
|
||
|
|
$display->loggedIn = 1;
|
||
|
|
$display->clientAddress = $clientAddress;
|
||
|
|
$display->macAddress = $macAddress;
|
||
|
|
$display->clientType = $clientType;
|
||
|
|
$display->clientVersion = $clientVersion;
|
||
|
|
$display->clientCode = $clientCode;
|
||
|
|
|
||
|
|
// Parse operatingSystem JSON data
|
||
|
|
$operatingSystemJson = json_decode($operatingSystem, false);
|
||
|
|
|
||
|
|
// Newer version of players will return a JSON value, but for older version, it will return a string.
|
||
|
|
// In case the json decode fails, use the operatingSystem string value as the default value for the osVersion.
|
||
|
|
$display->osVersion = $operatingSystemJson->version ?? $operatingSystem;
|
||
|
|
$display->osSdk = $operatingSystemJson->sdk ?? null;
|
||
|
|
$display->manufacturer = $operatingSystemJson->manufacturer ?? null;
|
||
|
|
$display->brand = $operatingSystemJson->brand ?? null;
|
||
|
|
$display->model = $operatingSystemJson->model ?? null;
|
||
|
|
|
||
|
|
$display->save(['validate' => false, 'audit' => false]);
|
||
|
|
|
||
|
|
// Log Bandwidth
|
||
|
|
$returnXml = $return->saveXML();
|
||
|
|
$this->logBandwidth($display->displayId, Bandwidth::$REGISTER, strlen($returnXml));
|
||
|
|
|
||
|
|
// Audit our return
|
||
|
|
$this->getLog()->debug($returnXml);
|
||
|
|
|
||
|
|
return $returnXml;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a string containing the required files xml for the requesting display
|
||
|
|
* @param string $serverKey The Server Key
|
||
|
|
* @param string $hardwareKey Display Hardware Key
|
||
|
|
* @return string $requiredXml Xml Formatted String
|
||
|
|
* @throws \SoapFault
|
||
|
|
* @throws \Xibo\Support\Exception\NotFoundException
|
||
|
|
*/
|
||
|
|
function RequiredFiles($serverKey, $hardwareKey)
|
||
|
|
{
|
||
|
|
$httpDownloads = ($this->getConfig()->getSetting('SENDFILE_MODE') != 'Off');
|
||
|
|
return $this->doRequiredFiles($serverKey, $hardwareKey, $httpDownloads);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get File
|
||
|
|
* @param string $serverKey The ServerKey for this CMS
|
||
|
|
* @param string $hardwareKey The HardwareKey for this Display
|
||
|
|
* @param int $fileId The ID
|
||
|
|
* @param string $fileType The File Type
|
||
|
|
* @param int $chunkOffset The Offset of the Chunk Requested
|
||
|
|
* @param string $chunkSize The Size of the Chunk Requested
|
||
|
|
* @return mixed
|
||
|
|
* @throws \SoapFault
|
||
|
|
* @throws GeneralException
|
||
|
|
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||
|
|
* @throws \Xibo\Support\Exception\NotFoundException
|
||
|
|
*/
|
||
|
|
function GetFile($serverKey, $hardwareKey, $fileId, $fileType, $chunkOffset, $chunkSize, $isDependency = false)
|
||
|
|
{
|
||
|
|
$this->logProcessor->setRoute('GetFile');
|
||
|
|
|
||
|
|
$sanitizer = $this->getSanitizer([
|
||
|
|
'serverKey' => $serverKey,
|
||
|
|
'hardwareKey' => $hardwareKey,
|
||
|
|
'fileId' => $fileId,
|
||
|
|
'fileType' => $fileType,
|
||
|
|
'chunkOffset' => $chunkOffset,
|
||
|
|
'chunkSize' => $chunkSize
|
||
|
|
]);
|
||
|
|
|
||
|
|
// Sanitize
|
||
|
|
$serverKey = $sanitizer->getString('serverKey');
|
||
|
|
$hardwareKey = $sanitizer->getString('hardwareKey');
|
||
|
|
if ($isDependency) {
|
||
|
|
$fileId = $sanitizer->getString('fileId');
|
||
|
|
} else {
|
||
|
|
$fileId = $sanitizer->getInt('fileId');
|
||
|
|
}
|
||
|
|
$fileType = $sanitizer->getString('fileType');
|
||
|
|
$chunkOffset = $sanitizer->getDouble('chunkOffset');
|
||
|
|
$chunkSize = $sanitizer->getDouble('chunkSize');
|
||
|
|
|
||
|
|
$libraryLocation = $this->getConfig()->getSetting('LIBRARY_LOCATION');
|
||
|
|
|
||
|
|
// Check the serverKey matches
|
||
|
|
if ($serverKey != $this->getConfig()->getSetting('SERVER_KEY')) {
|
||
|
|
throw new \SoapFault(
|
||
|
|
'Sender',
|
||
|
|
'The Server key you entered does not match with the server key at this address'
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Authenticate this request...
|
||
|
|
if (!$this->authDisplay($hardwareKey)) {
|
||
|
|
throw new \SoapFault('Receiver', 'This Display is not authorised.');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Now that we authenticated the Display, make sure we are sticking to our bandwidth limit
|
||
|
|
if (!$this->checkBandwidth($this->display->displayId)) {
|
||
|
|
throw new \SoapFault('Receiver', 'Bandwidth Limit exceeded');
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
$this->getLog()->debug(
|
||
|
|
'hardwareKey: ' . $hardwareKey . ', fileId: ' . $fileId . ', fileType: ' . $fileType .
|
||
|
|
', chunkOffset: ' . $chunkOffset . ', chunkSize: ' . $chunkSize
|
||
|
|
);
|
||
|
|
|
||
|
|
|
||
|
|
try {
|
||
|
|
if ($isDependency || ($fileType == 'media' && $fileId < 0)) {
|
||
|
|
// Validate the nonce
|
||
|
|
// If we are an older player downloading as media using a faux fileId, then this lookup
|
||
|
|
// should be performed against the `itemId`
|
||
|
|
$requiredFile = $this->requiredFileFactory->getByDisplayAndDependency(
|
||
|
|
$this->display->displayId,
|
||
|
|
$fileType,
|
||
|
|
$fileId,
|
||
|
|
!($fileType == 'media' && $fileId < 0)
|
||
|
|
);
|
||
|
|
|
||
|
|
// File is valid, see if we can return it.
|
||
|
|
$event = new XmdsDependencyRequestEvent($requiredFile);
|
||
|
|
$this->getDispatcher()->dispatch($event, 'xmds.dependency.request');
|
||
|
|
|
||
|
|
// Get the path
|
||
|
|
$path = $event->getRelativePath();
|
||
|
|
if (empty($path)) {
|
||
|
|
throw new NotFoundException(__('File not found'));
|
||
|
|
}
|
||
|
|
|
||
|
|
$path = $libraryLocation . $path;
|
||
|
|
|
||
|
|
$f = fopen($path, 'r');
|
||
|
|
if (!$f) {
|
||
|
|
throw new NotFoundException(__('Unable to get file pointer'));
|
||
|
|
}
|
||
|
|
|
||
|
|
fseek($f, $chunkOffset);
|
||
|
|
$file = fread($f, $chunkSize);
|
||
|
|
|
||
|
|
// Store file size for bandwidth log
|
||
|
|
$chunkSize = strlen($file);
|
||
|
|
|
||
|
|
if ($chunkSize === 0) {
|
||
|
|
throw new NotFoundException(__('Empty file'));
|
||
|
|
}
|
||
|
|
|
||
|
|
$requiredFile->bytesRequested = $requiredFile->bytesRequested + $chunkSize;
|
||
|
|
$requiredFile->save();
|
||
|
|
} else if ($fileType == 'layout') {
|
||
|
|
// Validate the nonce
|
||
|
|
$requiredFile = $this->requiredFileFactory->getByDisplayAndLayout($this->display->displayId, $fileId);
|
||
|
|
|
||
|
|
// Load the layout
|
||
|
|
$layout = $this->layoutFactory->concurrentRequestLock($this->layoutFactory->getById($fileId));
|
||
|
|
try {
|
||
|
|
$path = $layout->xlfToDisk();
|
||
|
|
} finally {
|
||
|
|
$this->layoutFactory->concurrentRequestRelease($layout);
|
||
|
|
}
|
||
|
|
|
||
|
|
$file = file_get_contents($path);
|
||
|
|
$chunkSize = filesize($path);
|
||
|
|
|
||
|
|
$requiredFile->bytesRequested = $requiredFile->bytesRequested + $chunkSize;
|
||
|
|
$requiredFile->save();
|
||
|
|
} else if ($fileType == 'media') {
|
||
|
|
// A normal media file.
|
||
|
|
$requiredFile = $this->requiredFileFactory->getByDisplayAndMedia(
|
||
|
|
$this->display->displayId,
|
||
|
|
$fileId
|
||
|
|
);
|
||
|
|
|
||
|
|
$media = $this->mediaFactory->getById($fileId);
|
||
|
|
$this->getLog()->debug(json_encode($media));
|
||
|
|
|
||
|
|
if (!file_exists($libraryLocation . $media->storedAs)) {
|
||
|
|
throw new NotFoundException(__('Media exists but file missing from library.'));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Return the Chunk size specified
|
||
|
|
$f = fopen($libraryLocation . $media->storedAs, 'r');
|
||
|
|
|
||
|
|
if (!$f) {
|
||
|
|
throw new NotFoundException(__('Unable to get file pointer'));
|
||
|
|
}
|
||
|
|
|
||
|
|
fseek($f, $chunkOffset);
|
||
|
|
|
||
|
|
$file = fread($f, $chunkSize);
|
||
|
|
|
||
|
|
// Store file size for bandwidth log
|
||
|
|
$chunkSize = strlen($file);
|
||
|
|
|
||
|
|
if ($chunkSize === 0) {
|
||
|
|
throw new NotFoundException(__('Empty file'));
|
||
|
|
}
|
||
|
|
|
||
|
|
$requiredFile->bytesRequested = $requiredFile->bytesRequested + $chunkSize;
|
||
|
|
$requiredFile->save();
|
||
|
|
} else {
|
||
|
|
throw new NotFoundException(__('Unknown FileType Requested.'));
|
||
|
|
}
|
||
|
|
} catch (NotFoundException $e) {
|
||
|
|
$this->getLog()->error('Not found FileId: ' . $fileId . '. FileType: '
|
||
|
|
. $fileType . '. ' . $e->getMessage());
|
||
|
|
throw new \SoapFault('Receiver', 'Requested an invalid file.');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Log Bandwidth
|
||
|
|
if ($isDependency) {
|
||
|
|
$this->logBandwidth($this->display->displayId, Bandwidth::$GET_DEPENDENCY, $chunkSize);
|
||
|
|
} else {
|
||
|
|
$this->logBandwidth($this->display->displayId, Bandwidth::$GETFILE, $chunkSize);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $file;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns the schedule for the hardware key specified
|
||
|
|
* @param string $serverKey
|
||
|
|
* @param string $hardwareKey
|
||
|
|
* @return string
|
||
|
|
* @throws \SoapFault
|
||
|
|
* @throws \Xibo\Support\Exception\NotFoundException
|
||
|
|
*/
|
||
|
|
function Schedule($serverKey, $hardwareKey)
|
||
|
|
{
|
||
|
|
return $this->doSchedule($serverKey, $hardwareKey);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Black List
|
||
|
|
* @param string $serverKey
|
||
|
|
* @param string $hardwareKey
|
||
|
|
* @param string $mediaId
|
||
|
|
* @param string $type
|
||
|
|
* @param string $reason
|
||
|
|
* @return bool
|
||
|
|
* @throws \SoapFault
|
||
|
|
* @throws \Xibo\Support\Exception\NotFoundException
|
||
|
|
*/
|
||
|
|
function BlackList($serverKey, $hardwareKey, $mediaId, $type, $reason)
|
||
|
|
{
|
||
|
|
return $this->doBlackList($serverKey, $hardwareKey, $mediaId, $type, $reason);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Submit client logging
|
||
|
|
* @param string $serverKey
|
||
|
|
* @param string $hardwareKey
|
||
|
|
* @param string $logXml
|
||
|
|
* @return bool
|
||
|
|
* @throws \SoapFault
|
||
|
|
* @throws \Xibo\Support\Exception\NotFoundException
|
||
|
|
*/
|
||
|
|
function SubmitLog($serverKey, $hardwareKey, $logXml)
|
||
|
|
{
|
||
|
|
return $this->doSubmitLog($serverKey, $hardwareKey, $logXml);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Submit display statistics to the server
|
||
|
|
* @param string $serverKey
|
||
|
|
* @param string $hardwareKey
|
||
|
|
* @param string $statXml
|
||
|
|
* @return bool
|
||
|
|
* @throws \SoapFault
|
||
|
|
* @throws \Xibo\Support\Exception\NotFoundException
|
||
|
|
*/
|
||
|
|
function SubmitStats($serverKey, $hardwareKey, $statXml)
|
||
|
|
{
|
||
|
|
return $this->doSubmitStats($serverKey, $hardwareKey, $statXml);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Store the media inventory for a client
|
||
|
|
* @param string $serverKey
|
||
|
|
* @param string $hardwareKey
|
||
|
|
* @param string $inventory
|
||
|
|
* @return bool
|
||
|
|
* @throws \SoapFault
|
||
|
|
* @throws \Xibo\Support\Exception\NotFoundException
|
||
|
|
*/
|
||
|
|
public function MediaInventory($serverKey, $hardwareKey, $inventory)
|
||
|
|
{
|
||
|
|
return $this->doMediaInventory($serverKey, $hardwareKey, $inventory);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Gets additional resources for assigned media
|
||
|
|
* @param string $serverKey
|
||
|
|
* @param string $hardwareKey
|
||
|
|
* @param int $layoutId
|
||
|
|
* @param string $regionId
|
||
|
|
* @param string $mediaId
|
||
|
|
* @return mixed
|
||
|
|
* @throws \SoapFault
|
||
|
|
* @throws \Xibo\Support\Exception\NotFoundException
|
||
|
|
*/
|
||
|
|
function GetResource($serverKey, $hardwareKey, $layoutId, $regionId, $mediaId)
|
||
|
|
{
|
||
|
|
return $this->doGetResource($serverKey, $hardwareKey, $layoutId, $regionId, $mediaId);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Notify Status
|
||
|
|
* @param string $serverKey
|
||
|
|
* @param string $hardwareKey
|
||
|
|
* @param string $status
|
||
|
|
* @return bool
|
||
|
|
* @throws \SoapFault
|
||
|
|
* @throws GeneralException
|
||
|
|
* @throws \Xibo\Support\Exception\NotFoundException
|
||
|
|
*/
|
||
|
|
public function NotifyStatus($serverKey, $hardwareKey, $status)
|
||
|
|
{
|
||
|
|
$this->logProcessor->setRoute('NotifyStatus');
|
||
|
|
|
||
|
|
$sanitizer = $this->getSanitizer([
|
||
|
|
'serverKey' => $serverKey,
|
||
|
|
'hardwareKey' => $hardwareKey
|
||
|
|
]);
|
||
|
|
// Sanitize
|
||
|
|
$serverKey = $sanitizer->getString('serverKey');
|
||
|
|
$hardwareKey = $sanitizer->getString('hardwareKey');
|
||
|
|
|
||
|
|
// Check the serverKey matches
|
||
|
|
if ($serverKey != $this->getConfig()->getSetting('SERVER_KEY')) {
|
||
|
|
throw new \SoapFault('Sender', 'The Server key you entered does not match with the server key at this address');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Auth this request...
|
||
|
|
if (!$this->authDisplay($hardwareKey)) {
|
||
|
|
throw new \SoapFault('Receiver', 'This Display is not authorised.');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Now that we authenticated the Display, make sure we are sticking to our bandwidth limit
|
||
|
|
if (!$this->checkBandwidth($this->display->displayId)) {
|
||
|
|
throw new \SoapFault('Receiver', "Bandwidth Limit exceeded");
|
||
|
|
}
|
||
|
|
|
||
|
|
// Important to keep this logging in place (status screen notification gets logged)
|
||
|
|
$this->getLog()->debug($status);
|
||
|
|
|
||
|
|
$this->logBandwidth($this->display->displayId, Bandwidth::$NOTIFYSTATUS, strlen($status));
|
||
|
|
|
||
|
|
$status = json_decode($status, true);
|
||
|
|
$sanitizedStatus = $this->getSanitizer($status);
|
||
|
|
|
||
|
|
$this->display->storageAvailableSpace = $sanitizedStatus->getInt('availableSpace', ['default' => $this->display->storageAvailableSpace]);
|
||
|
|
$this->display->storageTotalSpace = $sanitizedStatus->getInt('totalSpace', ['default' => $this->display->storageTotalSpace]);
|
||
|
|
$this->display->lastCommandSuccess = $sanitizedStatus->getCheckbox('lastCommandSuccess');
|
||
|
|
$this->display->deviceName = $sanitizedStatus->getString('deviceName', ['default' => $this->display->deviceName]);
|
||
|
|
$this->display->lanIpAddress = $sanitizedStatus->getString('lanIpAddress', ['default' => $this->display->lanIpAddress]);
|
||
|
|
$commercialLicenceString = $sanitizedStatus->getString('licenceResult', ['default' => null]);
|
||
|
|
|
||
|
|
// Commercial Licence Check, 0 - Not licensed, 1 - licensed, 2 - trial licence, 3 - not applicable
|
||
|
|
if (!empty($commercialLicenceString)) {
|
||
|
|
if ($commercialLicenceString === 'Licensed fully'
|
||
|
|
|| $commercialLicenceString === 'licensed'
|
||
|
|
|| $commercialLicenceString === 'full'
|
||
|
|
) {
|
||
|
|
$commercialLicence = 1;
|
||
|
|
} elseif ($commercialLicenceString === 'Trial' || $commercialLicenceString === 'trial') {
|
||
|
|
$commercialLicence = 2;
|
||
|
|
} else {
|
||
|
|
$commercialLicence = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->display->commercialLicence = $commercialLicence;
|
||
|
|
}
|
||
|
|
|
||
|
|
// commercial licence not applicable for Windows and Linux players.
|
||
|
|
if (in_array($this->display->clientType, ['windows', 'linux'])) {
|
||
|
|
$this->display->commercialLicence = 3;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($this->getConfig()->getSetting('DISPLAY_LOCK_NAME_TO_DEVICENAME') == 1 && $this->display->hasPropertyChanged('deviceName')) {
|
||
|
|
$this->display->display = $this->display->deviceName;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Timezone
|
||
|
|
$timeZone = $sanitizedStatus->getString('timeZone');
|
||
|
|
|
||
|
|
if (!empty($timeZone)) {
|
||
|
|
// Validate the provided data and log/ignore if not well formatted
|
||
|
|
if (array_key_exists($timeZone, DateFormatHelper::timezoneList())) {
|
||
|
|
$this->display->timeZone = $timeZone;
|
||
|
|
} else {
|
||
|
|
$this->getLog()->info('Ignoring Incorrect timezone string: ' . $timeZone);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Current Layout
|
||
|
|
// don't fail: xibosignage/xibo#2517
|
||
|
|
try {
|
||
|
|
$currentLayoutId = $sanitizedStatus->getInt('currentLayoutId');
|
||
|
|
|
||
|
|
if ($currentLayoutId !== null) {
|
||
|
|
$this->display->setCurrentLayoutId($this->getPool(), $currentLayoutId);
|
||
|
|
}
|
||
|
|
} catch (\Exception $exception) {
|
||
|
|
$this->getLog()->debug('Ignoring currentLayout due to a validation error.');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Status Dialog
|
||
|
|
$statusDialog = $sanitizedStatus->getString('statusDialog', ['default' => null]);
|
||
|
|
|
||
|
|
if ($statusDialog !== null) {
|
||
|
|
// special handling for Android Players (Other Players send status as json already)
|
||
|
|
if ($this->display->clientType == 'android') {
|
||
|
|
$statusDialog = json_encode($statusDialog);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Log in as an alert
|
||
|
|
$this->getLog()->alert($statusDialog);
|
||
|
|
|
||
|
|
// Cache on the display as transient data
|
||
|
|
try {
|
||
|
|
$this->display->setStatusWindow($this->getPool(), json_decode($statusDialog, true));
|
||
|
|
} catch (\Exception $exception) {
|
||
|
|
$this->getLog()->error('Unable to cache display status. e = ' . $exception->getMessage());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Resolution
|
||
|
|
$width = $sanitizedStatus->getInt('width');
|
||
|
|
$height = $sanitizedStatus->getInt('height');
|
||
|
|
|
||
|
|
if ($width != null && $height != null) {
|
||
|
|
// Determine the orientation
|
||
|
|
$this->display->orientation = ($width >= $height) ? 'landscape' : 'portrait';
|
||
|
|
$this->display->resolution = $width . 'x' . $height;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Lat/Long
|
||
|
|
$latitude = $sanitizedStatus->getDouble('latitude', ['default' => null]);
|
||
|
|
$longitude = $sanitizedStatus->getDouble('longitude', ['default' => null]);
|
||
|
|
|
||
|
|
if ($latitude != null && $longitude != null) {
|
||
|
|
$this->display->latitude = $latitude;
|
||
|
|
$this->display->longitude = $longitude;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Touch the display record
|
||
|
|
try {
|
||
|
|
if (count($this->display->getChangedProperties()) > 0) {
|
||
|
|
$this->display->save(Display::$saveOptionsMinimum);
|
||
|
|
}
|
||
|
|
} catch (GeneralException $xiboException) {
|
||
|
|
$this->getLog()->error($xiboException->getMessage());
|
||
|
|
throw new \SoapFault('Receiver', 'Unable to save status update');
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Submit ScreenShot
|
||
|
|
* @param string $serverKey
|
||
|
|
* @param string $hardwareKey
|
||
|
|
* @param string $screenShot
|
||
|
|
* @return bool
|
||
|
|
* @throws \SoapFault
|
||
|
|
* @throws GeneralException
|
||
|
|
* @throws \Xibo\Support\Exception\NotFoundException
|
||
|
|
*/
|
||
|
|
public function SubmitScreenShot($serverKey, $hardwareKey, $screenShot)
|
||
|
|
{
|
||
|
|
$this->logProcessor->setRoute('SubmitScreenShot');
|
||
|
|
|
||
|
|
$sanitizer = $this->getSanitizer([
|
||
|
|
'serverKey' => $serverKey,
|
||
|
|
'hardwareKey' => $hardwareKey,
|
||
|
|
]);
|
||
|
|
// Sanitize
|
||
|
|
$serverKey = $sanitizer->getString('serverKey');
|
||
|
|
$hardwareKey = $sanitizer->getString('hardwareKey');
|
||
|
|
|
||
|
|
$screenShotFmt = "jpg";
|
||
|
|
$screenShotMime = "image/jpeg";
|
||
|
|
$screenShotImg = false;
|
||
|
|
|
||
|
|
$converted = false;
|
||
|
|
$needConversion = false;
|
||
|
|
|
||
|
|
// Check the serverKey matches
|
||
|
|
if ($serverKey != $this->getConfig()->getSetting('SERVER_KEY')) {
|
||
|
|
throw new \SoapFault('Sender', 'The Server key you entered does not match with the server key at this address');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Auth this request...
|
||
|
|
if (!$this->authDisplay($hardwareKey)) {
|
||
|
|
throw new \SoapFault('Receiver', 'This Display is not authorised.');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Now that we authenticated the Display, make sure we are sticking to our bandwidth limit
|
||
|
|
if (!$this->checkBandwidth($this->display->displayId)) {
|
||
|
|
throw new \SoapFault('Receiver', "Bandwidth Limit exceeded");
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->getLog()->debug('Received Screen shot');
|
||
|
|
|
||
|
|
// Open this displays screen shot file and save this.
|
||
|
|
$location = $this->getConfig()->getSetting('LIBRARY_LOCATION') . 'screenshots/' . $this->display->displayId . '_screenshot.' . $screenShotFmt;
|
||
|
|
|
||
|
|
foreach (array('imagick', 'gd') as $imgDriver) {
|
||
|
|
Img::configure(array('driver' => $imgDriver));
|
||
|
|
try {
|
||
|
|
$screenShotImg = Img::make($screenShot);
|
||
|
|
} catch (\Exception $e) {
|
||
|
|
$this->getLog()->debug($imgDriver . ' - ' . $e->getMessage());
|
||
|
|
}
|
||
|
|
if ($screenShotImg !== false) {
|
||
|
|
$this->getLog()->debug('Use ' . $imgDriver);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($screenShotImg !== false) {
|
||
|
|
$imgMime = $screenShotImg->mime();
|
||
|
|
|
||
|
|
if ($imgMime != $screenShotMime) {
|
||
|
|
$needConversion = true;
|
||
|
|
try {
|
||
|
|
$this->getLog()->debug("converting: '" . $imgMime . "' to '" . $screenShotMime . "'");
|
||
|
|
$screenShot = (string) $screenShotImg->encode($screenShotFmt);
|
||
|
|
$converted = true;
|
||
|
|
} catch (\Exception $e) {
|
||
|
|
$this->getLog()->debug($e->getMessage());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// return early with false, keep screenShotRequested intact, let the Player retry.
|
||
|
|
if ($needConversion && !$converted) {
|
||
|
|
$this->logBandwidth($this->display->displayId, Bandwidth::$SCREENSHOT, filesize($location));
|
||
|
|
throw new \SoapFault('Receiver', __('Incorrect Screen shot Format'));
|
||
|
|
}
|
||
|
|
|
||
|
|
$fp = fopen($location, 'wb');
|
||
|
|
fwrite($fp, $screenShot);
|
||
|
|
fclose($fp);
|
||
|
|
|
||
|
|
// Touch the display record
|
||
|
|
$this->display->screenShotRequested = 0;
|
||
|
|
$this->display->save(Display::$saveOptionsMinimum);
|
||
|
|
|
||
|
|
// Cache the current screen shot time
|
||
|
|
$this->display->setCurrentScreenShotTime($this->getPool(), Carbon::now()->format(DateFormatHelper::getSystemFormat()));
|
||
|
|
|
||
|
|
$this->logBandwidth($this->display->displayId, Bandwidth::$SCREENSHOT, filesize($location));
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|