From 2b698f88931596d2633e16b6755d0bd0c2d39f47 Mon Sep 17 00:00:00 2001 From: Dan Garner Date: Mon, 19 Jul 2021 15:36:50 +0100 Subject: [PATCH 01/39] Applications: fix auth code flow. xibosignage/xibo#2571 --- lib/Controller/Applications.php | 11 ++++------- lib/Middleware/Handlers.php | 8 +++++++- web/api/authorize/index.php | 9 ++------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/Controller/Applications.php b/lib/Controller/Applications.php index 67d816f652..eb5a747466 100644 --- a/lib/Controller/Applications.php +++ b/lib/Controller/Applications.php @@ -196,20 +196,17 @@ public function authorizeRequest(Request $request, Response $response) public function authorize(Request $request, Response $response) { // Pull authorize params from our session - if (!$authParams = $this->session->get('authParams')) { + /** @var AuthorizationRequest $authRequest */ + $authRequest = $this->session->get('authParams'); + if (!$authRequest) { throw new InvalidArgumentException(__('Authorisation Parameters missing from session.'), 'authParams'); } $sanitizedQueryParams = $this->getSanitizer($request->getParams()); - // get auth server - /** @var AuthorizationRequest $authRequest */ - $authRequest = $this->session->get('authParams'); - $apiKeyPaths = $this->getConfig()->getApiKeyDetails(); - $privateKey = $apiKeyPaths['privateKeyPath']; - $encryptionKey = $apiKeyPaths['publicKeyPath']; + $encryptionKey = $apiKeyPaths['encryptionKey']; $server = new AuthorizationServer( $this->applicationFactory, diff --git a/lib/Middleware/Handlers.php b/lib/Middleware/Handlers.php index 545469ae9d..2d1c26fa7e 100644 --- a/lib/Middleware/Handlers.php +++ b/lib/Middleware/Handlers.php @@ -23,6 +23,7 @@ namespace Xibo\Middleware; use Illuminate\Support\Str; +use League\OAuth2\Server\Exception\OAuthServerException; use Nyholm\Psr7\Factory\Psr17Factory; use Slim\Exception\HttpNotFoundException; use Slim\Exception\HttpSpecializedException; @@ -62,7 +63,7 @@ public static function jsonErrorHandler($container) /** @var Response $response */ $response = $decoratedResponseFactory->createResponse(500); - if ($exception instanceof GeneralException) { + if ($exception instanceof GeneralException || $exception instanceof OAuthServerException) { return $exception->generateHttpResponse($response); } else if ($exception instanceof HttpSpecializedException) { return $response->withJson([ @@ -252,6 +253,11 @@ private static function writeLog($request, bool $logErrors, bool $logErrorDetail if ($logErrorDetails) { $logger->debug($exception->getTraceAsString()); + + $previous = $exception->getPrevious(); + if ($previous !== null) { + $logger->debug(get_class($previous) . ': ' . $previous->getMessage()); + } } } } diff --git a/web/api/authorize/index.php b/web/api/authorize/index.php index 010a70c8a5..4b621e76db 100644 --- a/web/api/authorize/index.php +++ b/web/api/authorize/index.php @@ -97,13 +97,8 @@ $app->getContainer()->get('logService')->debug('Request for access token using grant_type: %s', $request->getParam('grant_type')); $server = $app->getContainer()->get('server'); - try { - // Try to respond to the request - return $server->respondToAccessTokenRequest($request, $response); - } catch (\League\OAuth2\Server\Exception\OAuthServerException $exception) { - // All instances of OAuthServerException can be formatted into a HTTP response - return $exception->generateHttpResponse($response); - } + // Try to respond to the request + return $server->respondToAccessTokenRequest($request, $response); }); // Run app From a4f23d7160bcf2fc944373981e44ccf427c4c137 Mon Sep 17 00:00:00 2001 From: Dan Garner Date: Mon, 19 Jul 2021 16:36:43 +0100 Subject: [PATCH 02/39] Applications: fix auth code flow - missing refresh tokens. xibosignage/xibo#2571 --- lib/Middleware/ApiAuthentication.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Middleware/ApiAuthentication.php b/lib/Middleware/ApiAuthentication.php index ba7bcc5226..f277ef315a 100644 --- a/lib/Middleware/ApiAuthentication.php +++ b/lib/Middleware/ApiAuthentication.php @@ -22,12 +22,14 @@ namespace Xibo\Middleware; use League\OAuth2\Server\Grant\AuthCodeGrant; +use League\OAuth2\Server\Grant\RefreshTokenGrant; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\MiddlewareInterface as Middleware; use Psr\Http\Server\RequestHandlerInterface as RequestHandler; use Slim\App as App; +use Xibo\OAuth\RefreshTokenRepository; use Xibo\Support\Exception\ConfigurationException; /** @@ -99,6 +101,8 @@ public function process(Request $request, RequestHandler $handler): Response new \DateInterval('PT1H') ); + $server->enableGrantType(new RefreshTokenGrant(new RefreshTokenRepository())); + return $server; } catch (\LogicException $exception) { $logger->error($exception->getMessage()); From d0b89075ebc2eb6971ab11148428ff6808963b1b Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 20 Jul 2021 10:13:59 +0100 Subject: [PATCH 03/39] Applications : Better present redirects in edit form xibosignage/xibo#2570 --- views/applications-form-edit.twig | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/views/applications-form-edit.twig b/views/applications-form-edit.twig index 4cc8cbdb02..9b8f6417e2 100644 --- a/views/applications-form-edit.twig +++ b/views/applications-form-edit.twig @@ -1,6 +1,6 @@ {# /** - * Copyright (C) 2020 Xibo Signage Ltd + * Copyright (C) 2021 Xibo Signage Ltd * * Xibo - Digital Signage - http://www.xibo.org.uk * @@ -72,18 +72,25 @@ {% set helpText %}{% trans "Can this Application keep a secret?" %}{% endset %} {{ forms.checkbox("isConfidential", title, client.isConfidential, helpText) }} - {% set title %}{% trans "Redirect URI" %}{% endset %} + {% set title %}{% trans "New Redirect URI" %}{% endset %} {% set helpText %}{% trans "White listed redirect URI's that will be allowed, only application for Authorization Code Grants" %}{% endset %} {{ forms.input("redirectUri[]", title, "", helpText) }} + +
+ {{ "Existing Redirect URI"|trans }}: +
+ + {% for url in client.redirectUris %} + {{ forms.input("redirectUri[]", "", url.redirectUri) }} + {% endfor %}
{% set message %}{% trans "Select sharing to grant to this application (scopes)." %}{% endset %} {{ forms.message(message) }} - {% for url in client.redirectUris %} - {% set title %}{% trans "Redirect URI" %}{% endset %} - {{ forms.input("redirectUri[]", title, url.redirectUri) }} - {% endfor %} +
+ {{ "Scopes"|trans }}: +
{% for scope in scopes %} {% set title %}{{ scope.description }}{% endset %} From 58e0cf85b784a1da61855b4f78b9c49ac075ff21 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 20 Jul 2021 10:16:53 +0100 Subject: [PATCH 04/39] Applications : Fix error when auth request gets denied xibosignage/xibo#2569 --- lib/Controller/Applications.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/Controller/Applications.php b/lib/Controller/Applications.php index 67d816f652..1abe6725ae 100644 --- a/lib/Controller/Applications.php +++ b/lib/Controller/Applications.php @@ -231,18 +231,15 @@ public function authorize(Request $request, Response $response) // Default scope $server->setDefaultScope('all'); + // get oauth User Entity and set the UserId to the current web userId + $authRequest->setUser($this->getUser()); // We are authorized if ($sanitizedQueryParams->getString('authorization') === 'Approve') { - $authRequest->setAuthorizationApproved(true); - // get oauth User Entity and set the UserId to the current web userId - $authRequest->setUser($this->getUser()); - // Redirect back to the home page return $server->completeAuthorizationRequest($authRequest, $response); - } - else { + } else { $authRequest->setAuthorizationApproved(false); return $server->completeAuthorizationRequest($authRequest, $response); } From 8fbabfd5f7e6d56ff6d12a5d457fcc7acc9641b2 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 20 Jul 2021 10:18:46 +0100 Subject: [PATCH 05/39] Applications : Handle isConfidential flag in the backend --- lib/Controller/Applications.php | 1 + lib/Factory/ApplicationFactory.php | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/Controller/Applications.php b/lib/Controller/Applications.php index 1abe6725ae..fed7dde1fd 100644 --- a/lib/Controller/Applications.php +++ b/lib/Controller/Applications.php @@ -467,6 +467,7 @@ public function edit(Request $request, Response $response, $id) $client->name = $sanitizedParams->getString('name'); $client->authCode = $sanitizedParams->getCheckbox('authCode'); $client->clientCredentials = $sanitizedParams->getCheckbox('clientCredentials'); + $client->isConfidential = $sanitizedParams->getCheckbox('isConfidential'); if ($sanitizedParams->getCheckbox('resetKeys') == 1) { $client->resetSecret(); diff --git a/lib/Factory/ApplicationFactory.php b/lib/Factory/ApplicationFactory.php index 669093870d..e528333ee0 100644 --- a/lib/Factory/ApplicationFactory.php +++ b/lib/Factory/ApplicationFactory.php @@ -150,7 +150,8 @@ public function query($sortOrder = null, $filterBy = []) `user`.UserName AS owner, `oauth_clients`.authCode, `oauth_clients`.clientCredentials, - `oauth_clients`.userId '; + `oauth_clients`.userId, + `oauth_clients`.isConfidential'; $body = ' FROM `oauth_clients` '; $body .= ' INNER JOIN `user` ON `user`.userId = `oauth_clients`.userId '; @@ -173,8 +174,9 @@ public function query($sortOrder = null, $filterBy = []) // Sorting? $order = ''; - if (is_array($sortOrder)) + if (is_array($sortOrder)) { $order .= 'ORDER BY ' . implode(',', $sortOrder); + } $limit = ''; // Paging @@ -265,4 +267,4 @@ public function validateClient($clientIdentifier, $clientSecret, $grantType) return true; } -} \ No newline at end of file +} From ffde5ab467eab6df28e3f303babe423e56e448d8 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 20 Jul 2021 10:19:31 +0100 Subject: [PATCH 06/39] Applications : Fix scope unassign the application ids are strings not integers --- lib/Entity/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Entity/Application.php b/lib/Entity/Application.php index 1b9ab5cec3..7365d2b4f4 100644 --- a/lib/Entity/Application.php +++ b/lib/Entity/Application.php @@ -186,7 +186,7 @@ public function unassignScope($scope) { * @var ApplicationScope $a * @var ApplicationScope $b */ - return $a->getId() - $b->getId(); + return $a->getId() !== $b->getId(); }); } From e1f68d89898f8fe7063937b5f24263b4ab9f97e7 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 20 Jul 2021 10:20:34 +0100 Subject: [PATCH 07/39] Applications : Make the authorize page look a bit better --- views/applications-authorize-page.twig | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/views/applications-authorize-page.twig b/views/applications-authorize-page.twig index 990ed2aabb..bcc564f124 100644 --- a/views/applications-authorize-page.twig +++ b/views/applications-authorize-page.twig @@ -1,6 +1,6 @@ {# /** - * Copyright (C) 2020 Xibo Signage Ltd + * Copyright (C) 2021 Xibo Signage Ltd * * Xibo - Digital Signage - http://www.xibo.org.uk * @@ -24,10 +24,11 @@ {% import "inline.twig" as inline %} {% block pageContent %} -
+
{% trans "Authorize Request" %}
-

{{ authParams.client.getName() }} would like access

+

{{ authParams.client.getName() }}

+
{{ "would like access to the following scopes"|trans }} :
    {% for scope in authParams.scopes %} @@ -38,9 +39,11 @@
- - - +
+ + + +
From 8a28f13ff3899d1a43333a49ee95d5ec35a42f78 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 20 Jul 2021 10:23:06 +0100 Subject: [PATCH 08/39] Application authorize : Fix redirect to work for installation in a sub folder --- web/api/authorize/index.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/web/api/authorize/index.php b/web/api/authorize/index.php index 010a70c8a5..e3dc8093ff 100644 --- a/web/api/authorize/index.php +++ b/web/api/authorize/index.php @@ -82,12 +82,10 @@ /** @var \League\OAuth2\Server\AuthorizationServer $server */ $server = $app->getContainer()->get('server'); $authRequest = $server->validateAuthorizationRequest($request); - $app->getContainer()->get('session')->set('authParams', $authRequest); + // Redirect the user to the UI - save the auth params in the session. - //$app->getContainer()->get('session')->set('authParams', $authParams); - //$app->redirect(str_replace('/api/authorize/', '/application/authorize', $app->request()->getPath())); - // We know we are at /api/authorize, so convert that to /application/authorize - return $response->withRedirect('/application/authorize'); + $app->getContainer()->get('session')->set('authParams', $authRequest); + return $response->withRedirect(str_replace('/api/authorize/', '/application/authorize', $request->getUri()->getPath())); })->setName('home'); From ee70ef7f4cec5e6377d956cf7181ff4c9379df01 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 20 Jul 2021 10:27:06 +0100 Subject: [PATCH 09/39] Library Upload via URL : Fix url validation, re-organize how we attempt to get file size and extension do not throw exceptions if the head request fails --- lib/Controller/Library.php | 78 +++++++++++++------------------------- 1 file changed, 27 insertions(+), 51 deletions(-) diff --git a/lib/Controller/Library.php b/lib/Controller/Library.php index 2f62315be1..c676913e12 100644 --- a/lib/Controller/Library.php +++ b/lib/Controller/Library.php @@ -2559,7 +2559,7 @@ public function uploadFromUrl(Request $request, Response $response) } // Validate the URL - if (!v::url()->notEmpty()->validate(urldecode($url)) || !filter_var($url, FILTER_VALIDATE_URL)) { + if (!v::url()->notEmpty()->validate($url) || !filter_var($url, FILTER_VALIDATE_URL)) { throw new InvalidArgumentException(__('Provided URL is invalid'), 'url'); } @@ -2571,7 +2571,8 @@ public function uploadFromUrl(Request $request, Response $response) } // remote file size - $size = $this->getRemoteFileSize($url); + $downloadInfo = $this->getDownloadInfo($url); + $size = $downloadInfo['size']; if (ByteFormatter::toBytes(Environment::getMaxUploadSize()) < $size) { throw new InvalidArgumentException(sprintf(__('This file size exceeds your environment Max Upload Size %s'), Environment::getMaxUploadSize()), 'size'); @@ -2583,7 +2584,7 @@ public function uploadFromUrl(Request $request, Response $response) if (!empty($extension)) { $ext = $extension; } else { - $ext = $this->getRemoteFileExtension($url); + $ext = $downloadInfo['extension']; } // check if we have type provided in the request (available via API), if not get the module type from the extension @@ -2630,16 +2631,15 @@ public function uploadFromUrl(Request $request, Response $response) return $this->render($request, $response); } - /** - * @param $url - * @return int - * @throws InvalidArgumentException - */ - private function getRemoteFileSize($url) + private function getDownloadInfo($url) { - $size = -1; + $downloadInfo = []; $guzzle = new Client($this->getConfig()->getGuzzleProxy()); + // first try to get the extension from pathinfo + $extension = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION); + $size = -1; + try { $head = $guzzle->head($url); $contentLength = $head->getHeader('Content-Length'); @@ -2648,52 +2648,28 @@ private function getRemoteFileSize($url) $size = $value; } - } catch (RequestException $e) { - $this->getLog()->debug('Upload from url failed for URL ' . $url . ' with following message ' . $e->getMessage()); - throw new InvalidArgumentException(('File not found'), 'url'); - } - - if ($size <= 0) { - throw new InvalidArgumentException(('Cannot determine the file size'), 'size'); - } + if ($extension == '') { + $contentType = $head->getHeaderLine('Content-Type'); - return (int)$size; - } + $extension = $contentType; - /** - * @param $url - * @return string - * @throws InvalidArgumentException - */ - private function getRemoteFileExtension($url) - { - // first try to get the extension from pathinfo - $extension = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION); - - // failing that get the extension from Content-Type header via Guzzle - if ($extension == '') { - $guzzle = new Client($this->getConfig()->getGuzzleProxy()); - $head = $guzzle->head($url); - $contentType = $head->getHeaderLine('Content-Type'); - - $extension = $contentType; - - if ($contentType === 'binary/octet-stream' && $head->hasHeader('x-amz-meta-filetype')) { - $amazonContentType = $head->getHeaderLine('x-amz-meta-filetype'); - $extension = $amazonContentType; - } - - // get the extension corresponding to the mime type - $mimeTypes = new MimeTypes(); - $extension = $mimeTypes->getExtension($extension); - } + if ($contentType === 'binary/octet-stream' && $head->hasHeader('x-amz-meta-filetype')) { + $amazonContentType = $head->getHeaderLine('x-amz-meta-filetype'); + $extension = $amazonContentType; + } - // if we could not determine the file extension at this point, throw an error - if ($extension == '') { - throw new InvalidArgumentException(('Cannot determine the file extension'), 'extension'); + // get the extension corresponding to the mime type + $mimeTypes = new MimeTypes(); + $extension = $mimeTypes->getExtension($extension); + } + } catch (RequestException $e) { + $this->getLog()->debug('Upload from url failed for URL ' . $url . ' with following message ' . $e->getMessage()); } - return $extension; + $downloadInfo['size'] = $size; + $downloadInfo['extension'] = $extension; + + return $downloadInfo; } /** From 56b8a7d715df0b2bcf390464273b029971454b18 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 20 Jul 2021 10:33:56 +0100 Subject: [PATCH 10/39] Library Upload via URl : better debug message --- lib/Controller/Library.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Controller/Library.php b/lib/Controller/Library.php index c676913e12..8777d1d1d3 100644 --- a/lib/Controller/Library.php +++ b/lib/Controller/Library.php @@ -2663,7 +2663,7 @@ private function getDownloadInfo($url) $extension = $mimeTypes->getExtension($extension); } } catch (RequestException $e) { - $this->getLog()->debug('Upload from url failed for URL ' . $url . ' with following message ' . $e->getMessage()); + $this->getLog()->debug('Upload from url head request failed for URL ' . $url . ' with following message ' . $e->getMessage()); } $downloadInfo['size'] = $size; From 9ceef387e37c7ed54e59598be800dbb884630e84 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 20 Jul 2021 10:37:08 +0100 Subject: [PATCH 11/39] Applications Controller : authorize small refactor --- lib/Controller/Applications.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Controller/Applications.php b/lib/Controller/Applications.php index 1e4bdfc21b..cb64d9c1a2 100644 --- a/lib/Controller/Applications.php +++ b/lib/Controller/Applications.php @@ -230,16 +230,16 @@ public function authorize(Request $request, Response $response) // get oauth User Entity and set the UserId to the current web userId $authRequest->setUser($this->getUser()); + // We are authorized if ($sanitizedQueryParams->getString('authorization') === 'Approve') { $authRequest->setAuthorizationApproved(true); - - // Redirect back to the home page - return $server->completeAuthorizationRequest($authRequest, $response); } else { $authRequest->setAuthorizationApproved(false); - return $server->completeAuthorizationRequest($authRequest, $response); } + + // Redirect back to the specified redirect url + return $server->completeAuthorizationRequest($authRequest, $response); } /** From 3dde2f538de88e6874a07a8dbd3653a57b5c353d Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 20 Jul 2021 11:49:06 +0100 Subject: [PATCH 12/39] Notification View : Make sure we do not show not released notifications xibosignage/xibo#2573 --- lib/Factory/NotificationFactory.php | 5 +++++ lib/Widget/NotificationView.php | 3 +++ 2 files changed, 8 insertions(+) diff --git a/lib/Factory/NotificationFactory.php b/lib/Factory/NotificationFactory.php index d18cacfad2..2debe255f4 100644 --- a/lib/Factory/NotificationFactory.php +++ b/lib/Factory/NotificationFactory.php @@ -189,6 +189,11 @@ public function query($sortOrder = null, $filterBy = []) $params['createToDt'] = $sanitizedFilter->getInt('createToDt'); } + if ($sanitizedFilter->getInt('onlyReleased') === 1) { + $body .= ' AND `notification`.releaseDt <= :now '; + $params['now'] = Carbon::now()->format('U'); + } + // User Id? if ($sanitizedFilter->getInt('userId') !== null) { $body .= ' AND `notification`.notificationId IN ( diff --git a/lib/Widget/NotificationView.php b/lib/Widget/NotificationView.php index 1c47aed664..91a905f8cc 100644 --- a/lib/Widget/NotificationView.php +++ b/lib/Widget/NotificationView.php @@ -222,11 +222,13 @@ private function getNotifications($isPreview, $displayId = null) if ($isPreview) $notifications = $this->getNotificationFactory()->query(['releaseDt DESC', 'createDt DESC', 'subject'], [ 'releaseDt' => ($age === 0) ? null : Carbon::now()->subMinutes($age)->format('U'), + 'onlyReleased' => 1, 'userId' => $this->getUser()->userId ]); else $notifications = $this->getNotificationFactory()->query(['releaseDt DESC', 'createDt DESC', 'subject'], [ 'releaseDt' => ($age === 0) ? null : Carbon::now()->subMinutes($age)->format('U'), + 'onlyReleased' => 1, 'displayId' => $displayId ]); @@ -358,6 +360,7 @@ public function getModifiedDate($displayId) $notifications = $this->getNotificationFactory()->query(['releaseDt DESC', 'createDt DESC'], [ 'releaseDt' => ($age === 0) ? null : Carbon::now()->subMinutes($age)->format('U'), 'displayId' => $displayId, + 'onlyReleased' => 1, 'length' => 1 ]); From a4c65f9117cbbe32e2bc03f555aeeed3ac16a6ce Mon Sep 17 00:00:00 2001 From: Dan Garner Date: Tue, 20 Jul 2021 14:47:24 +0100 Subject: [PATCH 13/39] Applications: fix auth code flow - incorrectly encoded scopes in authCode. xibosignage/xibo#2571 --- lib/Entity/Application.php | 15 ++++++++++ lib/Entity/ApplicationScope.php | 16 ++--------- lib/Factory/ApplicationScopeFactory.php | 9 ++++-- lib/OAuth/ScopeEntity.php | 37 +++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 17 deletions(-) create mode 100644 lib/OAuth/ScopeEntity.php diff --git a/lib/Entity/Application.php b/lib/Entity/Application.php index 7365d2b4f4..a30000343b 100644 --- a/lib/Entity/Application.php +++ b/lib/Entity/Application.php @@ -26,6 +26,7 @@ use Xibo\Factory\ApplicationRedirectUriFactory; use Xibo\Factory\ApplicationScopeFactory; use Xibo\Helper\Random; +use Xibo\OAuth\ScopeEntity; use Xibo\Service\LogServiceInterface; use Xibo\Storage\StorageServiceInterface; @@ -369,6 +370,20 @@ public function getRedirectUri() } } + /** + * @return \League\OAuth2\Server\Entities\ScopeEntityInterface[] + */ + public function getScopes() + { + $scopes = []; + foreach ($this->scopes as $applicationScope) { + $scope = new ScopeEntity(); + $scope->setIdentifier($applicationScope->getId()); + $scopes[] = $scope; + } + return $scopes; + } + /** @inheritDoc */ public function isConfidential() { diff --git a/lib/Entity/ApplicationScope.php b/lib/Entity/ApplicationScope.php index eb07603104..f8dc7e76be 100644 --- a/lib/Entity/ApplicationScope.php +++ b/lib/Entity/ApplicationScope.php @@ -22,7 +22,6 @@ namespace Xibo\Entity; -use League\OAuth2\Server\Entities\ScopeEntityInterface; use Xibo\Service\LogServiceInterface; use Xibo\Storage\StorageServiceInterface; use Xibo\Support\Exception\AccessDeniedException; @@ -31,7 +30,7 @@ * Class ApplicationScope * @package Xibo\Entity */ -class ApplicationScope implements \JsonSerializable, ScopeEntityInterface +class ApplicationScope implements \JsonSerializable { use EntityTrait; @@ -87,15 +86,4 @@ public function checkRoute($method, $route) if (count($route) <= 0) throw new AccessDeniedException(__('Access to this route is denied for this scope')); } - - /** @inheritDoc */ - public function getIdentifier() :string - { - return $this->getId(); - } - - public function getDescription() :string - { - return $this->description; - } -} \ No newline at end of file +} diff --git a/lib/Factory/ApplicationScopeFactory.php b/lib/Factory/ApplicationScopeFactory.php index c2861fd5fa..8ca1194bc7 100644 --- a/lib/Factory/ApplicationScopeFactory.php +++ b/lib/Factory/ApplicationScopeFactory.php @@ -136,7 +136,10 @@ public function getScopeEntityByIdentifier($scopeIdentifier) $this->getLog()->debug('getScopeEntityByIdentifier: ' . $scopeIdentifier); try { - return $this->getById($scopeIdentifier); + $applicationScope = $this->getById($scopeIdentifier); + $scope = new ScopeEntity(); + $scope->setIdentifier($applicationScope->getId()); + return $scope; } catch (NotFoundException $e) { return null; } @@ -158,7 +161,7 @@ public function finalizeScopes(array $scopes, $grantType, ClientEntityInterface $found = false; /** @var \Xibo\Entity\Application $clientEntity */ - foreach ($clientEntity->scopes as $validScope) { + foreach ($clientEntity->getScopes() as $validScope) { if ($validScope->getIdentifier() === $scope->getIdentifier()) { $found = true; break; @@ -172,4 +175,4 @@ public function finalizeScopes(array $scopes, $grantType, ClientEntityInterface return $finalScopes; } -} \ No newline at end of file +} diff --git a/lib/OAuth/ScopeEntity.php b/lib/OAuth/ScopeEntity.php new file mode 100644 index 0000000000..056bd0227e --- /dev/null +++ b/lib/OAuth/ScopeEntity.php @@ -0,0 +1,37 @@ +. + */ + +namespace Xibo\OAuth; + +use League\OAuth2\Server\Entities\ScopeEntityInterface; +use League\OAuth2\Server\Entities\Traits\EntityTrait; +use League\OAuth2\Server\Entities\Traits\ScopeTrait; + +/** + * Class ScopeEntity + * @package Xibo\OAuth + */ +class ScopeEntity implements ScopeEntityInterface +{ + use ScopeTrait; + use EntityTrait; +} From 430184798a5504965724cd8008d1a039567a9f71 Mon Sep 17 00:00:00 2001 From: Dan Garner Date: Tue, 20 Jul 2021 15:11:21 +0100 Subject: [PATCH 14/39] Applications: scopes are now strings. xibosignage/xibo#2571 --- lib/Middleware/ApiAuthorization.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/Middleware/ApiAuthorization.php b/lib/Middleware/ApiAuthorization.php index 2c34e473cb..be46c1efe8 100644 --- a/lib/Middleware/ApiAuthorization.php +++ b/lib/Middleware/ApiAuthorization.php @@ -126,14 +126,13 @@ public function process(Request $request, RequestHandler $handler): Response if (is_array($scopes) && count($scopes) > 0) { foreach ($scopes as $scope) { // Valid routes - if ($scope->id != 'all') { + if ($scope !== 'all') { $logger->debug(sprintf('Test authentication for %s %s against scope %s', - $resource, $request->getMethod(), $scope->id)); + $resource, $request->getMethod(), $scope)); // Check the route and request method try { - $applicationScopeFactory->getById($scope->id)->checkRoute($request->getMethod(), - $resource); + $applicationScopeFactory->getById($scope)->checkRoute($request->getMethod(), $resource); } catch (NotFoundException $notFoundException) { throw new AccessDeniedException(); } From 2ccd840630920c13c7588376dd059d0e5c6052f4 Mon Sep 17 00:00:00 2001 From: Dan Garner Date: Tue, 20 Jul 2021 15:29:35 +0100 Subject: [PATCH 15/39] phpcs --- lib/Middleware/ApiAuthorization.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/Middleware/ApiAuthorization.php b/lib/Middleware/ApiAuthorization.php index be46c1efe8..901f7bdd22 100644 --- a/lib/Middleware/ApiAuthorization.php +++ b/lib/Middleware/ApiAuthorization.php @@ -127,8 +127,13 @@ public function process(Request $request, RequestHandler $handler): Response foreach ($scopes as $scope) { // Valid routes if ($scope !== 'all') { - $logger->debug(sprintf('Test authentication for %s %s against scope %s', - $resource, $request->getMethod(), $scope)); + $logger->debug( + sprintf('Test authentication for %s %s against scope %s', + $resource, + $request->getMethod(), + $scope + ) + ); // Check the route and request method try { From bfe15846a476401e7d2a0122b3959a3abfa14398 Mon Sep 17 00:00:00 2001 From: Dan Garner Date: Tue, 20 Jul 2021 15:44:26 +0100 Subject: [PATCH 16/39] phpcs --- lib/Middleware/ApiAuthorization.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Middleware/ApiAuthorization.php b/lib/Middleware/ApiAuthorization.php index 901f7bdd22..e8c8470ebc 100644 --- a/lib/Middleware/ApiAuthorization.php +++ b/lib/Middleware/ApiAuthorization.php @@ -128,7 +128,8 @@ public function process(Request $request, RequestHandler $handler): Response // Valid routes if ($scope !== 'all') { $logger->debug( - sprintf('Test authentication for %s %s against scope %s', + sprintf( + 'Test authentication for %s %s against scope %s', $resource, $request->getMethod(), $scope From a6508fa5d3b0a726ffe8fcc3a911741c20ad954e Mon Sep 17 00:00:00 2001 From: Dan Garner Date: Tue, 20 Jul 2021 16:23:19 +0100 Subject: [PATCH 17/39] Applications: scopes are now strings. xibosignage/xibo#2571 --- lib/Controller/Applications.php | 22 ++++++++++++++++++++-- views/applications-authorize-page.twig | 6 +++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/Controller/Applications.php b/lib/Controller/Applications.php index cb64d9c1a2..81c2af0a58 100644 --- a/lib/Controller/Applications.php +++ b/lib/Controller/Applications.php @@ -173,14 +173,32 @@ public function grid(Request $request, Response $response) public function authorizeRequest(Request $request, Response $response) { // Pull authorize params from our session - if (!$authParams = $this->session->get('authParams')) { + /** @var AuthorizationRequest $authParams */ + $authParams = $this->session->get('authParams'); + if (!$authParams) { throw new InvalidArgumentException(__('Authorisation Parameters missing from session.'), 'authParams'); } + // Process any scopes. + $scopes = []; + $authScopes = $authParams->getScopes(); + if ($authScopes !== null) { + foreach ($authScopes as $scope) { + $this->getLog()->debug('Loading scope: ' . $scope->getIdentifier()); + $scopes[] = $this->applicationScopeFactory->getById($scope->getIdentifier()); + } + } + + // `all` is the default scope + if (count($scopes) <= 0) { + $scopes[] = $this->applicationScopeFactory->getById('all'); + } + // Get, show page $this->getState()->template = 'applications-authorize-page'; $this->getState()->setData([ - 'authParams' => $authParams + 'authParams' => $authParams, + 'scopes' => $scopes ]); return $this->render($request, $response); diff --git a/views/applications-authorize-page.twig b/views/applications-authorize-page.twig index bcc564f124..e82d4487b4 100644 --- a/views/applications-authorize-page.twig +++ b/views/applications-authorize-page.twig @@ -28,12 +28,12 @@
{% trans "Authorize Request" %}

{{ authParams.client.getName() }}

-
{{ "would like access to the following scopes"|trans }} :
+
{{ "would like access to the following scopes"|trans }}:
    - {% for scope in authParams.scopes %} + {% for scope in scopes %}
  • - {{ scope.getIdentifier() }} : {{ scope.getDescription() }} + {{ scope.description }}
  • {% endfor %}
From dbffb2062bcf8c48ca6283d91d55144cb9646697 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 20 Jul 2021 16:35:56 +0100 Subject: [PATCH 18/39] Display Manage : Show Month and Year datePirckers adjust default fromDt and toDt to start and end of the current month respectively xibosignage/xibo#2575 --- lib/Controller/Display.php | 6 +++--- views/display-page-manage.twig | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Controller/Display.php b/lib/Controller/Display.php index 27ba9c3c06..3490c1c8af 100644 --- a/lib/Controller/Display.php +++ b/lib/Controller/Display.php @@ -377,9 +377,9 @@ function displayManage(Request $request, Response $response, $id) 'sizeRemaining' => round((double)($totalSize - $completeSize) / (pow(1024, $base)), 2), ], 'defaults' => [ - 'fromDate' => Carbon::now()->subSeconds(86400 * 35)->format(DateFormatHelper::getSystemFormat()), - 'fromDateOneDay' => Carbon::now()->subSeconds(86400)->format(DateFormatHelper::getSystemFormat()), - 'toDate' => Carbon::now()->format(DateFormatHelper::getSystemFormat()) + 'fromDate' => Carbon::now()->startOfMonth()->format(DateFormatHelper::getSystemFormat()), + 'fromDateOneDay' => Carbon::now()->subDay()->format(DateFormatHelper::getSystemFormat()), + 'toDate' => Carbon::now()->endOfMonth()->format(DateFormatHelper::getSystemFormat()) ] ]); diff --git a/views/display-page-manage.twig b/views/display-page-manage.twig index 1a2cfd967a..4ae3257dc4 100644 --- a/views/display-page-manage.twig +++ b/views/display-page-manage.twig @@ -194,8 +194,8 @@
- {{ inline.date("fromDt", "From Date", defaults.fromDate, "", "", "", "") }} - {{ inline.date("toDt", "To Date", defaults.toDate, "", "", "", "") }} + {{ inline.dateMonth("fromDt", "From Date", defaults.fromDate, "", "", "", "") }} + {{ inline.dateMonth("toDt", "To Date", defaults.toDate, "", "", "", "") }} {{ inline.hidden("displayId", display.displayId) }}
From b0ab80941e1fbc61a92a4ddacaacf858c773897a Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 22 Jul 2021 11:08:16 +0100 Subject: [PATCH 19/39] Display Profile : Fix copy with empty default command string xibosignage/xibo#2577 --- lib/Controller/DisplayProfile.php | 19 +++++++++++++++++-- lib/Entity/DisplayProfile.php | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/Controller/DisplayProfile.php b/lib/Controller/DisplayProfile.php index 79c61c44a8..33bf12456f 100644 --- a/lib/Controller/DisplayProfile.php +++ b/lib/Controller/DisplayProfile.php @@ -622,11 +622,26 @@ public function copy(Request $request, Response $response, $id) // Create a form out of the config object. $displayProfile = $this->displayProfileFactory->getById($id); - if ($this->getUser()->userTypeId != 1 && $this->getUser()->userId != $displayProfile->userId) + if ($this->getUser()->userTypeId != 1 && $this->getUser()->userId != $displayProfile->userId) { throw new AccessDeniedException(__('You do not have permission to delete this profile')); + } + // clear DisplayProfileId, commands and set isDefault to 0 $new = clone $displayProfile; $new->name = $this->getSanitizer($request->getParams())->getString('name'); + + foreach ($displayProfile->commands as $command) { + /* @var \Xibo\Entity\Command $command */ + if (!empty($command->commandStringDisplayProfile)) { + // if the original Display Profile has a commandString + // assign this command with the same commandString to new Display Profile + // commands with only default commandString are not directly assigned to Display profile + $command->commandString = $command->commandStringDisplayProfile; + $command->validationString = $command->validationStringDisplayProfile; + $new->assignCommand($command); + } + } + $new->save(); // Return @@ -639,4 +654,4 @@ public function copy(Request $request, Response $response, $id) return $this->render($request, $response); } -} \ No newline at end of file +} diff --git a/lib/Entity/DisplayProfile.php b/lib/Entity/DisplayProfile.php index 7a6be9a16e..9bca487afe 100644 --- a/lib/Entity/DisplayProfile.php +++ b/lib/Entity/DisplayProfile.php @@ -128,6 +128,7 @@ public function __construct($store, $log, $config, $dispatcher, $commandFactory) public function __clone() { $this->displayProfileId = null; + $this->commands = []; $this->isDefault = 0; } From 7baf5300660523163f789a77fd84b5cd515935ef Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 22 Jul 2021 11:38:15 +0100 Subject: [PATCH 20/39] Actions : Fix Layout Code search xibosignage/xibo#2578 --- lib/Controller/Layout.php | 5 ++++- lib/Factory/LayoutFactory.php | 22 +++++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/Controller/Layout.php b/lib/Controller/Layout.php index 008f01554e..2591e61aa8 100644 --- a/lib/Controller/Layout.php +++ b/lib/Controller/Layout.php @@ -2779,7 +2779,10 @@ public function discard(Request $request, Response $response, $id) public function getLayoutCodes(Request $request, Response $response) { $parsedParams = $this->getSanitizer($request->getQueryParams()); - $codes = $this->layoutFactory->getLayoutCodes($this->gridRenderFilter([], $parsedParams)); + + $codes = $this->layoutFactory->getLayoutCodes($this->gridRenderFilter([ + 'code' => $parsedParams->getString('code') + ], $parsedParams)); // Store the table rows $this->getState()->template = 'grid'; diff --git a/lib/Factory/LayoutFactory.php b/lib/Factory/LayoutFactory.php index 9a4f607c52..d12f449919 100644 --- a/lib/Factory/LayoutFactory.php +++ b/lib/Factory/LayoutFactory.php @@ -1767,12 +1767,20 @@ public function createNestedPlaylistWidgets($widgets, $combined, &$playlists) * @param array $filterBy * @return array */ - public function getLayoutCodes($filterBy = []) + public function getLayoutCodes($filterBy = []): array { $parsedFilter = $this->getSanitizer($filterBy); $params = []; $select = 'SELECT DISTINCT code '; - $body = ' FROM layout WHERE code IS NOT NULL ORDER BY code'; + $body = ' FROM layout WHERE code IS NOT NULL '; + + // get by Code + if ($parsedFilter->getString('code') != '') { + $body.= ' AND layout.code LIKE :code '; + $params['code'] = '%' . $parsedFilter->getString('code') . '%'; + } + + $order = ' ORDER BY code'; // Paging $limit = ''; @@ -1780,8 +1788,8 @@ public function getLayoutCodes($filterBy = []) $limit = ' LIMIT ' . intval($parsedFilter->getInt('start'), 0) . ', ' . $parsedFilter->getInt('length', ['default' => 10]); } - $sql = $select . $body . $limit; - $entries = $this->getStore()->select($sql, []); + $sql = $select . $body . $order . $limit; + $entries = $this->getStore()->select($sql, $params); // Paging if ($limit != '' && count($entries) > 0) { @@ -1973,7 +1981,7 @@ public function query($sortOrder = null, $filterBy = []) $this->nameFilter('layout', 'layout', $terms, $body, $params, ($parsedFilter->getCheckbox('useRegexForName') == 1)); } - if ($parsedFilter->getString('layoutExact', $filterBy) != '') { + if ($parsedFilter->getString('layoutExact') != '') { $body.= " AND layout.layout = :exact "; $params['exact'] = $parsedFilter->getString('layoutExact'); } @@ -2071,12 +2079,12 @@ public function query($sortOrder = null, $filterBy = []) } // get by Code - if ($parsedFilter->getString('code', $filterBy) != '') { + if ($parsedFilter->getString('code') != '') { $body.= " AND layout.code = :code "; $params['code'] = $parsedFilter->getString('code'); } - if ($parsedFilter->getString('codeLike', $filterBy) != '') { + if ($parsedFilter->getString('codeLike') != '') { $body.= ' AND layout.code LIKE :codeLike '; $params['codeLike'] = '%' . $parsedFilter->getString('codeLike') . '%'; } From 89cd18228ae870ccdd428573f08761fdcdb67906 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 22 Jul 2021 12:32:06 +0100 Subject: [PATCH 21/39] Ticker RSS : fix date string translation xibosignage/xibo#2576 --- lib/Widget/Ticker.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Widget/Ticker.php b/lib/Widget/Ticker.php index 128bb6c24d..5e5a0470f3 100644 --- a/lib/Widget/Ticker.php +++ b/lib/Widget/Ticker.php @@ -831,8 +831,8 @@ private function getRssItems($text) break; case '[Date]': - $replace = Carbon::createFromTimestamp($item->getDate()->format('U'))->format($dateFormat); - + Carbon::setLocale($this->getConfig()->getSetting('DEFAULT_LANGUAGE', 'en')); + $replace = Carbon::createFromTimestamp($item->getDate()->format('U'))->translatedFormat($dateFormat); break; case '[PermaLink]': From 8ea7de833aec1d859e44e92c1fa1537a9441bad3 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 22 Jul 2021 12:32:28 +0100 Subject: [PATCH 22/39] Notification View : Fix date string translation --- lib/Widget/NotificationView.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Widget/NotificationView.php b/lib/Widget/NotificationView.php index 91a905f8cc..554869709a 100644 --- a/lib/Widget/NotificationView.php +++ b/lib/Widget/NotificationView.php @@ -256,7 +256,8 @@ private function getNotifications($isPreview, $displayId = null) break; case '[Date]': - $replace = Carbon::createFromTimestamp($notification->releaseDt)->format($dateFormat); + Carbon::setLocale($this->getConfig()->getSetting('DEFAULT_LANGUAGE', 'en')); + $replace = Carbon::createFromTimestamp($notification->releaseDt)->translatedFormat($dateFormat); break; } From de750ac4d896360b6f1f90ca81137f91d947e756 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 22 Jul 2021 14:31:35 +0100 Subject: [PATCH 23/39] Schedule : Add SoV to Event title and Agenda view in Interrupt Layouts table xibosignage/xibo#2579 --- lib/Controller/Schedule.php | 4 ++++ ui/src/vendor/calendar/js/calendar.js | 3 ++- views/schedule-page.twig | 10 ++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/Controller/Schedule.php b/lib/Controller/Schedule.php index 25fbeb4404..a1b7db8fee 100644 --- a/lib/Controller/Schedule.php +++ b/lib/Controller/Schedule.php @@ -357,6 +357,10 @@ function eventData(Request $request, Response $response) $row->campaign = __('Private Item'); } $title = __('%s scheduled on %s', $row->campaign, $displayGroupList); + + if ($row->eventTypeId === \Xibo\Entity\Schedule::$INTERRUPT_EVENT) { + $title .= __(' with Share of Voice %d seconds per hour', $row->shareOfVoice); + } } // Day diff from start date to end date diff --git a/ui/src/vendor/calendar/js/calendar.js b/ui/src/vendor/calendar/js/calendar.js index 85593caa35..26b84d2c37 100644 --- a/ui/src/vendor/calendar/js/calendar.js +++ b/ui/src/vendor/calendar/js/calendar.js @@ -783,7 +783,8 @@ if(!String.prototype.formatNum) { layoutDisplayOrder: event.displayOrder, eventPriority: event.isPriority, itemClass: elementPriorityClass, - itemIcon: elementPriorityIcon + itemIcon: elementPriorityIcon, + shareOfVoice: event.shareOfVoice }); } } diff --git a/views/schedule-page.twig b/views/schedule-page.twig index f0c19b5704..d55aa77689 100644 --- a/views/schedule-page.twig +++ b/views/schedule-page.twig @@ -660,7 +660,11 @@ + <% if (layouts.type == 4) { %> + + {% verbatim %}<% if (layouts.type == 4) { %>{% endverbatim %} + + {% verbatim %}<% } %>{% endverbatim %} @@ -726,6 +733,9 @@ <% } %> + <% if (layout.shareOfVoice) { %> + + <% } %> <% From 23cf664ff97aa9656566cf517c3d75706e525089 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 22 Jul 2021 15:18:45 +0100 Subject: [PATCH 24/39] Carbon : call setLocale() in state middleware. xibosignage/xibo#2576 --- lib/Middleware/State.php | 3 +++ lib/Widget/NotificationView.php | 1 - lib/Widget/Ticker.php | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Middleware/State.php b/lib/Middleware/State.php index 7acc5651de..0a65ec0a8d 100644 --- a/lib/Middleware/State.php +++ b/lib/Middleware/State.php @@ -194,6 +194,9 @@ public static function setState(App $app, Request $request): Request // Setup the translations for gettext Translate::InitLocale($container->get('configService')); + // Set Carbon locale + Carbon::setLocale(Translate::GetLocale(2)); + // Default timezone date_default_timezone_set($container->get('configService')->getSetting("defaultTimezone")); diff --git a/lib/Widget/NotificationView.php b/lib/Widget/NotificationView.php index 554869709a..6278b9dc6a 100644 --- a/lib/Widget/NotificationView.php +++ b/lib/Widget/NotificationView.php @@ -256,7 +256,6 @@ private function getNotifications($isPreview, $displayId = null) break; case '[Date]': - Carbon::setLocale($this->getConfig()->getSetting('DEFAULT_LANGUAGE', 'en')); $replace = Carbon::createFromTimestamp($notification->releaseDt)->translatedFormat($dateFormat); break; } diff --git a/lib/Widget/Ticker.php b/lib/Widget/Ticker.php index 5e5a0470f3..a21fbb0178 100644 --- a/lib/Widget/Ticker.php +++ b/lib/Widget/Ticker.php @@ -831,7 +831,6 @@ private function getRssItems($text) break; case '[Date]': - Carbon::setLocale($this->getConfig()->getSetting('DEFAULT_LANGUAGE', 'en')); $replace = Carbon::createFromTimestamp($item->getDate()->format('U'))->translatedFormat($dateFormat); break; From 8120140ad211f8179f4c92b163e714946aad6079 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 27 Jul 2021 10:53:37 +0100 Subject: [PATCH 25/39] File Upload - Fix uploading multiple files at once xibosignage/xibo#2581 --- ui/src/core/file-upload.js | 10 ++++++---- ui/src/helpers/form-helpers.js | 2 +- views/layout-designer-page.twig | 2 +- views/layout-page.twig | 1 - views/library-page.twig | 2 +- views/media-manager-page.twig | 4 ++-- views/notification-page.twig | 2 +- views/playersoftware-page.twig | 2 +- 8 files changed, 13 insertions(+), 12 deletions(-) diff --git a/ui/src/core/file-upload.js b/ui/src/core/file-upload.js index 4b2b0f7cae..064fe94eeb 100644 --- a/ui/src/core/file-upload.js +++ b/ui/src/core/file-upload.js @@ -27,14 +27,16 @@ var videoImageCovers = {}; */ function openUploadForm(options) { - options = $.extend({}, { + options = $.extend(true, {}, { templateId: "template-file-upload", - multi: true, videoImageCovers: true, className: "upload-modal", animateDialog: true, formOpenedEvent: null, - layoutImport: false + templateOptions : { + layoutImport: false, + multi: true + } }, options); // Keep a cache of the upload template (unless we are a non-standard form) @@ -84,7 +86,7 @@ function openUploadForm(options) { } // If we are not a multi-upload, then limit to 1 - if (!options.multi) { + if (!options.templateOptions.multi) { uploadOptions = $.extend({}, uploadOptions, { maxNumberOfFiles: 1, limitMultiFileUploads: 1 diff --git a/ui/src/helpers/form-helpers.js b/ui/src/helpers/form-helpers.js index a5e68be622..8ca0a584c9 100644 --- a/ui/src/helpers/form-helpers.js +++ b/ui/src/helpers/form-helpers.js @@ -897,8 +897,8 @@ let formHelpers = function() { animateDialog: false, initialisedBy: "library-upload", className: self.namespace.getUploadDialogClassName(), - multi: false, templateOptions: { + multi: false, oldMediaId: mediaId, widgetId: widgetId, updateInAllChecked: uploadFormUpdateAllDefault, diff --git a/views/layout-designer-page.twig b/views/layout-designer-page.twig index 6c0e06a993..2692cc23d3 100644 --- a/views/layout-designer-page.twig +++ b/views/layout-designer-page.twig @@ -224,7 +224,6 @@ openUploadForm({ url: $(e.target).data().addNewBackgroundUrl, title: "{% trans "Add Background Image" %}", - multi: false, videoImageCovers: false, buttons: { main: { @@ -236,6 +235,7 @@ } }, templateOptions: { + multi: false, trans: { addFiles: "{% trans "Browse/Add Image" %}", startUpload: "{% trans "Start Upload" %}", diff --git a/views/layout-page.twig b/views/layout-page.twig index d18a58c2d0..8aadb5b6aa 100644 --- a/views/layout-page.twig +++ b/views/layout-page.twig @@ -312,7 +312,6 @@ openUploadForm({ url: "{{ url_for("layout.import") }}", title: "{{ "Upload Layout"|trans }}", - multi: true, videoImageCovers: false, buttons: { main: { diff --git a/views/library-page.twig b/views/library-page.twig index 7f9564fee0..3fa8b071e9 100644 --- a/views/library-page.twig +++ b/views/library-page.twig @@ -362,7 +362,6 @@ openUploadForm({ url: "{{ url_for("library.add") }}", title: "{% trans "Upload media" %}", - multi: false, buttons: { main: { label: "{% trans "Done" %}", @@ -374,6 +373,7 @@ } }, templateOptions: { + multi: false, oldMediaId: mediaId, oldFolderId: folderId, updateInAllChecked: {% if settings.LIBRARY_MEDIA_UPDATEINALL_CHECKB == 1 %}true{% else %}false{% endif %}, diff --git a/views/media-manager-page.twig b/views/media-manager-page.twig index b164169ba8..2f5c78bf27 100644 --- a/views/media-manager-page.twig +++ b/views/media-manager-page.twig @@ -172,7 +172,6 @@ openUploadForm({ url: "{{ url_for("library.add") }}", title: "{% trans "Upload media" %}", - multi: false, buttons: { main: { label: "{% trans "Done" %}", @@ -184,6 +183,7 @@ } }, templateOptions: { + multi: false, oldMediaId: mediaId, widgetId: widgetId, updateInAllChecked: {% if settings.LIBRARY_MEDIA_UPDATEINALL_CHECKB == 1 %}true{% else %}false{% endif %}, @@ -251,7 +251,6 @@ openUploadForm({ url: "{{ url_for("library.add") }}", title: "{% trans "Upload media" %}", - multi: false, buttons: { main: { label: "{% trans "Done" %}", @@ -263,6 +262,7 @@ } }, templateOptions: { + multi: false, oldMediaId: mediaId, widgetId: widgetId, updateInAllChecked: {% if settings.LIBRARY_MEDIA_UPDATEINALL_CHECKB == 1 %}true{% else %}false{% endif %}, diff --git a/views/notification-page.twig b/views/notification-page.twig index ef28e268f3..3ea3b82ba4 100644 --- a/views/notification-page.twig +++ b/views/notification-page.twig @@ -213,7 +213,6 @@ var upload = openUploadForm({ url: "{{ url_for("notification.addattachment") }}", title: "{% trans "Browse/Add attachment" %}", - multi: false, videoImageCovers: false, animateDialog: false, className: "second-dialog", @@ -227,6 +226,7 @@ } }, templateOptions: { + multi: false, trans: { addFiles: "{% trans "Browse/Add Attachment" %}", startUpload: "{% trans "Start Upload" %}", diff --git a/views/playersoftware-page.twig b/views/playersoftware-page.twig index 2394e3c52f..0c87935013 100644 --- a/views/playersoftware-page.twig +++ b/views/playersoftware-page.twig @@ -146,7 +146,6 @@ openUploadForm({ url: "{{ url_for("library.add") }}", title: "{% trans "Upload Version" %}", - multi: false, videoImageCovers: false, buttons: { main: { @@ -159,6 +158,7 @@ } }, templateOptions: { + multi: false, trans: { addFiles: "{% trans "Add files" %}", startUpload: "{% trans "Start upload" %}", From 678e13063c4e223edb70c2c29ae0e1c0afc80797 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 26 Jul 2021 16:19:06 +0100 Subject: [PATCH 26/39] User Delete improvements and fixes - cherry picked from 2.3.12 and adjusted xibosignage/xibo#2580 --- lib/Controller/DayPart.php | 7 +- lib/Controller/Layout.php | 21 ++++-- lib/Controller/User.php | 13 +++- lib/Entity/DayPart.php | 30 +++++++-- lib/Entity/DisplayProfile.php | 6 +- lib/Entity/User.php | 123 ++++++++++++++++++++++++++-------- lib/Factory/LayoutFactory.php | 2 +- lib/Factory/MediaFactory.php | 6 +- lib/Factory/UserFactory.php | 8 +-- views/user-form-delete.twig | 10 +-- views/user-form-edit.twig | 10 +-- 11 files changed, 175 insertions(+), 61 deletions(-) diff --git a/lib/Controller/DayPart.php b/lib/Controller/DayPart.php index 7f50f0fa39..cfea6be78f 100644 --- a/lib/Controller/DayPart.php +++ b/lib/Controller/DayPart.php @@ -1,6 +1,6 @@ dayPartFactory->getById($id); + if ($dayPart->isSystemDayPart()) { + throw new InvalidArgumentException('Cannot delete system dayPart', 'dayPartId'); + } + if (!$this->getUser()->checkDeleteable($dayPart)) { throw new AccessDeniedException(); } diff --git a/lib/Controller/Layout.php b/lib/Controller/Layout.php index 2591e61aa8..cc55897543 100644 --- a/lib/Controller/Layout.php +++ b/lib/Controller/Layout.php @@ -1,6 +1,6 @@ layoutFactory->getByParentId($id); } - // Work out our resolution - if ($layout->schemaVersion < 2) - $resolution = $this->resolutionFactory->getByDesignerDimensions($layout->width, $layout->height); - else - $resolution = $this->resolutionFactory->getByDimensions($layout->width, $layout->height); + // Work out our resolution, if it does not exist, create it. + try { + if ($layout->schemaVersion < 2) { + $resolution = $this->resolutionFactory->getByDesignerDimensions($layout->width, $layout->height); + } else { + $resolution = $this->resolutionFactory->getByDimensions($layout->width, $layout->height); + } + } catch (NotFoundException $notFoundException) { + $this->getLog()->info('Layout Designer with an unknown resolution, we will create it with name: ' . $layout->width . ' x ' . $layout->height); + + $resolution = $this->resolutionFactory->create($layout->width . ' x ' . $layout->height, (int)$layout->width, (int)$layout->height); + $resolution->userId = $this->userFactory->getSystemUser()->userId; + $resolution->save(); + } $moduleFactory = $this->moduleFactory; $isTemplate = $layout->hasTag('template'); diff --git a/lib/Controller/User.php b/lib/Controller/User.php index b10204f8f8..e43431f9c0 100644 --- a/lib/Controller/User.php +++ b/lib/Controller/User.php @@ -1,6 +1,6 @@ getUser()->isSuperAdmin() && $user->userId != $this->getConfig()->getSetting('SYSTEM_USER') + && $this->getUser()->userId !== $user->userId ) { // Delete $user->buttons[] = [ @@ -1030,6 +1031,7 @@ public function edit(Request $request, Response $response, $id) public function delete(Request $request, Response $response, $id) { $user = $this->userFactory->getById($id); + $sanitizedParams = $this->getSanitizer($request->getParams()); // System User if ($user->userId == $this->getConfig()->getSetting('SYSTEM_USER')) { @@ -1040,7 +1042,14 @@ public function delete(Request $request, Response $response, $id) throw new AccessDeniedException(); } - $sanitizedParams = $this->getSanitizer($request->getParams()); + if ($this->getUser()->userId === $user->userId) { + throw new InvalidArgumentException(__('Cannot delete your own User from the CMS.')); + } + + if ($sanitizedParams->getCheckbox('deleteAllItems') && $user->isSuperAdmin()) { + throw new InvalidArgumentException(__('Cannot delete all items owned by a Super Admin, please reassign to a different User.')); + } + $user->setChildAclDependencies($this->userGroupFactory); $user->setChildObjectDependencies($this->campaignFactory, $this->layoutFactory, $this->mediaFactory, $this->scheduleFactory, $this->displayFactory, $this->displayGroupFactory, $this->widgetFactory, $this->playerVersionFactory, $this->playlistFactory, $this->dataSetFactory, $this->dayPartFactory); diff --git a/lib/Entity/DayPart.php b/lib/Entity/DayPart.php index a84ed164d9..49fb207555 100644 --- a/lib/Entity/DayPart.php +++ b/lib/Entity/DayPart.php @@ -1,6 +1,6 @@ isAlways || $this->isCustom); + } + /** * @return int */ @@ -213,7 +218,8 @@ public function load() public function save($options = []) { $options = array_merge([ - 'validate' => true + 'validate' => true, + 'recalculateHash' => true ], $options); if ($options['validate']) @@ -225,11 +231,17 @@ public function save($options = []) // Update $this->update(); - // Compare the time hash with a new time hash to see if we need to update associated schedules - if ($this->timeHash != $this->calculateTimeHash()) - $this->handleEffectedSchedules(); - else - $this->getLog()->debug('Daypart hash identical, no need to update schedules. ' . $this->timeHash . ' vs ' . $this->calculateTimeHash()); + // When we change user on reassignAllTo, we do save dayPart, + // however it will not have required childObjectDependencies to run the below checks + // it is also not needed to run them when we just changed the owner. + if ($options['recalculateHash']) { + // Compare the time hash with a new time hash to see if we need to update associated schedules + if ($this->timeHash != $this->calculateTimeHash()) { + $this->handleEffectedSchedules(); + } else { + $this->getLog()->debug('Daypart hash identical, no need to update schedules. ' . $this->timeHash . ' vs ' . $this->calculateTimeHash()); + } + } } } @@ -238,6 +250,10 @@ public function save($options = []) */ public function delete() { + if ($this->isSystemDayPart()) { + throw new InvalidArgumentException('Cannot delete system dayParts'); + } + // Delete all events using this daypart $schedules = $this->scheduleFactory->getByDayPartId($this->dayPartId); diff --git a/lib/Entity/DisplayProfile.php b/lib/Entity/DisplayProfile.php index 9bca487afe..8eee31de82 100644 --- a/lib/Entity/DisplayProfile.php +++ b/lib/Entity/DisplayProfile.php @@ -1,6 +1,6 @@ isDefault === 1) { + throw new InvalidArgumentException(__('Cannot delete default Display Profile.'), 'isDefault'); + } + $this->getStore()->update('DELETE FROM `displayprofile` WHERE displayprofileid = :displayProfileId', ['displayProfileId' => $this->displayProfileId]); } diff --git a/lib/Entity/User.php b/lib/Entity/User.php index 00d7a3739a..e5abafafa1 100644 --- a/lib/Entity/User.php +++ b/lib/Entity/User.php @@ -1,6 +1,6 @@ campaigns = $this->campaignFactory->getByOwnerId($this->userId); $this->layouts = $this->layoutFactory->getByOwnerId($this->userId); - $this->media = $this->mediaFactory->getByOwnerId($this->userId); + $this->media = $this->mediaFactory->getByOwnerId($this->userId, 1); $this->events = $this->scheduleFactory->getByOwnerId($this->userId); $this->playlists = $this->playlistFactory->getByOwnerId($this->userId); $this->displayGroups = $this->displayGroupFactory->getByOwnerId($this->userId, -1); @@ -740,9 +740,12 @@ public function load($all = false) public function countChildren() { $this->load(true); + $displayProfiles = intval($this->getStore()->select('SELECT COUNT(*) AS cnt FROM `displayprofile` WHERE userId = :userId', ['userId' => $this->userId])[0]['cnt']); + $commands = intval($this->getStore()->select('SELECT COUNT(*) AS cnt FROM `command` WHERE userId = :userId', ['userId' => $this->userId])[0]['cnt']); + $savedReports = intval($this->getStore()->select('SELECT COUNT(*) AS cnt FROM `saved_report` WHERE userId = :userId', ['userId' => $this->userId])[0]['cnt']); - $count = count($this->campaigns) + count($this->layouts) + count($this->media) + count($this->events) + count($this->playlists) + count($this->displayGroups) + count($this->dayParts); - $this->getLog()->debug('Counted Children on %d, there are %d', $this->userId, $count); + $count = count($this->campaigns) + count($this->layouts) + count($this->media) + count($this->events) + count($this->playlists) + count($this->displayGroups) + count($this->dayParts) + $displayProfiles + $commands + $savedReports; + $this->getLog()->debug(sprintf('Counted Children on %d, there are %d', $this->userId, $count)); return $count; } @@ -755,17 +758,22 @@ public function countChildren() */ public function reassignAllTo($user) { - $this->getLog()->debug('Reassign all to %s', $user->userName); + $this->getLog()->debug(sprintf('Reassign all to %s', $user->userName)); $this->load(true); - $this->getLog()->debug('There are %d children', $this->countChildren()); + $this->getLog()->debug(sprintf('There are %d children', $this->countChildren())); + + // find System User, this is not necessarily the user we were asked to reassignAllTo + $systemUser = $this->userFactory->getSystemUser(); // Reassign media - $this->getStore()->update('UPDATE `media` SET userId = :userId WHERE userId = :oldUserId', [ - 'userId' => $user->userId, - 'oldUserId' => $this->userId - ]); + foreach ($this->media as $media) { + /* @var Media $media */ + ($media->mediaType === 'module') ? $media->setOwner($systemUser->userId) : $media->setOwner($user->getOwnerId()); + + $media->save(); + } // Reassign events $this->getStore()->update('UPDATE `schedule` SET userId = :userId WHERE userId = :oldUserId', [ @@ -804,10 +812,10 @@ public function reassignAllTo($user) ]); // Reassign display groups - $this->getStore()->update('UPDATE `displaygroup` SET userId = :userId WHERE userId = :oldUserId', [ - 'userId' => $user->userId, - 'oldUserId' => $this->userId - ]); + foreach ($this->displayGroups as $displayGroup) { + ($displayGroup->isDisplaySpecific === 1) ? $displayGroup->setOwner($systemUser->userId) : $displayGroup->setOwner($user->getOwnerId()); + $displayGroup->save(['saveTags' => false, 'manageDynamicDisplayLinks' => false]); + } // Reassign display profiles $this->getStore()->update('UPDATE `displayprofile` SET userId = :userId WHERE userId = :oldUserId', [ @@ -821,20 +829,37 @@ public function reassignAllTo($user) 'oldUserId' => $this->userId ]); + foreach ($this->dayParts as $dayPart) { + ($dayPart->isSystemDayPart()) ? $dayPart->setOwner($systemUser->userId) : $dayPart->setOwner($user->userId); + $dayPart->save(['validate' => false, 'recalculateHash' => false]); + } + // Reassign resolutions $this->getStore()->update('UPDATE `resolution` SET userId = :userId WHERE userId = :oldUserId', [ 'userId' => $user->userId, 'oldUserId' => $this->userId ]); - // Reassign Dayparts - $this->getStore()->update('UPDATE `daypart` SET userId = :userId WHERE userId = :oldUserId', [ + // Reassign Saved Reports + $this->getStore()->update('UPDATE `saved_report` SET userId = :userId WHERE userId = :oldUserId', [ 'userId' => $user->userId, 'oldUserId' => $this->userId ]); - // Reassign saved_resports - $this->getStore()->update('UPDATE `saved_report` SET userId = :userId WHERE userId = :oldUserId', [ + // Reassign Report Schedule + $this->getStore()->update('UPDATE `reportschedule` SET userId = :userId WHERE userId = :oldUserId', [ + 'userId' => $user->userId, + 'oldUserId' => $this->userId + ]); + + // Reassign Display Profiles + $this->getStore()->update('UPDATE `displayprofile` SET userId = :userId WHERE userId = :oldUserId', [ + 'userId' => $user->userId, + 'oldUserId' => $this->userId + ]); + + // Reassign Commands + $this->getStore()->update('UPDATE `command` SET userId = :userId WHERE userId = :oldUserId', [ 'userId' => $user->userId, 'oldUserId' => $this->userId ]); @@ -852,7 +877,7 @@ public function reassignAllTo($user) $this->loaded = false; $this->load(true); - $this->getLog()->debug('Reassign and reload complete, there are %d children', $this->countChildren()); + $this->getLog()->debug(sprintf('Reassign and reload complete, there are %d children', $this->countChildren())); } /** @@ -950,13 +975,16 @@ public function save($options = []) */ public function delete() { - $this->getLog()->debug('Deleting %d', $this->userId); + $this->getLog()->debug(sprintf('Deleting %d', $this->userId)); // We must ensure everything is loaded before we delete if ($this->hash == null) { $this->load(true); } + // get system user + $systemUser = $this->userFactory->getSystemUser(); + // Remove the user specific group $group = $this->userGroupFactory->getById($this->groupId); $group->delete(); @@ -993,11 +1021,23 @@ public function delete() $event->delete(); } + // this needs to happen before we delete media, otherwise we will get an error. + $this->getStore()->update('DELETE FROM `saved_report` WHERE userId = :userId', ['userId' => $this->userId]); + $this->getStore()->update('DELETE FROM `reportschedule` WHERE userId = :userId', ['userId' => $this->userId]); + // Delete any media foreach ($this->media as $media) { /* @var Media $media */ - $media->setChildObjectDependencies($this->layoutFactory, $this->widgetFactory, $this->displayGroupFactory, $this->displayFactory, $this->scheduleFactory, $this->playerVersionFactory); - $media->delete(); + if ($media->mediaType === 'module') { + // Modules files should be owned by a super admin, + // if we are deleting super admin, we would expect these to be reassigned already + // if that is not the case, due to changed userTypeId reassign them now instead of deleting. + $media->setOwner($systemUser->userId); + $media->save(); + } else { + $media->setChildObjectDependencies($this->layoutFactory, $this->widgetFactory, $this->displayGroupFactory, $this->displayFactory, $this->scheduleFactory, $this->playerVersionFactory); + $media->delete(); + } } // Delete Playlists owned by this user @@ -1007,22 +1047,51 @@ public function delete() } // Display Groups owned by this user - foreach($this->displayGroupFactory->getByOwnerId($this->userId) as $displayGroup) { - $displayGroup->setChildObjectDependencies($this->displayFactory, $this->layoutFactory, $this->mediaFactory, $this->scheduleFactory); - $displayGroup->delete(); + foreach ($this->displayGroups as $displayGroup) { + // Display specific Display Groups should be owned by a super admin, + // if we are deleting super admin, we would expect these to be reassigned already, + // if that is not the case, due to changed userTypeId reassign them now instead of deleting. + if ($displayGroup->isDisplaySpecific === 1) { + $displayGroup->setOwner($systemUser->userId); + $displayGroup->save(['saveTags' => false, 'manageDynamicDisplayLinks' => false]); + } else { + $displayGroup->setChildObjectDependencies($this->displayFactory, $this->layoutFactory, $this->mediaFactory, $this->scheduleFactory); + $displayGroup->delete(); + } } - foreach($this->dataSetFactory->getByOwnerId($this->userId) as $dataSet) { + foreach ($this->dataSetFactory->getByOwnerId($this->userId) as $dataSet) { $dataSet->delete(); } + // Delete Actions $this->getStore()->update('DELETE FROM `action` WHERE ownerId = :userId', ['userId' => $this->userId]); // Delete oAuth clients $this->getStore()->update('DELETE FROM `oauth_clients` WHERE userId = :userId', ['userId' => $this->userId]); + + foreach ($this->dayParts as $dayPart) { + // System DayParts should be owned by a super admin, + // if we are deleting super admin, we would expect these to be reassigned already + // if that is not the case, due to changed userTypeId reassign them now instead of deleting. + if ($dayPart->isSystemDayPart()) { + $dayPart->setOwner($systemUser->userId); + $dayPart->save(['validate' => false, 'recalculateHash' => false]); + } else { + $dayPart->setChildObjectDependencies($this->displayGroupFactory, $this->displayFactory, $this->layoutFactory, $this->mediaFactory, $this->scheduleFactory, $this->dayPartFactory); + $dayPart->delete(); + } + } + // Delete user specific entities $this->getStore()->update('DELETE FROM `resolution` WHERE userId = :userId', ['userId' => $this->userId]); - $this->getStore()->update('DELETE FROM `daypart` WHERE userId = :userId', ['userId' => $this->userId]); + $this->getStore()->update('DELETE FROM `command` WHERE userId = :userId', ['userId' => $this->userId]); + // if there are any default Display Profiles owned by this user, reassign them instead of deleting. + $this->getStore()->update('UPDATE `displayprofile` SET userId = :userId WHERE userId = :oldUserId AND isDefault = 1', [ + 'userId' => $systemUser->userId, + 'oldUserId' => $this->userId + ]); + $this->getStore()->update('DELETE FROM `displayprofile` WHERE userId = :userId', ['userId' => $this->userId]); $this->getStore()->update('DELETE FROM `session` WHERE userId = :userId', ['userId' => $this->userId]); $this->getStore()->update('DELETE FROM `user` WHERE userId = :userId', ['userId' => $this->userId]); } diff --git a/lib/Factory/LayoutFactory.php b/lib/Factory/LayoutFactory.php index d12f449919..288f7e2ff7 100644 --- a/lib/Factory/LayoutFactory.php +++ b/lib/Factory/LayoutFactory.php @@ -1164,7 +1164,7 @@ public function createFromZip($zipFile, $layoutName, $userId, $template, $replac } catch (NotFoundException $notFoundException) { $this->getLog()->info('Import is for an unknown resolution, we will create it with name: ' . $layout->width . ' x ' . $layout->height); - $resolution = $this->resolutionFactory->create($layout->width . ' x ' . $layout->height, $layout->width, $layout->height); + $resolution = $this->resolutionFactory->create($layout->width . ' x ' . $layout->height, (int)$layout->width, (int)$layout->height); $resolution->userId = $userId; $resolution->save(); } diff --git a/lib/Factory/MediaFactory.php b/lib/Factory/MediaFactory.php index ed1cf6b6a8..907621e08f 100644 --- a/lib/Factory/MediaFactory.php +++ b/lib/Factory/MediaFactory.php @@ -1,6 +1,6 @@ query(null, array('disableUserCheck' => 1, 'ownerId' => $ownerId, 'isEdited' => 1)); + return $this->query(null, ['disableUserCheck' => 1, 'ownerId' => $ownerId, 'isEdited' => 1, 'allModules' => $allModules]); } /** diff --git a/lib/Factory/UserFactory.php b/lib/Factory/UserFactory.php index 8c960a4275..ba510791c4 100644 --- a/lib/Factory/UserFactory.php +++ b/lib/Factory/UserFactory.php @@ -1,6 +1,6 @@ query(null, array('disableUserCheck' => 1, 'userTypeId' => 1)); + return $this->query(['userId'], ['disableUserCheck' => 1, 'userTypeId' => 1]); } /** @@ -198,9 +198,7 @@ public function getDoohUsers() */ public function getSystemUser() { - $user = $this->getById($this->configService->getSetting('SYSTEM_USER')); - - return $user; + return $this->getById($this->configService->getSetting('SYSTEM_USER')); } /** diff --git a/views/user-form-delete.twig b/views/user-form-delete.twig index 74f3507348..0b18a11c7c 100644 --- a/views/user-form-delete.twig +++ b/views/user-form-delete.twig @@ -1,6 +1,6 @@ {# /** - * Copyright (C) 2020 Xibo Signage Ltd + * Copyright (C) 2021 Xibo Signage Ltd * * Xibo - Digital Signage - http://www.xibo.org.uk * @@ -77,9 +77,11 @@ {% set message %}{% trans "Are you sure you want to delete? You may not be able to delete this user if they have associated content. You can retire users by using the Edit Button." %}{% endset %} {{ forms.message(message) }} - {% set title %}{% trans "Delete all items owned by this User?" %}{% endset %} - {% set helpText %}{% trans "Check to delete all items owned by this user, including Layouts, Media, Schedules, etc." %}{% endset %} - {{ forms.checkbox("deleteAllItems", title, 0, helpText) }} + {% if not user.isSuperAdmin() %} + {% set title %}{% trans "Delete all items owned by this User?" %}{% endset %} + {% set helpText %}{% trans "Check to delete all items owned by this user, including Layouts, Media, Schedules, etc." %}{% endset %} + {{ forms.checkbox("deleteAllItems", title, 0, helpText) }} + {% endif %} {% set title %}{% trans "Reassign items to another User" %}{% endset %} {% set helpText %}{% trans "Reassign all items this User owns to the selected User." %}{% endset %} diff --git a/views/user-form-edit.twig b/views/user-form-edit.twig index cd50cc2564..219a55aea4 100644 --- a/views/user-form-edit.twig +++ b/views/user-form-edit.twig @@ -1,6 +1,6 @@ {# /** - * Copyright (C) 2020 Xibo Signage Ltd + * Copyright (C) 2021 Xibo Signage Ltd * * Xibo - Digital Signage - http://www.xibo.org.uk * @@ -85,9 +85,11 @@ - {% set title %}{% trans "User Type" %}{% endset %} - {% set helpText %}{% trans "What is this users type?" %}{% endset %} - {{ forms.dropdown("userTypeId", "single", title, user.userTypeId, options.userTypes, "userTypeId", "userType", helpText) }} + {% if currentUser.isSuperAdmin() %} + {% set title %}{% trans "User Type" %}{% endset %} + {% set helpText %}{% trans "What is this users type?" %}{% endset %} + {{ forms.dropdown("userTypeId", "single", title, user.userTypeId, options.userTypes, "userTypeId", "userType", helpText) }} + {% endif %} {% set title %}{% trans "Library Quota" %}{% endset %} {% set helpText %}{% trans "The quota that should be applied. Enter 0 for no quota." %}{% endset %} From 8d9944324fb0d67435260f4edabbda6dd8fd9447 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 27 Jul 2021 12:08:29 +0100 Subject: [PATCH 27/39] System User : Reassign system objects on change xibosignage/xibo#2580 --- lib/Controller/Settings.php | 6 ++++++ lib/Entity/User.php | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/lib/Controller/Settings.php b/lib/Controller/Settings.php index 8e34f3ae3e..9c9a121926 100644 --- a/lib/Controller/Settings.php +++ b/lib/Controller/Settings.php @@ -757,6 +757,12 @@ public function update(Request $request, Response $response) private function handleChangedSettings($setting, $oldValue, $newValue, &$changedSettings) { if ($oldValue != $newValue) { + + if ($setting === 'SYSTEM_USER') { + $oldSystemUser = $this->userFactory->getById($oldValue); + $oldSystemUser->adjustSystemObjects($newValue); + } + if ($setting === 'ELEVATE_LOG_UNTIL') { $changedSettings[$setting] = Carbon::createFromTimestamp($oldValue)->format(DateFormatHelper::getSystemFormat()) . ' > ' . Carbon::createFromTimestamp($newValue)->format(DateFormatHelper::getSystemFormat()); } else { diff --git a/lib/Entity/User.php b/lib/Entity/User.php index e5abafafa1..e306d1c4f8 100644 --- a/lib/Entity/User.php +++ b/lib/Entity/User.php @@ -1672,4 +1672,31 @@ public function updateRecoveryCodes($recoveryCodes) $this->getStore()->update($sql, $params); } + + /** + * Triggered from Settings Controller when the SYSTEM_USER is changed + * following this change, we want to reassign some system objects to the new SYSTEM_USER + * + * @param int $newSystemUserId + */ + public function adjustSystemObjects(int $newSystemUserId) + { + // Reassign Module files + $this->getStore()->update('UPDATE `media` SET userId = :userId WHERE userId = :oldUserId AND type = \'module\'', [ + 'userId' => $newSystemUserId, + 'oldUserId' => $this->userId + ]); + + // Reassign Display specific Display Groups + $this->getStore()->update('UPDATE `displaygroup` SET userId = :userId WHERE userId = :oldUserId AND isDisplaySpecific = 1', [ + 'userId' => $newSystemUserId, + 'oldUserId' => $this->userId + ]); + + // Reassign system dayparts + $this->getStore()->update('UPDATE `daypart` SET userId = :userId WHERE userId = :oldUserId AND (isCustom = 1 OR isAlways = 1)', [ + 'userId' => $newSystemUserId, + 'oldUserId' => $this->userId + ]); + } } From 1f1b0930e56e449f30372d1629cd5458f45244e8 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 27 Jul 2021 12:14:22 +0100 Subject: [PATCH 28/39] User Delete : Handle notifications xibosignage/xibo#2580 --- lib/Entity/User.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/Entity/User.php b/lib/Entity/User.php index e306d1c4f8..d21fe983e0 100644 --- a/lib/Entity/User.php +++ b/lib/Entity/User.php @@ -870,6 +870,12 @@ public function reassignAllTo($user) 'oldUserId' => $this->userId ]); + // Reassign Notifications + $this->getStore()->update('UPDATE `notification` SET userId = :userId WHERE userId = :oldUserId', [ + 'userId' => $user->userId, + 'oldUserId' => $this->userId + ]); + // Delete oAuth Clients - security concern $this->getStore()->update('DELETE FROM `oauth_clients` WHERE userId = :userId', ['userId' => $this->userId]); @@ -1092,6 +1098,7 @@ public function delete() 'oldUserId' => $this->userId ]); $this->getStore()->update('DELETE FROM `displayprofile` WHERE userId = :userId', ['userId' => $this->userId]); + $this->getStore()->update('DELETE FROM `notification` WHERE userId = :userId', ['userId' => $this->userId]); $this->getStore()->update('DELETE FROM `session` WHERE userId = :userId', ['userId' => $this->userId]); $this->getStore()->update('DELETE FROM `user` WHERE userId = :userId', ['userId' => $this->userId]); } From ea0c82b74b64215c4742218213c11f567d714e69 Mon Sep 17 00:00:00 2001 From: Dan Garner Date: Tue, 27 Jul 2021 12:23:02 +0100 Subject: [PATCH 29/39] Refactor loading permissions for a user/entity xibosignage/xibo#2582 (cherry picked from commit 5c4f77ba19fa6924e6848ebdfeae70ebd9bb7ec4) --- lib/Factory/PermissionFactory.php | 48 ++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/lib/Factory/PermissionFactory.php b/lib/Factory/PermissionFactory.php index b76edc89b1..557c3bca8a 100644 --- a/lib/Factory/PermissionFactory.php +++ b/lib/Factory/PermissionFactory.php @@ -128,7 +128,7 @@ public function createForEveryone($userGroupFactory, $entity, $objectId, $view, * Get Permissions by Entity ObjectId * @param string $entity * @param int $objectId - * @return array[Permission] + * @return Permission[] */ public function getByObjectId($entity, $objectId) { @@ -169,14 +169,14 @@ public function getByObjectId($entity, $objectId) * @param int $objectId * @param array[string] $sortOrder * @param array[mixed] $filterBy - * @return array[Permission] + * @return Permission[] * @throws NotFoundException */ public function getAllByObjectId($user, $entity, $objectId, $sortOrder = null, $filterBy = []) { // Look up the entityId for any add operation that might occur $entityId = $this->getStore()->select('SELECT entityId FROM permissionentity WHERE entity = :entity', ['entity' => $entity]); - + $sanitizedFilter = $this->getSanitizer($filterBy); if (count($entityId) <= 0) { @@ -320,7 +320,7 @@ public function getAllByObjectId($user, $entity, $objectId, $sortOrder = null, $ * Gets all permissions for a user group * @param string $entity * @param int $groupId - * @return array[Permission] + * @return Permission[] */ public function getByGroupId($entity, $groupId) { @@ -361,28 +361,48 @@ public function getByGroupId($entity, $groupId) * Gets all permissions for a set of user groups * @param string $entity * @param int $userId - * @return array[Permission] + * @return Permission[] */ public function getByUserId($entity, $userId) { - $permissions = array(); + $permissions = []; $sql = ' - SELECT `permission`.`permissionId`, `permission`.`groupId`, `permission`.`objectId`, `permission`.`view`, `permission`.`edit`, `permission`.`delete`, permissionentity.entityId + SELECT `permission`.`permissionId`, + `permission`.`groupId`, + `permission`.`objectId`, + `permission`.`view`, + `permission`.`edit`, + `permission`.`delete`, + `permissionentity`.entityId FROM `permission` INNER JOIN `permissionentity` ON `permissionentity`.entityId = permission.entityId INNER JOIN `group` ON `group`.groupId = `permission`.groupId - LEFT OUTER JOIN `lkusergroup` + INNER JOIN `lkusergroup` ON `lkusergroup`.groupId = `group`.groupId - LEFT OUTER JOIN `user` + INNER JOIN `user` ON lkusergroup.UserID = `user`.UserID - AND `user`.userId = :userId - WHERE entity = :entity - AND (`user`.userId IS NOT NULL OR `group`.IsEveryone = 1) + WHERE `permissionentity`.entity = :entity + AND `user`.userId = :userId + UNION + SELECT `permission`.`permissionId`, + `permission`.`groupId`, + `permission`.`objectId`, + `permission`.`view`, + `permission`.`edit`, + `permission`.`delete`, + `permissionentity`.entityId + FROM `permission` + INNER JOIN `permissionentity` + ON `permissionentity`.entityId = permission.entityId + INNER JOIN `group` + ON `group`.groupId = `permission`.groupId + WHERE `permissionentity`.entity = :entity + AND `group`.IsEveryone = 1 '; - $params = array('entity' => $entity, 'userId' => $userId); + $params = ['entity' => $entity, 'userId' => $userId]; foreach ($this->getStore()->select($sql, $params) as $row) { $permission = $this->createEmpty(); @@ -414,4 +434,4 @@ public function getFullPermissions() $permission->modifyPermissions = 1; return $permission; } -} \ No newline at end of file +} From 273770be3726aa205a2ee3b8f3d79f383c630180 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 28 Jul 2021 10:23:58 +0100 Subject: [PATCH 30/39] Top/side bar and icon dashboard : Fix typo in the featureEnabled check xibosignage/xibo#2583 --- views/authed-sidebar.twig | 2 +- views/authed-topbar.twig | 2 +- views/dashboard-icon-page.twig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/views/authed-sidebar.twig b/views/authed-sidebar.twig index 0292d304ae..0546d0d6f9 100644 --- a/views/authed-sidebar.twig +++ b/views/authed-sidebar.twig @@ -70,7 +70,7 @@ {% endif %} {% endif %} - {% if currentUser.featureEnabled("user.view") and (currentUser.isGroupAdmin() or currentUser.isSuperAdmin()) %} + {% if currentUser.featureEnabled("users.view") and (currentUser.isGroupAdmin() or currentUser.isSuperAdmin()) %} {% set userMenuViewable = true %} {% else %} {% set userMenuViewable = false %} diff --git a/views/authed-topbar.twig b/views/authed-topbar.twig index 3c7d6f844b..fb1d1ef984 100644 --- a/views/authed-topbar.twig +++ b/views/authed-topbar.twig @@ -126,7 +126,7 @@ {% endif %} {% endif %} - {% if currentUser.featureEnabled("user.view") and (currentUser.isGroupAdmin() or currentUser.isSuperAdmin()) %} + {% if currentUser.featureEnabled("users.view") and (currentUser.isGroupAdmin() or currentUser.isSuperAdmin()) %} {% set userMenuViewable = true %} {% else %} {% set userMenuViewable = false %} diff --git a/views/dashboard-icon-page.twig b/views/dashboard-icon-page.twig index 40af6676a7..cd68fee88a 100644 --- a/views/dashboard-icon-page.twig +++ b/views/dashboard-icon-page.twig @@ -62,7 +62,7 @@ {% endif %} - {% if currentUser.featureEnabled("user.view") and (currentUser.isGroupAdmin() or currentUser.isSuperAdmin()) %} + {% if currentUser.featureEnabled("users.view") and (currentUser.isGroupAdmin() or currentUser.isSuperAdmin()) %}
From c4c96a70ecac37ee52edff0a0226f474e44e194f Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 28 Jul 2021 15:33:11 +0100 Subject: [PATCH 31/39] Layout add - Fix adding a Layout from Template with Drawer Region xibosignage/xibo#2584 --- lib/Controller/Layout.php | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/Controller/Layout.php b/lib/Controller/Layout.php index cc55897543..0277b8f703 100644 --- a/lib/Controller/Layout.php +++ b/lib/Controller/Layout.php @@ -376,7 +376,6 @@ function add(Request $request, Response $response) if ($templateId != 0) { // Load the template $template = $this->layoutFactory->loadById($templateId); - $template->load(); // Empty all of the ID's $layout = clone $template; @@ -392,13 +391,7 @@ function add(Request $request, Response $response) } // Set the owner - $layout->setOwner($this->getUser()->userId); - - // Ensure we have Playlists for each region - foreach ($layout->regions as $region) { - // Set the ownership of this region to the user creating from template - $region->setOwner($this->getUser()->userId, true); - } + $layout->setOwner($this->getUser()->userId, true); } else { $layout = $this->layoutFactory->createFromResolution( $resolutionId, @@ -428,12 +421,13 @@ function add(Request $request, Response $response) $layout->setOriginals(); } - foreach ($layout->regions as $region) { + $allRegions = array_merge($layout->regions, $layout->drawers); + foreach ($allRegions as $region) { /* @var Region $region */ if ($templateId != null && $template !== null) { // Match our original region id to the id in the parent layout - $original = $template->getRegion($region->getOriginalValue('regionId')); + $original = $template->getRegionOrDrawer($region->getOriginalValue('regionId')); // Make sure Playlist closure table from the published one are copied over $original->getPlaylist()->cloneClosureTable($region->getPlaylist()->playlistId); From 5d437d1a547fc7fe6322b022ad89751b8a67e310 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 30 Jul 2021 11:11:31 +0100 Subject: [PATCH 32/39] Video cover improvements and fixes xibosignage/xibo#2590 --- lib/Controller/Library.php | 32 ++++++++++++++++---------------- ui/src/core/file-upload.js | 4 ++-- views/base.twig | 1 + 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/Controller/Library.php b/lib/Controller/Library.php index 8777d1d1d3..e6552516c8 100644 --- a/lib/Controller/Library.php +++ b/lib/Controller/Library.php @@ -25,6 +25,7 @@ use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Psr7\Stream; +use Intervention\Image\ImageManagerStatic as Img; use Mimey\MimeTypes; use Respect\Validation\Validator as v; use Slim\Http\Response as Response; @@ -2680,6 +2681,8 @@ private function getDownloadInfo($url) * @param Response $response * * @return Response + * @throws AccessDeniedException + * @throws ConfigurationException * @throws InvalidArgumentException * @throws NotFoundException */ @@ -2687,30 +2690,27 @@ public function addThumbnail($request, $response) { $sanitizedParams = $this->getSanitizer($request->getParams()); $libraryLocation = $this->getConfig()->getSetting('LIBRARY_LOCATION'); + self::ensureLibraryExists($libraryLocation); - $image = $request->getParam('image'); + $imageData = $request->getParam('image'); $mediaId = $sanitizedParams->getInt('mediaId'); $media = $this->mediaFactory->getById($mediaId); - if (preg_match('/^data:image\/(\w+);base64,/', $image, $type)) { - $image = substr($image, strpos($image, ',') + 1); - $type = strtolower($type[1]); + if (!$this->getUser()->checkEditable($media)) { + throw new AccessDeniedException(); + } - if (!in_array($type, [ 'jpg', 'jpeg', 'gif', 'png' ])) { - throw new InvalidArgumentException(__('Provided base64 encoded image has incorrect file extension.')); - } - $image = str_replace( ' ', '+', $image ); - $image = base64_decode($image); + try { + Img::configure(['driver' => 'gd']); - if ($image === false) { - throw new InvalidArgumentException(__("Image decoding failed.")); - } - } else { - throw new InvalidArgumentException(__('Incorrect image data')); + // Load the image + $image = Img::make($imageData); + $image->save($libraryLocation . $mediaId . '_' . $media->mediaType . 'cover.png'); + } catch (\Exception $exception) { + $this->getLog()->error('Exception adding Video cover image. e = ' . $exception->getMessage()); + throw new InvalidArgumentException(__('Invalid image data')); } - file_put_contents($libraryLocation . "{$mediaId}_{$media->mediaType}cover.{$type}", $image); - return $response->withStatus(204); } diff --git a/ui/src/core/file-upload.js b/ui/src/core/file-upload.js index 064fe94eeb..c6e91825e8 100644 --- a/ui/src/core/file-upload.js +++ b/ui/src/core/file-upload.js @@ -303,9 +303,9 @@ function saveVideoCoverImage(data) { delete videoImageCovers[results.name]; // this calls function in library controller that decodes the image and - // saves it to library as "/{$mediaId}_videocover.{$type}". + // saves it to library as "{libraryLocation}/{$mediaId}_{mediaType}cover.png". $.ajax({ - url: "/library/thumbnail", + url: addMediaThumbnailUrl, type: "POST", data: thumbnailData }); diff --git a/views/base.twig b/views/base.twig index e8eecf68e2..931fc22854 100644 --- a/views/base.twig +++ b/views/base.twig @@ -122,6 +122,7 @@ var pingUrl = "{{ url_for("ping") }}"; var foldersUrl = "{{ url_for("folders.search") }}"; var permissionsUrl = "{{ url_for("user.set.permissions.multi", {entity: ":entity"}) }}"; + var addMediaThumbnailUrl = "{{ url_for("library.thumbnail.add") }}"; {% autoescape "js" %} var dataTablesLanguage = { From ca9b4d07e75336056af5156ad816ad77120c7bce Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 30 Jul 2021 12:46:32 +0100 Subject: [PATCH 33/39] Features : Remove usergroup.add feature, fix typos in feature names Hide Onboarding Settings tab from non super admin users (content of that tab was already hidden) xibosignage/xibo#2583 --- lib/Factory/UserGroupFactory.php | 5 ----- lib/routes-web.php | 4 +--- lib/routes.php | 8 +++----- views/usergroup-form-edit.twig | 2 +- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/lib/Factory/UserGroupFactory.php b/lib/Factory/UserGroupFactory.php index 2b95ca828a..05704c3522 100644 --- a/lib/Factory/UserGroupFactory.php +++ b/lib/Factory/UserGroupFactory.php @@ -615,11 +615,6 @@ public function getFeatures() 'group' => 'users-management', 'title' => __('Page which shows all User Groups that have been created') ], - 'usergroup.add' => [ - 'feature' => 'usergroup.add', - 'group' => 'users-management', - 'title' => __('Include "Add User Group" button to allow for additional User Groups to be added') - ], 'usergroup.modify' => [ 'feature' => 'usergroup.modify', 'group' => 'users-management', diff --git a/lib/routes-web.php b/lib/routes-web.php index 96f7e9dcc4..5a02aad301 100644 --- a/lib/routes-web.php +++ b/lib/routes-web.php @@ -469,9 +469,7 @@ ->addMiddleware(new FeatureAuth($app->getContainer(), ['usergroup.view'])) ->setName('group.view'); -$app->get('/group/form/add', ['\Xibo\Controller\UserGroup','addForm']) - ->addMiddleware(new FeatureAuth($app->getContainer(), ['usergroup.add'])) - ->setName('group.add.form'); +$app->get('/group/form/add', ['\Xibo\Controller\UserGroup','addForm'])->setName('group.add.form'); $app->group('', function(\Slim\Routing\RouteCollectorProxy $group) { $group->get('/group/form/edit/{id}', ['\Xibo\Controller\UserGroup','editForm'])->setName('group.edit.form'); diff --git a/lib/routes.php b/lib/routes.php index f1c06a6fa7..de2844471d 100644 --- a/lib/routes.php +++ b/lib/routes.php @@ -536,14 +536,14 @@ $app->post('/user/permissions/{entity}', ['\Xibo\Controller\User','permissionsMulti'])->setName('user.set.permissions.multi'); $app->post('/user', ['\Xibo\Controller\User','add']) - ->addMiddleware(new \Xibo\Middleware\FeatureAuth($app->getContainer(), ['user.add'])) + ->addMiddleware(new \Xibo\Middleware\FeatureAuth($app->getContainer(), ['users.add'])) ->setName('user.add'); $app->group('', function (RouteCollectorProxy $group) { $group->put('/user/{id}', ['\Xibo\Controller\User','edit'])->setName('user.edit'); $group->delete('/user/{id}', ['\Xibo\Controller\User','delete'])->setName('user.delete'); $group->post('/user/{id}/usergroup/assign', ['\Xibo\Controller\User','assignUserGroup'])->setName('user.assign.userGroup'); -})->addMiddleware(new \Xibo\Middleware\FeatureAuth($app->getContainer(), ['user.modify'])); +})->addMiddleware(new \Xibo\Middleware\FeatureAuth($app->getContainer(), ['users.modify'])); /** * User Group @@ -554,9 +554,7 @@ */ $app->get('/group', ['\Xibo\Controller\UserGroup','grid'])->setName('group.search'); -$app->post('/group', ['\Xibo\Controller\UserGroup','add']) - ->addMiddleware(new \Xibo\Middleware\FeatureAuth($app->getContainer(), ['usergroup.add'])) - ->setName('group.add'); +$app->post('/group', ['\Xibo\Controller\UserGroup','add'])->setName('group.add'); $app->group('', function (RouteCollectorProxy $group) { $group->put('/group/{id}', ['\Xibo\Controller\UserGroup','edit'])->setName('group.edit'); diff --git a/views/usergroup-form-edit.twig b/views/usergroup-form-edit.twig index feaef1fd77..2f2b391675 100644 --- a/views/usergroup-form-edit.twig +++ b/views/usergroup-form-edit.twig @@ -42,7 +42,7 @@
From f22b60e4708b32e534794528e0ff5934ff05c340 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 30 Jul 2021 12:48:32 +0100 Subject: [PATCH 34/39] Onboarding form - check folder.view feature Do not initialise the folder tree and hide Sharing step if said feature is disabled --- views/user-form-onboarding.twig | 10 +++++++--- views/user-page.twig | 4 ++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/views/user-form-onboarding.twig b/views/user-form-onboarding.twig index da17ccfdad..24cecf3ca1 100644 --- a/views/user-form-onboarding.twig +++ b/views/user-form-onboarding.twig @@ -43,10 +43,12 @@ 2

{{ "Credentials"|trans }}

+ {% if currentUser.featureEnabled("folder.view") %}
3

{{ "Sharing"|trans }}

+ {% endif %}
@@ -90,7 +92,7 @@ - diff --git a/views/user-page.twig b/views/user-page.twig index 121c552a7b..9b4eef1d83 100644 --- a/views/user-page.twig +++ b/views/user-page.twig @@ -282,7 +282,9 @@ $(dialog).find('[data-toggle="popover"]').popover(); // Init the folder panel + {% if currentUser.featureEnabled("folder.view") %} initFolderPanel(dialog); + {% endif %} var navListItems = $(dialog).find('div.setup-panel div a'), allWells = $(dialog).find('.setup-content'), @@ -344,6 +346,7 @@ XiboFormSubmit($form, e, function(xhr) { // Callback if (xhr.success && xhr.id) { + {% if currentUser.featureEnabled("folder.view") %} // Submit the folder ownerships var selected = $(dialog).find("#container-folder-tree").jstree("get_selected"); var groupIds = {}; @@ -361,6 +364,7 @@ toastr.error("{{ "Problem saving folder sharing, please check the User created." }}"); } }); + {% endif %} XiboDialogClose(); } From b55abd7363c8338f6f9183507c7b2f11622c0086 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 30 Jul 2021 13:44:10 +0100 Subject: [PATCH 35/39] Weather Tiles : make sure we check both darkSky and owm api key in the Widget edit form xibosignage/xibo#2588 --- modules/weather-form-edit.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/weather-form-edit.twig b/modules/weather-form-edit.twig index 181652c26a..ec36284217 100644 --- a/modules/weather-form-edit.twig +++ b/modules/weather-form-edit.twig @@ -286,7 +286,7 @@ - {% if module.getSetting("apiKey") == "" %} + {% if module.getSetting("apiKey") == "" and module.getSetting("owmApiKey") == "" %} {% set message %}{% trans "The Weather Widget has not been configured yet, please ask your CMS Administrator to look at it for you." %}{% endset %} {{ forms.message(message, "alert alert-danger") }} {% endif %} From 2ec68aa3c1d965b030c3f2eba15f12f9e8535e04 Mon Sep 17 00:00:00 2001 From: Dan Garner Date: Fri, 30 Jul 2021 16:05:36 +0100 Subject: [PATCH 36/39] Refactor layout concurrent lock. Only lock if we think we will build, and add finally blocks xibosignage/xibo#2591 --- lib/Controller/DisplayGroup.php | 14 +++-- lib/Controller/Layout.php | 96 ++++++++++++++++-------------- lib/Controller/Preview.php | 26 ++++---- lib/Entity/Layout.php | 24 ++++++++ lib/Factory/LayoutFactory.php | 13 +++- lib/XTR/MaintenanceRegularTask.php | 35 +++++++---- lib/Xmds/Soap.php | 12 ++-- lib/Xmds/Soap3.php | 7 ++- lib/Xmds/Soap4.php | 7 ++- 9 files changed, 147 insertions(+), 87 deletions(-) diff --git a/lib/Controller/DisplayGroup.php b/lib/Controller/DisplayGroup.php index f54271fd22..bb0ed61aab 100644 --- a/lib/Controller/DisplayGroup.php +++ b/lib/Controller/DisplayGroup.php @@ -1900,8 +1900,11 @@ public function changeLayout(Request $request, Response $response, $id) // in this case we should build it and notify before we send the action // notify should NOT collect now, as we will do that during our own action. $layout = $this->layoutFactory->concurrentRequestLock($layout); - $layout->xlfToDisk(['notify' => true, 'collectNow' => false]); - $this->layoutFactory->concurrentRequestRelease($layout); + try { + $layout->xlfToDisk(['notify' => true, 'collectNow' => false]); + } finally { + $this->layoutFactory->concurrentRequestRelease($layout); + } } } @@ -2092,8 +2095,11 @@ public function overlayLayout(Request $request, Response $response, $id) // in this case we should build it and notify before we send the action // notify should NOT collect now, as we will do that during our own action. $layout = $this->layoutFactory->concurrentRequestLock($layout); - $layout->xlfToDisk(['notify' => true, 'collectNow' => false]); - $this->layoutFactory->concurrentRequestRelease($layout); + try { + $layout->xlfToDisk(['notify' => true, 'collectNow' => false]); + } finally { + $this->layoutFactory->concurrentRequestRelease($layout); + } } } diff --git a/lib/Controller/Layout.php b/lib/Controller/Layout.php index 008f01554e..7c7c635628 100644 --- a/lib/Controller/Layout.php +++ b/lib/Controller/Layout.php @@ -2188,8 +2188,13 @@ public function status(Request $request, Response $response, $id) { // Get the layout $layout = $this->layoutFactory->concurrentRequestLock($this->layoutFactory->getById($id)); - $layout = $this->layoutFactory->decorateLockedProperties($layout); - $layout->xlfToDisk(); + try { + $layout = $this->layoutFactory->decorateLockedProperties($layout); + $layout->xlfToDisk(); + } finally { + // Release lock + $this->layoutFactory->concurrentRequestRelease($layout); + } switch ($layout->status) { @@ -2233,9 +2238,6 @@ public function status(Request $request, Response $response, $id) $this->session->refreshExpiry = false; } - // Release lock - $this->layoutFactory->concurrentRequestRelease($layout); - return $this->render($request, $response); } @@ -2627,52 +2629,54 @@ public function publishForm(Request $request, Response $response, $id) public function publish(Request $request, Response $response, $id) { Profiler::start('Layout::publish', $this->getLog()); - $layout = $this->layoutFactory->concurrentRequestLock($this->layoutFactory->getById($id)); - $sanitizedParams = $this->getSanitizer($request->getParams()); - $publishDate = $sanitizedParams->getDate('publishDate'); - $publishNow = $sanitizedParams->getCheckbox('publishNow'); - - // Make sure we have permission - if (!$this->getUser()->checkEditable($layout)) { - throw new AccessDeniedException(__('You do not have permissions to edit this layout')); - } - - // if we have publish date update it in database - if (isset($publishDate) && !$publishNow) { - $layout->setPublishedDate($publishDate); - } + $layout = $this->layoutFactory->concurrentRequestLock($this->layoutFactory->getById($id), true); + try { + $sanitizedParams = $this->getSanitizer($request->getParams()); + $publishDate = $sanitizedParams->getDate('publishDate'); + $publishNow = $sanitizedParams->getCheckbox('publishNow'); + + // Make sure we have permission + if (!$this->getUser()->checkEditable($layout)) { + throw new AccessDeniedException(__('You do not have permissions to edit this layout')); + } - // We want to take the draft layout, and update the campaign links to point to the draft, then remove the - // parent. - if ($publishNow || (isset($publishDate) && $publishDate->format('U') < Carbon::now()->format('U')) ) { - $draft = $this->layoutFactory->getByParentId($id); - $draft->publishDraft(); - $draft->load(); + // if we have publish date update it in database + if (isset($publishDate) && !$publishNow) { + $layout->setPublishedDate($publishDate); + } - // We also build the XLF at this point, and if we have a problem we prevent publishing and raise as an - // error message - $draft->xlfToDisk(['notify' => true, 'exceptionOnError' => true, 'exceptionOnEmptyRegion' => false]); + // We want to take the draft layout, and update the campaign links to point to the draft, then remove the + // parent. + if ($publishNow || (isset($publishDate) && $publishDate->format('U') < Carbon::now()->format('U'))) { + $draft = $this->layoutFactory->getByParentId($id); + $draft->publishDraft(); + $draft->load(); + + // We also build the XLF at this point, and if we have a problem we prevent publishing and raise as an + // error message + $draft->xlfToDisk(['notify' => true, 'exceptionOnError' => true, 'exceptionOnEmptyRegion' => false]); + + // Return + $this->getState()->hydrate([ + 'httpStatus' => 200, + 'message' => sprintf(__('Published %s'), $draft->layout), + 'data' => $draft + ]); + } else { + // Return + $this->getState()->hydrate([ + 'httpStatus' => 200, + 'message' => sprintf(__('Layout will be published on %s'), $publishDate), + 'data' => $layout + ]); + } - // Return - $this->getState()->hydrate([ - 'httpStatus' => 200, - 'message' => sprintf(__('Published %s'), $draft->layout), - 'data' => $draft - ]); - } else { - // Return - $this->getState()->hydrate([ - 'httpStatus' => 200, - 'message' => sprintf(__('Layout will be published on %s'), $publishDate), - 'data' => $layout - ]); + Profiler::end('Layout::publish', $this->getLog()); + } finally { + // Release lock + $this->layoutFactory->concurrentRequestRelease($layout); } - Profiler::end('Layout::publish', $this->getLog()); - - // Release lock - $this->layoutFactory->concurrentRequestRelease($layout); - return $this->render($request, $response); } diff --git a/lib/Controller/Preview.php b/lib/Controller/Preview.php index 7410d6dd53..042084700a 100644 --- a/lib/Controller/Preview.php +++ b/lib/Controller/Preview.php @@ -120,21 +120,21 @@ public function show(Request $request, Response $response, $id ) public function getXlf(Request $request, Response $response, $id) { $layout = $this->layoutFactory->concurrentRequestLock($this->layoutFactory->getById($id)); + try { + if (!$this->getUser()->checkViewable($layout)) { + throw new AccessDeniedException(); + } - if (!$this->getUser()->checkViewable($layout)) { - throw new AccessDeniedException(); - } - - echo file_get_contents($layout->xlfToDisk([ - 'notify' => false, - 'collectNow' => false, - ])); - - $this->setNoOutput(true); - - // Release lock - $this->layoutFactory->concurrentRequestRelease($layout); + echo file_get_contents($layout->xlfToDisk([ + 'notify' => false, + 'collectNow' => false, + ])); + $this->setNoOutput(); + } finally { + // Release lock + $this->layoutFactory->concurrentRequestRelease($layout); + } return $this->render($request, $response); } } diff --git a/lib/Entity/Layout.php b/lib/Entity/Layout.php index a70de6a178..da105219dc 100644 --- a/lib/Entity/Layout.php +++ b/lib/Entity/Layout.php @@ -302,6 +302,9 @@ class Layout implements \JsonSerializable // Handle empty regions private $hasEmptyRegion = false; + // Flag to indicate we've not built this layout this session. + private $hasBuilt = false; + public static $loadOptionsMinimum = [ 'loadPlaylists' => false, 'loadTags' => false, @@ -1921,6 +1924,24 @@ public function toZip($dataSetFactory, $fileName, $options = []) $zip->close(); } + /** + * Is a build of this layout required? + * @return bool + */ + public function isBuildRequired(): bool + { + return $this->status == 3 || !file_exists($this->getCachePath()); + } + + /** + * Has this Layout built this session? + * @return bool + */ + public function hasBuilt(): bool + { + return $this->hasBuilt; + } + /** * Save the XLF to disk if necessary * @param array $options @@ -2024,8 +2045,11 @@ public function xlfToDisk($options = []) 'notify' => $options['notify'], 'collectNow' => $options['collectNow'] ]); + + $this->hasBuilt = true; } else { $this->getLog()->debug('xlfToDisk: no build required for layoutId: ' . $this->layoutId); + $this->hasBuilt = false; } Profiler::end('Layout::xlfToDisk', $this->getLog()); diff --git a/lib/Factory/LayoutFactory.php b/lib/Factory/LayoutFactory.php index 9a4f607c52..e3fecfe0b8 100644 --- a/lib/Factory/LayoutFactory.php +++ b/lib/Factory/LayoutFactory.php @@ -2528,8 +2528,13 @@ public function decorateLockedProperties(Layout $layout): Layout * @param int $tries * @throws \Xibo\Support\Exception\GeneralException */ - public function concurrentRequestLock(Layout $layout, $pass = 1, $ttl = 300, $wait = 6, $tries = 10): Layout + public function concurrentRequestLock(Layout $layout, $force = false, $pass = 1, $ttl = 300, $wait = 6, $tries = 10): Layout { + // Does this layout require building? + if (!$force && !$layout->isBuildRequired()) { + return $layout; + } + $lock = $this->getPool()->getItem('locks/layout_build/' . $layout->campaignId); // Set the invalidation method to simply return the value (not that we use it, but it gets us a miss on expiry) @@ -2576,7 +2581,7 @@ public function concurrentRequestLock(Layout $layout, $pass = 1, $ttl = 300, $wa // Recursive request (we've decremented the number of tries) $pass++; - return $this->concurrentRequestLock($layout, $pass, $ttl, $wait, $tries); + return $this->concurrentRequestLock($layout, $force, $pass, $ttl, $wait, $tries); } } } @@ -2586,6 +2591,10 @@ public function concurrentRequestLock(Layout $layout, $pass = 1, $ttl = 300, $wa */ public function concurrentRequestRelease(Layout $layout) { + if (!$layout->hasBuilt()) { + return; + } + $this->getLog()->debug('Releasing lock ' . $layout->campaignId); $lock = $this->getPool()->getItem('locks/layout_build/' . $layout->campaignId); diff --git a/lib/XTR/MaintenanceRegularTask.php b/lib/XTR/MaintenanceRegularTask.php index 16f25a6bd3..c7a6963e46 100644 --- a/lib/XTR/MaintenanceRegularTask.php +++ b/lib/XTR/MaintenanceRegularTask.php @@ -246,12 +246,15 @@ private function buildLayouts() /* @var \Xibo\Entity\Layout $layout */ try { $layout = $this->layoutFactory->concurrentRequestLock($layout); - $layout->xlfToDisk(['notify' => true]); - - // Commit after each build - // https://github.com/xibosignage/xibo/issues/1593 - $this->store->commitIfNecessary(); - $this->layoutFactory->concurrentRequestRelease($layout); + try { + $layout->xlfToDisk(['notify' => true]); + + // Commit after each build + // https://github.com/xibosignage/xibo/issues/1593 + $this->store->commitIfNecessary(); + } finally { + $this->layoutFactory->concurrentRequestRelease($layout); + } } catch (\Exception $e) { $this->log->error('Maintenance cannot build Layout %d, %s.', $layout->layoutId, $e->getMessage()); } @@ -413,13 +416,19 @@ private function publishLayouts() throw new GeneralException(__($layout->statusMessage)); } else { // publish the layout - $layout = $this->layoutFactory->concurrentRequestLock($layout); - $draft = $this->layoutFactory->getByParentId($layout->layoutId); - $draft->publishDraft(); - $draft->load(); - $draft->xlfToDisk(['notify' => true, 'exceptionOnError' => true, 'exceptionOnEmptyRegion' => false]); - - $this->layoutFactory->concurrentRequestRelease($layout); + $layout = $this->layoutFactory->concurrentRequestLock($layout, true); + try { + $draft = $this->layoutFactory->getByParentId($layout->layoutId); + $draft->publishDraft(); + $draft->load(); + $draft->xlfToDisk([ + 'notify' => true, + 'exceptionOnError' => true, + 'exceptionOnEmptyRegion' => false + ]); + } finally { + $this->layoutFactory->concurrentRequestRelease($layout); + } $this->log->info('Published layout ID ' . $layout->layoutId . ' new layout id is ' . $draft->layoutId); } } catch (GeneralException $e) { diff --git a/lib/Xmds/Soap.php b/lib/Xmds/Soap.php index 20d1471ca0..d3473a8a35 100644 --- a/lib/Xmds/Soap.php +++ b/lib/Xmds/Soap.php @@ -617,12 +617,14 @@ protected function doRequiredFiles($serverKey, $hardwareKey, $httpDownloads) // Load this layout $layout = $this->layoutFactory->concurrentRequestLock($this->layoutFactory->loadById($layoutId)); - $layout->loadPlaylists(); - - // Make sure its XLF is up to date - $path = $layout->xlfToDisk(['notify' => false]); + try { + $layout->loadPlaylists(); - $this->layoutFactory->concurrentRequestRelease($layout); + // Make sure its XLF is up to date + $path = $layout->xlfToDisk(['notify' => false]); + } finally { + $this->layoutFactory->concurrentRequestRelease($layout); + } // If the status is *still* 4, then we skip this layout as it cannot build if ($layout->status === ModuleWidget::$STATUS_INVALID) { diff --git a/lib/Xmds/Soap3.php b/lib/Xmds/Soap3.php index f45b79436c..e878aba741 100644 --- a/lib/Xmds/Soap3.php +++ b/lib/Xmds/Soap3.php @@ -185,8 +185,11 @@ function GetFile($serverKey, $hardwareKey, $filePath, $fileType, $chunkOffset, $ // Load the layout $layout = $this->layoutFactory->concurrentRequestLock($this->layoutFactory->getById($fileId)); - $path = $layout->xlfToDisk(); - $this->layoutFactory->concurrentRequestRelease($layout); + try { + $path = $layout->xlfToDisk(); + } finally { + $this->layoutFactory->concurrentRequestRelease($layout); + } $file = file_get_contents($path); $chunkSize = filesize($path); diff --git a/lib/Xmds/Soap4.php b/lib/Xmds/Soap4.php index b00576857b..ef12ff5ff9 100644 --- a/lib/Xmds/Soap4.php +++ b/lib/Xmds/Soap4.php @@ -356,8 +356,11 @@ function GetFile($serverKey, $hardwareKey, $fileId, $fileType, $chunkOffset, $ch // Load the layout $layout = $this->layoutFactory->concurrentRequestLock($this->layoutFactory->getById($fileId)); - $path = $layout->xlfToDisk(); - $this->layoutFactory->concurrentRequestRelease($layout); + try { + $path = $layout->xlfToDisk(); + } finally { + $this->layoutFactory->concurrentRequestRelease($layout); + } $file = file_get_contents($path); $chunkSize = filesize($path); From 292b6faddecebc8f1b54ea63f22b1ff31fba6250 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 30 Jul 2021 16:51:28 +0100 Subject: [PATCH 37/39] Fix SAML redirects xibosignage/xibo#2585 --- lib/Middleware/SAMLAuthentication.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/Middleware/SAMLAuthentication.php b/lib/Middleware/SAMLAuthentication.php index b413d91a06..870f436d1a 100644 --- a/lib/Middleware/SAMLAuthentication.php +++ b/lib/Middleware/SAMLAuthentication.php @@ -85,17 +85,12 @@ public function addRoutes() $app->post('/saml/acs', function (\Slim\Http\ServerRequest $request, \Slim\Http\Response $response) { // Log some interesting things $this->getLog()->debug('Arrived at the ACS route with own URL: ' . Utils::getSelfRoutedURLNoQuery()); - $parsedRequest = $this->getSanitizer($request->getParsedBody()); - $routeParser = RouteContext::fromRequest($request)->getRouteParser(); // Pull out the SAML settings $samlSettings = $this->getConfig()->samlSettings; $auth = new Auth($samlSettings); $auth->processResponse(); - $priorRoute = ($parsedRequest->getString('priorRoute')); - $redirect = ($priorRoute == '' || $priorRoute == '/' || stripos($priorRoute, $routeParser->urlFor('login'))) ? $routeParser->urlFor('home') : $priorRoute; - // Check for errors $errors = $auth->getErrors(); @@ -275,7 +270,10 @@ public function addRoutes() ]); } - // Redirect to User Homepage + // Redirect back to the originally-requested url + $params = $request->getParams(); + $redirect = $params['RelayState'] ?? $this->getRouteParser()->urlFor('home'); + return $response->withRedirect($redirect); } }); From 88d4492aa5278c69b0fefe3cd897bda0ecb7c06d Mon Sep 17 00:00:00 2001 From: Dan Garner Date: Fri, 30 Jul 2021 17:41:53 +0100 Subject: [PATCH 38/39] Refactor layout concurrent lock. Only lock if we think we will build, and add finally blocks xibosignage/xibo#2591 --- lib/Controller/Layout.php | 2 +- lib/Factory/LayoutFactory.php | 4 ++-- lib/XTR/MaintenanceRegularTask.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Controller/Layout.php b/lib/Controller/Layout.php index 7c7c635628..cae37511eb 100644 --- a/lib/Controller/Layout.php +++ b/lib/Controller/Layout.php @@ -2674,7 +2674,7 @@ public function publish(Request $request, Response $response, $id) Profiler::end('Layout::publish', $this->getLog()); } finally { // Release lock - $this->layoutFactory->concurrentRequestRelease($layout); + $this->layoutFactory->concurrentRequestRelease($layout, true); } return $this->render($request, $response); diff --git a/lib/Factory/LayoutFactory.php b/lib/Factory/LayoutFactory.php index e3fecfe0b8..532dd37242 100644 --- a/lib/Factory/LayoutFactory.php +++ b/lib/Factory/LayoutFactory.php @@ -2589,9 +2589,9 @@ public function concurrentRequestLock(Layout $layout, $force = false, $pass = 1, /** * Release a lock on concurrent requests */ - public function concurrentRequestRelease(Layout $layout) + public function concurrentRequestRelease(Layout $layout, bool $force = false) { - if (!$layout->hasBuilt()) { + if (!$force && !$layout->hasBuilt()) { return; } diff --git a/lib/XTR/MaintenanceRegularTask.php b/lib/XTR/MaintenanceRegularTask.php index c7a6963e46..be9a32fe75 100644 --- a/lib/XTR/MaintenanceRegularTask.php +++ b/lib/XTR/MaintenanceRegularTask.php @@ -427,7 +427,7 @@ private function publishLayouts() 'exceptionOnEmptyRegion' => false ]); } finally { - $this->layoutFactory->concurrentRequestRelease($layout); + $this->layoutFactory->concurrentRequestRelease($layout, true); } $this->log->info('Published layout ID ' . $layout->layoutId . ' new layout id is ' . $draft->layoutId); } From 9aa89b95ef31b247b743652d3a78f3c33e5ae369 Mon Sep 17 00:00:00 2001 From: Dan Garner Date: Mon, 2 Aug 2021 10:31:56 +0100 Subject: [PATCH 39/39] Release preparation for 3.0.2 --- lib/Helper/Environment.php | 2 +- locale/default.pot | 193 ++++++++++++++++++++----------------- 2 files changed, 103 insertions(+), 92 deletions(-) diff --git a/lib/Helper/Environment.php b/lib/Helper/Environment.php index caaff7b4e0..5a2601b69b 100644 --- a/lib/Helper/Environment.php +++ b/lib/Helper/Environment.php @@ -29,7 +29,7 @@ */ class Environment { - public static $WEBSITE_VERSION_NAME = '3.0.1'; + public static $WEBSITE_VERSION_NAME = '3.0.2'; public static $XMDS_VERSION = '6'; public static $XLF_VERSION = 3; public static $VERSION_REQUIRED = '7.2.9'; diff --git a/locale/default.pot b/locale/default.pot index 02b620f5c3..5cf0c08a18 100644 --- a/locale/default.pot +++ b/locale/default.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-07-14 16:07+0100\n" +"POT-Creation-Date: 2021-08-02 10:29+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -3772,7 +3772,7 @@ msgstr "" #: cache/77/7706497b9b32c7de36827c213b2a8601711f0af41de2261b354b6b986d1c5049.php:181 #: cache/21/21e7349348636e2320001b4071000dd442f65742ad810e150643b3f9794881cf.php:165 #: cache/21/21e7349348636e2320001b4071000dd442f65742ad810e150643b3f9794881cf.php:294 -#: cache/94/94f9ceecb93c3316d3080d4aae46e60a999d6abef0ccb4af36413e5c7e83eb72.php:283 +#: cache/94/94f9ceecb93c3316d3080d4aae46e60a999d6abef0ccb4af36413e5c7e83eb72.php:292 #: cache/d6/d672ce9ebb470acd8dc2a0cb66e2234b139381f1e3d5dbeb664c11e99bc60f3f.php:188 #: cache/d6/d672ce9ebb470acd8dc2a0cb66e2234b139381f1e3d5dbeb664c11e99bc60f3f.php:319 #: cache/e5/e54ea368150fb0e872396ad12cdd6097e072346898812d5cd64c42b733049904.php:129 @@ -6399,7 +6399,7 @@ msgstr "" #: cache/3a/3a57d968d2b3ea047a6cbc125d3ffeb5cc016ca022840d3b4002b22e1a0c869b.php:56 #: cache/78/78e302e137cf151cd25aa928cf2056f410b12e070db936764c1ca5309902d112.php:73 #: cache/d6/d672ce9ebb470acd8dc2a0cb66e2234b139381f1e3d5dbeb664c11e99bc60f3f.php:96 -#: lib/XTR/MaintenanceRegularTask.php:268 +#: lib/XTR/MaintenanceRegularTask.php:271 msgid "Tidy Library" msgstr "" @@ -8046,7 +8046,7 @@ msgstr "" #: cache/2a/2aee6043c3cda7e0b32dc764721ad7a9402be770bc2456a747cbc713a42ea0b3.php:218 #: cache/63/6375875be8cd52bf7c309054babc8bde8599a27cf31c056351341a85ed859b16.php:265 #: cache/d3/d3273d78dbc6bccc3d7f19def2a4bef341bd1b3a6390ef65db8cadb616cd4a06.php:113 -#: lib/Entity/Layout.php:2013 lib/Controller/Region.php:697 +#: lib/Entity/Layout.php:2034 lib/Controller/Region.php:697 msgid "Empty Region" msgstr "" @@ -10335,7 +10335,7 @@ msgid "An optional description of the Layout. (1 - 250 characters)" msgstr "" #: cache/77/77a605daa65ad15865fc7916d845912dc07f146330a2cab026798adb2856c351.php:53 -#: lib/Middleware/Handlers.php:139 +#: lib/Middleware/Handlers.php:140 msgid "Sorry we could not find that page." msgstr "" @@ -10823,8 +10823,7 @@ msgid "Can this Application keep a secret?" msgstr "" #: cache/94/94f9ceecb93c3316d3080d4aae46e60a999d6abef0ccb4af36413e5c7e83eb72.php:209 -#: cache/94/94f9ceecb93c3316d3080d4aae46e60a999d6abef0ccb4af36413e5c7e83eb72.php:240 -msgid "Redirect URI" +msgid "New Redirect URI" msgstr "" #: cache/94/94f9ceecb93c3316d3080d4aae46e60a999d6abef0ccb4af36413e5c7e83eb72.php:214 @@ -10833,11 +10832,19 @@ msgid "" "Authorization Code Grants" msgstr "" -#: cache/94/94f9ceecb93c3316d3080d4aae46e60a999d6abef0ccb4af36413e5c7e83eb72.php:225 +#: cache/94/94f9ceecb93c3316d3080d4aae46e60a999d6abef0ccb4af36413e5c7e83eb72.php:224 +msgid "Existing Redirect URI" +msgstr "" + +#: cache/94/94f9ceecb93c3316d3080d4aae46e60a999d6abef0ccb4af36413e5c7e83eb72.php:248 msgid "Select sharing to grant to this application (scopes)." msgstr "" -#: cache/94/94f9ceecb93c3316d3080d4aae46e60a999d6abef0ccb4af36413e5c7e83eb72.php:288 +#: cache/94/94f9ceecb93c3316d3080d4aae46e60a999d6abef0ccb4af36413e5c7e83eb72.php:258 +msgid "Scopes" +msgstr "" + +#: cache/94/94f9ceecb93c3316d3080d4aae46e60a999d6abef0ccb4af36413e5c7e83eb72.php:297 msgid "" "Set the owner of this Application. If you are not an admin you will not be " "able to reverse this action." @@ -14572,6 +14579,10 @@ msgstr "" msgid "Authorize Request" msgstr "" +#: cache/51/519ed163d0b4033d1f84b24f4c2c3bf7bbd435d55f1432c273a058d772034ef5.php:65 +msgid "would like access to the following scopes" +msgstr "" + #: cache/51/51f7093cefb1adde75d1e7eb69243b2e5257a3697fb9238e024098b0ea0d12e2.php:56 msgid "Run Task Now" msgstr "" @@ -16931,7 +16942,7 @@ msgstr "" msgid "Please select at least 1 Playlist to embed" msgstr "" -#: lib/Widget/SubPlaylist.php:288 lib/Entity/Layout.php:2385 +#: lib/Widget/SubPlaylist.php:288 lib/Entity/Layout.php:2415 msgid "Cannot add the same SubPlaylist twice." msgstr "" @@ -16983,7 +16994,7 @@ msgstr "" msgid "Please enter a display group name" msgstr "" -#: lib/Entity/DisplayGroup.php:672 lib/Entity/Layout.php:1043 +#: lib/Entity/DisplayGroup.php:672 lib/Entity/Layout.php:1046 #: lib/Entity/DataSet.php:730 msgid "Description can not be longer than 254 characters" msgstr "" @@ -17069,7 +17080,7 @@ msgstr "" msgid "Existing data is incompatible with your new configuration" msgstr "" -#: lib/Entity/ApplicationScope.php:88 +#: lib/Entity/ApplicationScope.php:87 msgid "Access to this route is denied for this scope" msgstr "" @@ -17299,7 +17310,7 @@ msgstr "" msgid "The Region dimensions cannot be empty or negative" msgstr "" -#: lib/Entity/Region.php:393 lib/Entity/Layout.php:1062 +#: lib/Entity/Region.php:393 lib/Entity/Layout.php:1065 msgid "Layer must be 0 or a positive number" msgstr "" @@ -17329,57 +17340,57 @@ msgstr "" msgid "Folder needs to have a name, between 1 and 254 characters." msgstr "" -#: lib/Entity/Layout.php:527 +#: lib/Entity/Layout.php:530 msgid "Cannot find region" msgstr "" -#: lib/Entity/Layout.php:545 +#: lib/Entity/Layout.php:548 msgid "Cannot find drawer region" msgstr "" -#: lib/Entity/Layout.php:566 +#: lib/Entity/Layout.php:569 msgid "Cannot find Region or Drawer" msgstr "" -#: lib/Entity/Layout.php:937 +#: lib/Entity/Layout.php:940 msgid "This layout is used as the global default and cannot be deleted" msgstr "" -#: lib/Entity/Layout.php:1034 +#: lib/Entity/Layout.php:1037 msgid "The layout dimensions cannot be empty" msgstr "" -#: lib/Entity/Layout.php:1039 +#: lib/Entity/Layout.php:1042 msgid "Layout Name must be between 1 and 50 characters" msgstr "" -#: lib/Entity/Layout.php:1057 +#: lib/Entity/Layout.php:1060 #, php-format msgid "You already own a Layout called '%s'. Please choose another name." msgstr "" -#: lib/Entity/Layout.php:1068 +#: lib/Entity/Layout.php:1071 msgid "Please use only alphanumeric characters in Layout Code identifier" msgstr "" -#: lib/Entity/Layout.php:1080 +#: lib/Entity/Layout.php:1083 msgid "Layout with provided code already exists" msgstr "" -#: lib/Entity/Layout.php:1744 lib/Controller/Fault.php:122 +#: lib/Entity/Layout.php:1747 lib/Controller/Fault.php:122 msgid "Can't create ZIP. Error Code: " msgstr "" -#: lib/Entity/Layout.php:2005 +#: lib/Entity/Layout.php:2026 #, php-format msgid "There is an error with this Layout: %s" msgstr "" -#: lib/Entity/Layout.php:2052 lib/Entity/Layout.php:2169 +#: lib/Entity/Layout.php:2080 lib/Entity/Layout.php:2199 msgid "Not a Draft" msgstr "" -#: lib/Entity/Layout.php:2254 +#: lib/Entity/Layout.php:2284 msgid "Draft Layouts must have a parent" msgstr "" @@ -17572,7 +17583,7 @@ msgid "Tidy Logs" msgstr "" #: lib/XTR/MaintenanceDailyTask.php:99 lib/XTR/StatsArchiveTask.php:137 -#: lib/XTR/StatsArchiveTask.php:329 lib/Controller/User.php:413 +#: lib/XTR/StatsArchiveTask.php:332 lib/Controller/User.php:413 msgid "Disabled" msgstr "" @@ -17605,7 +17616,7 @@ msgid "Run report results: %s." msgstr "" #: lib/XTR/ReportScheduleTask.php:163 lib/XTR/AuditLogArchiveTask.php:175 -#: lib/XTR/StatsArchiveTask.php:212 lib/Service/ReportService.php:341 +#: lib/XTR/StatsArchiveTask.php:215 lib/Service/ReportService.php:341 #, php-format msgid "Can't create ZIP. Error Code: %s" msgstr "" @@ -17669,13 +17680,13 @@ msgstr "" msgid "AuditLog Export %s to %s" msgstr "" -#: lib/XTR/AuditLogArchiveTask.php:204 lib/XTR/StatsArchiveTask.php:277 +#: lib/XTR/AuditLogArchiveTask.php:204 lib/XTR/StatsArchiveTask.php:280 msgid "" "No super admins to use as the archive owner, please set one in the " "configuration." msgstr "" -#: lib/XTR/AuditLogArchiveTask.php:212 lib/XTR/StatsArchiveTask.php:285 +#: lib/XTR/AuditLogArchiveTask.php:212 lib/XTR/StatsArchiveTask.php:288 msgid "Archive Owner not found" msgstr "" @@ -17731,27 +17742,27 @@ msgstr "" msgid "Build Layouts" msgstr "" -#: lib/XTR/MaintenanceRegularTask.php:297 +#: lib/XTR/MaintenanceRegularTask.php:300 msgid "Library allowance exceeded" msgstr "" -#: lib/XTR/MaintenanceRegularTask.php:346 +#: lib/XTR/MaintenanceRegularTask.php:349 #, php-format msgid "%s is downloading %d files too many times" msgstr "" -#: lib/XTR/MaintenanceRegularTask.php:351 +#: lib/XTR/MaintenanceRegularTask.php:354 #, php-format msgid "" "Please check the bandwidth graphs and display status for %s to investigate " "the issue." msgstr "" -#: lib/XTR/MaintenanceRegularTask.php:378 +#: lib/XTR/MaintenanceRegularTask.php:381 msgid "Playlist Duration Updates" msgstr "" -#: lib/XTR/MaintenanceRegularTask.php:399 +#: lib/XTR/MaintenanceRegularTask.php:402 msgid "Publishing layouts with set publish dates" msgstr "" @@ -17764,16 +17775,16 @@ msgstr "" msgid "Stats Export %s to %s - %s" msgstr "" -#: lib/XTR/StatsArchiveTask.php:297 +#: lib/XTR/StatsArchiveTask.php:300 msgid "Tidy Stats" msgstr "" -#: lib/XTR/StatsArchiveTask.php:323 +#: lib/XTR/StatsArchiveTask.php:326 #, php-format msgid "Done - %d deleted." msgstr "" -#: lib/XTR/StatsArchiveTask.php:326 +#: lib/XTR/StatsArchiveTask.php:329 msgid "Error." msgstr "" @@ -17814,7 +17825,7 @@ msgstr "" msgid "Sorry this account does not exist or cannot be authenticated." msgstr "" -#: lib/Middleware/Handlers.php:148 +#: lib/Middleware/Handlers.php:149 msgid "Unexpected Error, please contact support." msgstr "" @@ -17979,10 +17990,10 @@ msgstr "" msgid "Assign Layouts" msgstr "" -#: lib/Controller/DisplayGroup.php:653 lib/Controller/DisplayGroup.php:2403 +#: lib/Controller/DisplayGroup.php:653 lib/Controller/DisplayGroup.php:2409 #: lib/Controller/Template.php:404 lib/Controller/Help.php:204 -#: lib/Controller/DataSetColumn.php:370 lib/Controller/Applications.php:371 -#: lib/Controller/Applications.php:438 lib/Controller/DisplayProfile.php:308 +#: lib/Controller/DataSetColumn.php:370 lib/Controller/Applications.php:383 +#: lib/Controller/Applications.php:450 lib/Controller/DisplayProfile.php:308 #: lib/Controller/DisplayProfile.php:635 lib/Controller/DayPart.php:418 #: lib/Controller/User.php:709 lib/Controller/Campaign.php:466 #: lib/Controller/Campaign.php:958 lib/Controller/Notification.php:589 @@ -17998,7 +18009,7 @@ msgstr "" #: lib/Controller/DisplayGroup.php:784 lib/Controller/Library.php:1272 #: lib/Controller/Help.php:236 lib/Controller/DataSetColumn.php:547 -#: lib/Controller/Applications.php:542 lib/Controller/DisplayProfile.php:465 +#: lib/Controller/Applications.php:555 lib/Controller/DisplayProfile.php:465 #: lib/Controller/DayPart.php:531 lib/Controller/User.php:971 #: lib/Controller/Campaign.php:636 lib/Controller/Notification.php:719 #: lib/Controller/Playlist.php:812 lib/Controller/Tag.php:521 @@ -18017,7 +18028,7 @@ msgstr "" #: lib/Controller/DisplayGroup.php:835 lib/Controller/Library.php:900 #: lib/Controller/Help.php:261 lib/Controller/DataSetColumn.php:637 -#: lib/Controller/Applications.php:574 lib/Controller/DisplayProfile.php:545 +#: lib/Controller/Applications.php:587 lib/Controller/DisplayProfile.php:545 #: lib/Controller/DayPart.php:642 lib/Controller/User.php:1070 #: lib/Controller/Campaign.php:717 lib/Controller/Notification.php:778 #: lib/Controller/Playlist.php:873 lib/Controller/Tag.php:649 @@ -18118,40 +18129,40 @@ msgid "Layouts unassigned from %s" msgstr "" #: lib/Controller/DisplayGroup.php:1718 lib/Controller/DisplayGroup.php:1767 -#: lib/Controller/DisplayGroup.php:1919 lib/Controller/DisplayGroup.php:1969 -#: lib/Controller/DisplayGroup.php:2109 lib/Controller/DisplayGroup.php:2211 -#: lib/Controller/DisplayGroup.php:2592 +#: lib/Controller/DisplayGroup.php:1922 lib/Controller/DisplayGroup.php:1972 +#: lib/Controller/DisplayGroup.php:2115 lib/Controller/DisplayGroup.php:2217 +#: lib/Controller/DisplayGroup.php:2598 #, php-format msgid "Command Sent to %s" msgstr "" -#: lib/Controller/DisplayGroup.php:1854 lib/Controller/DisplayGroup.php:2049 +#: lib/Controller/DisplayGroup.php:1854 lib/Controller/DisplayGroup.php:2052 msgid "Please provide a Layout ID or Campaign ID" msgstr "" -#: lib/Controller/DisplayGroup.php:1864 lib/Controller/DisplayGroup.php:2059 +#: lib/Controller/DisplayGroup.php:1864 lib/Controller/DisplayGroup.php:2062 msgid "Please provide Layout specific campaign ID" msgstr "" -#: lib/Controller/DisplayGroup.php:1870 lib/Controller/DisplayGroup.php:2065 +#: lib/Controller/DisplayGroup.php:1870 lib/Controller/DisplayGroup.php:2068 msgid "Cannot find layout by campaignId" msgstr "" -#: lib/Controller/DisplayGroup.php:1875 lib/Controller/DisplayGroup.php:2070 +#: lib/Controller/DisplayGroup.php:1875 lib/Controller/DisplayGroup.php:2073 msgid "Please provide Layout id or Campaign id" msgstr "" -#: lib/Controller/DisplayGroup.php:2234 lib/Controller/DisplayProfile.php:537 +#: lib/Controller/DisplayGroup.php:2240 lib/Controller/DisplayProfile.php:537 #: lib/Controller/DisplayProfile.php:567 lib/Controller/DisplayProfile.php:626 msgid "You do not have permission to delete this profile" msgstr "" -#: lib/Controller/DisplayGroup.php:2504 +#: lib/Controller/DisplayGroup.php:2510 #, php-format msgid "Display %s moved to Folder %s" msgstr "" -#: lib/Controller/DisplayGroup.php:2584 +#: lib/Controller/DisplayGroup.php:2590 msgid "Please provide a Trigger Code" msgstr "" @@ -18290,35 +18301,35 @@ msgstr "" msgid "Your library is full. Library Limit: %s MB" msgstr "" -#: lib/Controller/Library.php:2577 +#: lib/Controller/Library.php:2578 #, php-format msgid "This file size exceeds your environment Max Upload Size %s" msgstr "" -#: lib/Controller/Library.php:2609 +#: lib/Controller/Library.php:2610 #, php-format msgid "" "Invalid Module type or extension. Module type %s does not allow for %s " "extension" msgstr "" -#: lib/Controller/Library.php:2625 +#: lib/Controller/Library.php:2626 msgid "Media upload from URL was successful" msgstr "" -#: lib/Controller/Library.php:2724 +#: lib/Controller/Library.php:2700 msgid "Provided base64 encoded image has incorrect file extension." msgstr "" -#: lib/Controller/Library.php:2730 +#: lib/Controller/Library.php:2706 msgid "Image decoding failed." msgstr "" -#: lib/Controller/Library.php:2733 +#: lib/Controller/Library.php:2709 msgid "Incorrect image data" msgstr "" -#: lib/Controller/Library.php:2833 +#: lib/Controller/Library.php:2809 #, php-format msgid "Media %s moved to Folder %s" msgstr "" @@ -18348,23 +18359,23 @@ msgstr "" msgid "Latest news not enabled." msgstr "" -#: lib/Controller/Applications.php:177 lib/Controller/Applications.php:200 +#: lib/Controller/Applications.php:179 lib/Controller/Applications.php:220 msgid "Authorisation Parameters missing from session." msgstr "" -#: lib/Controller/Applications.php:363 lib/Controller/Applications.php:415 +#: lib/Controller/Applications.php:375 lib/Controller/Applications.php:427 msgid "Please enter Application name" msgstr "" -#: lib/Controller/Applications.php:419 +#: lib/Controller/Applications.php:431 msgid "Please select user" msgstr "" -#: lib/Controller/Applications.php:423 +#: lib/Controller/Applications.php:435 msgid "Invalid user type" msgstr "" -#: lib/Controller/Applications.php:532 +#: lib/Controller/Applications.php:545 msgid "You do not have permission to assign this user" msgstr "" @@ -18380,7 +18391,7 @@ msgstr "" #: lib/Controller/Action.php:276 lib/Controller/Action.php:423 #: lib/Controller/Action.php:485 lib/Controller/Action.php:614 #: lib/Controller/Action.php:672 lib/Controller/Action.php:732 -#: lib/Controller/Layout.php:2751 +#: lib/Controller/Layout.php:2755 msgid "Layout is not checked out" msgstr "" @@ -18975,9 +18986,9 @@ msgstr "" #: lib/Controller/Layout.php:805 lib/Controller/Layout.php:910 #: lib/Controller/Layout.php:958 lib/Controller/Layout.php:1008 #: lib/Controller/Layout.php:1077 lib/Controller/Layout.php:1116 -#: lib/Controller/Layout.php:2479 lib/Controller/Layout.php:2530 -#: lib/Controller/Layout.php:2569 lib/Controller/Layout.php:2637 -#: lib/Controller/Layout.php:2696 lib/Controller/Layout.php:2746 +#: lib/Controller/Layout.php:2481 lib/Controller/Layout.php:2532 +#: lib/Controller/Layout.php:2571 lib/Controller/Layout.php:2640 +#: lib/Controller/Layout.php:2700 lib/Controller/Layout.php:2750 msgid "You do not have permissions to edit this layout" msgstr "" @@ -19009,19 +19020,19 @@ msgstr "" msgid "For Layout %s Enable Stats Collection is set to %s" msgstr "" -#: lib/Controller/Layout.php:1424 lib/Controller/Layout.php:2197 +#: lib/Controller/Layout.php:1424 lib/Controller/Layout.php:2202 msgid "This Layout is ready to play" msgstr "" -#: lib/Controller/Layout.php:1428 lib/Controller/Layout.php:2201 +#: lib/Controller/Layout.php:1428 lib/Controller/Layout.php:2206 msgid "There are items on this Layout that can only be assessed by the Display" msgstr "" -#: lib/Controller/Layout.php:1432 lib/Controller/Layout.php:2205 +#: lib/Controller/Layout.php:1432 lib/Controller/Layout.php:2210 msgid "This Layout has not been built yet" msgstr "" -#: lib/Controller/Layout.php:1436 lib/Controller/Layout.php:2209 +#: lib/Controller/Layout.php:1436 lib/Controller/Layout.php:2214 msgid "This Layout is invalid and should not be scheduled" msgstr "" @@ -19067,39 +19078,39 @@ msgid "Cannot copy a Draft Layout" msgstr "" #: lib/Controller/Layout.php:2058 lib/Controller/Layout.php:2134 -#: lib/Controller/Layout.php:2265 lib/Controller/Layout.php:2302 +#: lib/Controller/Layout.php:2267 lib/Controller/Layout.php:2304 msgid "Cannot manage tags on a Draft Layout" msgstr "" -#: lib/Controller/Layout.php:2422 +#: lib/Controller/Layout.php:2424 msgid "Cannot download non-region specific module" msgstr "" -#: lib/Controller/Layout.php:2535 +#: lib/Controller/Layout.php:2537 msgid "Layout is already checked out" msgstr "" -#: lib/Controller/Layout.php:2544 +#: lib/Controller/Layout.php:2546 #, php-format msgid "Checked out %s" msgstr "" -#: lib/Controller/Layout.php:2659 +#: lib/Controller/Layout.php:2662 #, php-format msgid "Published %s" msgstr "" -#: lib/Controller/Layout.php:2666 +#: lib/Controller/Layout.php:2669 #, php-format msgid "Layout will be published on %s" msgstr "" -#: lib/Controller/Layout.php:2763 +#: lib/Controller/Layout.php:2767 #, php-format msgid "Discarded %s" msgstr "" -#: lib/Controller/Layout.php:2806 +#: lib/Controller/Layout.php:2810 msgid "This function is available only to Super Admins." msgstr "" @@ -20512,41 +20523,41 @@ msgstr "" msgid "Failed to write to database after %d retries. Please try again later." msgstr "" -#: lib/Xmds/Soap3.php:221 lib/Xmds/Soap4.php:396 +#: lib/Xmds/Soap3.php:224 lib/Xmds/Soap4.php:399 msgid "Unknown FileType Requested." msgstr "" -#: lib/Xmds/Soap.php:1701 +#: lib/Xmds/Soap.php:1703 msgid "Dates are too far apart" msgstr "" -#: lib/Xmds/Soap.php:2077 +#: lib/Xmds/Soap.php:2079 #, php-format msgid "Recovery for Display %s" msgstr "" -#: lib/Xmds/Soap.php:2078 +#: lib/Xmds/Soap.php:2080 #, php-format msgid "Display ID %d is now back online %s" msgstr "" -#: lib/Xmds/Soap.php:2149 +#: lib/Xmds/Soap.php:2151 msgid "Bandwidth allowance exceeded" msgstr "" -#: lib/Xmds/Soap4.php:376 +#: lib/Xmds/Soap4.php:379 msgid "Media exists but file missing from library. " msgstr "" -#: lib/Xmds/Soap4.php:380 +#: lib/Xmds/Soap4.php:383 msgid "Unable to get file pointer" msgstr "" -#: lib/Xmds/Soap4.php:390 +#: lib/Xmds/Soap4.php:393 msgid "Empty file" msgstr "" -#: lib/Xmds/Soap4.php:733 +#: lib/Xmds/Soap4.php:736 msgid "Incorrect Screen shot Format" msgstr ""
+ <% } else { %> + <% } %> <% if (layouts.type == 1) { %> {% endverbatim %}{% trans "Layouts" %}{% verbatim %} <% } else if (layouts.type == 3) { %> @@ -685,6 +689,9 @@ {% trans "From Date" %} {% trans "To Date" %} {% trans "Layout Duration" %}{% trans "Share of Voice" %}{% trans "Display Order" %} {% trans "Priority" %} {% trans "Visible" %}<%= layout.layoutDuration %><%= layout.shareOfVoice %><%= layout.layoutDisplayOrder %> <%= layout.eventPriority %>