芝麻web文件管理V1.00
编辑当前文件:/home/mgatv524/public_html/avenida/views/Xmds.zip
PK #qYOVY Y Soap.phpnu [ . */ namespace Xibo\Xmds; define('BLACKLIST_ALL', "All"); define('BLACKLIST_SINGLE', "Single"); use Jenssegers\Date\Date; use Slim\Log; use Stash\Interfaces\PoolInterface; use Stash\Invalidation; use Xibo\Entity\Bandwidth; use Xibo\Entity\Display; use Xibo\Entity\Schedule; use Xibo\Entity\Stat; use Xibo\Entity\Widget; use Xibo\Exception\ControllerNotImplemented; use Xibo\Exception\DeadlockException; use Xibo\Exception\InvalidArgumentException; use Xibo\Exception\NotFoundException; use Xibo\Exception\XiboException; use Xibo\Factory\BandwidthFactory; use Xibo\Factory\DataSetFactory; use Xibo\Factory\DayPartFactory; use Xibo\Factory\DisplayEventFactory; use Xibo\Factory\DisplayFactory; use Xibo\Factory\LayoutFactory; use Xibo\Factory\MediaFactory; use Xibo\Factory\ModuleFactory; use Xibo\Factory\NotificationFactory; use Xibo\Factory\PlayerVersionFactory; use Xibo\Factory\RegionFactory; use Xibo\Factory\RequiredFileFactory; use Xibo\Factory\ScheduleFactory; use Xibo\Factory\UserFactory; use Xibo\Factory\UserGroupFactory; use Xibo\Factory\WidgetFactory; use Xibo\Helper\ByteFormatter; use Xibo\Helper\Environment; use Xibo\Helper\Random; use Xibo\Service\ConfigServiceInterface; use Xibo\Service\DateServiceInterface; use Xibo\Service\LogServiceInterface; use Xibo\Service\SanitizerServiceInterface; use Xibo\Storage\StorageServiceInterface; use Xibo\Storage\TimeSeriesStoreInterface; use Xibo\Widget\ModuleWidget; /** * Class Soap * @package Xibo\Xmds */ class Soap { /** * @var Display */ protected $display; /** @var Date */ protected $fromFilter; /** @var Date */ protected $toFilter; /** @var Date */ protected $localFromFilter; /** @var Date */ protected $localToFilter; /** * @var LogProcessor */ protected $logProcessor; /** @var PoolInterface */ private $pool; /** @var StorageServiceInterface */ private $store; /** @var TimeSeriesStoreInterface */ private $timeSeriesStore; /** @var LogServiceInterface */ private $logService; /** @var DateServiceInterface */ private $dateService; /** @var SanitizerServiceInterface */ private $sanitizerService; /** @var ConfigServiceInterface */ private $configService; /** @var RequiredFileFactory */ protected $requiredFileFactory; /** @var ModuleFactory */ protected $moduleFactory; /** @var LayoutFactory */ protected $layoutFactory; /** @var DataSetFactory */ protected $dataSetFactory; /** @var DisplayFactory */ protected $displayFactory; /** @var UserGroupFactory */ protected $userGroupFactory; /** @var BandwidthFactory */ protected $bandwidthFactory; /** @var MediaFactory */ protected $mediaFactory; /** @var WidgetFactory */ protected $widgetFactory; /** @var RegionFactory */ protected $regionFactory; /** @var NotificationFactory */ protected $notificationFactory; /** @var DisplayEventFactory */ protected $displayEventFactory; /** @var ScheduleFactory */ protected $scheduleFactory; /** @var DayPartFactory */ protected $dayPartFactory; /** @var PlayerVersionFactory */ protected $playerVersionFactory; /** * Soap constructor. * @param LogProcessor $logProcessor * @param PoolInterface $pool * @param StorageServiceInterface $store * @param TimeSeriesStoreInterface $timeSeriesStore * @param LogServiceInterface $log * @param DateServiceInterface $date * @param SanitizerServiceInterface $sanitizer * @param ConfigServiceInterface $config * @param RequiredFileFactory $requiredFileFactory * @param ModuleFactory $moduleFactory * @param LayoutFactory $layoutFactory * @param DataSetFactory $dataSetFactory * @param DisplayFactory $displayFactory * @param UserFactory $userGroupFactory * @param BandwidthFactory $bandwidthFactory * @param MediaFactory $mediaFactory * @param WidgetFactory $widgetFactory * @param RegionFactory $regionFactory * @param NotificationFactory $notificationFactory * @param DisplayEventFactory $displayEventFactory * @param ScheduleFactory $scheduleFactory * @param DayPartFactory $dayPartFactory * @param PlayerVersionFactory $playerVersionFactory */ public function __construct($logProcessor, $pool, $store, $timeSeriesStore, $log, $date, $sanitizer, $config, $requiredFileFactory, $moduleFactory, $layoutFactory, $dataSetFactory, $displayFactory, $userGroupFactory, $bandwidthFactory, $mediaFactory, $widgetFactory, $regionFactory, $notificationFactory, $displayEventFactory, $scheduleFactory, $dayPartFactory, $playerVersionFactory) { $this->logProcessor = $logProcessor; $this->pool = $pool; $this->store = $store; $this->timeSeriesStore = $timeSeriesStore; $this->logService = $log; $this->dateService = $date; $this->sanitizerService = $sanitizer; $this->configService = $config; $this->requiredFileFactory = $requiredFileFactory; $this->moduleFactory = $moduleFactory; $this->layoutFactory = $layoutFactory; $this->dataSetFactory = $dataSetFactory; $this->displayFactory = $displayFactory; $this->userGroupFactory = $userGroupFactory; $this->bandwidthFactory = $bandwidthFactory; $this->mediaFactory = $mediaFactory; $this->widgetFactory = $widgetFactory; $this->regionFactory = $regionFactory; $this->notificationFactory = $notificationFactory; $this->displayEventFactory = $displayEventFactory; $this->scheduleFactory = $scheduleFactory; $this->dayPartFactory = $dayPartFactory; $this->playerVersionFactory = $playerVersionFactory; } /** * Get Cache Pool * @return \Stash\Interfaces\PoolInterface */ protected function getPool() { return $this->pool; } /** * Get Store * @return StorageServiceInterface */ protected function getStore() { return $this->store; } /** * Get Time Series Store * @return TimeSeriesStoreInterface */ protected function getTimeSeriesStore() { return $this->timeSeriesStore; } /** * Get Log * @return LogServiceInterface */ protected function getLog() { return $this->logService; } /** * Get Date * @return DateServiceInterface */ protected function getDate() { return $this->dateService; } /** * Get Sanitizer * @return SanitizerServiceInterface */ protected function getSanitizer() { return $this->sanitizerService; } /** * Get Config * @return ConfigServiceInterface */ protected function getConfig() { return $this->configService; } /** * Get Required Files (common) * @param $serverKey * @param $hardwareKey * @param bool $httpDownloads * @return string * @throws \SoapFault */ protected function doRequiredFiles($serverKey, $hardwareKey, $httpDownloads) { $this->logProcessor->setRoute('RequiredFiles'); // Sanitize $serverKey = $this->getSanitizer()->string($serverKey); $hardwareKey = $this->getSanitizer()->string($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'); } $libraryLocation = $this->getConfig()->getSetting("LIBRARY_LOCATION"); // auth this request... if (!$this->authDisplay($hardwareKey)) { throw new \SoapFault('Sender', '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"); } // Check the cache $cache = $this->getPool()->getItem($this->display->getCacheKey() . '/requiredFiles'); $cache->setInvalidationMethod(Invalidation::OLD); $output = $cache->get(); // Required Files caching operates in lockstep with nonce caching // - required files are cached for 4 hours // - nonces have an expiry of 1 day // - nonces are marked "used" when they get used // - nonce use/expiry is not checked for XMDS served files (getfile, getresource) // - nonce use/expiry is checked for HTTP served files (media, layouts) // - Each time a nonce is used through HTTP, the required files cache is invalidated so that new nonces // are generated for the next request. if ($cache->isHit()) { $this->getLog()->info('Returning required files from Cache for display ' . $this->display->display); // Log Bandwidth $this->logBandwidth($this->display->displayId, Bandwidth::$RF, strlen($output)); return $output; } // We need to regenerate // Lock the cache $cache->lock(120); // Generate a new nonce for this player and store it in the cache. $playerNonce = Random::generateString(32); $playerNonceCache = $this->pool->getItem('/display/nonce/' . $this->display->displayId); $playerNonceCache->set($playerNonce); $playerNonceCache->expiresAfter(86400); $this->pool->saveDeferred($playerNonceCache); // Get all required files for this display. // we will use this to drop items from the requirefile table if they are no longer in required files $rfIds = array_map(function ($element) { return intval($element['rfId']); }, $this->getStore()->select('SELECT rfId FROM `requiredfile` WHERE displayId = :displayId', ['displayId' => $this->display->displayId])); $newRfIds = []; // Build a new RF $requiredFilesXml = new \DOMDocument("1.0"); $fileElements = $requiredFilesXml->createElement("files"); $requiredFilesXml->appendChild($fileElements); // Filter criteria $this->setDateFilters(); // Add the filter dates to the RF xml document $fileElements->setAttribute('generated', $this->getDate()->getLocalDate()); $fileElements->setAttribute('fitlerFrom', $this->getDate()->getLocalDate($this->fromFilter)); $fileElements->setAttribute('fitlerTo', $this->getDate()->getLocalDate($this->toFilter)); // Get a list of all layout ids in the schedule right now // including any layouts that have been associated to our Display Group try { $dbh = $this->getStore()->getConnection(); $SQL = ' SELECT DISTINCT `lklayoutdisplaygroup`.layoutId FROM `lklayoutdisplaygroup` INNER JOIN `lkdgdg` ON `lkdgdg`.parentId = `lklayoutdisplaygroup`.displayGroupId INNER JOIN `lkdisplaydg` ON lkdisplaydg.DisplayGroupID = `lkdgdg`.childId INNER JOIN `layout` ON `layout`.layoutID = `lklayoutdisplaygroup`.layoutId WHERE lkdisplaydg.DisplayID = :displayId ORDER BY layoutId '; $params = array( 'displayId' => $this->display->displayId ); if ($this->display->isAuditing()) $this->getLog()->sql($SQL, $params); $sth = $dbh->prepare($SQL); $sth->execute($params); // Our layout list will always include the default layout $layouts = array(); $layouts[] = $this->display->defaultLayoutId; // Build up the other layouts into an array foreach ($sth->fetchAll() as $row) { $layouts[] = $this->getSanitizer()->int($row['layoutId']); } // Also look at the schedule foreach ($this->scheduleFactory->getForXmds($this->display->displayId, $this->fromFilter, $this->toFilter) as $row) { $schedule = $this->scheduleFactory->createEmpty()->hydrate($row); // Is this scheduled event a synchronised timezone? // if it is, then we get our events with respect to the timezone of the display $isSyncTimezone = ($schedule->syncTimezone == 1 && !empty($this->display->timeZone)); try { if ($isSyncTimezone) { $scheduleEvents = $schedule->getEvents($this->localFromFilter, $this->localToFilter); } else { $scheduleEvents = $schedule->getEvents($this->fromFilter, $this->toFilter); } } catch (XiboException $e) { $this->getLog()->error('Unable to getEvents for ' . $schedule->eventId); continue; } if (count($scheduleEvents) <= 0) continue; $this->getLog()->debug(count($scheduleEvents) . ' events for eventId ' . $schedule->eventId); $layoutId = $this->getSanitizer()->int($row['layoutId']); if ($layoutId != null && ($schedule->eventTypeId == Schedule::$LAYOUT_EVENT || $schedule->eventTypeId == Schedule::$OVERLAY_EVENT || $schedule->eventTypeId == Schedule::$INTERRUPT_EVENT || $schedule->eventTypeId == Schedule::$CAMPAIGN_EVENT)) { $layouts[] = $layoutId; } } } catch (\Exception $e) { $this->getLog()->error('Unable to get a list of layouts. ' . $e->getMessage()); return new \SoapFault('Sender', 'Unable to get a list of layouts'); } // Create a comma separated list to pass into the query which gets file nodes $layoutIdList = implode(',', $layouts); $playerVersionMediaId = $this->display->getSetting('versionMediaId', null, ['displayOverride' => true]); if ($this->display->clientType == 'sssp') { $playerVersionMediaId = null; } try { $dbh = $this->getStore()->getConnection(); // Run a query to get all required files for this display. // Include the following: // DownloadOrder: // 1 - Module System Files and fonts // 2 - Media Linked to Displays // 3 - Media Linked to Widgets in the Scheduled Layouts (linked through Playlists) // 4 - Background Images for all Scheduled Layouts // 5 - Media linked to display profile (linked through PlayerSoftware) $SQL = " SELECT 1 AS DownloadOrder, storedAs AS path, media.mediaID AS id, media.`MD5`, media.FileSize, media.released FROM `media` WHERE media.type = 'font' OR (media.type = 'module' AND media.moduleSystemFile = 1) UNION ALL SELECT 2 AS DownloadOrder, storedAs AS path, media.mediaID AS id, media.`MD5`, media.FileSize, media.released FROM `media` INNER JOIN `lkmediadisplaygroup` ON lkmediadisplaygroup.mediaid = media.MediaID INNER JOIN `lkdgdg` ON `lkdgdg`.parentId = `lkmediadisplaygroup`.displayGroupId INNER JOIN `lkdisplaydg` ON lkdisplaydg.DisplayGroupID = `lkdgdg`.childId WHERE lkdisplaydg.DisplayID = :displayId UNION ALL SELECT 3 AS DownloadOrder, storedAs AS path, media.mediaID AS id, media.`MD5`, media.FileSize, media.released FROM region INNER JOIN playlist ON playlist.regionId = region.regionId INNER JOIN lkplaylistplaylist ON lkplaylistplaylist.parentId = playlist.playlistId INNER JOIN widget ON widget.playlistId = lkplaylistplaylist.childId INNER JOIN lkwidgetmedia ON widget.widgetId = lkwidgetmedia.widgetId INNER JOIN media ON media.mediaId = lkwidgetmedia.mediaId WHERE region.layoutId IN (%s) UNION ALL SELECT 4 AS DownloadOrder, storedAs AS path, media.mediaId AS id, media.`MD5`, media.FileSize, media.released FROM `media` WHERE `media`.mediaID IN ( SELECT backgroundImageId FROM `layout` WHERE layoutId IN (%s) ) "; $params = ['displayId' => $this->display->displayId]; if ($playerVersionMediaId != null) { $SQL .= " UNION ALL SELECT 5 AS DownloadOrder, storedAs AS path, media.mediaId AS id, media.`MD5`, media.fileSize, media.released FROM `media` WHERE `media`.type = 'playersoftware' AND `media`.mediaId = :playerVersionMediaId "; $params['playerVersionMediaId'] = $playerVersionMediaId; } $SQL .= " ORDER BY DownloadOrder "; // Sub layoutId list $SQL = sprintf($SQL, $layoutIdList, $layoutIdList); if ($this->display->isAuditing()) { $this->getLog()->sql($SQL, $params); } $sth = $dbh->prepare($SQL); $sth->execute($params); // Prepare a SQL statement in case we need to update the MD5 and FileSize on media nodes. $mediaSth = $dbh->prepare('UPDATE media SET `MD5` = :md5, FileSize = :size WHERE MediaID = :mediaid'); // Keep a list of path names added to RF to prevent duplicates $pathsAdded = array(); foreach ($sth->fetchAll() as $row) { // Media $path = $this->getSanitizer()->string($row['path']); $id = $this->getSanitizer()->string($row['id']); $md5 = $row['MD5']; $fileSize = $this->getSanitizer()->int($row['FileSize']); $released = $this->getSanitizer()->int($row['released']); // Check we haven't added this before if (in_array($path, $pathsAdded)) continue; // Do we need to calculate a new MD5? // If they are empty calculate them and save them back to the media. if ($md5 == '' || $fileSize == 0) { $md5 = md5_file($libraryLocation . $path); $fileSize = filesize($libraryLocation . $path); // Update the media record with this information $mediaSth->execute(array('md5' => $md5, 'size' => $fileSize, 'mediaid' => $id)); } // Add nonce $mediaNonce = $this->requiredFileFactory->createForMedia($this->display->displayId, $id, $fileSize, $path, $released)->save(); // skip media which has released == 0 or 2 if ($released == 0 || $released == 2) { continue; } $newRfIds[] = $mediaNonce->rfId; // Add the file node $file = $requiredFilesXml->createElement("file"); $file->setAttribute("type", 'media'); $file->setAttribute("id", $id); $file->setAttribute("size", $fileSize); $file->setAttribute("md5", $md5); if ($httpDownloads) { // Serve a link instead (standard HTTP link) $file->setAttribute("path", $this->generateRequiredFileDownloadPath('M', $id, $playerNonce)); $file->setAttribute("saveAs", $path); $file->setAttribute("download", 'http'); } else { $file->setAttribute("download", 'xmds'); $file->setAttribute("path", $path); } $fileElements->appendChild($file); // Add to paths added $pathsAdded[] = $path; } } catch (\Exception $e) { $this->getLog()->error('Unable to get a list of required files. ' . $e->getMessage()); $this->getLog()->debug($e->getTraceAsString()); return new \SoapFault('Sender', 'Unable to get a list of files'); } // Get an array of modules to use $modules = $this->moduleFactory->get(); // Reset the paths added array to start again with layouts $pathsAdded = []; // Go through each layout and see if we need to supply any resource nodes. foreach ($layouts as $layoutId) { try { // Check we haven't added this before if (in_array($layoutId, $pathsAdded)) continue; // Load this layout $layout = $this->layoutFactory->loadById($layoutId); $layout->loadPlaylists(); // Make sure its XLF is up to date $path = $layout->xlfToDisk(['notify' => false]); // If the status is *still* 4, then we skip this layout as it cannot build if ($layout->status === ModuleWidget::$STATUS_INVALID) { $this->getLog()->debug('Skipping layoutId ' . $layout->layoutId . ' which wont build'); continue; } // For layouts the MD5 column is the layout xml $fileSize = filesize($path); $md5 = md5_file($path); $fileName = basename($path); // Log if ($this->display->isAuditing()) $this->getLog()->debug('MD5 for layoutid ' . $layoutId . ' is: [' . $md5 . ']'); // Add nonce $layoutNonce = $this->requiredFileFactory->createForLayout($this->display->displayId, $layoutId, $fileSize, $fileName)->save(); $newRfIds[] = $layoutNonce->rfId; // Add the Layout file element $file = $requiredFilesXml->createElement("file"); $file->setAttribute("type", 'layout'); $file->setAttribute("id", $layoutId); $file->setAttribute("size", $fileSize); $file->setAttribute("md5", $md5); // Permissive check for http layouts - always allow unless windows and <= 120 $supportsHttpLayouts = !($this->display->clientType == 'windows' && $this->display->clientCode <= 120); if ($httpDownloads && $supportsHttpLayouts) { // Serve a link instead (standard HTTP link) $file->setAttribute("path", $this->generateRequiredFileDownloadPath('L', $layoutId, $playerNonce)); $file->setAttribute("saveAs", $fileName); $file->setAttribute("download", 'http'); } else { $file->setAttribute("download", 'xmds'); $file->setAttribute("path", $layoutId); } // Get the Layout Modified Date $layoutModifiedDt = $this->getDate()->parse($layout->modifiedDt, 'Y-m-d H:i:s'); // Load the layout XML and work out if we have any ticker / text / dataset media items // Append layout resources before layout so they are downloaded first. // If layouts are set to expire immediately, the new layout will use the old resources if // the layout is downloaded first. foreach ($layout->regions as $region) { $playlist = $region->getPlaylist(); $playlist->setModuleFactory($this->moduleFactory); // Playlists might mean we include a widget more than once per region // if so, we only want to download a single copy of its resource node // if it is included in 2 regions - we most likely want a copy for each $resourcesAdded = []; foreach ($playlist->expandWidgets() as $widget) { /* @var Widget $widget */ if ($widget->type == 'ticker' || $widget->type == 'text' || $widget->type == 'datasetview' || $widget->type == 'webpage' || $widget->type == 'embedded' || $modules[$widget->type]->renderAs == 'html' ) { // If we've already parsed this widget in this region, then don't bother doing it again // we will only generate the same details. if (in_array($widget->widgetId, $resourcesAdded)) { continue; } // We've added this widget already $resourcesAdded[] = $widget->widgetId; // Add nonce $getResourceRf = $this->requiredFileFactory->createForGetResource($this->display->displayId, $widget->widgetId)->save(); $newRfIds[] = $getResourceRf->rfId; // Make me a module from the widget, so I can ask it whether it has an updated last accessed // date or not. $module = $this->moduleFactory->createWithWidget($widget); // Get the widget modified date // we will use the later of this vs the layout modified date as the updated attribute on // required files $widgetModifiedDt = $module->getModifiedDate($this->display->displayId); $cachedDt = $module->getCacheDate($this->display->displayId); // Updated date is the greater of layout/widget modified date $updatedDt = ($layoutModifiedDt->greaterThan($widgetModifiedDt)) ? $layoutModifiedDt : $widgetModifiedDt; // Finally compare against the cached date, and see if that has updated us at all $updatedDt = ($updatedDt->greaterThan($cachedDt)) ? $updatedDt : $cachedDt; // Append this item to required files $resourceFile = $requiredFilesXml->createElement("file"); $resourceFile->setAttribute('type', 'resource'); $resourceFile->setAttribute('id', $widget->widgetId); $resourceFile->setAttribute('layoutid', $layoutId); $resourceFile->setAttribute('regionid', $region->regionId); $resourceFile->setAttribute('mediaid', $widget->widgetId); $resourceFile->setAttribute('updated', $updatedDt->format('U')); $fileElements->appendChild($resourceFile); } } } // Append Layout $fileElements->appendChild($file); // Add to paths added $pathsAdded[] = $layoutId; } catch (XiboException $e) { $this->getLog()->error('Layout not found - ID: ' . $layoutId . ', skipping.'); continue; } } // Add a blacklist node $blackList = $requiredFilesXml->createElement("file"); $blackList->setAttribute("type", "blacklist"); $fileElements->appendChild($blackList); try { $dbh = $this->getStore()->getConnection(); $sth = $dbh->prepare('SELECT MediaID FROM blacklist WHERE DisplayID = :displayid AND isIgnored = 0'); $sth->execute(array( 'displayid' => $this->display->displayId )); // Add a black list element for each file foreach ($sth->fetchAll() as $row) { $file = $requiredFilesXml->createElement("file"); $file->setAttribute("id", $row['MediaID']); $blackList->appendChild($file); } } catch (\Exception $e) { $this->getLog()->error('Unable to get a list of blacklisted files. ' . $e->getMessage()); return new \SoapFault('Sender', 'Unable to get a list of blacklisted files'); } if ($this->display->isAuditing()) { $this->getLog()->debug($requiredFilesXml->saveXML()); } // Return the results of requiredFiles() $requiredFilesXml->formatOutput = true; $output = $requiredFilesXml->saveXML(); // Cache $cache->set($output); // RF cache expires every 4 hours $cache->expiresAfter(3600*4); $this->getPool()->saveDeferred($cache); // Remove any required files that remain in the array of rfIds $rfIds = array_values(array_diff($rfIds, $newRfIds)); if (count($rfIds) > 0) { $this->getLog()->debug('Removing ' . count($rfIds) . ' from requiredfiles'); try { $this->getStore()->updateWithDeadlockLoop('DELETE FROM `requiredfile` WHERE rfId IN (' . implode(',', array_fill(0, count($rfIds), '?')) . ')', $rfIds); } catch (DeadlockException $deadlockException) { $this->getLog()->error('Deadlock when deleting required files - ignoring and continuing with request'); } } // Set any remaining required files to have 0 bytes requested (as we've generated a new nonce) $this->getStore()->update('UPDATE `requiredfile` SET bytesRequested = 0 WHERE displayId = :displayId', [ 'displayId' => $this->display->displayId ]); // Phone Home? $this->phoneHome(); // Log Bandwidth $this->logBandwidth($this->display->displayId, Bandwidth::$RF, strlen($output)); return $output; } /** * @param $serverKey * @param $hardwareKey * @param array $options * @return mixed * @throws \SoapFault */ protected function doSchedule($serverKey, $hardwareKey, $options = []) { $this->logProcessor->setRoute('Schedule'); $options = array_merge(['dependentsAsNodes' => false, 'includeOverlays' => false], $options); // Sanitize $serverKey = $this->getSanitizer()->string($serverKey); $hardwareKey = $this->getSanitizer()->string($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('Sender', "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"); } // Check the cache $cache = $this->getPool()->getItem($this->display->getCacheKey() . '/schedule'); $cache->setInvalidationMethod(Invalidation::OLD); $output = $cache->get(); if ($cache->isHit()) { $this->getLog()->info('Returning Schedule from Cache for display %s. Options %s.', $this->display->display, json_encode($options)); // Log Bandwidth $this->logBandwidth($this->display->displayId, Bandwidth::$SCHEDULE, strlen($output)); return $output; } // We need to regenerate // Lock the cache $cache->lock(120); // Generate the Schedule XML $scheduleXml = new \DOMDocument("1.0"); $layoutElements = $scheduleXml->createElement("schedule"); $scheduleXml->appendChild($layoutElements); // Filter criteria $this->setDateFilters(); // Add the filter dates to the RF xml document $layoutElements->setAttribute('generated', $this->getDate()->getLocalDate()); $layoutElements->setAttribute('filterFrom', $this->getDate()->getLocalDate($this->fromFilter)); $layoutElements->setAttribute('filterTo', $this->getDate()->getLocalDate($this->toFilter)); try { $dbh = $this->getStore()->getConnection(); // Get all the module dependants $sth = $dbh->prepare("SELECT DISTINCT StoredAs FROM `media` WHERE media.type = 'font' OR (media.type = 'module' AND media.moduleSystemFile = 1) "); $sth->execute(array()); $rows = $sth->fetchAll(); $moduleDependents = array(); foreach ($rows as $dependent) { $moduleDependents[] = $dependent['StoredAs']; } // Add file nodes to the $fileElements // Firstly get all the scheduled layouts $events = $this->scheduleFactory->getForXmds($this->display->displayId, $this->fromFilter, $this->toFilter, $options); // If our dependents are nodes, then build a list of layouts we can use to query for nodes $layoutDependents = []; // Layouts (pop in the default) $layoutIds = [$this->display->defaultLayoutId]; // Calculate a sync key $syncKey = []; // Preparse events foreach ($events as $event) { if ($event['layoutId'] != null && !in_array($event['layoutId'], $layoutIds)) { $layoutIds[] = $event['layoutId']; } // Are we a sync event? if (intval($event['syncEvent']) == 1) { $syncKey[] = $event['eventId']; } } $syncKey = (count($syncKey) > 0) ? implode('-', $syncKey) : ''; $SQL = ' SELECT DISTINCT `region`.layoutId, `media`.storedAs FROM region INNER JOIN playlist ON playlist.regionId = region.regionId INNER JOIN lkplaylistplaylist ON lkplaylistplaylist.parentId = playlist.playlistId INNER JOIN widget ON widget.playlistId = lkplaylistplaylist.childId INNER JOIN lkwidgetmedia ON widget.widgetId = lkwidgetmedia.widgetId INNER JOIN media ON media.mediaId = lkwidgetmedia.mediaId WHERE region.layoutId IN (' . implode(',', $layoutIds) . ') AND media.type <> \'module\' '; foreach ($this->getStore()->select($SQL, []) as $row) { if (!array_key_exists($row['layoutId'], $layoutDependents)) $layoutDependents[$row['layoutId']] = []; $layoutDependents[$row['layoutId']][] = $row['storedAs']; } $this->getLog()->debug('Resolved dependents for Schedule: %s.', json_encode($layoutDependents, JSON_PRETTY_PRINT)); $overlayNodes = null; // We must have some results in here by this point foreach ($events as $row) { $schedule = $this->scheduleFactory->createEmpty()->hydrate($row); // Is this scheduled event a synchronised timezone? // if it is, then we get our events with respect to the timezone of the display $isSyncTimezone = ($schedule->syncTimezone == 1 && !empty($this->display->timeZone)); try { if ($isSyncTimezone) { $scheduleEvents = $schedule->getEvents($this->localFromFilter, $this->localToFilter); } else { $scheduleEvents = $schedule->getEvents($this->fromFilter, $this->toFilter); } } catch (XiboException $e) { $this->getLog()->error('Unable to getEvents for ' . $schedule->eventId); continue; } $this->getLog()->debug(count($scheduleEvents) . ' events for eventId ' . $schedule->eventId); foreach ($scheduleEvents as $scheduleEvent) { $eventTypeId = $row['eventTypeId']; $layoutId = $row['layoutId']; $commandCode = $row['code']; // Handle the from/to date of the events we have been returned (they are all returned with respect to // the current CMS timezone) // Does the Display have a timezone? if ($isSyncTimezone) { $fromDt = $this->getDate()->getLocalDate($scheduleEvent->fromDt, null, $this->display->timeZone); $toDt = $this->getDate()->getLocalDate($scheduleEvent->toDt, null, $this->display->timeZone); } else { $fromDt = $this->getDate()->getLocalDate($scheduleEvent->fromDt); $toDt = $this->getDate()->getLocalDate($scheduleEvent->toDt); } $scheduleId = $row['eventId']; $is_priority = $this->getSanitizer()->int($row['isPriority']); if ($eventTypeId == Schedule::$LAYOUT_EVENT || $eventTypeId == Schedule::$INTERRUPT_EVENT || $eventTypeId == Schedule::$CAMPAIGN_EVENT) { // Ensure we have a layoutId (we may not if an empty campaign is assigned) // https://github.com/xibosignage/xibo/issues/894 if ($layoutId == 0 || empty($layoutId)) { $this->getLog()->info('Player has empty event scheduled. Display = %s, EventId = %d', $this->display->display, $scheduleId); continue; } // Check the layout status // https://github.com/xibosignage/xibo/issues/743 if (intval($row['status']) > 3) { $this->getLog()->info('Player has invalid layout scheduled. Display = %s, LayoutId = %d', $this->display->display, $layoutId); continue; } // Add a layout node to the schedule $layout = $scheduleXml->createElement("layout"); $layout->setAttribute("file", $layoutId); $layout->setAttribute("fromdt", $fromDt); $layout->setAttribute("todt", $toDt); $layout->setAttribute("scheduleid", $scheduleId); $layout->setAttribute("priority", $is_priority); $layout->setAttribute("syncEvent", $syncKey); $layout->setAttribute("shareOfVoice", $row['shareOfVoice'] ?? 0); $layout->setAttribute("isGeoAware", $row['isGeoAware'] ?? 0); $layout->setAttribute("geoLocation", $row['geoLocation'] ?? null); // Handle dependents if (array_key_exists($layoutId, $layoutDependents)) { if ($options['dependentsAsNodes']) { // Add the dependents to the layout as new nodes $dependentNode = $scheduleXml->createElement("dependents"); foreach ($layoutDependents[$layoutId] as $storedAs) { $fileNode = $scheduleXml->createElement("file", $storedAs); $dependentNode->appendChild($fileNode); } $layout->appendChild($dependentNode); } else { // Add the dependents to the layout as an attribute $layout->setAttribute("dependents", implode(',', $layoutDependents[$layoutId])); } } $layoutElements->appendChild($layout); } else if ($eventTypeId == Schedule::$COMMAND_EVENT) { // Add a command node to the schedule $command = $scheduleXml->createElement("command"); $command->setAttribute("date", $fromDt); $command->setAttribute("scheduleid", $scheduleId); $command->setAttribute('code', $commandCode); $layoutElements->appendChild($command); } else if ($eventTypeId == Schedule::$OVERLAY_EVENT && $options['includeOverlays']) { // Ensure we have a layoutId (we may not if an empty campaign is assigned) // https://github.com/xibosignage/xibo/issues/894 if ($layoutId == 0 || empty($layoutId)) { $this->getLog()->error('Player has empty event scheduled. Display = %s, EventId = %d', $this->display->display, $scheduleId); continue; } // Check the layout status // https://github.com/xibosignage/xibo/issues/743 if (intval($row['status']) > 3) { $this->getLog()->error('Player has invalid layout scheduled. Display = %s, LayoutId = %d', $this->display->display, $layoutId); continue; } if ($overlayNodes == null) { $overlayNodes = $scheduleXml->createElement('overlays'); } $overlay = $scheduleXml->createElement('overlay'); $overlay->setAttribute("file", $layoutId); $overlay->setAttribute("fromdt", $fromDt); $overlay->setAttribute("todt", $toDt); $overlay->setAttribute("scheduleid", $scheduleId); $overlay->setAttribute("priority", $is_priority); $overlay->setAttribute("isGeoAware", $row['isGeoAware'] ?? 0); $overlay->setAttribute("geoLocation", $row['geoLocation'] ?? null); // Add to the overlays node list $overlayNodes->appendChild($overlay); } } } // Add the overlay nodes if we had any if ($overlayNodes != null) { $layoutElements->appendChild($overlayNodes); } } catch (\Exception $e) { $this->getLog()->error('Error getting the schedule. ' . $e->getMessage()); return new \SoapFault('Sender', 'Unable to get the schedule'); } // Are we interleaving the default? if ($this->display->incSchedule == 1) { // Add as a node at the end of the schedule. $layout = $scheduleXml->createElement("layout"); $layout->setAttribute("file", $this->display->defaultLayoutId); $layout->setAttribute("fromdt", '2000-01-01 00:00:00'); $layout->setAttribute("todt", '2030-01-19 00:00:00'); $layout->setAttribute("scheduleid", 0); $layout->setAttribute("priority", 0); if ($options['dependentsAsNodes'] && array_key_exists($this->display->defaultLayoutId, $layoutDependents)) { $dependentNode = $scheduleXml->createElement("dependents"); foreach ($layoutDependents[$this->display->defaultLayoutId] as $storedAs) { $fileNode = $scheduleXml->createElement("file", $storedAs); $dependentNode->appendChild($fileNode); } $layout->appendChild($dependentNode); } $layoutElements->appendChild($layout); } // Add on the default layout node $default = $scheduleXml->createElement("default"); $default->setAttribute("file", $this->display->defaultLayoutId); if ($options['dependentsAsNodes'] && array_key_exists($this->display->defaultLayoutId, $layoutDependents)) { $dependentNode = $scheduleXml->createElement("dependents"); foreach ($layoutDependents[$this->display->defaultLayoutId] as $storedAs) { $fileNode = $scheduleXml->createElement("file", $storedAs); $dependentNode->appendChild($fileNode); } $default->appendChild($dependentNode); } $layoutElements->appendChild($default); // Add on a list of global dependants $globalDependents = $scheduleXml->createElement("dependants"); foreach ($moduleDependents as $dep) { $dependent = $scheduleXml->createElement("file", $dep); $globalDependents->appendChild($dependent); } $layoutElements->appendChild($globalDependents); // Format the output $scheduleXml->formatOutput = true; if ($this->display->isAuditing()) $this->getLog()->debug($scheduleXml->saveXML()); $output = $scheduleXml->saveXML(); // Cache $cache->set($output); $cache->expiresAt($this->toFilter); $this->getPool()->saveDeferred($cache); // Log Bandwidth $this->logBandwidth($this->display->displayId, Bandwidth::$SCHEDULE, strlen($output)); return $output; } /** * @param $serverKey * @param $hardwareKey * @param $mediaId * @param $type * @param $reason * @return bool|\SoapFault * @throws \SoapFault */ protected function doBlackList($serverKey, $hardwareKey, $mediaId, $type, $reason) { $this->logProcessor->setRoute('BlackList'); // Sanitize $serverKey = $this->getSanitizer()->string($serverKey); $hardwareKey = $this->getSanitizer()->string($hardwareKey); $mediaId = $this->getSanitizer()->string($mediaId); $type = $this->getSanitizer()->string($type); $reason = $this->getSanitizer()->string($reason); // 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.", $hardwareKey); } // 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"); } if ($this->display->isAuditing()) $this->getLog()->debug('Blacklisting ' . $mediaId . ' for ' . $reason); try { $dbh = $this->getStore()->getConnection(); // Check to see if this media / display is already blacklisted (and not ignored) $sth = $dbh->prepare('SELECT BlackListID FROM blacklist WHERE MediaID = :mediaid AND isIgnored = 0 AND DisplayID = :displayid'); $sth->execute(array( 'mediaid' => $mediaId, 'displayid' => $this->display->displayId )); $results = $sth->fetchAll(); if (count($results) == 0) { $insertSth = $dbh->prepare(' INSERT INTO blacklist (MediaID, DisplayID, ReportingDisplayID, Reason) VALUES (:mediaid, :displayid, :reportingdisplayid, :reason) '); // Insert the black list record if ($type == BLACKLIST_SINGLE) { $insertSth->execute(array( 'mediaid' => $mediaId, 'displayid' => $this->display->displayId, 'reportingdisplayid' => $this->display->displayId, 'reason' => $reason )); } else { $displaySth = $dbh->prepare('SELECT displayID FROM `display`'); $displaySth->execute(); foreach ($displaySth->fetchAll() as $row) { $insertSth->execute(array( 'mediaid' => $mediaId, 'displayid' => $row['displayID'], 'reportingdisplayid' => $this->display->displayId, 'reason' => $reason )); } } } else { if ($this->display->isAuditing()) $this->getLog()->debug($mediaId . ' already black listed'); } } catch (\Exception $e) { $this->getLog()->error('Unable to query for Blacklist records. ' . $e->getMessage()); return new \SoapFault('Sender', "Unable to query for BlackList records."); } $this->logBandwidth($this->display->displayId, Bandwidth::$BLACKLIST, strlen($reason)); return true; } /** * @param $serverKey * @param $hardwareKey * @param $logXml * @return bool * @throws \SoapFault */ protected function doSubmitLog($serverKey, $hardwareKey, $logXml) { $this->logProcessor->setRoute('SubmitLog'); // Sanitize $serverKey = $this->getSanitizer()->string($serverKey); $hardwareKey = $this->getSanitizer()->string($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('Sender', '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"); } // Load the XML into a DOMDocument $document = new \DOMDocument("1.0"); if (!$document->loadXML($logXml)) { $this->getLog()->error('Malformed XML from Player, this will be discarded. The Raw XML String provided is: ' . $logXml); $this->getLog()->debug('XML log: ' . $logXml); return true; } // Current log level $logLevel = $this->logProcessor->getLevel(); $discardedLogs = 0; // Get the display timezone to use when adjusting log dates. $defaultTimeZone = $this->getConfig()->getSetting('defaultTimezone'); // Store processed logs in an array $logs = []; foreach ($document->documentElement->childNodes as $node) { /* @var \DOMElement $node */ // Make sure we don't consider any text nodes if ($node->nodeType == XML_TEXT_NODE) continue; // Zero out the common vars $scheduleId = ""; $layoutId = ""; $mediaId = ""; $method = ''; $thread = ''; $type = ''; // This will be a bunch of trace nodes $message = $node->textContent; // Each element should have a category and a date $date = $node->getAttribute('date'); $cat = strtolower($node->getAttribute('category')); if ($date == '' || $cat == '') { $this->getLog()->error('Log submitted without a date or category attribute'); continue; } // Does this meet the current log level? if ($cat == 'error') { $recordLogLevel = Log::ERROR; $levelName = 'ERROR'; } else if ($cat == 'audit' || $cat == 'trace') { $recordLogLevel = Log::DEBUG; $levelName = 'DEBUG'; } else if ($cat == 'debug') { $recordLogLevel = Log::INFO; $levelName = 'INFO'; } else { $recordLogLevel = Log::NOTICE; $levelName = 'NOTICE'; } if ($recordLogLevel > $logLevel) { $discardedLogs++; continue; } // Adjust the date according to the display timezone try { $date = ($this->display->timeZone != null) ? Date::createFromFormat('Y-m-d H:i:s', $date, $this->display->timeZone)->tz($defaultTimeZone) : Date::createFromFormat('Y-m-d H:i:s', $date); $date = $this->getDate()->getLocalDate($date); } catch (\Exception $e) { // Protect against the date format being inreadable $this->getLog()->debug('Date format unreadable on log message: ' . $date); // Use now instead $date = $this->getDate()->getLocalDate(); } // Get the date and the message (all log types have these) foreach ($node->childNodes as $nodeElements) { if ($nodeElements->nodeName == 'scheduleID') { $scheduleId = $nodeElements->textContent; } else if ($nodeElements->nodeName == 'layoutID') { $layoutId = $nodeElements->textContent; } else if ($nodeElements->nodeName == 'mediaID') { $mediaId = $nodeElements->textContent; } else if ($nodeElements->nodeName == 'type') { $type = $nodeElements->textContent; } else if ($nodeElements->nodeName == 'method') { $method = $nodeElements->textContent; } else if ($nodeElements->nodeName == 'message') { $message = $nodeElements->textContent; } else if ($nodeElements->nodeName == 'thread') { if ($nodeElements->textContent != '') $thread = '[' . $nodeElements->textContent . '] '; } } // If the message is still empty, take the entire node content if ($message == '') $message = $node->textContent; // Add the IDs to the message if ($scheduleId != '') $message .= ' scheduleId: ' . $scheduleId; if ($layoutId != '') $message .= ' layoutId: '. $layoutId; if ($mediaId != '') $message .= ' mediaId: ' . $mediaId; // Trim the page if it is over 50 characters. $page = $thread . $method . $type; if (strlen($page) >= 50) $page = substr($page, 0, 49); $logs[] = [ $this->logProcessor->getUid(), $date, 'PLAYER', $levelName, $page, 'POST', $message, 0, $this->display->displayId ]; } if (count($logs) > 0) { // Insert $sql = 'INSERT INTO log (runNo, logdate, channel, type, page, function, message, userid, displayid) VALUES '; $placeHolders = '(?, ?, ?, ?, ?, ?, ?, ?, ?)'; $sql = $sql . implode(', ', array_fill(1, count($logs), $placeHolders)); // Flatten the array $data = []; foreach ($logs as $log) { foreach ($log as $field) { $data[] = $field; } } // Insert $this->getStore()->isolated($sql, $data); } else { $this->getLog()->info('0 logs resolved from log package'); } if ($discardedLogs > 0) $this->getLog()->info('Discarded ' . $discardedLogs . ' logs. Consider adjusting your display profile log level. Resolved level is ' . $logLevel); $this->logBandwidth($this->display->displayId, Bandwidth::$SUBMITLOG, strlen($logXml)); return true; } /** * @param $serverKey * @param $hardwareKey * @param $statXml * @return bool * @throws \SoapFault */ protected function doSubmitStats($serverKey, $hardwareKey, $statXml) { $this->logProcessor->setRoute('SubmitStats'); // Sanitize $serverKey = $this->getSanitizer()->string($serverKey); $hardwareKey = $this->getSanitizer()->string($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"); } if ($this->display->isAuditing()) { $this->getLog()->debug('Received XML. ' . $statXml); } if ($statXml == "") { throw new \SoapFault('Receiver', "Stat XML is empty."); } // Store an array of parsed stat data for insert $now = $this->getDate()->parse(); // Get the display timezone to use when adjusting log dates. $defaultTimeZone = $this->getConfig()->getSetting('defaultTimezone'); // Count stats processed from XML $statCount = 0; // Load the XML into a DOMDocument $document = new \DOMDocument("1.0"); $document->loadXML($statXml); $layoutIdsNotFound = []; $widgetIdsNotFound = []; foreach ($document->documentElement->childNodes as $node) { /* @var \DOMElement $node */ // Make sure we don't consider any text nodes if ($node->nodeType == XML_TEXT_NODE) continue; // Each element should have these attributes $fromdt = $node->getAttribute('fromdt'); $todt = $node->getAttribute('todt'); $type = $node->getAttribute('type'); $duration = $node->getAttribute('duration'); $count = $node->getAttribute('count'); $engagements = []; foreach ($node->childNodes as $nodeElements) { /* @var \DOMElement $nodeElements */ if ($nodeElements->nodeName == 'engagements') { $i = 0; foreach ($nodeElements->childNodes as $child) { /* @var \DOMElement $child */ if ($child->nodeName == 'engagement') { $engagements[$i]['tag'] = $child->getAttribute('tag'); $engagements[$i]['duration'] = (int) $child->getAttribute('duration'); $engagements[$i]['count'] = (int) $child->getAttribute('count'); $i++; } } } } if ($fromdt == '' || $todt == '' || $type == '') { $this->getLog()->info('Stat submitted without the fromdt, todt or type attributes.'); continue; } // if fromdt and to dt are same then ignore them if ($fromdt == $todt) { $this->getLog()->debug('Ignoring a Stat record because the fromDt (' . $fromdt. ') and toDt (' . $todt. ') are the same'); continue; } $scheduleId = $node->getAttribute('scheduleid'); if (empty($scheduleId)) { $scheduleId = 0; } $layoutId = $node->getAttribute('layoutid'); if ($type != 'event') { // Handle the splash screen if ($layoutId == 'splash') { // only logging this message one time if (!in_array($layoutId, $layoutIdsNotFound)) { $layoutIdsNotFound[] = $layoutId; $this->getLog()->info('Splash Screen Statistic Ignored'); } continue; } } // Slightly confusing behaviour here to support old players without introducting a different call in // xmds v=5. // MediaId is actually the widgetId (since 1.8) and the mediaId is looked up by this service $widgetId = $node->getAttribute('mediaid'); $mediaId = null; // Ignore old "background" stat records. if ($widgetId === 'background') { $this->getLog()->info('Ignoring old "background" stat record.'); continue; } // The mediaId (really widgetId) might well be null if ($widgetId == 'null' || $widgetId == '') { $widgetId = 0; } else { // Try to get details for this widget try { if (in_array($widgetId, $widgetIdsNotFound)) { continue; } $mediaId = $this->widgetFactory->getWidgetForStat($widgetId); // If the mediaId is empty, then we can assume we're a stat for a region specific widget if ($mediaId === null) { $type = 'widget'; } } catch (NotFoundException $notFoundException) { // Widget isn't found // we can only log this and move on // only logging this message one time if (!in_array($widgetId, $widgetIdsNotFound)) { $widgetIdsNotFound[] = $widgetId; $this->getLog()->error('Stat for a widgetId that doesnt exist: ' . $widgetId); } continue; } } $tag = $node->getAttribute('tag'); if ($tag == 'null') $tag = null; if ($fromdt > $todt) { $this->getLog()->debug('From date is greater than to date: ' . $fromdt . ', toDt: ' . $todt); continue; } // Adjust the date according to the display timezone // stats are returned in the local date/time of the Player // the CMS will have been configured with that Player's timezone, so we can convert accordingly. try { // From date $fromdt = ($this->display->timeZone != null) ? Date::createFromFormat('Y-m-d H:i:s', $fromdt, $this->display->timeZone)->tz($defaultTimeZone) : Date::createFromFormat('Y-m-d H:i:s', $fromdt); // To date $todt = ($this->display->timeZone != null) ? Date::createFromFormat('Y-m-d H:i:s', $todt, $this->display->timeZone)->tz($defaultTimeZone) : Date::createFromFormat('Y-m-d H:i:s', $todt); // Do we need to set the duration of this record (we will do for older individually collected stats) if ($duration == '') { $duration = $todt->diffInSeconds($fromdt); // If the duration is enormous, then we have an eroneous message from the player if ($duration > (86400 * 365)) { throw new InvalidArgumentException('Dates are too far apart', 'duration'); } } } catch (\Exception $e) { // Protect against the date format being unreadable $this->getLog()->error('Stat with a from or to date that cannot be understood. fromDt: ' . $fromdt . ', toDt: ' . $todt . '. E = ' . $e->getMessage()); continue; } // Important - stats will now send display entity instead of displayId $stats = [ 'type' => $type, 'statDate' => $now, 'fromDt' => $fromdt, 'toDt' => $todt, 'scheduleId' => $scheduleId, 'display' => $this->display, 'layoutId' => (int) $layoutId, 'mediaId' => $mediaId, 'tag' => $tag, 'widgetId' => (int) $widgetId, 'duration' => (int) $duration, 'count' => ($count != '') ? (int) $count : 1, 'engagements' => (count($engagements) > 0) ? $engagements : [], ]; $this->getTimeSeriesStore()->addStat($stats); $statCount++; } /*Insert stats*/ if ($statCount > 0) { $this->getTimeSeriesStore()->addStatFinalize(); } else { $this->getLog()->info('0 stats resolved from data package'); } $this->logBandwidth($this->display->displayId, Bandwidth::$SUBMITSTATS, strlen($statXml)); return true; } /** * @param $serverKey * @param $hardwareKey * @param $inventory * @return bool * @throws \SoapFault */ protected function doMediaInventory($serverKey, $hardwareKey, $inventory) { $this->logProcessor->setRoute('MediaInventory'); // Sanitize $serverKey = $this->getSanitizer()->string($serverKey); $hardwareKey = $this->getSanitizer()->string($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"); } if ($this->display->isAuditing()) { $this->getLog()->debug($inventory); } // Check that the $inventory contains something if ($inventory == '') { throw new \SoapFault('Receiver', 'Inventory Cannot be Empty'); } // Load the XML into a DOMDocument $document = new \DOMDocument("1.0"); $document->loadXML($inventory); // Assume we are complete (but we are getting some) $mediaInventoryComplete = 1; $xpath = new \DOMXPath($document); $fileNodes = $xpath->query("//file"); foreach ($fileNodes as $node) { /* @var \DOMElement $node */ // What type of file? try { $requiredFile = null; switch ($node->getAttribute('type')) { case 'media': $requiredFile = $this->requiredFileFactory->getByDisplayAndMedia($this->display->displayId, $node->getAttribute('id')); break; case 'layout': $requiredFile = $this->requiredFileFactory->getByDisplayAndLayout($this->display->displayId, $node->getAttribute('id')); break; case 'resource': $requiredFile = $this->requiredFileFactory->getByDisplayAndWidget($this->display->displayId, $node->getAttribute('id')); break; default: $this->getLog()->debug('Skipping unknown node in media inventory: %s - %s.', $node->getAttribute('type'), $node->getAttribute('id')); continue; } // File complete? $complete = $node->getAttribute('complete'); $requiredFile->complete = $complete; $requiredFile->save(); // If this item is a 0 then set not complete if ($complete == 0) $mediaInventoryComplete = 2; } catch (NotFoundException $e) { $this->getLog()->error('Unable to find file in media inventory: ' . $node->getAttribute('type') . '. ' . $node->getAttribute('id')); } } $this->display->mediaInventoryStatus = $mediaInventoryComplete; // Only call save if this property has actually changed. if ($this->display->hasPropertyChanged('mediaInventoryStatus')) { $this->getLog()->debug('Media Inventory status changed to ' . $this->display->mediaInventoryStatus); // If we are complete, then drop the player nonce cache if ($this->display->mediaInventoryStatus == 1) { $this->getLog()->debug('Media Inventory tells us that all downloads are complete, clearing the nonce for this display'); $this->pool->deleteItem('/display/nonce/' . $this->display->displayId); } $this->display->saveMediaInventoryStatus(); } $this->logBandwidth($this->display->displayId, Bandwidth::$MEDIAINVENTORY, strlen($inventory)); return true; } /** * @param $serverKey * @param $hardwareKey * @param $layoutId * @param $regionId * @param $mediaId * @return mixed * @throws \SoapFault */ protected function doGetResource($serverKey, $hardwareKey, $layoutId, $regionId, $mediaId) { $this->logProcessor->setRoute('GetResource'); // Sanitize $serverKey = $this->getSanitizer()->string($serverKey); $hardwareKey = $this->getSanitizer()->string($hardwareKey); $layoutId = $this->getSanitizer()->int($layoutId); $regionId = $this->getSanitizer()->string($regionId); $mediaId = $this->getSanitizer()->string($mediaId); // 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"); } // The MediaId is actually the widgetId try { $requiredFile = $this->requiredFileFactory->getByDisplayAndWidget($this->display->displayId, $mediaId); $module = $this->moduleFactory->createWithWidget($this->widgetFactory->loadByWidgetId($mediaId), $this->regionFactory->getById($regionId)); $resource = $module->getResourceOrCache($this->display->displayId); $requiredFile->bytesRequested = $requiredFile->bytesRequested + strlen($resource); $requiredFile->save(); if ($resource == '') throw new ControllerNotImplemented(); } catch (NotFoundException $notEx) { throw new \SoapFault('Receiver', 'Requested an invalid file.'); } catch (\Exception $e) { $this->getLog()->error('Unknown error during getResource. E = ' . $e->getMessage()); $this->getLog()->debug($e->getTraceAsString()); throw new \SoapFault('Receiver', 'Unable to get the media resource'); } // Log Bandwidth $this->logBandwidth($this->display->displayId, Bandwidth::$GETRESOURCE, strlen($resource)); return $resource; } /** * PHONE_HOME if required */ protected function phoneHome() { if ($this->getConfig()->getSetting('PHONE_HOME') == 1) { // Find out when we last PHONED_HOME :D // If it's been > 28 days since last PHONE_HOME then if ($this->getConfig()->getSetting('PHONE_HOME_DATE') < (time() - (60 * 60 * 24 * 28))) { try { $dbh = $this->getStore()->getConnection(); // Retrieve number of displays $sth = $dbh->prepare('SELECT COUNT(*) AS Cnt FROM `display` WHERE `licensed` = 1'); $sth->execute(); $PHONE_HOME_CLIENTS = $sth->fetchColumn(); // Retrieve version number $PHONE_HOME_VERSION = Environment::$WEBSITE_VERSION_NAME; $PHONE_HOME_URL = $this->getConfig()->getSetting('PHONE_HOME_URL') . "?id=" . urlencode($this->getConfig()->getSetting('PHONE_HOME_KEY')) . "&version=" . urlencode($PHONE_HOME_VERSION) . "&numClients=" . urlencode($PHONE_HOME_CLIENTS); if ($this->display->isAuditing()) $this->getLog()->notice("audit", "PHONE_HOME_URL " . $PHONE_HOME_URL, "xmds", "RequiredFiles"); // Set PHONE_HOME_TIME to NOW. $sth = $dbh->prepare('UPDATE `setting` SET `value` = :time WHERE `setting`.`setting` = :setting LIMIT 1'); $sth->execute(array( 'time' => time(), 'setting' => 'PHONE_HOME_DATE' )); @file_get_contents($PHONE_HOME_URL); if ($this->display->isAuditing()) $this->getLog()->notice("audit", "PHONE_HOME [OUT]", "xmds", "RequiredFiles"); } catch (\Exception $e) { $this->getLog()->error($e->getMessage()); return false; } } } } /** * Authenticates the display * @param string $hardwareKey * @return bool */ protected function authDisplay($hardwareKey) { try { $this->display = $this->displayFactory->getByLicence($hardwareKey); if ($this->display->licensed != 1) return false; // Configure our log processor $this->logProcessor->setDisplay($this->display->displayId, ($this->display->isAuditing())); return true; } catch (NotFoundException $e) { $this->getLog()->error($e->getMessage()); return false; } } /** * Alert Display Up * @throws \phpmailerException * @throws NotFoundException */ protected function alertDisplayUp() { $maintenanceEnabled = $this->getConfig()->getSetting('MAINTENANCE_ENABLED'); if ($this->display->loggedIn == 0) { $this->getLog()->info('Display %s was down, now its up.', $this->display->display); // Log display up $this->displayEventFactory->createEmpty()->displayUp($this->display->displayId); $dayPartId = $this->display->getSetting('dayPartId', null,['displayOverride' => true]); $operatingHours = true; if ($dayPartId !== null) { try { $dayPart = $this->dayPartFactory->getById($dayPartId); $startTimeArray = explode(':', $dayPart->startTime); $startTime = Date::now()->setTime(intval($startTimeArray[0]), intval($startTimeArray[1])); $endTimeArray = explode(':', $dayPart->endTime); $endTime = Date::now()->setTime(intval($endTimeArray[0]), intval($endTimeArray[1])); $now = Date::now(); // exceptions foreach ($dayPart->exceptions as $exception) { // check if we are on exception day and if so override the startTime and endTime accordingly if ($exception['day'] == Date::now()->format('D')) { $exceptionsStartTime = explode(':', $exception['start']); $startTime = Date::now()->setTime(intval($exceptionsStartTime[0]), intval($exceptionsStartTime[1])); $exceptionsEndTime = explode(':', $exception['end']); $endTime = Date::now()->setTime(intval($exceptionsEndTime[0]), intval($exceptionsEndTime[1])); } } // check if we are inside the operating hours for this display - we use that flag to decide if we need to create a notification and send an email. if (($now >= $startTime && $now <= $endTime)) { $operatingHours = true; } else { $operatingHours = false; } } catch (NotFoundException $e) { $this->getLog()->debug('Unknown dayPartId set on Display Profile for displayId ' . $this->display->displayId); } } // Do we need to email? if ($this->display->emailAlert == 1 && ($maintenanceEnabled == 'On' || $maintenanceEnabled == 'Protected') && $this->getConfig()->getSetting('MAINTENANCE_EMAIL_ALERTS') == 1) { // for displays without dayPartId set, this is always true, otherwise we check if we are inside the operating hours set for this display if ($operatingHours) { $subject = sprintf(__("Recovery for Display %s"), $this->display->display); $body = sprintf(__("Display ID %d is now back online %s"), $this->display->displayId, $this->getDate()->parse()); // Create a notification assigned to system wide user groups try { $notification = $this->notificationFactory->createSystemNotification($subject, $body, $this->getDate()->parse()); // Add in any displayNotificationGroups, with permissions foreach ($this->userGroupFactory->getDisplayNotificationGroups($this->display->displayGroupId) as $group) { $notification->assignUserGroup($group); } $notification->save(); } catch (\Exception $e) { $this->getLog()->error('Unable to send email alert for display %s with subject %s and body %s', $this->display->display, $subject, $body); } } else { $this->getLog()->info('Not sending recovery email for Display - ' . $this->display->display . ' we are outside of its operating hours'); } } else { $this->getLog()->debug(sprintf('No email required. Email Alert: %d, Enabled: %s, Email Enabled: %s.', $this->display->emailAlert, $maintenanceEnabled, $this->getConfig()->getSetting('MAINTENANCE_EMAIL_ALERTS'))); } } } /** * Get the Client IP Address * @return string */ protected function getIp() { $clientIp = ''; $keys = array('X_FORWARDED_FOR', 'HTTP_X_FORWARDED_FOR', 'CLIENT_IP', 'REMOTE_ADDR'); foreach ($keys as $key) { if (isset($_SERVER[$key])) { $clientIp = $_SERVER[$key]; break; } } return $clientIp; } /** * Check we haven't exceeded the bandwidth limits * - Note, display logging doesn't work in here, this is CMS level logging * * @param int $displayId The Display ID * @return bool true if the check passes, false if it fails * @throws NotFoundException */ protected function checkBandwidth($displayId) { // Uncomment to enable auditing. //$this->logProcessor->setDisplay(0, true); $this->display = $this->displayFactory->getById($displayId); $xmdsLimit = $this->getConfig()->getSetting('MONTHLY_XMDS_TRANSFER_LIMIT_KB'); $displayBandwidthLimit = $this->display->bandwidthLimit; try { $bandwidthUsage = 0; if ($this->bandwidthFactory->isBandwidthExceeded($xmdsLimit, $bandwidthUsage)) { // Bandwidth Exceeded // Create a notification if we don't already have one today for this display. $subject = __('Bandwidth allowance exceeded'); $date = $this->dateService->parse(); if (count($this->notificationFactory->getBySubjectAndDate($subject, $this->dateService->getLocalDate($date->startOfDay(), 'U'), $this->dateService->getLocalDate($date->addDay(1)->startOfDay(), 'U'))) <= 0) { $body = __(sprintf('Bandwidth allowance of %s exceeded. Used %s', ByteFormatter::format($xmdsLimit * 1024), ByteFormatter::format($bandwidthUsage))); $notification = $this->notificationFactory->createSystemNotification( $subject, $body, $this->dateService->parse() ); $notification->save(); $this->getLog()->critical($subject); } return false; } elseif ($this->bandwidthFactory->isBandwidthExceeded($displayBandwidthLimit, $bandwidthUsage, $displayId)) { // Bandwidth Exceeded // Create a notification if we don't already have one today for this display. $subject = __(sprintf('Display ID %d exceeded the bandwidth limit', $this->display->displayId)); $date = $this->dateService->parse(); if (count($this->notificationFactory->getBySubjectAndDate($subject, $this->dateService->getLocalDate($date->startOfDay(), 'U'), $this->dateService->getLocalDate($date->addDay(1)->startOfDay(), 'U'))) <= 0) { $body = __(sprintf('Display bandwidth limit %s exceeded. Used %s for Display Id %d', ByteFormatter::format($displayBandwidthLimit * 1024), ByteFormatter::format($bandwidthUsage), $this->display->displayId)); $notification = $this->notificationFactory->createSystemNotification( $subject, $body, $this->dateService->parse() ); $notification->save(); $this->getLog()->critical($subject); } return false; } else { // Bandwidth not exceeded. return true; } } catch (\Exception $e) { $this->getLog()->error($e->getMessage()); return false; } } /** * Log Bandwidth Usage * @param int $displayId * @param string $type * @param int $sizeInBytes */ protected function logBandwidth($displayId, $type, $sizeInBytes) { $this->bandwidthFactory->createAndSave($type, $displayId, $sizeInBytes); } /** * Generate a file download path for HTTP downloads, taking into account the precence of a CDN. * @param $type * @param $itemId * @param $nonce * @return string */ protected function generateRequiredFileDownloadPath($type, $itemId, $nonce) { $saveAsPath = Wsdl::getRoot() . '?file=' . $nonce . '&displayId=' . $this->display->displayId . '&type=' . $type . '&itemId=' . $itemId; // CDN? $cdnUrl = $this->configService->getSetting('CDN_URL'); if ($cdnUrl != '') { // Serve a link to the CDN return 'http' . ( ( (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https') ) ? 's' : '') . '://' . $cdnUrl . urlencode($saveAsPath); } else { // Serve a HTTP link to XMDS return $saveAsPath; } } /** * Set Date Filters */ protected function setDateFilters() { // Hour to hour time bands for the query // Rf lookahead is the number of seconds ahead we should consider. // it may well be less than 1 hour, and if so we cannot do hour to hour time bands, we need to do // now, forwards. // Start with now: $fromFilter = $this->getDate()->parse(); // If this Display is in a different timezone, then we need to set that here for these filter criteria if (!empty($this->display->timeZone)) { $fromFilter->setTimezone($this->display->timeZone); } $rfLookAhead = $this->getSanitizer()->int($this->getConfig()->getSetting('REQUIRED_FILES_LOOKAHEAD')); if ($rfLookAhead >= 3600) { // Go from the top of this hour $fromFilter ->minute(0) ->second(0); } // If we're set to look ahead, then do so - otherwise grab only a 1 hour slice if ($this->getConfig()->getSetting('SCHEDULE_LOOKAHEAD') == 1) { $toFilter = $fromFilter->copy()->addSeconds($rfLookAhead); } else { $toFilter = $fromFilter->copy()->addHour(); } // Make sure our filters are expressed in CMS time, so that when we run the query we don't lose the timezone $this->localFromFilter = $fromFilter; $this->localToFilter = $toFilter; $this->fromFilter = $this->getDate()->parse($fromFilter->format('Y-m-d H:i:s')); $this->toFilter = $this->getDate()->parse($toFilter->format('Y-m-d H:i:s')); $this->getLog()->debug(sprintf('FromDT = %s [%d]. ToDt = %s [%d]', $fromFilter->toRssString(), $fromFilter->format('U'), $toFilter->toRssString(), $toFilter->format('U'))); } } PK #qY1 1 service_v4.wsdlnu [
Register the Display with the CMS
The files required by the requesting display
Gets the file requested
Gets the schedule
Set media to be blacklisted
Submit Logging from the Client
Submit Display statistics from the Client
Report back the clients MediaInventory
Gets the file requested
Report back the current status
Submit a screen shot for a display
PK #qYzj= = Soap5.phpnu [ logProcessor->setRoute('RegisterDisplay'); // Sanitize $serverKey = $this->getSanitizer()->string($serverKey); $hardwareKey = $this->getSanitizer()->string($hardwareKey); $displayName = $this->getSanitizer()->string($displayName); $clientType = $this->getSanitizer()->string($clientType); $clientVersion = $this->getSanitizer()->string($clientVersion); $clientCode = $this->getSanitizer()->int($clientCode); $macAddress = $this->getSanitizer()->string($macAddress); $clientAddress = $this->getIp(); $xmrChannel = $this->getSanitizer()->string($xmrChannel); $xmrPubKey = trim($this->getSanitizer()->string($xmrPubKey)); if ($xmrPubKey != '' && !str_contains($xmrPubKey, 'BEGIN PUBLIC KEY')) { $xmrPubKey = "-----BEGIN PUBLIC KEY-----\n" . $xmrPubKey . "\n-----END PUBLIC KEY-----\n"; } // 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); // Uncomment this if we want additional logging in register. //$this->logProcessor->setDisplay(0, 1); // 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 = $this->getDate()->parse(); // Append the time $displayElement->setAttribute('date', $this->getDate()->getLocalDate($dateNow)); $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 Registered and awaiting Authorisation from an Administrator in the CMS'); } 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']; // Disable the CEF browser option on Windows players if (strtolower($settingName) == 'usecefwebbrowser' && ($clientType == 'windows')) { $arrayItem['value'] = 0; } // Override the XMR address if empty if (strtolower($settingName) == 'xmrnetworkaddress' && $arrayItem['value'] == '') { $arrayItem['value'] = $this->getConfig()->getSetting('XMR_PUB_ADDRESS'); } $value = (isset($arrayItem['value']) ? $arrayItem['value'] : $arrayItem['default']); // Patch download and update windows to make sure they are only 00:00 // 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(':', $value); $value = $timeParts[0] . ':' . $timeParts[1]; } $node = $return->createElement($settingName, $value); if (isset($arrayItem['type'])) { $node->setAttribute('type', $arrayItem['type']); } $displayElement->appendChild($node); } // Player upgrades $version = ''; try { $upgradeMediaId = $this->display->getSetting('versionMediaId', null, ['displayOverride' => true]); if ($clientType != 'windows' && $upgradeMediaId != null) { $version = $this->playerVersionFactory->getByMediaId($upgradeMediaId); if ($clientType == 'android') { $version = json_encode([ 'id' => $upgradeMediaId, 'file' => $version->storedAs, 'code' => $version->code ]); } elseif ($clientType == 'lg') { $version = json_encode([ 'id' => $upgradeMediaId, 'file' => $version->storedAs, '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' => $upgradeMediaId, 'file' => $version->storedAs, '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); // cms move $displayMoveAddress = ($clientType == 'windows') ? 'NewCmsAddress' : 'newCmsAddress'; $node = $return->createElement($displayMoveAddress, $display->newCmsAddress); if ($clientType == 'windows') { $node->setAttribute('type', 'string'); } $displayElement->appendChild($node); $displayMoveKey = ($clientType == 'windows') ? 'NewCmsKey' : 'newCmsKey'; $node = $return->createElement($displayMoveKey, $display->newCmsKey); if ($clientType == 'windows') { $node->setAttribute('type', 'string'); } $displayElement->appendChild($node); // Add some special settings $nodeName = ($clientType == 'windows') ? 'DisplayName' : 'displayName'; $node = $return->createElement($nodeName); $node->appendChild($return->createTextNode($display->display)); if ($clientType == 'windows') { $node->setAttribute('type', 'string'); } $displayElement->appendChild($node); $nodeName = ($clientType == 'windows') ? 'ScreenShotRequested' : 'screenShotRequested'; $node = $return->createElement($nodeName, $display->screenShotRequested); if ($clientType == 'windows') { $node->setAttribute('type', 'checkbox'); } $displayElement->appendChild($node); $nodeName = ($clientType == 'windows') ? 'DisplayTimeZone' : 'displayTimeZone'; $node = $return->createElement($nodeName, (!empty($display->timeZone)) ? $display->timeZone : ''); if ($clientType == 'windows') { $node->setAttribute('type', 'string'); } $displayElement->appendChild($node); if (!empty($display->timeZone)) { // Calculate local time $dateNow->timezone($display->timeZone); // Append Local Time $displayElement->setAttribute('localTimezone', $display->timeZone); $displayElement->setAttribute('localDate', $this->getDate()->getLocalDate($dateNow)); } // Commands $commands = $display->getCommands(); if (count($commands) > 0) { // Append a command element $commandElement = $return->createElement('commands'); $displayElement->appendChild($commandElement); // Append each individual command foreach ($display->getCommands() as $command) { try { /* @var \Xibo\Entity\Command $command */ $node = $return->createElement($command->code); $commandString = $return->createElement('commandString', $command->commandString); $validationString = $return->createElement('validationString', $command->validationString); $node->appendChild($commandString); $node->appendChild($validationString); $commandElement->appendChild($node); } catch (\DOMException $DOMException) { $this->getLog()->error('Cannot add command to settings for displayId ' . $this->display->displayId . ', ' . $DOMException->getMessage()); } } } // Check to see if the channel/pubKey are already entered if ($display->isAuditing()) { $this->getLog()->debug('xmrChannel: [' . $xmrChannel . ']. xmrPublicKey: [' . $xmrPubKey . ']'); } // Update the Channel $display->xmrChannel = $xmrChannel; // Update the PUB Key only if it has been cleared if ($display->xmrPubKey == '') $display->xmrPubKey = $xmrPubKey; } } 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(); $display->xmrChannel = $xmrChannel; $display->xmrPubKey = $xmrPubKey; 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 is now Registered and awaiting Authorisation from an Administrator in the CMS'); else $displayElement->setAttribute('message', 'Display is active and ready to start.'); } // Send Notification if required $this->alertDisplayUp(); $display->lastAccessed = time(); $display->loggedIn = 1; $display->clientAddress = $clientAddress; $display->macAddress = $macAddress; $display->clientType = $clientType; $display->clientVersion = $clientVersion; $display->clientCode = $clientCode; //$display->operatingSystem = $operatingSystem; $display->save(Display::$saveOptionsMinimum); // cache checks $cacheSchedule = $this->getPool()->getItem($this->display->getCacheKey() . '/schedule'); $cacheSchedule->setInvalidationMethod(Invalidation::OLD); $displayElement->setAttribute('checkSchedule', ($cacheSchedule->isHit() ? crc32($cacheSchedule->get()) : "")); $cacheRF = $this->getPool()->getItem($this->display->getCacheKey() . '/requiredFiles'); $cacheRF->setInvalidationMethod(Invalidation::OLD); $displayElement->setAttribute('checkRf', ($cacheRF->isHit() ? crc32($cacheRF->get()) : "")); // Log Bandwidth $returnXml = $return->saveXML(); $this->logBandwidth($display->displayId, Bandwidth::$REGISTER, strlen($returnXml)); // Audit our return $this->getLog()->debug($returnXml); return $returnXml; } /** * Returns the schedule for the hardware key specified * @return string * @param string $serverKey * @param string $hardwareKey * @throws \SoapFault */ function Schedule($serverKey, $hardwareKey) { return $this->doSchedule($serverKey, $hardwareKey, ['dependentsAsNodes' => true, 'includeOverlays' => true]); } }PK #qYHy9 9 LogProcessor.phpnu [ log = $log; $this->uid = $uid; $this->method = $method; } /** * @param $route */ public function setRoute($route) { $this->route = $route; } /** * @param $displayId * @param bool $isAuditing */ public function setDisplay($displayId, $isAuditing) { if ($isAuditing) $this->log->setLevel(\Xibo\Service\LogService::resolveLogLevel('debug')); $this->displayId = $displayId; } /** * Get Log Level * @return int */ public function getLevel() { return $this->log->getLevel(); } /** * Get UID * @return string */ public function getUid() { return $this->uid; } /** * @param array $record * @return array */ public function __invoke(array $record) { $record['extra']['displayId'] = $this->displayId; $record['extra']['route'] = $this->route; $record['extra']['method'] = $this->method; return $record; } }PK #qYz'm 'm Soap4.phpnu [ . */ namespace Xibo\Xmds; use Intervention\Image\ImageManagerStatic as Img; use Jenssegers\Date\Date; use Xibo\Entity\Bandwidth; use Xibo\Entity\Display; use Xibo\Exception\NotFoundException; use Xibo\Exception\XiboException; use Xibo\Helper\Random; /** * Class Soap4 * @package Xibo\Xmds */ 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 * @return string * @throws \SoapFault */ public function RegisterDisplay($serverKey, $hardwareKey, $displayName, $clientType, $clientVersion, $clientCode, $operatingSystem, $macAddress, $xmrChannel = null, $xmrPubKey = null) { $this->logProcessor->setRoute('RegisterDisplay'); // Sanitize $serverKey = $this->getSanitizer()->string($serverKey); $hardwareKey = $this->getSanitizer()->string($hardwareKey); $displayName = $this->getSanitizer()->string($displayName); $clientType = $this->getSanitizer()->string($clientType); $clientVersion = $this->getSanitizer()->string($clientVersion); $clientCode = $this->getSanitizer()->int($clientCode); $macAddress = $this->getSanitizer()->string($macAddress); $clientAddress = $this->getIp(); // 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 = $this->getDate()->parse(); // Append the time $displayElement->setAttribute('date', $this->getDate()->getLocalDate($dateNow)); $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']; $node = $return->createElement($settingName, (isset($arrayItem['value']) ? $arrayItem['value'] : $arrayItem['default'])); if (isset($arrayItem['type'])) { $node->setAttribute('type', $arrayItem['type']); } // 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'] = Date::now()->setTime(intval($timeParts[0]), intval($timeParts[1])); } } $node = $return->createElement($arrayItem['name'], (isset($arrayItem['value']) ? $arrayItem['value'] : $arrayItem['default'])); $node->setAttribute('type', $arrayItem['type']); $displayElement->appendChild($node); } // Player upgrades $version = ''; try { $upgradeMediaId = $this->display->getSetting('versionMediaId', null, ['displayOverride' => true]); if ($clientType != 'windows' && $upgradeMediaId != null) { $version = $this->playerVersionFactory->getByMediaId($upgradeMediaId); if ($clientType == 'android') { $version = json_encode([ 'id' => $upgradeMediaId, 'file' => $version->storedAs, 'code' => $version->code ]); } elseif ($clientType == 'lg') { $version = json_encode([ 'id' => $upgradeMediaId, 'file' => $version->storedAs, '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' => $upgradeMediaId, 'file' => $version->storedAs, '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', $this->getDate()->getLocalDate($dateNow)); } } } 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 = time(); $display->loggedIn = 1; $display->clientAddress = $clientAddress; $display->macAddress = $macAddress; $display->clientType = $clientType; $display->clientVersion = $clientVersion; $display->clientCode = $clientCode; //$display->operatingSystem = $operatingSystem; $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 */ 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 */ function GetFile($serverKey, $hardwareKey, $fileId, $fileType, $chunkOffset, $chunkSize) { $this->logProcessor->setRoute('GetFile'); // Sanitize $serverKey = $this->getSanitizer()->string($serverKey); $hardwareKey = $this->getSanitizer()->string($hardwareKey); $fileId = $this->getSanitizer()->int($fileId); $fileType = $this->getSanitizer()->string($fileType); $chunkOffset = $this->getSanitizer()->int($chunkOffset); $chunkSize = $this->getSanitizer()->int($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"); } if ($this->display->isAuditing()) { $this->getLog()->debug('hardwareKey: ' . $hardwareKey . ', fileId: ' . $fileId . ', fileType: ' . $fileType . ', chunkOffset: ' . $chunkOffset . ', chunkSize: ' . $chunkSize); } try { if ($fileType == "layout") { $fileId = $this->getSanitizer()->int($fileId); // Validate the nonce $requiredFile = $this->requiredFileFactory->getByDisplayAndLayout($this->display->displayId, $fileId); // Load the layout $layout = $this->layoutFactory->getById($fileId); $path = $layout->xlfToDisk(); $file = file_get_contents($path); $chunkSize = filesize($path); $requiredFile->bytesRequested = $requiredFile->bytesRequested + $chunkSize; $requiredFile->save(); } else if ($fileType == "media") { // Validate the nonce $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. ' . $libraryLocation); // Return the Chunk size specified if (!$f = fopen($libraryLocation . $media->storedAs, 'r')) 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 $this->logBandwidth($this->display->displayId, Bandwidth::$GETFILE, $chunkSize); return $file; } /** * Returns the schedule for the hardware key specified * @return string * @param string $serverKey * @param string $hardwareKey * @throws \SoapFault */ 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 */ function BlackList($serverKey, $hardwareKey, $mediaId, $type, $reason) { return $this->doBlackList($serverKey, $hardwareKey, $mediaId, $type, $reason); } /** * Submit client logging * @return bool * @param string $serverKey * @param string $hardwareKey * @param string $logXml * @throws \SoapFault */ function SubmitLog($serverKey, $hardwareKey, $logXml) { return $this->doSubmitLog($serverKey, $hardwareKey, $logXml); } /** * Submit display statistics to the server * @return bool * @param string $serverKey * @param string $hardwareKey * @param string $statXml * @throws \SoapFault */ 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 */ 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 */ 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 */ public function NotifyStatus($serverKey, $hardwareKey, $status) { $this->logProcessor->setRoute('NotifyStatus'); // Sanitize $serverKey = $this->getSanitizer()->string($serverKey); $hardwareKey = $this->getSanitizer()->string($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) if ($this->display->isAuditing()) { $this->getLog()->debug($status); } $this->logBandwidth($this->display->displayId, Bandwidth::$NOTIFYSTATUS, strlen($status)); $status = json_decode($status, true); $this->display->storageAvailableSpace = $this->getSanitizer()->getInt('availableSpace', $this->display->storageAvailableSpace, $status); $this->display->storageTotalSpace = $this->getSanitizer()->getInt('totalSpace', $this->display->storageTotalSpace, $status); $this->display->lastCommandSuccess = $this->getSanitizer()->getCheckbox('lastCommandSuccess', $this->display->lastCommandSuccess, $status); $this->display->deviceName = $this->getSanitizer()->getString('deviceName', $this->display->deviceName, $status); $commercialLicenceString = $this->getSanitizer()->getString('licenceResult', null, $status); // Commercial Licence Check, 0 - Not licensed, 1 - licensed, 2 - trial licence, 3 - not applicable if (!empty($commercialLicenceString)) { if ($commercialLicenceString === 'Licensed fully') { $commercialLicence = 1; } elseif ($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 = $this->getSanitizer()->getString('timeZone', $status); if (!empty($timeZone)) { // Validate the provided data and log/ignore if not well formatted if (array_key_exists($timeZone, $this->getDate()->timezoneList())) { $this->display->timeZone = $timeZone; } else { $this->getLog()->info('Ignoring Incorrect timezone string: ' . $timeZone); } } // Current Layout $currentLayoutId = $this->getSanitizer()->getInt('currentLayoutId', $status); if ($currentLayoutId !== null) { $this->display->setCurrentLayoutId($this->getPool(), $currentLayoutId); } // Status Dialog $statusDialog = $this->getSanitizer()->getString('statusDialog', null, $status); if ($statusDialog !== null) { $this->getLog()->alert($statusDialog); } // Resolution $width = $this->getSanitizer()->getInt('width', null, $status); $height = $this->getSanitizer()->getInt('height', null, $status); if ($width != null && $height != null) { // Determine the orientation $this->display->orientation = ($width >= $height) ? 'landscape' : 'portrait'; $this->display->resolution = $width . 'x' . $height; } // Lat/Long $latitude = $this->getSanitizer()->getDouble('latitude', null, $status); $longitude = $this->getSanitizer()->getDouble('longitude', null, $status); 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 (XiboException $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 */ public function SubmitScreenShot($serverKey, $hardwareKey, $screenShot) { $this->logProcessor->setRoute('SubmitScreenShot'); // Sanitize $serverKey = $this->getSanitizer()->string($serverKey); $hardwareKey = $this->getSanitizer()->string($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"); } if ($this->display->isAuditing()) { $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) { if ($this->display->isAuditing()) $this->getLog()->debug($imgDriver . " - " . $e->getMessage()); } if($screenShotImg !== false) { if ($this->display->isAuditing()) $this->getLog()->debug("Use " . $imgDriver); break; } } if ($screenShotImg !== false) { $imgMime = $screenShotImg->mime(); if($imgMime != $screenShotMime) { $needConversion = true; try { if ($this->display->isAuditing()) $this->getLog()->debug("converting: '" . $imgMime . "' to '" . $screenShotMime . "'"); $screenShot = (string) $screenShotImg->encode($screenShotFmt); $converted = true; } catch (\Exception $e) { if ($this->display->isAuditing()) $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(), $this->getDate()->getLocalDate()); $this->logBandwidth($this->display->displayId, Bandwidth::$SCREENSHOT, filesize($location)); return true; } } PK #qYӧK Wsdl.phpnu [ path = $path; $this->version = $version; } public function output() { // We need to buffer the output so that we can send a Content-Length header with the WSDL ob_start(); $wsdl = file_get_contents($this->path); $wsdl = str_replace('{{XMDS_LOCATION}}', $this->getRoot() . '?v=' . $this->version, $wsdl); echo $wsdl; // Get the contents of the buffer and work out its length $buffer = ob_get_contents(); $length = strlen($buffer); // Output the headers header('Content-Type: text/xml; charset=ISO-8859-1\r\n'); header('Content-Length: ' . $length); // Flush the buffer ob_end_flush(); } /** * get Root url * @return string */ public static function getRoot() { # Check REQUEST_URI is set. IIS doesn't set it so we need to build it # Attribution: # Code snippet from http://support.ecenica.com/web-hosting/scripting/troubleshooting-scripting-errors/how-to-fix-server-request_uri-php-error-on-windows-iis/ # Released under BSD License # Copyright (c) 2009, Ecenica Limited All rights reserved. if (!isset($_SERVER['REQUEST_URI'])) { $_SERVER['REQUEST_URI'] = $_SERVER['PHP_SELF']; if (isset($_SERVER['QUERY_STRING'])) { $_SERVER['REQUEST_URI'].='?'.$_SERVER['QUERY_STRING']; } } ## End Code Snippet $request = explode('?', $_SERVER['REQUEST_URI']); return ((new HttpsDetect())->getUrl()) . '/' . ltrim($request[0], '/'); } }PK #qYC$( ( Soap3.phpnu [ . */ namespace Xibo\Xmds; use Xibo\Entity\Bandwidth; use Xibo\Exception\NotFoundException; /** * Class Soap3 * @package Xibo\Xmds */ class Soap3 extends Soap { /** * Registers a new display * @param string $serverKey * @param string $hardwareKey * @param string $displayName * @param string $version * @return string * @throws \SoapFault */ public function RegisterDisplay($serverKey, $hardwareKey, $displayName, $version) { $this->logProcessor->setRoute('RegisterDisplay'); // Sanitize $serverKey = $this->getSanitizer()->string($serverKey); $hardwareKey = $this->getSanitizer()->string($hardwareKey); // Check the serverKey matches the one we have 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).'); // Check in the database for this hardwareKey try { $display = $this->displayFactory->getByLicence($hardwareKey); if (!$display->isDisplaySlotAvailable()) { $display->licensed = 0; } $this->logProcessor->setDisplay($display->displayId, ($display->isAuditing())); if ($display->licensed == 0) { $active = 'Display is awaiting licensing approval from an Administrator.'; } else { $active = 'Display is active and ready to start.'; } // Touch $display->lastAccessed = time(); $display->loggedIn = 1; $display->save(['validate' => false, 'audit' => false]); // Log Bandwidth $this->logBandwidth($display->displayId, Bandwidth::$REGISTER, strlen($active)); $this->getLog()->debug($active, $display->displayId); return $active; } catch (NotFoundException $e) { $this->getLog()->error('Attempt to register a Version 3 Display with key %s.', $hardwareKey); throw new \SoapFault('Sender', 'You cannot register an old display against this CMS.'); } } /** * Returns a string containing the required files xml for the requesting display * @param string $serverKey * @param string $hardwareKey Display Hardware Key * @param string $version * @return string $requiredXml Xml Formatted * @throws \SoapFault */ function RequiredFiles($serverKey, $hardwareKey, $version) { $httpDownloads = false; 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 string $filePath * @param string $fileType The File Type * @param int $chunkOffset The Offset of the Chunk Requested * @param string $chunkSize The Size of the Chunk Requested * @param string $version * @return string * @throws \SoapFault */ function GetFile($serverKey, $hardwareKey, $filePath, $fileType, $chunkOffset, $chunkSize, $version) { $this->logProcessor->setRoute('GetFile'); // Sanitize $serverKey = $this->getSanitizer()->string($serverKey); $hardwareKey = $this->getSanitizer()->string($hardwareKey); $filePath = $this->getSanitizer()->string($filePath); $fileType = $this->getSanitizer()->string($fileType); $chunkOffset = $this->getSanitizer()->int($chunkOffset); $chunkSize = $this->getSanitizer()->int($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"); } if ($this->display->isAuditing()) { $this->getLog()->debug("[IN] Params: [$hardwareKey] [$filePath] [$fileType] [$chunkOffset] [$chunkSize]"); } $file = null; if (empty($filePath)) { $this->getLog()->error('Soap3 GetFile request without a file path. Maybe a player missing ?v= parameter'); throw new \SoapFault('Receiver', 'GetFile request is missing file path - is this version compatible with this CMS?'); } try { // Handle fetching the file if ($fileType == "layout") { $fileId = $this->getSanitizer()->int($filePath); // Validate the nonce $requiredFile = $this->requiredFileFactory->getByDisplayAndLayout($this->display->displayId, $fileId); // Load the layout $layout = $this->layoutFactory->getById($fileId); $path = $layout->xlfToDisk(); $file = file_get_contents($path); $chunkSize = filesize($path); $requiredFile->bytesRequested = $requiredFile->bytesRequested + $chunkSize; $requiredFile->save(); } else if ($fileType == "media") { // Get the ID if (strstr($filePath, '/') || strstr($filePath, '\\')) throw new NotFoundException("Invalid file path."); $fileId = explode('.', $filePath); // Validate the nonce $requiredFile = $this->requiredFileFactory->getByDisplayAndMedia($this->display->displayId, $fileId[0]); // Return the Chunk size specified $f = fopen($libraryLocation . $filePath, 'r'); fseek($f, $chunkOffset); $file = fread($f, $chunkSize); // Store file size for bandwidth log $chunkSize = strlen($file); $requiredFile->bytesRequested = $requiredFile->bytesRequested + $chunkSize; $requiredFile->save(); } else { throw new NotFoundException('Unknown FileType Requested.'); } } catch (NotFoundException $e) { $this->getLog()->error($e->getMessage()); throw new \SoapFault('Receiver', 'Requested an invalid file.'); } // Log Bandwidth $this->logBandwidth($this->display->displayId, Bandwidth::$GETFILE, $chunkSize); return $file; } /** * Returns the schedule for the hardware key specified * @return string * @param string $serverKey * @param string $hardwareKey * @param string $version * @throws \SoapFault */ function Schedule($serverKey, $hardwareKey, $version) { return $this->doSchedule($serverKey, $hardwareKey); } /** * BlackList * @return bool * @param string $serverKey * @param string $hardwareKey * @param string $mediaId * @param string $type * @param string $reason * @param string $version * @throws \SoapFault */ function BlackList($serverKey, $hardwareKey, $mediaId, $type, $reason, $version) { return $this->doBlackList($serverKey, $hardwareKey, $mediaId, $type, $reason); } /** * Submit client logging * @return bool * @param string $version * @param string $serverKey * @param string $hardwareKey * @param string $logXml * @throws \SoapFault */ function SubmitLog($version, $serverKey, $hardwareKey, $logXml) { return $this->doSubmitLog($serverKey, $hardwareKey, $logXml); } /** * Submit display statistics to the server * @return bool * @param string $version * @param string $serverKey * @param string $hardwareKey * @param string $statXml * @throws \SoapFault */ function SubmitStats($version, $serverKey, $hardwareKey, $statXml) { return $this->doSubmitStats($serverKey, $hardwareKey, $statXml); } /** * Store the media inventory for a client * @param string $version * @param string $serverKey * @param string $hardwareKey * @param string $inventory * @return bool * @throws \SoapFault */ public function MediaInventory($version, $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 * @param string $version * @return string * @throws \SoapFault */ function GetResource($serverKey, $hardwareKey, $layoutId, $regionId, $mediaId, $version) { return $this->doGetResource($serverKey, $hardwareKey, $layoutId, $regionId, $mediaId); } } PK #qY22 22 service_v5.wsdlnu [
Register the Display with the CMS
The files required by the requesting display
Gets the file requested
Gets the schedule
Set media to be blacklisted
Submit Logging from the Client
Submit Display statistics from the Client
Report back the clients MediaInventory
Gets the file requested
Report back the current status
Submit a screen shot for a display
PK #qYH@B* B* service_v3.wsdlnu [
Register the Display with the CMS
The files required by the requesting display
Gets the file requested
Gets the schedule
Set media to be blacklisted
Submit Logging from the Client
Submit Display statistics from the Client
Report back the clients MediaInventory
Gets the file requested
PK #qYOVY Y Soap.phpnu [ PK #qY1 1 Y service_v4.wsdlnu [ PK #qYzj= = ɋ Soap5.phpnu [ PK #qYHy9 9 LogProcessor.phpnu [ PK #qYz'm 'm Soap4.phpnu [ PK #qYӧK }= Wsdl.phpnu [ PK #qYC$( ( E Soap3.phpnu [ PK #qY22 22 Nn service_v5.wsdlnu [ PK #qYH@B* B* service_v3.wsdlnu [ PK @