diff --git a/lib/Controller/Display.php b/lib/Controller/Display.php index 7fb493c4fe..39fa173ffd 100644 --- a/lib/Controller/Display.php +++ b/lib/Controller/Display.php @@ -1949,6 +1949,12 @@ function delete(Request $request, Response $response, $id) throw new AccessDeniedException(); } + if ($display->isLead()) { + throw new InvalidArgumentException( + __('Cannot delete a Lead Display of a Sync Group'), + ); + } + $display->delete(); // Return diff --git a/lib/Controller/Folder.php b/lib/Controller/Folder.php index 83d57d4a8d..2d6082c048 100644 --- a/lib/Controller/Folder.php +++ b/lib/Controller/Folder.php @@ -334,11 +334,23 @@ public function delete(Request $request, Response $response, $folderId) throw new AccessDeniedException(); } + if ($folder->isHome()) { + throw new InvalidArgumentException( + __('Cannot remove Folder set as home Folder for a user'), + 'folderId', + __('Change home Folder for Users using this Folder before deleting') + ); + } + try { $folder->delete(); } catch (\Exception $exception) { $this->getLog()->debug('Folder delete failed with message: ' . $exception->getMessage()); - throw new InvalidArgumentException(__('Cannot remove Folder with content'), 'folderId', __('Reassign objects from this Folder before deleting.')); + throw new InvalidArgumentException( + __('Cannot remove Folder with content'), + 'folderId', + __('Reassign objects from this Folder before deleting.') + ); } // Return diff --git a/lib/Controller/Schedule.php b/lib/Controller/Schedule.php index 729e0a97cd..ab63d31967 100644 --- a/lib/Controller/Schedule.php +++ b/lib/Controller/Schedule.php @@ -2193,6 +2193,36 @@ public function grid(Request $request, Response $response) { $params = $this->getSanitizer($request->getParams()); + $displayGroupIds = $params->getIntArray('displayGroupIds', ['default' => []]); + $originalDisplayGroupIds = $displayGroupIds; + + if (!$this->getUser()->isSuperAdmin()) { + $userDisplayGroupIds = array_map(function ($element) { + /** @var \Xibo\Entity\DisplayGroup $element */ + return $element->displayGroupId; + }, $this->displayGroupFactory->query(null, ['isDisplaySpecific' => -1])); + + // Reset the list to only those display groups that intersect and if 0 have been provided, only those from + // the user list + $displayGroupIds = (count($displayGroupIds) > 0) + ? array_intersect($displayGroupIds, $userDisplayGroupIds) + : $userDisplayGroupIds; + + $this->getLog()->debug('Resolved list of display groups [' + . json_encode($displayGroupIds) . '] from provided list [' + . json_encode($originalDisplayGroupIds) . '] and user list [' + . json_encode($userDisplayGroupIds) . ']'); + + // If we have none, then we do not return any events. + if (count($displayGroupIds) <= 0) { + $this->getState()->template = 'grid'; + $this->getState()->recordsTotal = $this->scheduleFactory->countLast(); + $this->getState()->setData([]); + + return $this->render($request, $response); + } + } + $events = $this->scheduleFactory->query( $this->gridRenderSort($params), $this->gridRenderFilter([ @@ -2202,7 +2232,7 @@ public function grid(Request $request, Response $response) 'geoAware' => $params->getInt('geoAware'), 'recurring' => $params->getInt('recurring'), 'campaignId' => $params->getInt('campaignId'), - 'displayGroupIds' => $params->getIntArray('displayGroupIds'), + 'displayGroupIds' => $displayGroupIds, 'name' => $params->getString('name'), 'useRegexForName' => $params->getCheckbox('useRegexForName'), 'logicalOperatorName' => $params->getString('logicalOperatorName'), diff --git a/lib/Controller/SyncGroup.php b/lib/Controller/SyncGroup.php index cccea6bfae..4bc2003b5f 100644 --- a/lib/Controller/SyncGroup.php +++ b/lib/Controller/SyncGroup.php @@ -134,6 +134,7 @@ public function grid(Request $request, Response $response): Response|\Psr\Http\M 'name' => $parsedQueryParams->getString('name'), 'folderId' => $parsedQueryParams->getInt('folderId'), 'ownerId' => $parsedQueryParams->getInt('ownerId'), + 'leadDisplayId' => $parsedQueryParams->getInt('leadDisplayId') ]; $syncGroups = $this->syncGroupFactory->query( diff --git a/lib/Entity/Display.php b/lib/Entity/Display.php index c35aca65c7..0623977ad1 100644 --- a/lib/Entity/Display.php +++ b/lib/Entity/Display.php @@ -1389,4 +1389,18 @@ public function setStatusWindow($pool, $status) return $this; } + + /** + * Check if this Display is set as Lead Display on any Sync Group + * @return bool + */ + public function isLead(): bool + { + $syncGroups = $this->getStore()->select( + 'SELECT syncGroupId FROM `syncgroup` WHERE `syncgroup`.leadDisplayId = :displayId', + ['displayId' => $this->displayId] + ); + + return count($syncGroups) > 0; + } } diff --git a/lib/Entity/Folder.php b/lib/Entity/Folder.php index 8f22aeb3f9..99594c3569 100644 --- a/lib/Entity/Folder.php +++ b/lib/Entity/Folder.php @@ -455,4 +455,17 @@ public function isTheSameBranch(int $newParentFolderId): bool return $found; } + + /** + * Check if this folder is used as Home Folder for any existing Users + * @return bool + */ + public function isHome(): bool + { + $userIds = $this->getStore()->select('SELECT userId FROM `user` WHERE `user`.homeFolderId = :folderId', [ + 'folderId' => $this->id + ]); + + return count($userIds) > 0; + } } diff --git a/lib/Factory/SyncGroupFactory.php b/lib/Factory/SyncGroupFactory.php index 37d0c21532..5240a1ebe9 100644 --- a/lib/Factory/SyncGroupFactory.php +++ b/lib/Factory/SyncGroupFactory.php @@ -193,6 +193,11 @@ public function query($sortOrder = null, $filterBy = []): array $params['folderId'] = $parsedBody->getInt('folderId'); } + if ($parsedBody->getInt('leadDisplayId') !== null) { + $body .= ' AND `syncgroup`.leadDisplayId = :leadDisplayId '; + $params['leadDisplayId'] = $parsedBody->getInt('leadDisplayId'); + } + // View Permissions $this->viewPermissionSql( 'Xibo\Entity\SyncGroup', diff --git a/lib/Helper/XiboUploadHandler.php b/lib/Helper/XiboUploadHandler.php index f43d41c501..bae67dd2c8 100644 --- a/lib/Helper/XiboUploadHandler.php +++ b/lib/Helper/XiboUploadHandler.php @@ -408,6 +408,13 @@ protected function handle_form_data($file, $index) // Get the Playlist $playlist = $controller->getPlaylistFactory()->getById($this->options['playlistId']); + if (!$playlist->isEditable()) { + throw new InvalidArgumentException( + __('This Layout is not a Draft, please checkout.'), + 'layoutId' + ); + } + // Create a Widget and add it to our region $widget = $controller->getWidgetFactory()->create( $this->options['userId'], diff --git a/lib/Service/DisplayNotifyService.php b/lib/Service/DisplayNotifyService.php index ef44a854b7..1be919ad5c 100644 --- a/lib/Service/DisplayNotifyService.php +++ b/lib/Service/DisplayNotifyService.php @@ -279,7 +279,7 @@ public function notifyByCampaignId($campaignId) ) campaigns ON campaigns.campaignId = `schedule`.campaignId WHERE ( - (`schedule`.FromDT < :toDt AND IFNULL(`schedule`.toDt, `schedule`.fromDt) > :fromDt) + (`schedule`.FromDT < :toDt AND IFNULL(`schedule`.toDt, UNIX_TIMESTAMP()) > :fromDt) OR `schedule`.recurrence_range >= :fromDt OR ( IFNULL(`schedule`.recurrence_range, 0) = 0 AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\' @@ -436,7 +436,7 @@ public function notifyByDataSetId($dataSetId) AND `widgetoption`.option = \'dataSetId\' AND `widgetoption`.value = :activeDataSetId WHERE ( - (schedule.FromDT < :toDt AND IFNULL(`schedule`.toDt, `schedule`.fromDt) > :fromDt) + (schedule.FromDT < :toDt AND IFNULL(`schedule`.toDt, UNIX_TIMESTAMP()) > :fromDt) OR `schedule`.recurrence_range >= :fromDt OR ( IFNULL(`schedule`.recurrence_range, 0) = 0 AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\' @@ -588,7 +588,7 @@ public function notifyByPlaylistId($playlistId) ON `playlist`.regionId = `region`.regionId WHERE `playlist`.playlistId = :playlistId AND ( - (schedule.FromDT < :toDt AND IFNULL(`schedule`.toDt, `schedule`.fromDt) > :fromDt) + (schedule.FromDT < :toDt AND IFNULL(`schedule`.toDt, UNIX_TIMESTAMP()) > :fromDt) OR `schedule`.recurrence_range >= :fromDt OR ( IFNULL(`schedule`.recurrence_range, 0) = 0 AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\' @@ -741,7 +741,7 @@ public function notifyByLayoutCode($code) ) campaigns ON campaigns.campaignId = `schedule`.campaignId WHERE ( - (`schedule`.FromDT < :toDt AND IFNULL(`schedule`.toDt, `schedule`.fromDt) > :fromDt) + (`schedule`.FromDT < :toDt AND IFNULL(`schedule`.toDt, UNIX_TIMESTAMP()) > :fromDt) OR `schedule`.recurrence_range >= :fromDt OR ( IFNULL(`schedule`.recurrence_range, 0) = 0 AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\' @@ -769,7 +769,7 @@ public function notifyByLayoutCode($code) ON lkdisplaydg.DisplayID = display.displayID WHERE schedule.actionLayoutCode = :code AND ( - (`schedule`.FromDT < :toDt AND IFNULL(`schedule`.toDt, `schedule`.fromDt) > :fromDt) + (`schedule`.FromDT < :toDt AND IFNULL(`schedule`.toDt, UNIX_TIMESTAMP()) > :fromDt) OR `schedule`.recurrence_range >= :fromDt OR ( IFNULL(`schedule`.recurrence_range, 0) = 0 AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\' diff --git a/lib/routes.php b/lib/routes.php index 5345daf70f..8f0ff8c285 100644 --- a/lib/routes.php +++ b/lib/routes.php @@ -50,7 +50,7 @@ * description="Xibo CMS API. Using HTTP formData requests. All PUT requests require Content-Type:application/x-www-form-urlencoded header.", - * version="3.3", + * version="4.0", * termsOfService="https://xibosignage.com/legal", * @SWG\License( * name="AGPLv3 or later", diff --git a/ui/src/core/xibo-cms.js b/ui/src/core/xibo-cms.js index 314ad6d28c..1590870be5 100644 --- a/ui/src/core/xibo-cms.js +++ b/ui/src/core/xibo-cms.js @@ -3870,11 +3870,13 @@ function initJsTreeAjax(container, id, isForm, ttl, onReady = null, onSelected = $('#container-folder-tree').jstree(true).refresh(); } } else { - toastr.error(translations.folderWithContent); - console.log(data.message); + if (data.message !== undefined) { + toastr.error(data.message) + } else { + toastr.error(translations.folderWithContent); + } $(container).jstree(true).refresh(); } - } }); }).bind("changed.jstree", function (e, data) { diff --git a/views/syncgroup-page.twig b/views/syncgroup-page.twig index 1be0986939..9cfd87970c 100644 --- a/views/syncgroup-page.twig +++ b/views/syncgroup-page.twig @@ -48,6 +48,9 @@ {% set title %}{% trans "Name" %}{% endset %} {{ inline.inputNameGrid('name', title) }} + {% set title %}{% trans "Lead Display ID" %}{% endset %} + {{ inline.input("leadDisplayId", title) }} + {{ inline.hidden("folderId") }}