From f7eaddbb9101b87cbc02d7ccd4ea4839d6dfb38b Mon Sep 17 00:00:00 2001 From: Dan Garner Date: Fri, 8 Dec 2023 16:09:20 +0000 Subject: [PATCH] Misc issues 4.0.6 (#2263) * Layout: improve thumbnails for Canvas regions. Ensure thumbnail is taken on publish. xibosignage/xibo#3248 * Menu board: always format fractional digits xibosignageltd/xibo-private#542 * Bump to 4.0.6 * User: fix null homepage error xibosignage/xibo#3264 * XMDS: fault should trim too long reason xibosignage/xibo#3230 * Ad Campaign: impressions target type too long xibosignage/xibo#3235 --- lib/Controller/Campaign.php | 4 +-- lib/Controller/Layout.php | 9 +++++- lib/Entity/Campaign.php | 6 ++-- lib/Factory/UserGroupFactory.php | 10 +++++-- lib/Helper/Environment.php | 2 +- lib/XTR/CampaignSchedulerTask.php | 4 +-- lib/Xmds/Soap6.php | 13 ++++++--- modules/templates/product-elements.xml | 2 ++ tests/XMDS.http | 19 +++++++++++++ ui/src/layout-editor/layout.js | 11 +++++--- ui/src/layout-editor/main.js | 38 ++++++++++++++++---------- views/campaign-builder.twig | 2 +- views/campaign-form-add.twig | 2 +- views/campaign-page.twig | 2 ++ views/menuboard-product-page.twig | 10 +++++-- 15 files changed, 95 insertions(+), 39 deletions(-) diff --git a/lib/Controller/Campaign.php b/lib/Controller/Campaign.php index 46bd143e89..81219b85ce 100644 --- a/lib/Controller/Campaign.php +++ b/lib/Controller/Campaign.php @@ -527,7 +527,7 @@ public function addForm(Request $request, Response $response) * @SWG\Parameter( * name="targetType", * in="formData", - * description="For ad campaigns, how do we measure the target? plays|budget", + * description="For ad campaigns, how do we measure the target? plays|budget|imp", * type="string", * required=false * ), @@ -774,7 +774,7 @@ public function editForm(Request $request, Response $response, $id) * @SWG\Parameter( * name="targetType", * in="formData", - * description="For ad campaigns, how do we measure the target? plays|budget", + * description="For ad campaigns, how do we measure the target? plays|budget|imp", * type="string", * required=false * ), diff --git a/lib/Controller/Layout.php b/lib/Controller/Layout.php index b55e07c54d..462ef51372 100644 --- a/lib/Controller/Layout.php +++ b/lib/Controller/Layout.php @@ -1941,7 +1941,7 @@ function editBackgroundForm(Request $request, Response $response, $id) if (!$this->getUser()->checkEditable($layout)) { throw new AccessDeniedException(); } - + // Edits always happen on Drafts, get the draft Layout using the Parent Layout ID if ($layout->schemaVersion < 2) { $resolution = $this->resolutionFactory->getByDesignerDimensions($layout->width, $layout->height); @@ -3055,9 +3055,16 @@ public function addThumbnail(Request $request, Response $response, $id): Respons $image = Img::canvas($layout->width, $layout->height, $layout->backgroundColor); } + $countRegions = count($layout->regions); + // Draw some regions on it. foreach ($layout->regions as $region) { try { + // We don't do this for the canvas region. + if ($countRegions > 1 && $region->type === 'canvas') { + continue; + } + // Get widgets in this region $playlist = $region->getPlaylist()->setModuleFactory($this->moduleFactory); $widgets = $playlist->expandWidgets(); diff --git a/lib/Entity/Campaign.php b/lib/Entity/Campaign.php index b8c24e52da..34bfeda296 100644 --- a/lib/Entity/Campaign.php +++ b/lib/Entity/Campaign.php @@ -126,7 +126,7 @@ class Campaign implements \JsonSerializable public $listPlayOrder; /** - * @SWG\Property(description="For an ad campaign, what's the target type, plays|budget") + * @SWG\Property(description="For an ad campaign, what's the target type, plays|budget|imp") * @var string */ public $targetType; @@ -397,7 +397,7 @@ public function getProgress(?Carbon $testDate = null): CampaignProgress if ($this->targetType === 'budget') { $progress->progressTarget = ($this->spend / $this->target) * 100; - } else if ($this->targetType === 'impressions') { + } else if ($this->targetType === 'imp') { $progress->progressTarget = ($this->impressions / $this->target) * 100; } else { $progress->progressTarget = ($this->plays / $this->target) * 100; @@ -485,7 +485,7 @@ public function validate() } if ($this->type === 'ad') { - if (!in_array($this->targetType, ['plays', 'budget', 'impressions'])) { + if (!in_array($this->targetType, ['plays', 'budget', 'imp'])) { throw new InvalidArgumentException(__('Invalid target type'), 'targetType'); } diff --git a/lib/Factory/UserGroupFactory.php b/lib/Factory/UserGroupFactory.php index 70d3a20aad..173b2287dd 100644 --- a/lib/Factory/UserGroupFactory.php +++ b/lib/Factory/UserGroupFactory.php @@ -884,12 +884,16 @@ public function getFeatures() } /** - * @param string $homepage The home page id - * @return array|mixed + * @param string|null $homepage The home page id + * @return \Xibo\Entity\Homepage * @throws \Xibo\Support\Exception\NotFoundException */ - public function getHomepageByName(string $homepage) + public function getHomepageByName(?string $homepage): Homepage { + if (empty($homepage)) { + throw new NotFoundException(__('Homepage has not been set')); + } + $homepages = $this->getHomepages(); if (!array_key_exists($homepage, $homepages)) { diff --git a/lib/Helper/Environment.php b/lib/Helper/Environment.php index c4affe9eeb..5a86aabd80 100644 --- a/lib/Helper/Environment.php +++ b/lib/Helper/Environment.php @@ -30,7 +30,7 @@ */ class Environment { - public static $WEBSITE_VERSION_NAME = '4.0.5'; + public static $WEBSITE_VERSION_NAME = '4.0.6'; public static $XMDS_VERSION = '7'; public static $XLF_VERSION = 4; public static $VERSION_REQUIRED = '8.1.0'; diff --git a/lib/XTR/CampaignSchedulerTask.php b/lib/XTR/CampaignSchedulerTask.php index cbc962b541..4ae024943c 100644 --- a/lib/XTR/CampaignSchedulerTask.php +++ b/lib/XTR/CampaignSchedulerTask.php @@ -2,7 +2,7 @@ /* * Copyright (C) 2023 Xibo Signage Ltd * - * Xibo - Digital Signage - http://www.xibo.org.uk + * Xibo - Digital Signage - https://xibosignage.com * * This file is part of Xibo. * @@ -232,7 +232,7 @@ public function run() // cost/impressions/displays are sums. if ($campaign->targetType === 'budget') { $playsNeededPerLayout = $targetNeededPerLayout / $costPerPlay; - } else if ($campaign->targetType === 'impressions') { + } else if ($campaign->targetType === 'imp') { $playsNeededPerLayout = $targetNeededPerLayout / $impressionsPerPlay; } else { $playsNeededPerLayout = $targetNeededPerLayout / $countDisplays; diff --git a/lib/Xmds/Soap6.php b/lib/Xmds/Soap6.php index e68ca9d9d9..57425efc91 100644 --- a/lib/Xmds/Soap6.php +++ b/lib/Xmds/Soap6.php @@ -1,8 +1,8 @@ $serverKey, 'hardwareKey' => $hardwareKey ]); - + // Sanitize $serverKey = $sanitizer->getString('serverKey'); $hardwareKey = $sanitizer->getString('hardwareKey'); @@ -90,6 +90,11 @@ public function reportFaults(string $serverKey, string $hardwareKey, string $fau $mediaId = $sanitizedFaultAlert->getInt('mediaId'); $widgetId = $sanitizedFaultAlert->getInt('widgetId'); + // Trim the reason if it is too long + if (strlen($reason) >= 255) { + $reason = substr($reason, 0, 255); + } + try { $dbh = $this->getStore()->getConnection(); diff --git a/modules/templates/product-elements.xml b/modules/templates/product-elements.xml index 59692efd48..af4cef133f 100644 --- a/modules/templates/product-elements.xml +++ b/modules/templates/product-elements.xml @@ -97,6 +97,8 @@ var options = {}; if (properties.currencyCode && properties.currencyCode !== '') { options.style = 'currency'; options.currency = properties.currencyCode; +} else { + options.minimumFractionDigits = 2; } value = new Intl.NumberFormat(undefined, options).format(value); diff --git a/tests/XMDS.http b/tests/XMDS.http index 0a5f3bb37d..989f26080c 100644 --- a/tests/XMDS.http +++ b/tests/XMDS.http @@ -128,6 +128,25 @@ Content-Type: application/xml ### +POST {{url}}/xmds.php?v=7 +Content-Type: application/xml + + + + + {{serverKey}} + {{hardwareKey}} + [{"code":5000, "reason": "Too long: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}] + + + + +### + # Get the fileID from the Required Files response. GET {{url}}/xmds.php?file=12.xlf&displayId=1&type=L&itemId=12&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230502T000000Z&X-Amz-Expires=1683048895&X-Amz-SignedHeaders=host&X-Amz-Signature=7c876be170afb29d194e7b035be6969198c22a32c22e163e2696754bb1163f5d diff --git a/ui/src/layout-editor/layout.js b/ui/src/layout-editor/layout.js index 5d586b5fd4..2117706b36 100644 --- a/ui/src/layout-editor/layout.js +++ b/ui/src/layout-editor/layout.js @@ -451,10 +451,13 @@ Layout.prototype.publish = function() { toastr.success(res.message); - // Redirect to the new published layout ( read only mode ) - window.location.href = - urlsForApi.layout.designer.url.replace( - ':id', res.data.layoutId) + '?vM=1'; + // Update the thumbnail + lD.uploadThumbnail().finally(() => { + // Redirect to the new published layout ( read only mode ) + window.location.href = + urlsForApi.layout.designer.url.replace( + ':id', res.data.layoutId) + '?vM=1'; + }); } else { lD.common.hideLoadingScreen(); diff --git a/ui/src/layout-editor/main.js b/ui/src/layout-editor/main.js index 6136c8384c..b596ef4104 100644 --- a/ui/src/layout-editor/main.js +++ b/ui/src/layout-editor/main.js @@ -914,8 +914,7 @@ lD.showPublishScreen = function() { "#publishNow", "", ".publish-date-control" - ); - lD.uploadThumbnail($("#layoutPublishForm #publishPreview"));`, + );`, 'lD.layout.publish();', ); }; @@ -4169,6 +4168,7 @@ lD.importFromProvider = function(items) { /** * Take and upload a thumbnail * @param {object} targetToAttach DOM object to attach the thumbnail to + * @return {Promise} */ lD.uploadThumbnail = function(targetToAttach) { if ($(targetToAttach).length > 0) { @@ -4179,19 +4179,27 @@ lD.uploadThumbnail = function(targetToAttach) { ); $(targetToAttach).removeClass('d-none'); } - const linkToAPI = urlsForApi.layout.addThumbnail; - const requestPath = linkToAPI.url.replace(':id', lD.layout.layoutId); - $.ajax({ - url: requestPath, - type: 'POST', - success: function() { - // Attach to target - if ($(targetToAttach).length > 0) { - $(targetToAttach).find('.thumb-preview') - .replaceWith($('') - .attr('src', requestPath)); - } - }, + + return new Promise(function(resolve, reject) { + const linkToAPI = urlsForApi.layout.addThumbnail; + const requestPath = linkToAPI.url.replace(':id', lD.layout.layoutId); + $.ajax({ + url: requestPath, + type: 'POST', + success: function() { + // Attach to target + if ($(targetToAttach).length > 0) { + $(targetToAttach).find('.thumb-preview') + .replaceWith($('') + .attr('src', requestPath)); + } + + resolve(); + }, + fail: function() { + reject(); + }, + }); }); }; diff --git a/views/campaign-builder.twig b/views/campaign-builder.twig index 6b23113e4d..9505b925e6 100644 --- a/views/campaign-builder.twig +++ b/views/campaign-builder.twig @@ -88,7 +88,7 @@ {% set options = [ { id: "plays", name: "Plays"|trans }, { id: "budget", name: "Budget"|trans }, - { id: "impressions", name: "Impressions"|trans }, + { id: "imp", name: "Impressions"|trans }, ] %} {{ forms.dropdown("targetType", "single", title, campaign.targetType, options, "id", "name", helpText, "campaign-type-ad") }} diff --git a/views/campaign-form-add.twig b/views/campaign-form-add.twig index b3cb8dfb26..f2fd643a1d 100644 --- a/views/campaign-form-add.twig +++ b/views/campaign-form-add.twig @@ -109,7 +109,7 @@ {% set options = [ { id: "plays", name: "Plays"|trans }, { id: "budget", name: "Budget"|trans }, - { id: "impressions", name: "Impressions"|trans }, + { id: "imp", name: "Impressions"|trans }, ] %} {{ forms.dropdown("targetType", "single", title, "both", options, "id", "name", helpText, "campaign-type-ad") }} diff --git a/views/campaign-page.twig b/views/campaign-page.twig index 060df20a63..3b5e7b3f16 100644 --- a/views/campaign-page.twig +++ b/views/campaign-page.twig @@ -260,6 +260,8 @@ return '{{ "Plays"|trans }}'; } else if (data === 'budget') { return '{{ "Budget"|trans }}'; + } else if (data === 'imp') { + return '{{ "Impressions"|trans }}'; } return data; }, diff --git a/views/menuboard-product-page.twig b/views/menuboard-product-page.twig index cb1d57b383..be8c5ca6c1 100644 --- a/views/menuboard-product-page.twig +++ b/views/menuboard-product-page.twig @@ -111,8 +111,14 @@ responsivePriority: 2 }, { - "data": "price", - responsivePriority: 2 + data: 'price', + responsivePriority: 2, + render: function (data, type) { + if (type !== 'display' || data === null || data === '') { + return data; + } + return new Intl.NumberFormat(undefined, {minimumFractionDigits: 2}).format(data); + }, }, { responsivePriority: 3,