diff --git a/composer.json b/composer.json index 28418bbf59..6d957baf28 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,7 @@ "guzzlehttp/guzzle": "7.5.*", "league/oauth2-client": "^2.4", "james-heinrich/getid3": "^1.9", - "onelogin/php-saml": "3.3.x", + "onelogin/php-saml": "4.1.*", "infostars/picofeed": "dev-westphal/php8", "xibosignage/xibo-xmr": "0.*", "tedivm/stash": "^v0.17.6", diff --git a/composer.lock b/composer.lock index 1640ab93fa..b3dea24496 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dc0352ce4a19ec98a0d35254f4906151", + "content-hash": "b1df8dedaf8e58fb6fc024dc7e4dddf5", "packages": [ { "name": "akrabat/ip-address-middleware", @@ -3284,34 +3284,35 @@ }, { "name": "onelogin/php-saml", - "version": "3.3.1", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/onelogin/php-saml.git", - "reference": "bb34489635cd5c7eb1b42833e4c57ca1c786a81a" + "reference": "b22a57ebd13e838b90df5d3346090bc37056409d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/onelogin/php-saml/zipball/bb34489635cd5c7eb1b42833e4c57ca1c786a81a", - "reference": "bb34489635cd5c7eb1b42833e4c57ca1c786a81a", + "url": "https://api.github.com/repos/onelogin/php-saml/zipball/b22a57ebd13e838b90df5d3346090bc37056409d", + "reference": "b22a57ebd13e838b90df5d3346090bc37056409d", "shasum": "" }, "require": { - "php": ">=5.4", - "robrichards/xmlseclibs": ">=3.0.4" + "php": ">=7.3", + "robrichards/xmlseclibs": ">=3.1.1" }, "require-dev": { - "pdepend/pdepend": "^2.5.0", - "php-coveralls/php-coveralls": "^1.0.2 || ^2.0", - "phploc/phploc": "^2.1 || ^3.0 || ^4.0", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1", - "sebastian/phpcpd": "^2.0 || ^3.0 || ^4.0", - "squizlabs/php_codesniffer": "^3.1.1" + "pdepend/pdepend": "^2.8.0", + "php-coveralls/php-coveralls": "^2.0", + "phploc/phploc": "^4.0 || ^5.0 || ^6.0 || ^7.0", + "phpunit/phpunit": "^9.5", + "sebastian/phpcpd": "^4.0 || ^5.0 || ^6.0 ", + "squizlabs/php_codesniffer": "^3.5.8" }, "suggest": { "ext-curl": "Install curl lib to be able to use the IdPMetadataParser for parsing remote XMLs", - "ext-gettext": "Install gettext and php5-gettext libs to handle translations", - "ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)" + "ext-dom": "Install xml lib", + "ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)", + "ext-zlib": "Install zlib" }, "type": "library", "autoload": { @@ -3335,7 +3336,7 @@ "issues": "https://github.com/onelogin/php-saml/issues", "source": "https://github.com/onelogin/php-saml/" }, - "time": "2019-11-06T16:59:38+00:00" + "time": "2022-07-15T20:44:36+00:00" }, { "name": "paragonie/random_compat", diff --git a/lib/Controller/Display.php b/lib/Controller/Display.php index 39fa173ffd..5911c5c8cf 100644 --- a/lib/Controller/Display.php +++ b/lib/Controller/Display.php @@ -504,6 +504,7 @@ public function getFilters(SanitizerInterface $parsedQueryParams): array 'syncGroupId' => $parsedQueryParams->getInt('syncGroupId'), 'syncGroupIdMembers' => $parsedQueryParams->getInt('syncGroupIdMembers'), 'xmrRegistered' => $parsedQueryParams->getInt('xmrRegistered'), + 'isPlayerSupported' => $parsedQueryParams->getInt('isPlayerSupported'), ]; } @@ -649,6 +650,13 @@ public function getFilters(SanitizerInterface $parsedQueryParams): array * type="integer", * required=false * ), + * @SWG\Parameter( + * name="isPlayerSupported", + * in="query", + * description="Filter by whether the player is supported (1 or 0)", + * type="integer", + * required=false + * ), * @SWG\Response( * response=200, * description="successful operation", diff --git a/lib/Factory/DisplayFactory.php b/lib/Factory/DisplayFactory.php index 72b2ef0209..977720f973 100644 --- a/lib/Factory/DisplayFactory.php +++ b/lib/Factory/DisplayFactory.php @@ -25,6 +25,7 @@ use Xibo\Entity\Display; use Xibo\Entity\User; +use Xibo\Helper\Environment; use Xibo\Service\ConfigServiceInterface; use Xibo\Service\DisplayNotifyServiceInterface; use Xibo\Support\Exception\NotFoundException; @@ -685,6 +686,17 @@ public function query($sortOrder = null, $filterBy = []) $body .= ' AND `display`.xmrChannel IS NULL '; } + // Player version supported + if ($parsedBody->getInt('isPlayerSupported') !== null) { + if ($parsedBody->getInt('isPlayerSupported') === 1) { + $body .= ' AND `display`.client_code >= :playerSupport '; + } else { + $body .= ' AND `display`.client_code < :playerSupport '; + } + + $params['playerSupport'] = Environment::$PLAYER_SUPPORT; + } + $this->viewPermissionSql( 'Xibo\Entity\DisplayGroup', $body, diff --git a/lib/Helper/Environment.php b/lib/Helper/Environment.php index b437915c91..c96f1a9686 100644 --- a/lib/Helper/Environment.php +++ b/lib/Helper/Environment.php @@ -35,6 +35,7 @@ class Environment public static $XLF_VERSION = 4; public static $VERSION_REQUIRED = '8.1.0'; public static $VERSION_UNSUPPORTED = '9.0'; + public static $PLAYER_SUPPORT = 300; /** @var null cache migration status for the whole request */ private static $migrationStatus = null; diff --git a/lib/Middleware/CASAuthentication.php b/lib/Middleware/CASAuthentication.php index d0854fa23d..65d56e1686 100644 --- a/lib/Middleware/CASAuthentication.php +++ b/lib/Middleware/CASAuthentication.php @@ -47,7 +47,7 @@ class CASAuthentication extends AuthenticationBase public function addRoutes() { $app = $this->app; - $app->getContainer()->logoutRoute = 'cas.logout'; + $app->getContainer()->set('logoutRoute', 'cas.logout'); $app->map(['GET', 'POST'], '/cas/login', function (\Slim\Http\ServerRequest $request, \Slim\Http\Response $response) { diff --git a/lib/Middleware/SAMLAuthentication.php b/lib/Middleware/SAMLAuthentication.php index 5165456536..a14ceaab8f 100644 --- a/lib/Middleware/SAMLAuthentication.php +++ b/lib/Middleware/SAMLAuthentication.php @@ -51,7 +51,7 @@ class SAMLAuthentication extends AuthenticationBase public function addRoutes() { $app = $this->app; - $app->getContainer()->logoutRoute = 'saml.logout'; + $app->getContainer()->set('logoutRoute', 'saml.logout'); // Route providing SAML metadata $app->get('/saml/metadata', function (Request $request, Response $response) { diff --git a/lib/Middleware/Theme.php b/lib/Middleware/Theme.php index b0aa560999..c3d0546064 100644 --- a/lib/Middleware/Theme.php +++ b/lib/Middleware/Theme.php @@ -128,6 +128,7 @@ public static function setTheme(ContainerInterface $container, Request $request, ]; $view['version'] = Environment::$WEBSITE_VERSION_NAME; $view['revision'] = Environment::getGitCommit(); + $view['playerVersion'] = Environment::$PLAYER_SUPPORT; $view['isDevMode'] = Environment::isDevMode(); $samlSettings = $container->get('configService')->samlSettings; if (isset($samlSettings['workflow']) diff --git a/lib/Widget/RssProvider.php b/lib/Widget/RssProvider.php index c2ec8471c1..22812a4887 100644 --- a/lib/Widget/RssProvider.php +++ b/lib/Widget/RssProvider.php @@ -122,7 +122,7 @@ public function fetchData(DataProviderInterface $dataProvider): WidgetProviderIn usort($feedItems, function ($a, $b) { /* @var Item $a */ /* @var Item $b */ - return $a->getDate()->getTimestamp() - $b->getDate()->getTimestamp(); + return $b->getDate()->getTimestamp() - $a->getDate()->getTimestamp(); }); } diff --git a/modules/hls.xml b/modules/hls.xml index b915e291fc..6303579e30 100644 --- a/modules/hls.xml +++ b/modules/hls.xml @@ -50,7 +50,7 @@ - + Mute? Should the video be muted? %defaultMute% diff --git a/views/display-page.twig b/views/display-page.twig index d82cefff39..377e0d2bf1 100644 --- a/views/display-page.twig +++ b/views/display-page.twig @@ -1,6 +1,6 @@ {# /** - * Copyright (C) 2022 Xibo Signage Ltd + * Copyright (C) 2023 Xibo Signage Ltd * * Xibo - Digital Signage - http://www.xibo.org.uk * @@ -172,6 +172,16 @@ { optionid: "3", option: notApplicable} ] %} {{ inline.dropdown("commercialLicence", "single", title, "", options, "optionid", "option") }} + + {% set title %}{% trans "Player supported?" %}{% endset %} + {% set yesOption %}{% trans "Yes" %}{% endset %} + {% set noOption %}{% trans "No" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: 1, option: yesOption}, + { optionid: 0, option: noOption}, + ] %} + {{ inline.dropdown("isPlayerSupported", "single", title, "", options, "optionid", "option") }} @@ -228,6 +238,7 @@ {% trans "Last Accessed" %} {% trans "Display Profile" %} {% trans "Version" %} + {% trans "Supported?" %} {% trans "Device Name" %} {% trans "IP Address" %} {% trans "Mac Address" %} @@ -306,8 +317,8 @@ //"; + }, + visible: false + }, {"data": "deviceName", "visible": false, responsivePriority: 5}, {"data": "clientAddress", "visible": false, responsivePriority: 6}, {"data": "macAddress", responsivePriority: 5}, @@ -696,6 +719,9 @@ // - let map; - - // Wait until we have a width and height. - setTimeout(initializeMap, 1000); - // Map resizing when folder is toggled window.refreshDisplayMap = function () { if (map) { // is display map visible?? - if ($("#display-map").is(":visible")) { + if ($displayMap.is(':visible')) { map.invalidateSize(); } else { map.setView(map.getCenter(), map.getZoom()); @@ -762,7 +790,7 @@ // Tile layer let tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { - attribution: '© OpenStreetMap' + attribution: '© OpenStreetMap' }).addTo(map); // Print button @@ -774,7 +802,7 @@ }).addTo(map); // Create the marker cluster group - var markerClusterGroup = L.markerClusterGroup({ + markerClusterGroup = L.markerClusterGroup({ maxClusterRadius: function (mapZoom) { return mapZoom > 9 ? 20 : 80; }, @@ -815,14 +843,13 @@ } }); - let bounds; - bounds = map.getBounds().toBBoxString(); + let bounds = map.getBounds().toBBoxString(); map.on("moveend", _.debounce(function() { bounds = map.getBounds().toBBoxString(); // is display map visible?? const isDisplayMapVisible = $displayMap.is(":visible"); - addMarkersToMap(!isDisplayMapVisible); + addMarkersToMap(bounds, !isDisplayMapVisible); }, 500)); map.on("resize", function() { @@ -831,205 +858,205 @@ // Get display points and add to the map // Do not clear layers - addMarkersToMap(false); + addMarkersToMap(bounds, false); // Bind the filter form - $('.XiboGrid').find(".XiboFilter form input").on("keyup", addMarkersToMap); - $('.XiboGrid').find(".XiboFilter form input, .XiboFilter form select").on("change", addMarkersToMap); + $('.XiboGrid').find('.XiboFilter form input').on('keyup', function() { addMarkersToMap(bounds); }); + $('.XiboGrid').find('.XiboFilter form select').on('change', function() { addMarkersToMap(bounds); }); - // Cache displayId - var added = []; - - // Add display markers to the cluster group - function addMarkersToMap(clear = true) { - - if (clear) { - markerClusterGroup.clearLayers(); - added = []; - // console.log('Clear Layer'); - } - - // Make an ajax request for the displays feature - // Load GeoJSON data and add it to the marker cluster group - $.ajax($displayMap.data('displaysUrl'), { - method: 'GET', - data: $('.XiboGrid').find(".XiboFilter form").serialize() + '&bounds=' + bounds, - success: function(response) { - // displays - if (response.features.length > 0) { - - // Define icons for display - let uptoDateLoggedInIcon = L.icon({ - iconUrl: '{{ theme.rootUri() }}dist/assets/map-marker-green-check.png', - iconRetinaUrl: '{{ theme.rootUri() }}dist/assets/map-marker-green-check-2x.png', - iconSize: [24, 40], - iconAnchor: [12, 40] - }); - let uptoDateLoggedOutIcon = L.icon({ - iconUrl: '{{ theme.rootUri() }}dist/assets/map-marker-green-cross.png', - iconRetinaUrl: '{{ theme.rootUri() }}dist/assets/map-marker-green-cross-2x.png', - iconSize: [24, 40], - iconAnchor: [12, 40] - }); - let outOfDateLoggedInIcon = L.icon({ - iconUrl: '{{ theme.rootUri() }}dist/assets/map-marker-yellow-check.png', - iconRetinaUrl: '{{ theme.rootUri() }}dist/assets/map-marker-yellow-check-2x.png', - iconSize: [24, 40], - iconAnchor: [12, 40] - }); - let outOfDateLoggedOutIcon = L.icon({ - iconUrl: '{{ theme.rootUri() }}dist/assets/map-marker-yellow-cross.png', - iconRetinaUrl: '{{ theme.rootUri() }}dist/assets/map-marker-yellow-cross-2x.png', - iconSize: [24, 40], - iconAnchor: [12, 40] - }); - let downloadingLoggedInIcon = L.icon({ - iconUrl: '{{ theme.rootUri() }}dist/assets/map-marker-red-check.png', - iconRetinaUrl: '{{ theme.rootUri() }}dist/assets/map-marker-red-check-2x.png', - iconSize: [24, 40], - iconAnchor: [12, 40] - }); - let downloadingLoggedOutIcon = L.icon({ - iconUrl: '{{ theme.rootUri() }}dist/assets/map-marker-red-cross.png', - iconRetinaUrl: '{{ theme.rootUri() }}dist/assets/map-marker-red-cross-2x.png', - iconSize: [24, 40], - iconAnchor: [12, 40] - }); - - // Loop through features (GeoJSON data) and add each marker to the cluster - let feature = L.geoJSON(response, { - pointToLayer: function (feature, latlng) { - let icon; - - const icons = { - 1: { - true: uptoDateLoggedInIcon, - false: uptoDateLoggedOutIcon - }, - 3: { - true: outOfDateLoggedInIcon, - false: outOfDateLoggedOutIcon - }, - default: { - true: downloadingLoggedInIcon, - false: downloadingLoggedOutIcon - } - }; - - // The value of "mediaInventoryStatus" and "loggedIn" determines the "icon" - const loggedIn = feature.properties.loggedIn ? true: false; - const iconType = icons[feature.properties.mediaInventoryStatus] || icons.default; - icon = iconType[loggedIn]; - - let options = { - icon: icon - }; - - let popup = '' + feature.properties.display + ''; - if (feature.properties.orientation) { - popup += '
Orientation: ' + feature.properties.orientation + '
'; - } - if (feature.properties.status) { - popup += '
Status: ' + feature.properties.status + ''; - if (feature.properties.loggedIn) { - popup += ' (Logged in)
'; - } else { - popup += ' (Not logged in) '; - } - } - if (feature.properties.displayProfile) { - popup += '
Display profile: ' + feature.properties.displayProfile + '
'; - } - if (feature.properties.resolution) { - popup += '
Resolution: ' + feature.properties.resolution + '
'; - } - if (feature.properties.lastAccessed) { - let lastAccessed = moment(feature.properties.lastAccessed, "X").tz ? - moment(feature.properties.lastAccessed, "X").tz(timezone).format(jsDateFormat) : - moment(feature.properties.lastAccessed, "X").format(jsDateFormat); - - popup += '
Last accessed: ' + lastAccessed + '
'; - } - if (feature.properties.thumbnail) { - popup += '
'; - } - - if (!added.includes(feature.properties.displayId)) { - - // Cache displayId - added.push(feature.properties.displayId); - - var marker = L.marker(latlng, options); - - // Add the inventory status to each marker so that we can count - // the status based displays in iconCreateFunction - marker.mediaInventoryStatus = feature.properties.mediaInventoryStatus; - - // Add a marker - return marker.bindPopup(popup) - .openPopup() - .addTo(markerClusterGroup); - } - }, - }); - } - } - }); - - // Add the cluster group to the map - markerClusterGroup.addTo(map); + // Hide map/ Show Display List + $displayMap.hide(); + } - markerClusterGroup.on('clustermouseover', function(event) { - var clusterMarkers = event.layer.getAllChildMarkers(); + // Add display markers to the cluster group + function addMarkersToMap(bounds, clear = true) { - let upToDate = 0; - let outOfDate = 0; - let downloading = 0; + if (clear) { + markerClusterGroup.clearLayers(); + displayIdCache = []; + } - clusterMarkers.forEach(function(marker) { - switch (marker.mediaInventoryStatus) { - case 1: - upToDate++; - break; - case 3: - outOfDate++; - break; - default: - downloading++; - break; - } - }); + if (!$displayMap.is(':visible')) { + return; + } - let popContent = '
Total number of displays'; - const statuses = [ - { count: upToDate, text: 'Up to date' }, - { count: outOfDate, text: 'Out of date' }, - { count: downloading, text: 'Downloading' } - ]; - for (const { count, text } of statuses) { - if (count > 0) { - popContent += `
${text}: ${count}
`; - } + // Make an ajax request for the displays feature + // Load GeoJSON data and add it to the marker cluster group + $.ajax($displayMap.data('displaysUrl'), { + method: 'GET', + data: $('.XiboGrid').find(".XiboFilter form").serialize() + '&bounds=' + bounds, + success: function(response) { + // displays + if (response.features.length > 0) { + + // Define icons for display + let uptoDateLoggedInIcon = L.icon({ + iconUrl: '{{ theme.rootUri() }}dist/assets/map-marker-green-check.png', + iconRetinaUrl: '{{ theme.rootUri() }}dist/assets/map-marker-green-check-2x.png', + iconSize: [24, 40], + iconAnchor: [12, 40] + }); + let uptoDateLoggedOutIcon = L.icon({ + iconUrl: '{{ theme.rootUri() }}dist/assets/map-marker-green-cross.png', + iconRetinaUrl: '{{ theme.rootUri() }}dist/assets/map-marker-green-cross-2x.png', + iconSize: [24, 40], + iconAnchor: [12, 40] + }); + let outOfDateLoggedInIcon = L.icon({ + iconUrl: '{{ theme.rootUri() }}dist/assets/map-marker-yellow-check.png', + iconRetinaUrl: '{{ theme.rootUri() }}dist/assets/map-marker-yellow-check-2x.png', + iconSize: [24, 40], + iconAnchor: [12, 40] + }); + let outOfDateLoggedOutIcon = L.icon({ + iconUrl: '{{ theme.rootUri() }}dist/assets/map-marker-yellow-cross.png', + iconRetinaUrl: '{{ theme.rootUri() }}dist/assets/map-marker-yellow-cross-2x.png', + iconSize: [24, 40], + iconAnchor: [12, 40] + }); + let downloadingLoggedInIcon = L.icon({ + iconUrl: '{{ theme.rootUri() }}dist/assets/map-marker-red-check.png', + iconRetinaUrl: '{{ theme.rootUri() }}dist/assets/map-marker-red-check-2x.png', + iconSize: [24, 40], + iconAnchor: [12, 40] + }); + let downloadingLoggedOutIcon = L.icon({ + iconUrl: '{{ theme.rootUri() }}dist/assets/map-marker-red-cross.png', + iconRetinaUrl: '{{ theme.rootUri() }}dist/assets/map-marker-red-cross-2x.png', + iconSize: [24, 40], + iconAnchor: [12, 40] + }); + + // Loop through features (GeoJSON data) and add each marker to the cluster + let feature = L.geoJSON(response, { + pointToLayer: function (feature, latlng) { + let icon; + + const icons = { + 1: { + true: uptoDateLoggedInIcon, + false: uptoDateLoggedOutIcon + }, + 3: { + true: outOfDateLoggedInIcon, + false: outOfDateLoggedOutIcon + }, + default: { + true: downloadingLoggedInIcon, + false: downloadingLoggedOutIcon } - popContent += '
'; - - var popup = L.popup() - .setLatLng(event.latlng) - .setContent(popContent) - .openOn(map); - }).on('clustermouseout',function(event){ - map.closePopup(); - }).on('clusterclick',function(event){ - map.closePopup(); - }); + }; + + // The value of "mediaInventoryStatus" and "loggedIn" determines the "icon" + const loggedIn = feature.properties.loggedIn ? true: false; + const iconType = icons[feature.properties.mediaInventoryStatus] || icons.default; + icon = iconType[loggedIn]; + + let options = { + icon: icon + }; + + let popup = '' + feature.properties.display + ''; + if (feature.properties.orientation) { + popup += '
Orientation: ' + feature.properties.orientation + '
'; + } + if (feature.properties.status) { + popup += '
Status: ' + feature.properties.status + ''; + if (feature.properties.loggedIn) { + popup += ' (Logged in)
'; + } else { + popup += ' (Not logged in) '; + } + } + if (feature.properties.displayProfile) { + popup += '
Display profile: ' + feature.properties.displayProfile + '
'; + } + if (feature.properties.resolution) { + popup += '
Resolution: ' + feature.properties.resolution + '
'; + } + if (feature.properties.lastAccessed) { + let lastAccessed = moment(feature.properties.lastAccessed, "X").tz ? + moment(feature.properties.lastAccessed, "X").tz(timezone).format(jsDateFormat) : + moment(feature.properties.lastAccessed, "X").format(jsDateFormat); + + popup += '
Last accessed: ' + lastAccessed + '
'; + } + if (feature.properties.thumbnail) { + popup += '
'; + } + + if (!displayIdCache.includes(feature.properties.displayId)) { + + // Cache displayId + displayIdCache.push(feature.properties.displayId); + + var marker = L.marker(latlng, options); + + // Add the inventory status to each marker so that we can count + // the status based displays in iconCreateFunction + marker.mediaInventoryStatus = feature.properties.mediaInventoryStatus; + + // Add a marker + return marker.bindPopup(popup) + .openPopup() + .addTo(markerClusterGroup); + } + }, + }); } + } + }); - // Hide map/ Show Display List - $displayMap.hide(); - } + // Add the cluster group to the map + markerClusterGroup.addTo(map); + + markerClusterGroup.on('clustermouseover', function(event) { + var clusterMarkers = event.layer.getAllChildMarkers(); + + let upToDate = 0; + let outOfDate = 0; + let downloading = 0; + + clusterMarkers.forEach(function(marker) { + switch (marker.mediaInventoryStatus) { + case 1: + upToDate++; + break; + case 3: + outOfDate++; + break; + default: + downloading++; + break; + } + }); + + let popContent = '
Total number of displays'; + const statuses = [ + { count: upToDate, text: 'Up to date' }, + { count: outOfDate, text: 'Out of date' }, + { count: downloading, text: 'Downloading' } + ]; + for (const { count, text } of statuses) { + if (count > 0) { + popContent += `
${text}: ${count}
`; + } + } + popContent += '
'; + + var popup = L.popup() + .setLatLng(event.latlng) + .setContent(popContent) + .openOn(map); + }).on('clustermouseout',function(event){ + map.closePopup(); + }).on('clusterclick',function(event){ + map.closePopup(); + }); + } // Creating a pie chart for cluster childrens using HTML, CSS - var createPieChart = function(data, colors) { + const createPieChart = function(data, colors) { // Get the total of all the data let total = data.reduce(function(a, b) { return a + b;